docs(v2): document plugins and lifecycle APIs (#1724)

* wip docs(v2): lifecycle apis

* docs(v2): edit advanced plugins

* wip plugins & lifecycle api docs

* wip plugins doc

* Update advanced-plugins.md

* misc: update plugins docs

* misc: remove excessive line
This commit is contained in:
Wei Gao 2019-10-08 15:23:03 +08:00 committed by Yangshun Tay
parent c4cc7f881b
commit 4ce1b582cf
3 changed files with 190 additions and 31 deletions

View file

@ -3,34 +3,72 @@ id: advanced-plugins
title: Plugins
---
In this doc, we talk about the design intention of plugins, the lifecycle methods, how you may write your own plugins, etc.
A plugin is a package that exports a class which can be instantiated with configurable options (provided by the user) and its various lifecycle methods will be invoked by the Docusaurus runtime.
Plugins are one of the best ways to add functionality to our Docusaurus. Plugins allow third-party developers to extend or modify the default functionality that Docusaurus provides.
Docusaurus Plugins are very similar to [Gatsby Plugins](https://www.gatsbyjs.org/plugins/) and [VuePress Plugins](https://v1.vuepress.vuejs.org/plugin/)<!-- TODO: is this the correct link? -->. The main difference here is that Docusaurus plugins don't allow using other plugins. Docusaurus provides [presets](./presets.md) for the use scenarios for plugins that are meant to work together.
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.
## How to create plugins
_This section is a work in progress._
<!--
outline:
- jump start a plugin
- refer to lifecycle APIs
- describe mindset how plugins should work
Plugins are modules which export a function that takes in the context, options and returns a plain JavaScript object that has some properties defined.
<!-- TODO
- move the list of plugins (maybe to links to each plugin's READMEs)
- add guides on how to create plugins
-->
In this doc, we talk about the design intention of plugins and how you may write your own plugins.
Docusaurus Plugins are very similar to [Gatsby Plugins](https://www.gatsbyjs.org/plugins/) and [VuePress Plugins](https://v1.vuepress.vuejs.org/plugin/). The main difference here is that Docusaurus plugins don't allow using other plugins. Docusaurus provides [presets](./advanced-presets.md) to bundle plugins that are meant to work together.
## Plugins design
Docusaurus' implementation of the plugins system provides us with a convenient way to hook into the website's lifecycle to modify what goes on during development/build, which involves (but not limited to) extending the webpack config, modifying the data being loaded and creating new components to be used in a page.
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. A plugin can also provide React components to be used together with the non-UI functionality. You can also specify a resolution rule for the plugin to find its components to call, which you then supply with a [theme](./advanced-themes.md).
## Creating plugins
A plugin is a module which exports a function that takes two parameters and returns an object when executed.
### Module definition
The exported modules for plugins are called with two parameters: `context` and `options` and returns a JavaScript object with defining the [lifecycle APIs](./lifecycle-apis.md).
```js
// Example contents of a Docusaurus plugin.
module.exports = function(context, options) {
// ...
return {
name: 'my-docusaurus-plugin',
async loadContent() { ... },
async contentLoaded({content, actions}) { ... },
...
};
};
```
#### `context`
`context` is plugin-agnostic and the same object will be passed into all plugins used for a Docusaurus website. The `context` object contains the following fields:
```js
interface LoadContext {
siteDir: string;
generatedFilesDir: string;
siteConfig: DocusaurusConfig;
cliOptions: CLIOptions;
outDir: string;
baseUrl: string;
}
interface CLIOptions {
[option: string]: any;
}
```
#### `options`
`options` are the [second optional parameter when the plugins are used](./using-plugins.md#configuring-plugins). `options` is plugin-specific and are specified by the user when they use it in `docusaurus.config.js` or if preset contains the plugin. The preset will then be in-charge of passing the correct options into the plugin. It is up to individual plugins to define what options it takes.
#### Return value
The returned object value should implement the [lifecycle APIs](./lifecycle-apis.md).
## Official plugins
List of [official plugins](https://github.com/facebook/docusaurus/tree/master/packages) created by Docusaurus.
Find the list of official Docusaurus plugins [here](https://github.com/facebook/docusaurus/tree/master/packages).
### `@docusaurus/plugin-content-blog`
@ -182,7 +220,7 @@ module.exports = {
googleAnalytics: {
trackingID: 'UA-141789564-1',
},
}
},
};
```
@ -206,7 +244,7 @@ module.exports = {
gtag: {
trackingID: 'UA-141789564-1',
},
}
},
};
```

View file

@ -5,7 +5,7 @@ title: Themes
In this doc, we discuss how themes are designed and how you can write your own themes.
## Theme design
## Themes design
While themes share the exact same lifecycle methods with plugins, their implementations can look very different from those of plugins based on themes' designed objectives.

View file

@ -7,11 +7,128 @@ _This section is a work in progress._
Lifecycle APIs are shared by Themes and Plugins.
<!-- TODO: explain lifecycle methods -->
## `getPathsToWatch(): string[]`
- `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.
Specifies the paths to watch for plugins and themes. The paths are watched by the dev server so that the plugin lifecycles are reloaded when contents in the watched paths change. Note that the plugins and themes modules are initially called with `context` and `options` from Node, which you may use to find the necessary directory information about the site.
```js
const contentPath = path.resolve(context.siteDir, options.path);
getPathsToWatch() {
const {include = []} = options;
const globPattern = include.map(pattern => `${contentPath}/${pattern}`);
return [...globPattern];
}
```
## `async loadContent()`
Plugins should use this lifecycle to fetch from data sources (filesystem, remote API, headless CMS, etc).
## `async contentLoaded({content, actions})`
Plugins should use the data loaded in `loadContent` and construct the pages/routes that consume the loaded data.
### `content`
`contentLoaded` will be called _after_ `loadContent` is done, the return value of `loadContent()` will be passed to `contentLoaded` as `content`.
### `actions`
`actions` contain two functions:
- `addRoute(config: RouteConfig): void`
- `createData(name: string, data: Object): Promise<string>`
where `RouteConfig` is an object with the necessary data to configure a route to add to the website:
```js
interface RouteConfig {
path: string;
component: string;
modules?: RouteModule;
routes?: RouteConfig[];
exact?: boolean;
}
interface RouteModule {
[module: string]: Module | RouteModule | RouteModule[];
}
```
Example `addRoute` call:
```js
addRoute({
path: permalink,
component: blogPostComponent,
exact: true,
modules: {
content: source,
metadata: metadataPath,
prevItem: prevItem && prevItem.metadataPath,
nextItem: nextItem && nextItem.metadataPath,
},
});
```
And `createData` takes a file name relative to to your plugin's directory, a string for the `JSON.stringify` result of your data, and will return a path to the module which you may then use as the path to items in your `RouteModule`. The modules will be loaded when the related pages are loaded following our optimizations according to the [PRPL pattern](https://developers.google.com/web/fundamentals/performance/prpl-pattern/).
## `configureWebpack(config, isServer, utils)`
Modifies the internal webpack config. If the return value is a JavaScript object, it will be merged into the final config using [`webpack-merge`](https://github.com/survivejs/webpack-merge). If it is a function, it will be called and receive `config` as the first argument and an `isServer` flag as the argument argument.
### `config`
`configureWebpack` is called with `config` generated according to client/server build. You may treat this as the base config to be merged with.
### `isServer`
`configureWebpack` will be called both in server build and in client build. The server build receives `true` and the client build receives `false` as `isServer`.
### `utils`
The initial call to `configureWebpack` also receives a util object consists of three functions:
- `getStyleLoaders(isServer: boolean, cssOptions: {[key: string]: any}): Loader[]`
- `getCacheLoader(isServer: boolean, cacheOptions?: {}): Loader | null`
- `getCacheLoader(isServer: boolean, cacheOptions?: {}): Loader | null`
You may use them to return your webpack configures conditionally.
Example:
```js
configureWebpack(config, isServer, {getBabelLoader, getCacheLoader}) {
const {rehypePlugins, remarkPlugins, truncateMarker} = options;
return {
module: {
rules: [
{
test: /(\.mdx?)$/,
use: [
getCacheLoader(isServer),
getBabelLoader(isServer),
{
loader: '@docusaurus/mdx-loader',
options: {
remarkPlugins,
rehypePlugins,
},
},
{
loader: path.resolve(__dirname, './markdownLoader.js'),
options: {
truncateMarker,
},
},
].filter(Boolean),
},
],
},
};
},
```
<!--
For example, the in docusaurus-plugin-content-docs:
@ -20,6 +137,10 @@ For example, the in docusaurus-plugin-content-docs:
In contentLoaded, for each doc Markdown file, a route is created: /doc/installation, /doc/getting-started, etc.
-->
## Example
Mind model for a presumptuous plugin implementation.
```jsx
const DEFAULT_OPTIONS = {
// Some defaults.