Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin API first draft #505

Closed
wants to merge 15 commits into from
Closed

Plugin API first draft #505

wants to merge 15 commits into from

Conversation

sapegin
Copy link
Member

@sapegin sapegin commented Jun 22, 2017

  • plugins config option
  • Load plugins and pass to the style guide
  • Convert slots to core plugins
  • Ability to modify style guide config from a plugin (?)
  • Containers (similar to slow but wraps children in all passed wrappers)
  • Add validation
  • Add tests
  • Add docs

I have no idea how to do:

  • Store and change state for plugins (I suppose it should be separate state for style guide / component / example).
  • Change style guide in any way (for example something similar to the Wrapper component).

@sapegin sapegin mentioned this pull request Jun 22, 2017
@codecov-io
Copy link

codecov-io commented Jun 22, 2017

Codecov Report

Merging #505 into master will increase coverage by 0.01%.
The diff coverage is 98%.

Impacted Files Coverage Δ
src/plugins/isolate/IsolateButton.js 100% <ø> (ø)
...rc/rsg-components/ReactComponent/ReactComponent.js 83.33% <ø> (ø) ⬆️
src/plugins/usage/UsageTabButton.js 100% <ø> (ø)
scripts/make-webpack-config.js 97.05% <ø> (ø) ⬆️
...rc/rsg-components/Playground/PlaygroundRenderer.js 100% <ø> (ø) ⬆️
src/plugins/code-editor/CodeTabButton.js 100% <ø> (ø)
scripts/schemas/config.js 84% <100%> (+1.39%) ⬆️
loaders/styleguide-loader.js 100% <100%> (ø) ⬆️
src/plugins/usage/index.js 100% <100%> (ø)
src/consts.js 100% <100%> (ø) ⬆️
... and 12 more

