feat: async plugin creator functions (#6166)

This commit is contained in:
Sébastien Lorber 2021-12-22 19:10:49 +01:00 committed by GitHub
parent f8a670966e
commit b393700a61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 90 additions and 79 deletions

View file

@ -103,7 +103,7 @@ describe('loadBlog', () => {
posixPath(path.relative(siteDir, p)), posixPath(path.relative(siteDir, p)),
); );
expect(relativePathsToWatch).toEqual([ expect(relativePathsToWatch).toEqual([
'blog/authors.yml', 'i18n/en/docusaurus-plugin-content-blog/authors.yml',
'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}', 'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}',
'blog/**/*.{md,mdx}', 'blog/**/*.{md,mdx}',
]); ]);

View file

@ -51,11 +51,12 @@ import {
} from './blogUtils'; } from './blogUtils';
import {BlogPostFrontMatter} from './blogFrontMatter'; import {BlogPostFrontMatter} from './blogFrontMatter';
import {createBlogFeedFiles} from './feed'; import {createBlogFeedFiles} from './feed';
import {getAuthorsMapFilePath} from './authors';
export default function pluginContentBlog( export default async function pluginContentBlog(
context: LoadContext, context: LoadContext,
options: PluginOptions, options: PluginOptions,
): Plugin<BlogContent> { ): Promise<Plugin<BlogContent>> {
if (options.admonitions) { if (options.admonitions) {
options.remarkPlugins = options.remarkPlugins.concat([ options.remarkPlugins = options.remarkPlugins.concat([
[admonitions, options.admonitions], [admonitions, options.admonitions],
@ -89,24 +90,23 @@ export default function pluginContentBlog(
const aliasedSource = (source: string) => const aliasedSource = (source: string) =>
`~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`; `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
const authorsMapFilePath = await getAuthorsMapFilePath({
authorsMapPath: options.authorsMapPath,
contentPaths,
});
return { return {
name: 'docusaurus-plugin-content-blog', name: 'docusaurus-plugin-content-blog',
getPathsToWatch() { getPathsToWatch() {
const {include, authorsMapPath} = options; const {include} = options;
const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap( const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap(
(contentPath) => include.map((pattern) => `${contentPath}/${pattern}`), (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
); );
// TODO: we should read this path in plugin! but plugins do not support async init for now :'( return [authorsMapFilePath, ...contentMarkdownGlobs].filter(
// const authorsMapFilePath = await getAuthorsMapFilePath({authorsMapPath,contentPaths,}); Boolean,
// simplified impl, better than nothing for now: ) as string[];
const authorsMapFilePath = path.join(
contentPaths.contentPath,
authorsMapPath,
);
return [authorsMapFilePath, ...contentMarkdownGlobs];
}, },
async getTranslationFiles() { async getTranslationFiles() {

View file

@ -115,7 +115,7 @@ describe('sidebar', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'wrong-sidebars.json'); const sidebarPath = path.join(siteDir, 'wrong-sidebars.json');
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
sidebarPath, sidebarPath,
@ -129,7 +129,7 @@ describe('sidebar', () => {
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
await expect(async () => { await expect(async () => {
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
sidebarPath: 'wrong-path-sidebar.json', sidebarPath: 'wrong-path-sidebar.json',
@ -148,7 +148,7 @@ describe('sidebar', () => {
test('site with undefined sidebar', async () => { test('site with undefined sidebar', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
sidebarPath: undefined, sidebarPath: undefined,
@ -177,7 +177,7 @@ describe('sidebar', () => {
test('site with disabled sidebar', async () => { test('site with disabled sidebar', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
sidebarPath: false, sidebarPath: false,
@ -196,7 +196,7 @@ describe('empty/no docs website', () => {
test('no files in docs folder', async () => { test('no files in docs folder', async () => {
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
await fs.ensureDir(path.join(siteDir, 'docs')); await fs.ensureDir(path.join(siteDir, 'docs'));
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, {}), normalizePluginOptions(OptionsSchema, {}),
); );
@ -209,14 +209,14 @@ describe('empty/no docs website', () => {
test('docs folder does not exist', async () => { test('docs folder does not exist', async () => {
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
expect(() => await expect(
pluginContentDocs( pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: `path/doesnt/exist`, path: `path/doesnt/exist`,
}), }),
), ),
).toThrowError( ).rejects.toThrowError(
`The docs folder does not exist for version "current". A docs folder is expected to be found at ${ `The docs folder does not exist for version "current". A docs folder is expected to be found at ${
process.platform === 'win32' process.platform === 'win32'
? 'path\\doesnt\\exist' ? 'path\\doesnt\\exist'
@ -231,7 +231,7 @@ describe('simple website', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json'); const sidebarPath = path.join(siteDir, 'sidebars.json');
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: 'docs', path: 'docs',
@ -349,7 +349,7 @@ describe('versioned website', () => {
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json'); const sidebarPath = path.join(siteDir, 'sidebars.json');
const routeBasePath = 'docs'; const routeBasePath = 'docs';
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
routeBasePath, routeBasePath,
@ -494,7 +494,7 @@ describe('versioned website (community)', () => {
const sidebarPath = path.join(siteDir, 'community_sidebars.json'); const sidebarPath = path.join(siteDir, 'community_sidebars.json');
const routeBasePath = 'community'; const routeBasePath = 'community';
const pluginId = 'community'; const pluginId = 'community';
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
id: 'community', id: 'community',
@ -607,7 +607,7 @@ describe('site with doc label', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json'); const sidebarPath = path.join(siteDir, 'sidebars.json');
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: 'docs', path: 'docs',
@ -645,7 +645,7 @@ describe('site with full autogenerated sidebar', () => {
'site-with-autogenerated-sidebar', 'site-with-autogenerated-sidebar',
); );
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: 'docs', path: 'docs',
@ -697,7 +697,7 @@ describe('site with partial autogenerated sidebars', () => {
'site-with-autogenerated-sidebar', 'site-with-autogenerated-sidebar',
); );
const context = await loadContext(siteDir, {}); const context = await loadContext(siteDir, {});
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: 'docs', path: 'docs',
@ -749,7 +749,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => {
'site-with-autogenerated-sidebar', 'site-with-autogenerated-sidebar',
); );
const context = await loadContext(siteDir, {}); const context = await loadContext(siteDir, {});
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: 'docs', path: 'docs',
@ -783,7 +783,7 @@ describe('site with custom sidebar items generator', () => {
'site-with-autogenerated-sidebar', 'site-with-autogenerated-sidebar',
); );
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const plugin = pluginContentDocs( const plugin = await pluginContentDocs(
context, context,
normalizePluginOptions(OptionsSchema, { normalizePluginOptions(OptionsSchema, {
path: 'docs', path: 'docs',

View file

@ -58,10 +58,10 @@ import type {PropTagsListPage} from '@docusaurus/plugin-content-docs';
import {createSidebarsUtils} from './sidebars/utils'; import {createSidebarsUtils} from './sidebars/utils';
import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex'; import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex';
export default function pluginContentDocs( export default async function pluginContentDocs(
context: LoadContext, context: LoadContext,
options: PluginOptions, options: PluginOptions,
): Plugin<LoadedContent> { ): Promise<Plugin<LoadedContent>> {
const {siteDir, generatedFilesDir, baseUrl, siteConfig} = context; const {siteDir, generatedFilesDir, baseUrl, siteConfig} = context;
const versionsMetadata = readVersionsMetadata({context, options}); const versionsMetadata = readVersionsMetadata({context, options});

View file

@ -22,6 +22,7 @@
"@docusaurus/mdx-loader": "2.0.0-beta.14", "@docusaurus/mdx-loader": "2.0.0-beta.14",
"@docusaurus/utils": "2.0.0-beta.14", "@docusaurus/utils": "2.0.0-beta.14",
"@docusaurus/utils-validation": "2.0.0-beta.14", "@docusaurus/utils-validation": "2.0.0-beta.14",
"fs-extra": "^10.0.0",
"globby": "^11.0.2", "globby": "^11.0.2",
"remark-admonitions": "^1.2.1", "remark-admonitions": "^1.2.1",
"tslib": "^2.3.1", "tslib": "^2.3.1",

View file

@ -16,7 +16,7 @@ describe('docusaurus-plugin-content-pages', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website'); const siteDir = path.join(__dirname, '__fixtures__', 'website');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const pluginPath = 'src/pages'; const pluginPath = 'src/pages';
const plugin = pluginContentPages( const plugin = await pluginContentPages(
context, context,
normalizePluginOptions({ normalizePluginOptions({
path: pluginPath, path: pluginPath,
@ -77,7 +77,7 @@ describe('docusaurus-plugin-content-pages', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website'); const siteDir = path.join(__dirname, '__fixtures__', 'website');
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const pluginPath = 'src/pages'; const pluginPath = 'src/pages';
const plugin = pluginContentPages( const plugin = await pluginContentPages(
{ {
...context, ...context,
i18n: { i18n: {

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import fs from 'fs'; import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import { import {
encodePath, encodePath,
@ -45,10 +45,10 @@ export function getContentPathList(contentPaths: PagesContentPaths): string[] {
const isMarkdownSource = (source: string) => const isMarkdownSource = (source: string) =>
source.endsWith('.md') || source.endsWith('.mdx'); source.endsWith('.md') || source.endsWith('.mdx');
export default function pluginContentPages( export default async function pluginContentPages(
context: LoadContext, context: LoadContext,
options: PluginOptions, options: PluginOptions,
): Plugin<LoadedContent | null> { ): Promise<Plugin<LoadedContent | null>> {
if (options.admonitions) { if (options.admonitions) {
options.remarkPlugins = options.remarkPlugins.concat([ options.remarkPlugins = options.remarkPlugins.concat([
[admonitions, options.admonitions || {}], [admonitions, options.admonitions || {}],
@ -90,7 +90,7 @@ export default function pluginContentPages(
async loadContent() { async loadContent() {
const {include} = options; const {include} = options;
if (!fs.existsSync(contentPaths.contentPath)) { if (!(await fs.pathExists(contentPaths.contentPath))) {
return null; return null;
} }

View file

@ -317,7 +317,9 @@ export type LoadedPlugin<Content = unknown> = InitializedPlugin<Content> & {
}; };
export type PluginModule = { export type PluginModule = {
<T, X>(context: LoadContext, options: T): Plugin<X>; <Options, Content>(context: LoadContext, options: Options):
| Plugin<Content>
| Promise<Plugin<Content>>;
validateOptions?: <T>(data: OptionValidationContext<T>) => T; validateOptions?: <T>(data: OptionValidationContext<T>) => T;
validateThemeConfig?: <T>(data: ThemeConfigValidationContext<T>) => T; validateThemeConfig?: <T>(data: ThemeConfigValidationContext<T>) => T;
getSwizzleComponentList?: () => string[]; getSwizzleComponentList?: () => string[];

View file

@ -15,7 +15,7 @@ export default async function externalCommand(
): Promise<void> { ): Promise<void> {
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const pluginConfigs = loadPluginConfigs(context); const pluginConfigs = loadPluginConfigs(context);
const plugins = initPlugins({pluginConfigs, context}); const plugins = await initPlugins({pluginConfigs, context});
// Plugin Lifecycle - extendCli. // Plugin Lifecycle - extendCli.
plugins.forEach((plugin) => { plugins.forEach((plugin) => {

View file

@ -149,7 +149,7 @@ export default async function swizzle(
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const pluginConfigs = loadPluginConfigs(context); const pluginConfigs = loadPluginConfigs(context);
const pluginNames = getPluginNames(pluginConfigs); const pluginNames = getPluginNames(pluginConfigs);
const plugins = initPlugins({ const plugins = await initPlugins({
pluginConfigs, pluginConfigs,
context, context,
}); });
@ -209,7 +209,7 @@ export default async function swizzle(
// support both commonjs and ES style exports // support both commonjs and ES style exports
const plugin = pluginModule.default ?? pluginModule; const plugin = pluginModule.default ?? pluginModule;
const pluginInstance = plugin(context, pluginOptions); const pluginInstance = await plugin(context, pluginOptions);
const themePath = typescript const themePath = typescript
? pluginInstance.getTypeScriptThemePath?.() ? pluginInstance.getTypeScriptThemePath?.()
: pluginInstance.getThemePath?.(); : pluginInstance.getThemePath?.();

View file

@ -124,7 +124,7 @@ async function transformMarkdownFile(
async function getPathsToWatch(siteDir: string): Promise<string[]> { async function getPathsToWatch(siteDir: string): Promise<string[]> {
const context = await loadContext(siteDir); const context = await loadContext(siteDir);
const pluginConfigs = loadPluginConfigs(context); const pluginConfigs = loadPluginConfigs(context);
const plugins = initPlugins({ const plugins = await initPlugins({
pluginConfigs, pluginConfigs,
context, context,
}); });

View file

@ -78,7 +78,7 @@ export default async function writeTranslations(
locale: options.locale, locale: options.locale,
}); });
const pluginConfigs = loadPluginConfigs(context); const pluginConfigs = loadPluginConfigs(context);
const plugins = initPlugins({ const plugins = await initPlugins({
pluginConfigs, pluginConfigs,
context, context,
}); });

View file

@ -17,7 +17,7 @@ describe('initPlugins', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-plugin'); const siteDir = path.join(__dirname, '__fixtures__', 'site-with-plugin');
const context = await loadContext(siteDir, options); const context = await loadContext(siteDir, options);
const pluginConfigs = loadPluginConfigs(context); const pluginConfigs = loadPluginConfigs(context);
const plugins = initPlugins({ const plugins = await initPlugins({
pluginConfigs, pluginConfigs,
context, context,
}); });

View file

@ -78,7 +78,7 @@ export async function loadPlugins({
themeConfigTranslated: ThemeConfig; themeConfigTranslated: ThemeConfig;
}> { }> {
// 1. Plugin Lifecycle - Initialization/Constructor. // 1. Plugin Lifecycle - Initialization/Constructor.
const plugins: InitializedPlugin[] = initPlugins({ const plugins: InitializedPlugin[] = await initPlugins({
pluginConfigs, pluginConfigs,
context, context,
}); });

View file

@ -124,13 +124,13 @@ function getThemeValidationFunction(
} }
} }
export default function initPlugins({ export default async function initPlugins({
pluginConfigs, pluginConfigs,
context, context,
}: { }: {
pluginConfigs: PluginConfig[]; pluginConfigs: PluginConfig[];
context: LoadContext; context: LoadContext;
}): InitializedPlugin[] { }): Promise<InitializedPlugin[]> {
// We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json // We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json
// declares the dependency on these plugins. // declares the dependency on these plugins.
const pluginRequire = createRequire(context.siteConfigPath); const pluginRequire = createRequire(context.siteConfigPath);
@ -184,37 +184,45 @@ export default function initPlugins({
} }
} }
const plugins: InitializedPlugin[] = pluginConfigs async function initializePlugin(
.map((pluginConfig) => { pluginConfig: PluginConfig,
if (!pluginConfig) { ): Promise<InitializedPlugin> {
return null; const normalizedPluginConfig = normalizePluginConfig(
} pluginConfig,
const normalizedPluginConfig = normalizePluginConfig( pluginRequire,
pluginConfig, );
pluginRequire, const pluginVersion: DocusaurusPluginVersionInformation =
); doGetPluginVersion(normalizedPluginConfig);
const pluginVersion: DocusaurusPluginVersionInformation = const pluginOptions = doValidatePluginOptions(normalizedPluginConfig);
doGetPluginVersion(normalizedPluginConfig);
const pluginOptions = doValidatePluginOptions(normalizedPluginConfig);
// Side-effect: merge the normalized theme config in the original one // Side-effect: merge the normalized theme config in the original one
context.siteConfig.themeConfig = { context.siteConfig.themeConfig = {
...context.siteConfig.themeConfig, ...context.siteConfig.themeConfig,
...doValidateThemeConfig(normalizedPluginConfig), ...doValidateThemeConfig(normalizedPluginConfig),
}; };
const pluginInstance = normalizedPluginConfig.plugin( const pluginInstance = await normalizedPluginConfig.plugin(
context, context,
pluginOptions, pluginOptions,
); );
return { return {
...pluginInstance, ...pluginInstance,
options: pluginOptions, options: pluginOptions,
version: pluginVersion, version: pluginVersion,
}; };
}) }
.filter(<T>(item: T): item is Exclude<T, null> => Boolean(item));
const plugins: InitializedPlugin[] = (
await Promise.all(
pluginConfigs.map((pluginConfig) => {
if (!pluginConfig) {
return null;
}
return initializePlugin(pluginConfig);
}),
)
).filter(<T>(item: T): item is Exclude<T, null> => Boolean(item));
ensureUniquePluginInstanceIds(plugins); ensureUniquePluginInstanceIds(plugins);

View file

@ -17,7 +17,7 @@ Every plugin is imported as a module. The module is expected to have the followi
## Plugin constructor ## Plugin constructor
The plugin module's default export is a constructor function with the signature `(context: LoadContext, options: PluginOptions) => Plugin`. The plugin module's default export is a constructor function with the signature `(context: LoadContext, options: PluginOptions) => Plugin | Promise<Plugin>`.
### `context` {#context} ### `context` {#context}
@ -47,7 +47,7 @@ Here's a mind model for a presumptuous plugin implementation.
// A JavaScript function that returns an object. // A JavaScript function that returns an object.
// `context` is provided by Docusaurus. Example: siteConfig can be accessed from context. // `context` is provided by Docusaurus. Example: siteConfig can be accessed from context.
// `opts` is the user-defined options. // `opts` is the user-defined options.
function myPlugin(context, opts) { async function myPlugin(context, opts) {
return { return {
// A compulsory field used as the namespace for directories to cache // A compulsory field used as the namespace for directories to cache
// the intermediate data for each plugin. // the intermediate data for each plugin.

View file

@ -119,7 +119,7 @@ Docusaurus' implementation of the plugins system provides us with a convenient w
## Creating plugins {#creating-plugins} ## Creating plugins {#creating-plugins}
A plugin is a function that takes two parameters: `context` and `options`. It returns a plugin instance object. You can create plugins as functions or modules. For more information, refer to the [plugin method references section](./api/plugin-methods/README.md). A plugin is a function that takes two parameters: `context` and `options`. It returns a plugin instance object (or a promise). You can create plugins as functions or modules. For more information, refer to the [plugin method references section](./api/plugin-methods/README.md).
### Functional definition {#functional-definition} ### Functional definition {#functional-definition}
@ -130,7 +130,7 @@ module.exports = {
// ... // ...
plugins: [ plugins: [
// highlight-start // highlight-start
function myPlugin(context, options) { async function myPlugin(context, options) {
// ... // ...
return { return {
name: 'my-plugin', name: 'my-plugin',
@ -167,7 +167,7 @@ module.exports = {
Then in the folder `my-plugin` you can create an index.js such as this: Then in the folder `my-plugin` you can create an index.js such as this:
```js title="my-plugin.js" ```js title="my-plugin.js"
module.exports = function myPlugin(context, options) { module.exports = async function myPlugin(context, options) {
// ... // ...
return { return {
name: 'my-plugin', name: 'my-plugin',