From b393700a613ee00b8e59d347283f68495acb68ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Wed, 22 Dec 2021 19:10:49 +0100 Subject: [PATCH] feat: async plugin creator functions (#6166) --- .../src/__tests__/index.test.ts | 2 +- .../src/index.ts | 24 +++---- .../src/__tests__/index.test.ts | 30 ++++---- .../src/index.ts | 4 +- .../package.json | 1 + .../src/__tests__/index.test.ts | 4 +- .../src/index.ts | 8 +-- packages/docusaurus-types/src/index.d.ts | 4 +- packages/docusaurus/src/commands/external.ts | 2 +- packages/docusaurus/src/commands/swizzle.ts | 4 +- .../src/commands/writeHeadingIds.ts | 2 +- .../src/commands/writeTranslations.ts | 2 +- .../src/server/plugins/__tests__/init.test.ts | 2 +- .../docusaurus/src/server/plugins/index.ts | 2 +- .../docusaurus/src/server/plugins/init.ts | 68 +++++++++++-------- website/docs/api/plugin-methods/README.md | 4 +- website/docs/using-plugins.md | 6 +- 17 files changed, 90 insertions(+), 79 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 35f7431905..1b8bb2fed4 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -103,7 +103,7 @@ describe('loadBlog', () => { posixPath(path.relative(siteDir, p)), ); expect(relativePathsToWatch).toEqual([ - 'blog/authors.yml', + 'i18n/en/docusaurus-plugin-content-blog/authors.yml', 'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}', 'blog/**/*.{md,mdx}', ]); diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 9b9e98fb87..aa7d85622f 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -51,11 +51,12 @@ import { } from './blogUtils'; import {BlogPostFrontMatter} from './blogFrontMatter'; import {createBlogFeedFiles} from './feed'; +import {getAuthorsMapFilePath} from './authors'; -export default function pluginContentBlog( +export default async function pluginContentBlog( context: LoadContext, options: PluginOptions, -): Plugin { +): Promise> { if (options.admonitions) { options.remarkPlugins = options.remarkPlugins.concat([ [admonitions, options.admonitions], @@ -89,24 +90,23 @@ export default function pluginContentBlog( const aliasedSource = (source: string) => `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`; + const authorsMapFilePath = await getAuthorsMapFilePath({ + authorsMapPath: options.authorsMapPath, + contentPaths, + }); + return { name: 'docusaurus-plugin-content-blog', getPathsToWatch() { - const {include, authorsMapPath} = options; + const {include} = options; const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap( (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`), ); - // TODO: we should read this path in plugin! but plugins do not support async init for now :'( - // const authorsMapFilePath = await getAuthorsMapFilePath({authorsMapPath,contentPaths,}); - // simplified impl, better than nothing for now: - const authorsMapFilePath = path.join( - contentPaths.contentPath, - authorsMapPath, - ); - - return [authorsMapFilePath, ...contentMarkdownGlobs]; + return [authorsMapFilePath, ...contentMarkdownGlobs].filter( + Boolean, + ) as string[]; }, async getTranslationFiles() { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 914d7f2a27..87e55c7a2e 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -115,7 +115,7 @@ describe('sidebar', () => { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const context = await loadContext(siteDir); const sidebarPath = path.join(siteDir, 'wrong-sidebars.json'); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { sidebarPath, @@ -129,7 +129,7 @@ describe('sidebar', () => { const context = await loadContext(siteDir); await expect(async () => { - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { sidebarPath: 'wrong-path-sidebar.json', @@ -148,7 +148,7 @@ describe('sidebar', () => { test('site with undefined sidebar', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); const context = await loadContext(siteDir); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { sidebarPath: undefined, @@ -177,7 +177,7 @@ describe('sidebar', () => { test('site with disabled sidebar', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); const context = await loadContext(siteDir); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { sidebarPath: false, @@ -196,7 +196,7 @@ describe('empty/no docs website', () => { test('no files in docs folder', async () => { const context = await loadContext(siteDir); await fs.ensureDir(path.join(siteDir, 'docs')); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, {}), ); @@ -209,14 +209,14 @@ describe('empty/no docs website', () => { test('docs folder does not exist', async () => { const context = await loadContext(siteDir); - expect(() => + await expect( pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { 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 ${ process.platform === 'win32' ? 'path\\doesnt\\exist' @@ -231,7 +231,7 @@ describe('simple website', () => { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const context = await loadContext(siteDir); const sidebarPath = path.join(siteDir, 'sidebars.json'); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { path: 'docs', @@ -349,7 +349,7 @@ describe('versioned website', () => { const context = await loadContext(siteDir); const sidebarPath = path.join(siteDir, 'sidebars.json'); const routeBasePath = 'docs'; - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { routeBasePath, @@ -494,7 +494,7 @@ describe('versioned website (community)', () => { const sidebarPath = path.join(siteDir, 'community_sidebars.json'); const routeBasePath = 'community'; const pluginId = 'community'; - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { id: 'community', @@ -607,7 +607,7 @@ describe('site with doc label', () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); const context = await loadContext(siteDir); const sidebarPath = path.join(siteDir, 'sidebars.json'); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { path: 'docs', @@ -645,7 +645,7 @@ describe('site with full autogenerated sidebar', () => { 'site-with-autogenerated-sidebar', ); const context = await loadContext(siteDir); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { path: 'docs', @@ -697,7 +697,7 @@ describe('site with partial autogenerated sidebars', () => { 'site-with-autogenerated-sidebar', ); const context = await loadContext(siteDir, {}); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { path: 'docs', @@ -749,7 +749,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => { 'site-with-autogenerated-sidebar', ); const context = await loadContext(siteDir, {}); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { path: 'docs', @@ -783,7 +783,7 @@ describe('site with custom sidebar items generator', () => { 'site-with-autogenerated-sidebar', ); const context = await loadContext(siteDir); - const plugin = pluginContentDocs( + const plugin = await pluginContentDocs( context, normalizePluginOptions(OptionsSchema, { path: 'docs', diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index bb549d3e70..a1e0752f77 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -58,10 +58,10 @@ import type {PropTagsListPage} from '@docusaurus/plugin-content-docs'; import {createSidebarsUtils} from './sidebars/utils'; import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex'; -export default function pluginContentDocs( +export default async function pluginContentDocs( context: LoadContext, options: PluginOptions, -): Plugin { +): Promise> { const {siteDir, generatedFilesDir, baseUrl, siteConfig} = context; const versionsMetadata = readVersionsMetadata({context, options}); diff --git a/packages/docusaurus-plugin-content-pages/package.json b/packages/docusaurus-plugin-content-pages/package.json index ce8ccf2c99..6772e2f169 100644 --- a/packages/docusaurus-plugin-content-pages/package.json +++ b/packages/docusaurus-plugin-content-pages/package.json @@ -22,6 +22,7 @@ "@docusaurus/mdx-loader": "2.0.0-beta.14", "@docusaurus/utils": "2.0.0-beta.14", "@docusaurus/utils-validation": "2.0.0-beta.14", + "fs-extra": "^10.0.0", "globby": "^11.0.2", "remark-admonitions": "^1.2.1", "tslib": "^2.3.1", diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index 920dc6cf6f..b3fffbb02c 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -16,7 +16,7 @@ describe('docusaurus-plugin-content-pages', () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const context = await loadContext(siteDir); const pluginPath = 'src/pages'; - const plugin = pluginContentPages( + const plugin = await pluginContentPages( context, normalizePluginOptions({ path: pluginPath, @@ -77,7 +77,7 @@ describe('docusaurus-plugin-content-pages', () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const context = await loadContext(siteDir); const pluginPath = 'src/pages'; - const plugin = pluginContentPages( + const plugin = await pluginContentPages( { ...context, i18n: { diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index 13b9293bcd..78ba34d908 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import fs from 'fs'; +import fs from 'fs-extra'; import path from 'path'; import { encodePath, @@ -45,10 +45,10 @@ export function getContentPathList(contentPaths: PagesContentPaths): string[] { const isMarkdownSource = (source: string) => source.endsWith('.md') || source.endsWith('.mdx'); -export default function pluginContentPages( +export default async function pluginContentPages( context: LoadContext, options: PluginOptions, -): Plugin { +): Promise> { if (options.admonitions) { options.remarkPlugins = options.remarkPlugins.concat([ [admonitions, options.admonitions || {}], @@ -90,7 +90,7 @@ export default function pluginContentPages( async loadContent() { const {include} = options; - if (!fs.existsSync(contentPaths.contentPath)) { + if (!(await fs.pathExists(contentPaths.contentPath))) { return null; } diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index d28e55b8ba..219c9ecb04 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -317,7 +317,9 @@ export type LoadedPlugin = InitializedPlugin & { }; export type PluginModule = { - (context: LoadContext, options: T): Plugin; + (context: LoadContext, options: Options): + | Plugin + | Promise>; validateOptions?: (data: OptionValidationContext) => T; validateThemeConfig?: (data: ThemeConfigValidationContext) => T; getSwizzleComponentList?: () => string[]; diff --git a/packages/docusaurus/src/commands/external.ts b/packages/docusaurus/src/commands/external.ts index f0420b778c..5100ddff24 100644 --- a/packages/docusaurus/src/commands/external.ts +++ b/packages/docusaurus/src/commands/external.ts @@ -15,7 +15,7 @@ export default async function externalCommand( ): Promise { const context = await loadContext(siteDir); const pluginConfigs = loadPluginConfigs(context); - const plugins = initPlugins({pluginConfigs, context}); + const plugins = await initPlugins({pluginConfigs, context}); // Plugin Lifecycle - extendCli. plugins.forEach((plugin) => { diff --git a/packages/docusaurus/src/commands/swizzle.ts b/packages/docusaurus/src/commands/swizzle.ts index 32c422de43..cda946df6a 100644 --- a/packages/docusaurus/src/commands/swizzle.ts +++ b/packages/docusaurus/src/commands/swizzle.ts @@ -149,7 +149,7 @@ export default async function swizzle( const context = await loadContext(siteDir); const pluginConfigs = loadPluginConfigs(context); const pluginNames = getPluginNames(pluginConfigs); - const plugins = initPlugins({ + const plugins = await initPlugins({ pluginConfigs, context, }); @@ -209,7 +209,7 @@ export default async function swizzle( // support both commonjs and ES style exports const plugin = pluginModule.default ?? pluginModule; - const pluginInstance = plugin(context, pluginOptions); + const pluginInstance = await plugin(context, pluginOptions); const themePath = typescript ? pluginInstance.getTypeScriptThemePath?.() : pluginInstance.getThemePath?.(); diff --git a/packages/docusaurus/src/commands/writeHeadingIds.ts b/packages/docusaurus/src/commands/writeHeadingIds.ts index 20c6563d1a..cba7821543 100644 --- a/packages/docusaurus/src/commands/writeHeadingIds.ts +++ b/packages/docusaurus/src/commands/writeHeadingIds.ts @@ -124,7 +124,7 @@ async function transformMarkdownFile( async function getPathsToWatch(siteDir: string): Promise { const context = await loadContext(siteDir); const pluginConfigs = loadPluginConfigs(context); - const plugins = initPlugins({ + const plugins = await initPlugins({ pluginConfigs, context, }); diff --git a/packages/docusaurus/src/commands/writeTranslations.ts b/packages/docusaurus/src/commands/writeTranslations.ts index 8e91ce9505..d61291affb 100644 --- a/packages/docusaurus/src/commands/writeTranslations.ts +++ b/packages/docusaurus/src/commands/writeTranslations.ts @@ -78,7 +78,7 @@ export default async function writeTranslations( locale: options.locale, }); const pluginConfigs = loadPluginConfigs(context); - const plugins = initPlugins({ + const plugins = await initPlugins({ pluginConfigs, context, }); diff --git a/packages/docusaurus/src/server/plugins/__tests__/init.test.ts b/packages/docusaurus/src/server/plugins/__tests__/init.test.ts index 474e319387..b6f3ddc13b 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/init.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/init.test.ts @@ -17,7 +17,7 @@ describe('initPlugins', () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-plugin'); const context = await loadContext(siteDir, options); const pluginConfigs = loadPluginConfigs(context); - const plugins = initPlugins({ + const plugins = await initPlugins({ pluginConfigs, context, }); diff --git a/packages/docusaurus/src/server/plugins/index.ts b/packages/docusaurus/src/server/plugins/index.ts index beaa4dc9f8..7ca6d6f0af 100644 --- a/packages/docusaurus/src/server/plugins/index.ts +++ b/packages/docusaurus/src/server/plugins/index.ts @@ -78,7 +78,7 @@ export async function loadPlugins({ themeConfigTranslated: ThemeConfig; }> { // 1. Plugin Lifecycle - Initialization/Constructor. - const plugins: InitializedPlugin[] = initPlugins({ + const plugins: InitializedPlugin[] = await initPlugins({ pluginConfigs, context, }); diff --git a/packages/docusaurus/src/server/plugins/init.ts b/packages/docusaurus/src/server/plugins/init.ts index 4cb26188ee..d875080a74 100644 --- a/packages/docusaurus/src/server/plugins/init.ts +++ b/packages/docusaurus/src/server/plugins/init.ts @@ -124,13 +124,13 @@ function getThemeValidationFunction( } } -export default function initPlugins({ +export default async function initPlugins({ pluginConfigs, context, }: { pluginConfigs: PluginConfig[]; context: LoadContext; -}): InitializedPlugin[] { +}): Promise { // We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json // declares the dependency on these plugins. const pluginRequire = createRequire(context.siteConfigPath); @@ -184,37 +184,45 @@ export default function initPlugins({ } } - const plugins: InitializedPlugin[] = pluginConfigs - .map((pluginConfig) => { - if (!pluginConfig) { - return null; - } - const normalizedPluginConfig = normalizePluginConfig( - pluginConfig, - pluginRequire, - ); - const pluginVersion: DocusaurusPluginVersionInformation = - doGetPluginVersion(normalizedPluginConfig); - const pluginOptions = doValidatePluginOptions(normalizedPluginConfig); + async function initializePlugin( + pluginConfig: PluginConfig, + ): Promise { + const normalizedPluginConfig = normalizePluginConfig( + pluginConfig, + pluginRequire, + ); + const pluginVersion: DocusaurusPluginVersionInformation = + doGetPluginVersion(normalizedPluginConfig); + const pluginOptions = doValidatePluginOptions(normalizedPluginConfig); - // Side-effect: merge the normalized theme config in the original one - context.siteConfig.themeConfig = { - ...context.siteConfig.themeConfig, - ...doValidateThemeConfig(normalizedPluginConfig), - }; + // Side-effect: merge the normalized theme config in the original one + context.siteConfig.themeConfig = { + ...context.siteConfig.themeConfig, + ...doValidateThemeConfig(normalizedPluginConfig), + }; - const pluginInstance = normalizedPluginConfig.plugin( - context, - pluginOptions, - ); + const pluginInstance = await normalizedPluginConfig.plugin( + context, + pluginOptions, + ); - return { - ...pluginInstance, - options: pluginOptions, - version: pluginVersion, - }; - }) - .filter((item: T): item is Exclude => Boolean(item)); + return { + ...pluginInstance, + options: pluginOptions, + version: pluginVersion, + }; + } + + const plugins: InitializedPlugin[] = ( + await Promise.all( + pluginConfigs.map((pluginConfig) => { + if (!pluginConfig) { + return null; + } + return initializePlugin(pluginConfig); + }), + ) + ).filter((item: T): item is Exclude => Boolean(item)); ensureUniquePluginInstanceIds(plugins); diff --git a/website/docs/api/plugin-methods/README.md b/website/docs/api/plugin-methods/README.md index 5b046d9a74..f3d09ee01f 100644 --- a/website/docs/api/plugin-methods/README.md +++ b/website/docs/api/plugin-methods/README.md @@ -17,7 +17,7 @@ Every plugin is imported as a module. The module is expected to have the followi ## 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`. ### `context` {#context} @@ -47,7 +47,7 @@ Here's a mind model for a presumptuous plugin implementation. // A JavaScript function that returns an object. // `context` is provided by Docusaurus. Example: siteConfig can be accessed from context. // `opts` is the user-defined options. -function myPlugin(context, opts) { +async function myPlugin(context, opts) { return { // A compulsory field used as the namespace for directories to cache // the intermediate data for each plugin. diff --git a/website/docs/using-plugins.md b/website/docs/using-plugins.md index 5961d24a02..d2ec837673 100644 --- a/website/docs/using-plugins.md +++ b/website/docs/using-plugins.md @@ -119,7 +119,7 @@ Docusaurus' implementation of the plugins system provides us with a convenient w ## 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} @@ -130,7 +130,7 @@ module.exports = { // ... plugins: [ // highlight-start - function myPlugin(context, options) { + async function myPlugin(context, options) { // ... return { name: 'my-plugin', @@ -167,7 +167,7 @@ module.exports = { Then in the folder `my-plugin` you can create an index.js such as this: ```js title="my-plugin.js" -module.exports = function myPlugin(context, options) { +module.exports = async function myPlugin(context, options) { // ... return { name: 'my-plugin',