跳到主要内容

生命周期 API

在构建过程中,插件并行加载以获取自身内容并将其渲染为路由。插件还可以配置 webpack 或对生成的文件进行后处理。

async loadContent()

插件应使用此生命周期从数据源(文件系统、远程 API、无头 CMS 等)获取数据或进行一些服务器端处理。返回值是它需要的内容。

例如,下面的插件返回一个 1 到 10 之间的随机整数作为内容。

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'docusaurus-plugin',
async loadContent() {
return 1 + Math.floor(Math.random() * 10);
},
};
}

async contentLoaded({content, actions})

loadContent 中加载的数据将在 contentLoaded 中被消费。它可以被渲染为路由、注册为全局数据等。

content

contentLoaded 将在 loadContent 完成后被调用。loadContent() 的返回值将作为 content 传递给 contentLoaded

actions

actions 包含三个函数:

addRoute(config: RouteConfig): void

创建要添加到网站的路由。

export type RouteConfig = {
/**
* 带有前导斜杠。尾随斜杠将由配置标准化。
*/
path: string;
/**
* 用于渲染此路由的组件,打包器可以 `require` 的路径。
*/
component: string;
/**
* 属性。每个条目应为 `[propName]: pathToPropModule`(使用 `createData` 创建)
*/
modules?: RouteModules;
/**
* 路由上下文将包装 `component`。使用 `useRouteContext` 检索此处声明的内容。
* 请注意,此处声明的所有自定义路由上下文都将在 {@link RouteContext.data} 下命名空间化。
*/
context?: RouteModules;
/**
* 嵌套路由配置,对于具有子路由的"布局路由"很有用。
*/
routes?: RouteConfig[];
/**
* React 路由器配置选项:`exact` 路由不会匹配子路由。
*/
exact?: boolean;
/**
* React 路由器配置选项:`strict` 路由对尾随斜杠的存在敏感。
*/
strict?: boolean;
/**
* 用于对路由进行排序。
* 优先级较高的路由将首先匹配。
*/
priority?: number;
/**
* 可选的路由元数据
*/
metadata?: RouteMetadata;
/**
* 额外的属性;将在客户端可用。
*/
[propName: string]: unknown;
};

/**
* 插件作者可以为创建的路由分配额外的元数据
* 它仅在 Node.js 端可用,不会发送到浏览器
* 可选:鼓励但不要求插件作者提供它
*
* 一些插件可能使用这些数据提供额外的功能。
* 站点地图插件就是这种情况,用于支持"lastmod"。
* 另请参见:https://github.com/facebook/docusaurus/pull/9954
*/
export type RouteMetadata = {
/**
* 导致创建当前路由的源代码文件路径
* 在官方内容插件中,这通常是 Markdown 或 React 文件
* 此路径预期相对于站点目录
*/
sourceFilePath?: string;
/**
* 此路由的最后更新日期
* 这通常从 sourceFilePath 的 Git 历史中读取
* 但也可以通过其他方式提供(通常通过前置内容)
*
* 这主要是为了为站点地图插件添加"lastmod"支持而引入的
* 请参见 https://github.com/facebook/docusaurus/pull/9954
*/
lastUpdatedAt?: number;
};

type RouteModules = {
[module: string]: Module | RouteModules | RouteModules[];
};

type Module =
| {
path: string;
__import?: boolean;
query?: ParsedUrlQueryInput;
}
| string;

createData(name: string, data: any): Promise<string>

一个声明性回调,用于创建静态数据(通常是 JSON 或字符串),稍后可以作为属性提供给路由。接受文件名和要存储的数据,并返回实际数据文件的路径。

例如,下面的插件创建了一个 /friends 页面,显示 Your friends are: Yangshun, Sebastien

website/src/components/Friends.js
import React from 'react';

export default function FriendsComponent({friends}) {
return <div>Your friends are {friends.join(',')}</div>;
}
docusaurus-friends-plugin/src/index.js
export default function friendsPlugin(context, options) {
return {
name: 'docusaurus-friends-plugin',
async contentLoaded({content, actions}) {
const {createData, addRoute} = actions;
// 创建 friends.json
const friends = ['Yangshun', 'Sebastien'];
const friendsJsonPath = await createData(
'friends.json',
JSON.stringify(friends),
);

// 添加 '/friends' 路由,并确保接收 friends 属性
addRoute({
path: '/friends',
component: '@site/src/components/Friends.js',
modules: {
// propName -> JSON 文件路径
friends: friendsJsonPath,
},
exact: true,
});
},
};
}

setGlobalData(data: any): void

此函数允许创建一些全局插件数据,可以从任何页面读取,包括其他插件创建的页面和主题布局。

这些数据可以通过 useGlobalDatausePluginData 钩子在客户端/主题代码中访问。

注意

全局数据是全局的:其大小会影响站点所有页面的加载时间,因此请尽量保持其小。尽可能使用 createData 和页面特定的数据。

例如,下面的插件创建了一个 /friends 页面,显示 Your friends are: Yangshun, Sebastien

website/src/components/Friends.js
import React from 'react';
import {usePluginData} from '@docusaurus/useGlobalData';

