We've implemented a basic version of plugins, themes and presets and would like to get the opinion from the community regarding the API and some design decisions before proceeding further.
Feel free to check out the packages within https://github.com/facebook/Docusaurus/tree/master/packages to get a sense of what has been done.
Glossary
Plugins
A plugin is a package that exports a class which can be instantiated with configurable options (provided by the user) and its various methods will be invoked by the Docusaurus runtime. Examples of plugin lifecycle methods are:
loadContent - Plugins should fetch from data sources (filesystem, remote API, etc)
contentLoaded - Plugins should use the data loaded in loadContent and construct the pages/routes that consume the data
configureWebpack - To extend the webpack config via webpack-merge.
For example, the in docusaurus-plugin-content-docs:
- In
loadContent, it loads the doc Markdown files based on the specified directory in options (defaulting to docs).
- In
contentLoaded, for each doc Markdown file, a route is created: /doc/installation, /doc/getting-started, etc.
Plugin design is very similar to Gatsby and VuePress, but most plugins work like that anyway The main difference here is that Docusaurus plugins currently doesn't allow including other plugins. If plugins are meant to used together, it would have to be done using presets.
In most cases, plugins are there to fetch data and create routes. A plugin could take in components as part of its options and to act as the wrapper for the page.
Themes
Themes are plugins with exactly the same lifecycle methods, but they mostly exist to add component aliases by extending the webpack config, and run after all existing plugins. The reason for the differentiation is so that by running after the pure plugins, the webpack resolve aliases that the theme sets will take precedence.
How Theming is Done
Users can install themes, that provide a set of components, e.g. Navbar, Layout, Footer. Themes are plugins but most (all?) of them shouldn't be using the loadContent lifecyle.
Users can use these components in their code by importing them using the @theme webpack alias:
import Navbar from '@theme/Navbar';
The alias @theme can refer to a few directories, in the following priority:
- A user's
website/theme directory, which is a special directory that has the higher precedence.
- A Docusaurus theme packages's
theme directory.
- Fallback components provided by Docusaurus core (usually not needed).
Given the following structure
website
├── node_modules
│ └── docusaurus-theme
│ └── theme
│ └── Navbar.js
└── theme
└── Navbar.js
website/theme/Navbar.js takes precedence whenever @theme/Navbar is imported. This behavior is called component swizzling. In iOS, method swizzling is the process of changing the implementation of an existing selector (method). In the context of a website, component swizzling will mean providing an alternative component that takes precedence over the theme-provided component.
The default Docusaurus theme will provide most components out of the box, and users can swizzle the components by adding a component of the same name in the website/themes directory if they want to modify how it looks and behaves.
Themes are for providing UI components to present the content. Most content plugins would need to be paired with a theme in order to be actually useful. The UI is a separate layer from the data schema, so it makes it easy to swap out the themes for other designs (if someone wants to use Bootstrap for example).
A default Docusaurus blog would have in the config:
// docusaurus.config.js
{
theme: ['theme-blog'],
plugins: ['plugin-content-blog'],
}
and if someone wants to use Bootstrap styling:
// docusaurus.config.js
{
theme: ['theme-blog-bootstrap'],
plugins: ['plugin-content-blog'],
}
The content plugin remains the same and the only thing they need to change will be the theme.
Presets
A package that exports a config file which can contain a set of themes, plugins, and potentially other presets (TBD). The purpose of presets is to bundle themes and plugins together so that they can be easily shared and upgraded.
RFC
- Should presets be able to contain other presets?
I was originally envisioning this sort of hierarchy if we could have presets within presets.
preset-classic
├── theme-classic
│ ├── Navbar.js
│ ├── Layout.js
│ └── Footer.js
├── preset-docs
│ ├── theme-docs
│ │ ├── DocPage.js
│ │ └── DocItem.js
│ └── plugin-content-docs
│ └── index.js
├── preset-blog
│ ├── theme-blog
│ │ ├── BlogPage.js
│ │ └── BlogItem.js
│ └── plugin-content-blog
│ └── index.js
└── plugin-pages
└── index.js
plugin-content-blog reads the data and creates routes, and theme-blog provide the components for the routes to use.
And if a user simply wants to use a blog, they just have to install preset-blog instead of having to install both theme and plugin separately.
However, this simpler hierarchy could also work:
preset-classic
├── theme-classic
│ ├── Navbar.js
│ ├── Layout.js
│ └── Footer.js
├── plugin-docs
│ ├── theme
│ │ ├── DocPage.js
│ │ └── DocItem.js
│ └── index.js
├── plugin-blog
│ ├── theme
│ │ ├── BlogPage.js
│ │ └── BlogItem.js
│ └── index.js
└── plugin-pages
└── index.js
The difference here is that plugins can also provide some theme components. However, this blurs the lines between plugins and themes which might not be good (should I now provide components in a plugin or a theme?). I think having a clear separation is great for discoverability of new plugins and communicating what exactly a package does.
- Should themes be allowed to include sub-themes and plugins be allowed to include sub-plugins? I feel that allowing this will encourage recursive nesting of plugins and themes which could end up being really messy. By allowing theme/plugin composition via presets, the hierarchy stays relatively flat.
cc frequent Docusaurus users - @JoelMarcey @jaredpalmer @jordwalke @SimenB @chenglou @microbouji @hzoo @markerikson
We've implemented a basic version of plugins, themes and presets and would like to get the opinion from the community regarding the API and some design decisions before proceeding further.
Feel free to check out the packages within https://github.com/facebook/Docusaurus/tree/master/packages to get a sense of what has been done.
Glossary
Plugins
A plugin is a package that exports a class which can be instantiated with configurable options (provided by the user) and its various methods will be invoked by the Docusaurus runtime. Examples of plugin lifecycle methods are:
loadContent- Plugins should fetch from data sources (filesystem, remote API, etc)contentLoaded- Plugins should use the data loaded inloadContentand construct the pages/routes that consume the dataconfigureWebpack- To extend the webpack config viawebpack-merge.For example, the in
docusaurus-plugin-content-docs:loadContent, it loads the doc Markdown files based on the specified directory in options (defaulting todocs).contentLoaded, for each doc Markdown file, a route is created:/doc/installation,/doc/getting-started, etc.Plugin design is very similar to Gatsby and VuePress, but most plugins work like that anyway The main difference here is that Docusaurus plugins currently doesn't allow including other plugins. If plugins are meant to used together, it would have to be done using presets.
In most cases, plugins are there to fetch data and create routes. A plugin could take in components as part of its options and to act as the wrapper for the page.
Themes
Themes are plugins with exactly the same lifecycle methods, but they mostly exist to add component aliases by extending the webpack config, and run after all existing plugins. The reason for the differentiation is so that by running after the pure plugins, the webpack resolve aliases that the theme sets will take precedence.
How Theming is Done
Users can install themes, that provide a set of components, e.g.
Navbar,Layout,Footer. Themes are plugins but most (all?) of them shouldn't be using theloadContentlifecyle.Users can use these components in their code by importing them using the
@themewebpack alias:The alias
@themecan refer to a few directories, in the following priority:website/themedirectory, which is a special directory that has the higher precedence.themedirectory.Given the following structure
website/theme/Navbar.jstakes precedence whenever@theme/Navbaris imported. This behavior is called component swizzling. In iOS, method swizzling is the process of changing the implementation of an existing selector (method). In the context of a website, component swizzling will mean providing an alternative component that takes precedence over the theme-provided component.The default Docusaurus theme will provide most components out of the box, and users can swizzle the components by adding a component of the same name in the
website/themesdirectory if they want to modify how it looks and behaves.Themes are for providing UI components to present the content. Most content plugins would need to be paired with a theme in order to be actually useful. The UI is a separate layer from the data schema, so it makes it easy to swap out the themes for other designs (if someone wants to use Bootstrap for example).
A default Docusaurus blog would have in the config:
and if someone wants to use Bootstrap styling:
The content plugin remains the same and the only thing they need to change will be the theme.
Presets
A package that exports a config file which can contain a set of themes, plugins, and potentially other presets (TBD). The purpose of presets is to bundle themes and plugins together so that they can be easily shared and upgraded.
RFC
I was originally envisioning this sort of hierarchy if we could have presets within presets.
plugin-content-blogreads the data and creates routes, andtheme-blogprovide the components for the routes to use.And if a user simply wants to use a blog, they just have to install
preset-bloginstead of having to install both theme and plugin separately.However, this simpler hierarchy could also work:
The difference here is that plugins can also provide some theme components. However, this blurs the lines between plugins and themes which might not be good (should I now provide components in a plugin or a theme?). I think having a clear separation is great for discoverability of new plugins and communicating what exactly a package does.
cc frequent Docusaurus users - @JoelMarcey @jaredpalmer @jordwalke @SimenB @chenglou @microbouji @hzoo @markerikson