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;