export default function FriendsComponent() {
const {friends} = usePluginData('docusaurus-friends-plugin');
return <div>Your friends are {friends.join(',')}</div>;
}
docusaurus-friends-plugin/src/index.js
export default function friendsPlugin(context, options) {
return {
name: 'docusaurus-friends-plugin',
async contentLoaded({content, actions}) {
const {setGlobalData, addRoute} = actions;
// Create friends global data
setGlobalData({friends: ['Yangshun', 'Sebastien']});

// Add the '/friends' routes
addRoute({
path: '/friends',
component: '@site/src/components/Friends.js',
exact: true,
});
},
};
}

configureWebpack(config, isServer, utils, content)

Modifies the internal webpack config. If the return value is a JavaScript object, it will be merged into the final config using webpack-merge. If it is a function, it will be called and receive config as the first argument and an isServer flag as the second argument.

注意

The API of configureWebpack will be modified in the future to accept an object (configureWebpack({config, isServer, utils, content}))

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

configureWebpack also receives an util object:

  • getStyleLoaders(isServer: boolean, cssOptions: {[key: string]: any}): Loader[]
  • getJSLoader(isServer: boolean, cacheOptions?: {}): Loader | null

You may use them to return your webpack configuration conditionally.

For example, this plugin below modify the webpack config to transpile .foo files.

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'custom-docusaurus-plugin',
configureWebpack(config, isServer, utils) {
const {getJSLoader} = utils;
return {
module: {
rules: [
{
test: /\.foo$/,
use: [getJSLoader(isServer), 'my-custom-webpack-loader'],
},
],
},
};
},
};
}

content

configureWebpack will be called both with the content loaded by the plugin.

Merge strategy

We merge the Webpack configuration parts of plugins into the global Webpack config using webpack-merge.

It is possible to specify the merge strategy. For example, if you want a webpack rule to be prepended instead of appended:

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'custom-docusaurus-plugin',
configureWebpack(config, isServer, utils) {
return {
mergeStrategy: {'module.rules': 'prepend'},
module: {rules: [myRuleToPrepend]},
};
},
};
}

Read the webpack-merge strategy doc for more details.

Configuring dev server

The dev server can be configured through returning a devServer field.

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'custom-docusaurus-plugin',
configureWebpack(config, isServer, utils) {
return {
devServer: {
open: '/docs', // Opens localhost:3000/docs instead of localhost:3000/
},
};
},
};
}

configurePostCss(options)

Modifies postcssOptions of postcss-loader during the generation of the client bundle.

Should return the mutated postcssOptions.

By default, postcssOptions looks like this:

const postcssOptions = {
ident: 'postcss',
plugins: [require('autoprefixer')],
};

Example:

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'docusaurus-plugin',
configurePostCss(postcssOptions) {
// Appends new PostCSS plugin.
postcssOptions.plugins.push(require('postcss-import'));
return postcssOptions;
},
};
}

postBuild(props)

Called when a (production) build finishes.

interface Props {
siteDir: string;
generatedFilesDir: string;
siteConfig: DocusaurusConfig;
outDir: string;
baseUrl: string;
headTags: string;
preBodyTags: string;
postBodyTags: string;
routesPaths: string[];
routesBuildMetadata: {[location: string]: {noIndex: boolean}};
plugins: Plugin<any>[];
content: Content;
}

Example:

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'docusaurus-plugin',
async postBuild({siteConfig = {}, routesPaths = [], outDir}) {
// Print out to console all the rendered routes.
routesPaths.map((route) => {
console.log(route);
});
},
};
}

injectHtmlTags({content})

Inject head and/or body HTML tags to Docusaurus generated HTML.

injectHtmlTags will be called both with the content loaded by the plugin.

function injectHtmlTags(): {
headTags?: HtmlTags;
preBodyTags?: HtmlTags;
postBodyTags?: HtmlTags;
};

type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[];

type HtmlTagObject = {
/**
* Attributes of the HTML tag
* E.g. `{'disabled': true, 'value': 'demo', 'rel': 'preconnect'}`
*/
attributes?: {
[attributeName: string]: string | boolean;
};
/**
* The tag name e.g. `div`, `script`, `link`, `meta`
*/
tagName: string;
/**
* The inner HTML
*/
innerHTML?: string;
};

Example:

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'docusaurus-plugin',
loadContent: async () => {
return {remoteHeadTags: await fetchHeadTagsFromAPI()};
},
injectHtmlTags({content}) {
return {
headTags: [
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://www.github.com',
},
},
...content.remoteHeadTags,
],
preBodyTags: [
{
tagName: 'script',
attributes: {
charset: 'utf-8',
src: '/noflash.js',
},
},
],
postBodyTags: [`<div> This is post body </div>`],
};
},
};
}

Tags will be added as follows:

  • headTags will be inserted before the closing </head> tag after scripts added by config.
  • preBodyTags will be inserted after the opening <body> tag before any child elements.
  • postBodyTags will be inserted before the closing </body> tag after all child elements.

getClientModules()

Returns an array of paths to the client modules that are to be imported into the client bundle.

As an example, to make your theme load a customCss or customJs file path from options passed in by the user:

my-theme/src/index.js
export default function (context, options) {
const {customCss, customJs} = options || {};
return {
name: 'name-of-my-theme',
getClientModules() {
return [customCss, customJs];
},
};
}