@@ -71,21 +71,24 @@ export default class Playground extends Component {
preview={<Preview code={code} evalInContext={evalInContext} />}
tabButtons={
<Slot
name="exampleTabButtons"
name="exampleTabButton"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a more specific name like "playgroundTabButton" would be better here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// { id: 'pizza', render: ({ foo }) => <div>{foo}</div> }
const rendered = fills.map(({ id, render }, index) => {
// Render only specified fill
if (onlyActive && id !== active) {
Copy link
Collaborator

@n1313 n1313 Jun 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I don't fully understand this bit. Can there be a case when active is set, but onlyActive isn't? What would be the intention of it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, tab buttons for example: we need to show all of them but highlight only one.

@@ -9,30 +9,26 @@ export default function Slot({ name, active, onlyActive, className, props = {} }
throw new Error(`Slot "${name}" not found, available slots: ${Object.keys(slots).join(', ')}`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic of this warning is probably incorrect: it warns if an existing slot can't find any fills (which should be totally ok: you could have an extension point but nothing occupying it at the moment), but instead it should warn if a fill requests a slot that doesn't exist.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All slots are empty objects by default, so the check will work (it’s already working in 5.4 ;–)

@n1313
Copy link
Collaborator

n1313 commented Jun 23, 2017

This looks like a great first step. Some parts are a bit unclear to me, like how would a third-party plugin be registered (webpack loaders will have to be reconfigured for this, probably?) and configured, but with a bit of documentation it should be fine.

I think it would be great if styles or classnames were somehow exposed to the plugins, so that they could use them to look like a natural part of the UI. A third-party button plugin will not have easy access to TabButton component and won't be able to easily follow any future changes in RSG UI (or any customizations that an end-user might have). Passing refs for relevant HTML nodes would be useful, too.

@sapegin
Copy link
Member Author

sapegin commented Jun 23, 2017

how would a third-party plugin be registered (webpack loaders will have to be reconfigured for this, probably?) and configured

Like this, via style guide config:

module.exports = {
  plugins: {
    // These are Node module names
    'my-awesome-plugin': true,
    'my-another-plugin': {
      // Some options here
    }
  }
};

I think it would be great if styles or classnames were somehow exposed to the plugins, so that they could use them to look like a natural part of the UI. A third-party button plugin will not have easy access to TabButton component and won't be able to easily follow any future changes in RSG UI (or any customizations that an end-user might have).

Plugins are run in the same webpack context as the Styleguidist UI so you could import rsg-components/TabButton or rsg-componets/Styled to have access to all theme variables.

Passing refs for relevant HTML nodes would be useful, too.

Not sure about that yet ;-)

@n1313
Copy link
Collaborator

n1313 commented Jun 23, 2017

Are plugins going to be loaded in alphabetical order? Should plugins in config be an array?

@sapegin
Copy link
Member Author

sapegin commented Jun 23, 2017

Alphabetical doesn’t make any sense ;-) It should work similar to PostCSS. Or we can make an array, similar to Babel:

module.exports = {
  plugins: [
    // These are Node module names
    'my-awesome-plugin',
    ['my-another-plugin', {
      // Some options here
    }]
  ]
};

@n1313
Copy link
Collaborator

n1313 commented Jun 23, 2017

Yes, I think the babel way is good.

@sapegin
Copy link
Member Author

sapegin commented Jun 23, 2017

Cool, I’m fine with it too — both are familiar for many users.

@MicheleBertoli
Copy link

Well done, @sapegin.
This looks pretty cool, thank you very much.

My only concerns are about the props the plugins' components receive (I'm thinking about Snapguidist).
I'm also missing a lot of context here, so these might be dumb questions:

  • which props do the components receive? different props for each slot?
  • are the actions (e.g. hide/show for docs) pre-defined by the slots?
  • is it possible for components in different slots communicate to each other (I guess is covered by "Store and change state for plugins")?

@sapegin
Copy link
Member Author

sapegin commented Jun 26, 2017

which props do the components receive? different props for each slot?

All fills in a slot receive the same props except active, passed via props prop. Right now they don’t receive much but we just need to pass more ;-)

are the actions (e.g. hide/show for docs) pre-defined by the slots?

There’s no such action ;-) Tabs are using the same Slot component and parent component stores active tab ID in its state and changes it on tab button click.

is it possible for components in different slots communicate to each other (I guess is covered by "Store and change state for plugins")?

It should be possible and most likely will be covered by that, at least partially. But I need more feedback and ideas here ;-| @MicheleBertoli

@n1313
Copy link
Collaborator

n1313 commented Jun 27, 2017

I have played a bit with this branch and it looks promising.

It is easy to start developing a test plugin: just checkout the branch and work in one of the examples. The plugin loader happily accepts local paths so you don't have to touch npm while you develop:

  // in styleguide.config.js
  plugins: {
    //...
    './plugins/test': {
      options: { /* plugin configuration options */ }
    }
  }

The configuration options from above can be accessed in your plugin like this:

// in ./plugins/test/index.js
//...
const testPlugin = (globalConfig, pluginConfig) => {
  // plugin code
}
export default testPlugin;

I haven't tried doing this yet but it looks like you can export multiple components (or "fills") from one plugin module and they will share the module code and state, @MicheleBertoli.

I think that the next steps for this proposal should be:

  1. considering plugin load order and changing plugin list into an array as discussed above.
  2. standardising and implementing consistent "fill addresses": every fill should receive component name, path and markdown block index (if applicable), so that a plugin could be configured to only work on certain components/markdowns. Maybe even going as far as putting this into standard plugin config.
  3. making a decision on what data and methods should be available to plugins, and whether all plugins should receive the same data regardless of their slot. For example, should props data ("usage") be available to toolbar buttons? I think it should be possible to pass this data "inside" a plugin by exporting two fills, one for docsTabButton and one for exampleToolbarButton and then sharing it between them, but is this a good way? Could it be done better?
  4. coming up with a way to change the styles of rsg-components like Playground from plugins. Consider a plugin that switches Playground background color from plain white to striped, or that switches its width from 100% to 320px to simulate mobile view. A hacky way to do this would be via DOM manipulation. A better way would be to give plugins some sort of an API to do that, maybe by passing refs?
  5. considering sharing the "parent rsg-component" to plugins, so that they have easy access to its props and state, and could modify it by setState. One interesting possibility here is if we do this and rewrite rsg-components so that their styles are in their state, then modifying their styles from plugins will be very easy.
  6. improving plugin loader so that class components (class Something extends Component) can be used as fills. Currently this is not supported (only simple functions that export JSX are allowed), or I couldn't understand how to make it work.

@sapegin
Copy link
Member Author

sapegin commented Jun 27, 2017

considering plugin load order and changing plugin list into an array as discussed above.

Feel free to do it if you have time! ;-)

every fill should receive component name, path and markdown block index (if applicable), so that a plugin could be configured to only work on certain components/markdowns. Maybe even going as far as putting this into standard plugin config.

Yup, right now they receive some random data ;-|

making a decision on what data and methods should be available to plugins, and whether all plugins should receive the same data regardless of their slot.

For example, should props data ("usage") be available to toolbar buttons?

If it makes implementation simpler why not?

I think it should be possible to pass this data "inside" a plugin by exporting two fills, one for docsTabButton and one for exampleToolbarButton and then sharing it between them, but is this a good way? Could it be done better?

I think we should try to implement some useful plugins and then we’ll see what work and what doesn’t.

coming up with a way to change the styles of rsg-components like Playground from plugins. Consider a plugin that switches Playground background color from plain white to striped, or that switches its width from 100% to 320px to simulate mobile view. A hacky way to do this would be via DOM manipulation. A better way would be to give plugins some sort of an API to do that, maybe by passing refs?

I think JSS dynamic properties is a way to go here. We’ll need to pass more data to some component and make plugins be able to pass styles.

considering sharing the "parent rsg-component" to plugins, so that they have easy access to its props and state, and can modify it by setState.

Something like that will be necessary. I’d rather pass state and setState implicitly than the whole this.

improving plugin loader so that class components (class Something extends Component) can be used as fills. Currently this is not supported (only simple functions that export JSX are allowed), or I couldn't understand how to make it work.

It supposed to work already, I’ve probably missed something ;-)

