From 74d26d4f3d234de9915deb0421f87a5ff54894e5 Mon Sep 17 00:00:00 2001 From: Yangshun Tay Date: Sun, 3 Mar 2019 18:46:09 -0800 Subject: [PATCH] feat(v2): plugin lifecycle - add generateRoutes() API --- v2/lib/load/index.js | 98 ++++++---- v2/lib/load/routes.js | 247 ++++++++++++++------------ v2/plugins/docusaurus-content-blog.js | 23 ++- 3 files changed, 221 insertions(+), 147 deletions(-) diff --git a/v2/lib/load/index.js b/v2/lib/load/index.js index 7fe7ce77c4..edd8203a6f 100644 --- a/v2/lib/load/index.js +++ b/v2/lib/load/index.js @@ -79,36 +79,69 @@ module.exports = async function load(siteDir) { `export default ${JSON.stringify(pagesMetadatas, null, 2)};`, ); - const contentsStore = {}; - // Process plugins. - if (siteConfig.plugins) { - const context = {env, siteDir, siteConfig}; - // Currently runs all plugins in parallel and not order-dependent. We could change - // this in future if there's a need. - await Promise.all( - siteConfig.plugins.map(async ({name, options: opts}) => { - // TODO: Resolve using node_modules as well. - // eslint-disable-next-line - const Plugin = require(path.resolve(__dirname, '../../plugins', name)); - const plugin = new Plugin(opts, context); - const {options} = plugin; - const contents = await plugin.load(); - const pluginContents = { - options, - contents, - }; - contentsStore[options.contentKey] = pluginContents; - const pluginCacheDir = path.join(generatedFilesDir, name); - fs.ensureDirSync(pluginCacheDir); - await generate( - pluginCacheDir, - options.cacheFileName, - JSON.stringify(contents, null, 2), - ); - }), - ); - } + const pluginConfigs = siteConfig.plugins || []; + const context = {env, siteDir, siteConfig}; + + // Initialize plugins. + const plugins = pluginConfigs.map(({name, options: opts}) => { + // TODO: Resolve using node_modules as well. + // eslint-disable-next-line + const Plugin = require(path.resolve(__dirname, '../../plugins', name)); + const plugin = new Plugin(opts, context); + return { + name, + plugin, + }; + }); + + // Plugin lifecycle - loadContents(). + const contentsStore = {}; + // Currently plugins run lifecycle in parallel and are not order-dependent. We could change + // this in future if there are plugins which need to run in certain order or depend on + // others for data. + const pluginsLoadedContents = await Promise.all( + plugins.map(async ({plugin, name}) => { + if (!plugin.loadContents) { + return null; + } + + const {options} = plugin; + const contents = await plugin.loadContents(); + const pluginContents = { + options, + contents, + }; + contentsStore[options.contentKey] = pluginContents; + const pluginCacheDir = path.join(generatedFilesDir, name); + fs.ensureDirSync(pluginCacheDir); + await generate( + pluginCacheDir, + options.cacheFileName, + JSON.stringify(contents, null, 2), + ); + + return contents; + }), + ); + + // Plugin lifecycle - generateRoutes(). + const pluginRouteConfigs = []; + const actions = { + addRoute: config => pluginRouteConfigs.push(config), + }; + await Promise.all( + plugins.map(async ({plugin}, index) => { + if (!plugin.generateRoutes) { + return; + } + const contents = pluginsLoadedContents[index]; + await plugin.generateRoutes({ + contents, + actions, + }); + }), + ); // Resolve outDir. const outDir = path.resolve(siteDir, 'build'); @@ -140,9 +173,10 @@ module.exports = async function load(siteDir) { }; // Generate React Router Config. - const routesConfig = await genRoutesConfig(props); - await generate(generatedFilesDir, 'routes.js', routesConfig); - + const routesConfig = await genRoutesConfig({ + ...props, + pluginRouteConfigs, + }); await generate(generatedFilesDir, 'routes.js', routesConfig); return props; diff --git a/v2/lib/load/routes.js b/v2/lib/load/routes.js index c5f9369e2d..c7082df564 100644 --- a/v2/lib/load/routes.js +++ b/v2/lib/load/routes.js @@ -12,143 +12,162 @@ async function genRoutesConfig({ docsMetadatas = {}, pagesMetadatas = [], contentsStore = {}, + pluginRouteConfigs = [], }) { + const modules = [ + `import React from 'react';`, + `import Loadable from 'react-loadable';`, + `import Loading from '@theme/Loading';`, + `import Doc from '@theme/Doc';`, + `import DocBody from '@theme/DocBody';`, + `import BlogPage from '@theme/BlogPage';`, + `import Pages from '@theme/Pages';`, + `import NotFound from '@theme/NotFound';`, + ]; + // Docs. const {docsUrl, baseUrl} = siteConfig; function genDocsRoute(metadata) { const {permalink, source} = metadata; return ` - { - path: '${permalink}', - exact: true, - component: Loadable({ - loader: () => import(/* webpackPrefetch: true */ '${source}'), - loading: Loading, - render(loaded, props) { - let Content = loaded.default; - return ( - - - - ); - } - }) - }`; +{ + path: '${permalink}', + exact: true, + component: Loadable({ + loader: () => import(/* webpackPrefetch: true */ '${source}'), + loading: Loading, + render(loaded, props) { + let Content = loaded.default; + return ( + + + + ); + } + }) +}`; } const rootDocsUrl = normalizeUrl([baseUrl, docsUrl]); const docsRoutes = ` - { - path: '${rootDocsUrl}', - component: Doc, - routes: [${Object.values(docsMetadatas) - .map(genDocsRoute) - .join(',')}], - }`; +{ + path: '${rootDocsUrl}', + component: Doc, + routes: [${Object.values(docsMetadatas) + .map(genDocsRoute) + .join(',')}], +}`; // Pages. function genPagesRoute(metadata) { const {permalink, source} = metadata; return ` - { - path: '${permalink}', - exact: true, - component: Loadable({ - loader: () => import(/* webpackPrefetch: true */ '${source}'), - loading: Loading, - render(loaded, props) { - let Content = loaded.default; - return ( - - - - ); - } - }) - }`; +{ + path: '${permalink}', + exact: true, + component: Loadable({ + loader: () => import(/* webpackPrefetch: true */ '${source}'), + loading: Loading, + render(loaded, props) { + let Content = loaded.default; + return ( + + + + ); + } + }) +}`; } // Blog. - function genBlogRoute(metadata) { - const {permalink, source} = metadata; - if (metadata.isBlogPage) { - const {posts} = metadata; - return ` - { - path: '${permalink}', - exact: true, - component: Loadable.Map({ - loader: { - ${posts - .map( - (p, i) => - `post${i}: () => import(/* webpackPrefetch: true */ '${ - p.source - }')`, - ) - .join(',\n\t\t\t\t')} - }, - loading: Loading, - render(loaded, props) { - ${posts - .map((p, i) => `const Post${i} = loaded.post${i}.default;`) - .join('\n\t\t\t\t')} - return ( - - ${posts.map((p, i) => ``).join(' ')} - - ) - } - }) - }`; - } - + function genBlogPageRoute(metadata) { + const {permalink} = metadata; + const {posts} = metadata; return ` - { - path: '${permalink}', - exact: true, - component: Loadable({ - loader: () => import(/* webpackPrefetch: true */ '${source}'), - loading: Loading, - render(loaded, props) { - let MarkdownContent = loaded.default; - return ( - - - - ); - } - }) - }`; +{ + path: '${permalink}', + exact: true, + component: Loadable.Map({ + loader: { + ${posts + .map( + (post, index) => + `post${index}: () => import(/* webpackPrefetch: true */ '${ + post.source + }')`, + ) + .join(',\n\t\t\t\t')} + }, + loading: Loading, + render(loaded, props) { + ${posts + .map((p, i) => `const Post${i} = loaded.post${i}.default;`) + .join('\n\t\t\t\t')} + return ( + + ${posts.map((p, i) => ``).join(' ')} + + ) + } + }) +}`; } const notFoundRoute = ` - { - path: '*', - component: NotFound, - }`; +{ + path: '*', + component: NotFound, +}`; - return ( - `import React from 'react';\n` + - `import Loadable from 'react-loadable';\n` + - `import Loading from '@theme/Loading';\n` + - `import Doc from '@theme/Doc';\n` + - `import DocBody from '@theme/DocBody';\n` + - `import BlogPost from '@theme/BlogPost';\n` + - `import BlogPage from '@theme/BlogPage';\n` + - `import Pages from '@theme/Pages';\n` + - `import NotFound from '@theme/NotFound';\n` + - `const routes = [ - ${pagesMetadatas.map(genPagesRoute).join(',')}, - ${docsRoutes}, - ${ - contentsStore.blog - ? contentsStore.blog.contents.map(genBlogRoute).join(',') - : '' - }, - ${notFoundRoute}\n];\n` + - `export default routes;\n` - ); + const routes = pluginRouteConfigs.map(pluginRouteConfig => { + const {path, component, metadata, content} = pluginRouteConfig; + return ` +{ + path: '${path}', + exact: true, + component: Loadable.Map({ + loader: { + Content: () => import('${content}'), + Component: () => import('${component}'), + }, + loading: Loading, + render(loaded, props) { + const Content = loaded.Content.default; + const Component = loaded.Component.default; + return ( + + + + ) + } + }) +}`; + }); + + return ` +${modules.join('\n')} + +const routes = [ +// Docs.${pagesMetadatas.map(genPagesRoute).join(',')}, + +// Pages.${docsRoutes}, + +// Blog.${ + contentsStore.blog + ? contentsStore.blog.contents + .filter(metadata => metadata.isBlogPage) + .map(genBlogPageRoute) + .join(',') + : '' + }, + +// Plugins.${routes.join(',')}, + +// Not Found.${notFoundRoute}, +]; + +export default routes;\n`; } module.exports = genRoutesConfig; diff --git a/v2/plugins/docusaurus-content-blog.js b/v2/plugins/docusaurus-content-blog.js index 8c9b525e49..6d0d1f2ee9 100644 --- a/v2/plugins/docusaurus-content-blog.js +++ b/v2/plugins/docusaurus-content-blog.js @@ -26,6 +26,8 @@ const DEFAULT_OPTIONS = { include: ['*.md'], // Extensions to include. pageCount: 10, // How many entries per page. cacheFileName: 'blogMetadata.json', + blogPageComponent: '@theme/BlogPage', + blogPostComponent: '@theme/BlogPost', }; class DocusaurusContentBlogPlugin { @@ -34,7 +36,7 @@ class DocusaurusContentBlogPlugin { this.context = context; } - async load() { + async loadContents() { const {pageCount, path: filePath, include, routeBasePath} = this.options; const {env, siteConfig, siteDir} = this.context; const blogDir = path.resolve(siteDir, filePath); @@ -101,6 +103,25 @@ class DocusaurusContentBlogPlugin { return blogMetadata; } + + async generateRoutes({contents, actions}) { + const {blogPostComponent} = this.options; + const {addRoute} = actions; + contents.forEach(metadata => { + const {permalink, source} = metadata; + if (metadata.isBlogPage) { + // TODO: Handle blog page. + return; + } + + addRoute({ + path: permalink, + component: blogPostComponent, + metadata, + content: source, + }); + }); + } } module.exports = DocusaurusContentBlogPlugin;