跳到主要内容

生命周期 API

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

async loadContent()

插件应在此生命周期中从数据源(如文件系统、远程 API、Headless 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;
/**
* 用于渲染该路由的组件,需为 bundler 可 require 的路径。
*/
component: string;
/**
* 组件 props。每一项应为 `[propName]: pathToPropModule`(由 `createData` 创建)
*/
modules?: RouteModules;
/**
* 路由上下文会包裹 `component`。可通过 `useRouteContext` 获取此处声明的内容。所有自定义路由上下文会被命名空间化到 {@link RouteContext.data} 下。
*/
context?: RouteModules;
/**
* 嵌套路由配置,适用于有子路由的"布局路由"。
*/
routes?: RouteConfig[];
/**
* React router 配置项:`exact` 路由不会匹配子路由。
*/
exact?: boolean;
/**
* React router 配置项:`strict` 路由对结尾斜杠敏感。
*/
strict?: boolean;
/**
* 用于路由排序。
* 优先级高的路由会优先匹配。
*/
priority?: number;
/**
* 可选路由元数据
*/
metadata?: RouteMetadata;
/**
* 额外 props,会在客户端可用。
*/
[propName: string]: unknown;
};

/**
* 插件作者可为创建的路由分配额外元数据
* 仅在 Node.js 侧可用,不会发送到浏览器
* 可选:鼓励插件作者提供,但不是强制要求
*
* 某些插件可能会用到这些数据以提供额外功能。
* 例如 sitemap 插件用于支持"lastmod"。
* 参考:https://github.com/facebook/docusaurus/pull/9954
*/
export type RouteMetadata = {
/**
* 当前路由对应的源代码文件路径
* 官方内容插件通常为 Markdown 或 React 文件
* 路径应为相对于站点目录的相对路径
*/
sourceFilePath?: string;
/**
* 路由的最后更新时间
* 通常通过 sourceFilePath 的 Git 历史获取
* 也可通过其他方式(如 front matter)提供
*
* 该字段主要用于 sitemap 插件的"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 或字符串),可作为 props 提供给你的路由。接收文件名和要存储的数据,返回实际数据文件的路径。

例如,下面这个插件会创建一个 /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 props
addRoute({
path: '/friends',
component: '@site/src/components/Friends.js',
modules: {
// propName -> JSON 文件路径
friends: friendsJsonPath,
},
exact: true,
});
},
};
}

setGlobalData(data: any): void

该函数允许你创建全局插件数据,可在任意页面(包括其他插件创建的页面和主题布局)读取。

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

注意

全局数据是全局的:其体积会影响所有页面的加载速度,请尽量保持精简。优先使用 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;
// 创建 friends 全局数据
setGlobalData({friends: ['Yangshun', 'Sebastien']});

// 添加 '/friends' 路由
addRoute({
path: '/friends',
component: '@site/src/components/Friends.js',
exact: true,
});
},
};
}

configureWebpack(config, isServer, utils, content)

修改内部 webpack 配置。如果返回值为 JavaScript 对象,则会通过 webpack-merge 合并进最终配置;如果为函数,则会接收 config 作为第一个参数,isServer 作为第二个参数。

注意

configureWebpack 的 API 未来会调整为接收对象参数(configureWebpack({config, isServer, utils, content})

config

configureWebpack 会接收根据客户端/服务端构建生成的 config。你可以将其视为待合并的基础配置。

isServer

configureWebpack 会在服务端和客户端构建时都被调用。服务端构建时 isServer 为 true,客户端构建时为 false。

utils

configureWebpack 还会接收一个工具对象:

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

你可以根据需要使用这些工具返回你的 webpack 配置。

例如,下面这个插件会修改 webpack 配置以编译 .foo 文件:

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 也会接收到插件加载的内容。

合并策略

我们会使用 webpack-merge 将插件的 Webpack 配置合并到全局配置中。

你可以指定合并策略。例如,如果你希望某条 webpack 规则前置而不是追加:

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]},
};
},
};
}

更多细节请参考 webpack-merge 策略文档

配置开发服务器

你可以通过返回 devServer 字段配置开发服务器。

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'custom-docusaurus-plugin',
configureWebpack(config, isServer, utils) {
return {
devServer: {
open: '/docs', // 启动时自动打开 localhost:3000/docs 而不是 localhost:3000/
},
};
},
};
}

configurePostCss(options)

用于修改客户端 bundle 生成过程中的 postcss-loaderpostcssOptions

应返回修改后的 postcssOptions

默认情况下,postcssOptions 如下:

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

示例:

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'docusaurus-plugin',
configurePostCss(postcssOptions) {
// 添加新的 PostCSS 插件
postcssOptions.plugins.push(require('postcss-import'));
return postcssOptions;
},
};
}

postBuild(props)

在(生产环境)构建完成后调用。

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;
}

示例:

docusaurus-plugin/src/index.js
export default function (context, options) {
return {
name: 'docusaurus-plugin',
async postBuild({siteConfig = {}, routesPaths = [], outDir}) {
// 控制台输出所有渲染的路由
routesPaths.map((route) => {
console.log(route);
});
},
};
}

injectHtmlTags({content})

向道格龙生成的 HTML 注入 head 和/或 body 标签。

injectHtmlTags 会接收插件加载的内容。

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

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

type HtmlTagObject = {
/**
* HTML 标签的属性
* 例如:`{'disabled': true, 'value': 'demo', 'rel': 'preconnect'}`
*/
attributes?: {
[attributeName: string]: string | boolean;
};
/**
* 标签名,例如 `div`、`script`、`link`、`meta`
*/
tagName: string;
/**
* 标签内部 HTML
*/
innerHTML?: string;
};

示例:

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>`],
};
},
};
}

标签会按如下方式插入:

  • headTags 会在 config 添加的脚本后、</head> 标签前插入。
  • preBodyTags 会在 <body> 标签打开后、所有子元素前插入。
  • postBodyTags 会在所有子元素后、</body> 标签前插入。

getClientModules()

返回需要导入到客户端 bundle 的客户端模块路径数组。

例如,你可以让主题根据用户传入的 options 加载 customCsscustomJs 文件路径:

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