@n1313
Copy link
Collaborator

n1313 commented Jun 27, 2017

It supposed to work already

loadPlugins.js expects render to be a function, so it complains if I export a component there. How should it be done?

@sapegin
Copy link
Member Author

sapegin commented Jun 27, 2017

But this is a check for a plugin itself, not for a fill. I don’t see any checks for fills.

@n1313
Copy link
Collaborator

n1313 commented Jun 27, 2017

...right. Huh. I guess I'm doing something wrong somewhere else.

@sapegin
Copy link
Member Author

sapegin commented Jun 27, 2017

It may be me I just don’t see where yet ;-)

@n1313
Copy link
Collaborator

n1313 commented Jun 27, 2017

What do you think about swapping the order of arguments in plugin constructor? Now it is globalConfig, pluginConfig, I propose pluginConfig, globalConfig. I think most people will be more interested in pluginConfig and not globalConfig.

@sapegin
Copy link
Member Author

sapegin commented Jun 27, 2017

Maybe, I don’t have a strong preference ;-)

@n1313
Copy link
Collaborator

n1313 commented Jun 27, 2017

@sapegin, I've added two commits to change the order of arguments and change the object into array.

type: 'object',
default: {},
type: 'array',
default: [],
process: val =>
CORE_PLUGINS.reduce((allPlugins, plugin) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think now just concat should be enough ;-)

@sapegin
Copy link
Member Author

sapegin commented Nov 10, 2017

I’ve merged the master branch here, going to add a few more things and release with 6.1.0. It will be very alpha but you’ll be able to implement some very basic plugins.

@sapegin
Copy link
Member Author

sapegin commented Nov 10, 2017

I’ve also fixed the CI accidentally ;-)

@sapegin
Copy link
Member Author

sapegin commented Nov 13, 2017

@isratc That’s an interesting way of managing open source ;-)

I’m planning this for 6.1.0 which depends on several PRs so I can’t say anything more that ”soonish”. Could you share your use case? Is there any missing features? It’s very very limited for now. In any case you can try to write your plugin using this branch — your feedback will be very valuable.

@sapegin
Copy link
Member Author

sapegin commented Nov 15, 2017

@isratc I’m not sure what you’re trying to do and how is it related to Styleguidist plugins API. Please open a new issue and make a demo project that we can debug based on this repo: https://github.com/styleguidist/example

@sapegin sapegin modified the milestones: 6.1.0, 6.2.0 Dec 19, 2017
@sapegin sapegin removed this from the 6.2.0 milestone Jan 10, 2018
@jspears
Copy link
Contributor

jspears commented Apr 25, 2018

You might consider using the plugin system from MrBuilder
mr-builder . It has babel style plugins, but also (optional) autoinstall of plugins, command line configuration, env configuration and config file configuration. It was designed to be a generic plugin system, and is also used in the next version of subschema to allow for injection of components.

@tuchk4
Copy link

tuchk4 commented Aug 3, 2018

@sapegin any plans to merge it?

@sapegin
Copy link
Member Author

sapegin commented Aug 3, 2018

I'll need some help to make it ready, even as an MVP.

@stale
Copy link

stale bot commented Dec 28, 2018

😴 This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week without any further activity. Thank you for your contributions.

@stale stale bot added the wontfix label Dec 28, 2018
@sapegin sapegin self-assigned this Dec 28, 2018
@stale stale bot closed this Jan 4, 2019
@mehrad77
Copy link

mehrad77 commented Feb 3, 2020

Oh :( sad to see this MR is closed

@sapegin sapegin deleted the pluginapi branch November 11, 2020 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants