feat(v2): generate metadata via plugins (#1273)

This commit is contained in:
Yangshun Tay 2019-03-10 19:08:36 -07:00 committed by GitHub
parent cefee2dec1
commit 53a123cb55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 97 additions and 50 deletions

View file

@ -82,7 +82,7 @@ module.exports = async function start(siteDir, cliOptions = {}) {
{ {
inject: false, inject: false,
hash: true, hash: true,
template: path.resolve(__dirname, '../core/devTemplate.ejs'), template: path.resolve(__dirname, '../core/index.html.template.ejs'),
filename: 'index.html', filename: 'index.html',
title: siteConfig.title, title: siteConfig.title,
}, },

View file

@ -9,32 +9,18 @@ import React, {useState} from 'react';
import {renderRoutes} from 'react-router-config'; import {renderRoutes} from 'react-router-config';
import routes from '@generated/routes'; // eslint-disable-line import routes from '@generated/routes'; // eslint-disable-line
// TODO: Generalize for blog plugin. import metadata from '@generated/metadata'; // eslint-disable-line
import blogMetadata from '@generated/docusaurus-plugin-content-blog/blogMetadata.json'; // eslint-disable-line
import docsMetadatas from '@generated/docsMetadatas'; // eslint-disable-line
import env from '@generated/env'; // eslint-disable-line
import docsSidebars from '@generated/docsSidebars'; // eslint-disable-line
import pagesMetadatas from '@generated/pagesMetadatas'; // eslint-disable-line
import siteConfig from '@generated/docusaurus.config'; //eslint-disable-line import siteConfig from '@generated/docusaurus.config'; //eslint-disable-line
import DocusaurusContext from '@docusaurus/context'; // eslint-disable-line
import DocusaurusContext from '@docusaurus/context'; // TODO: Allow choosing prismjs theme.
// TODO: allow choosing prismjs theme
import 'prismjs/themes/prism.css'; import 'prismjs/themes/prism.css';
const data = {
blogMetadata,
docsMetadatas,
docsSidebars,
env,
pagesMetadatas,
siteConfig,
};
function App() { function App() {
const [context, setContext] = useState({}); const [context, setContext] = useState({});
return ( return (
<DocusaurusContext.Provider value={{...data, ...context, setContext}}> <DocusaurusContext.Provider
value={{siteConfig, ...metadata, ...context, setContext}}>
{renderRoutes(routes)} {renderRoutes(routes)}
</DocusaurusContext.Provider> </DocusaurusContext.Provider>
); );

View file

@ -0,0 +1,18 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
<% imports.forEach(({name, path}) => { %>
import <%= name %> from '<%= path %>';
<% }); %>
const contents = {
<% imports.forEach(({name}) => { %>
<%= name %>,
<% }); %>
};
export default contents;

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
const ejs = require('ejs');
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path'); const path = require('path');
const loadConfig = require('./config'); const loadConfig = require('./config');
@ -92,12 +93,10 @@ module.exports = async function load(siteDir) {
}); });
// Plugin lifecycle - loadContents(). // Plugin lifecycle - loadContents().
// TODO: consider whether we still need contentsStore since it is not being used elsewhere now
const contentsStore = {};
// Currently plugins run lifecycle in parallel and are not order-dependent. We could change // 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 // this in future if there are plugins which need to run in certain order or depend on
// others for data. // others for data.
const pluginsLoadedContents = await Promise.all( const pluginsLoadedMetadata = await Promise.all(
plugins.map(async plugin => { plugins.map(async plugin => {
if (!plugin.loadContents) { if (!plugin.loadContents) {
return null; return null;
@ -105,21 +104,23 @@ module.exports = async function load(siteDir) {
const name = plugin.getName(); const name = plugin.getName();
const {options} = plugin; const {options} = plugin;
const contents = await plugin.loadContents(); const {metadataKey, metadataFileName} = options;
const pluginContents = { const metadata = await plugin.loadContents();
options, const pluginContentPath = path.join(name, metadataFileName);
contents, const pluginContentDir = path.join(generatedFilesDir, name);
}; fs.ensureDirSync(pluginContentDir);
contentsStore[options.contentKey] = pluginContents;
const pluginCacheDir = path.join(generatedFilesDir, name);
fs.ensureDirSync(pluginCacheDir);
await generate( await generate(
pluginCacheDir, pluginContentDir,
options.cacheFileName, metadataFileName,
JSON.stringify(contents, null, 2), JSON.stringify(metadata, null, 2),
); );
const contentPath = path.join('@generated', pluginContentPath);
return contents; return {
metadataKey,
contentPath,
metadata,
};
}), }),
); );
@ -133,9 +134,9 @@ module.exports = async function load(siteDir) {
if (!plugin.generateRoutes) { if (!plugin.generateRoutes) {
return; return;
} }
const contents = pluginsLoadedContents[index]; const loadedMetadata = pluginsLoadedMetadata[index];
await plugin.generateRoutes({ await plugin.generateRoutes({
contents, metadata: loadedMetadata.metadata,
actions, actions,
}); });
}), }),
@ -160,6 +161,42 @@ module.exports = async function load(siteDir) {
}); });
await generate(generatedFilesDir, 'routes.js', routesConfig); await generate(generatedFilesDir, 'routes.js', routesConfig);
// Generate contents metadata.
const metadataTemplateFile = path.resolve(
__dirname,
'../core/metadata.template.ejs',
);
const metadataTemplate = fs.readFileSync(metadataTemplateFile).toString();
const pluginMetadataImports = pluginsLoadedMetadata.map(
({metadataKey, contentPath}) => ({
name: metadataKey,
path: contentPath,
}),
);
const metadataFile = ejs.render(metadataTemplate, {
imports: [
...pluginMetadataImports,
{
name: 'docsMetadatas',
path: '@generated/docsMetadatas',
},
{
name: 'env',
path: '@generated/env',
},
{
name: 'docsSidebars',
path: '@generated/docsSidebars',
},
{
name: 'pagesMetadatas',
path: '@generated/pagesMetadatas',
},
],
});
await generate(generatedFilesDir, 'metadata.js', metadataFile);
const props = { const props = {
siteConfig, siteConfig,
siteDir, siteDir,
@ -176,7 +213,6 @@ module.exports = async function load(siteDir) {
versionedDir, versionedDir,
translatedDir, translatedDir,
generatedFilesDir, generatedFilesDir,
contentsStore,
routesPaths, routesPaths,
plugins, plugins,
}; };

View file

@ -64,6 +64,7 @@
"connect-history-api-fallback": "^1.5.0", "connect-history-api-fallback": "^1.5.0",
"css-loader": "^1.0.0", "css-loader": "^1.0.0",
"docsearch.js": "^2.5.2", "docsearch.js": "^2.5.2",
"ejs": "^2.6.1",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"front-matter": "^2.3.0", "front-matter": "^2.3.0",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",

View file

@ -20,17 +20,17 @@ function fileToUrl(fileName) {
} }
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
contentKey: 'blog', metadataKey: 'blogMetadata',
metadataFileName: 'blogMetadata.json',
path: 'blog', // Path to data on filesystem. path: 'blog', // Path to data on filesystem.
routeBasePath: 'blog', // URL Route. routeBasePath: 'blog', // URL Route.
include: ['*.md'], // Extensions to include. include: ['*.md, *.mdx'], // Extensions to include.
pageCount: 10, // How many entries per page. pageCount: 10, // How many entries per page.
cacheFileName: 'blogMetadata.json',
blogPageComponent: '@theme/BlogPage', blogPageComponent: '@theme/BlogPage',
blogPostComponent: '@theme/BlogPost', blogPostComponent: '@theme/BlogPost',
}; };
class DocusaurusContentBlogPlugin { class DocusaurusPluginContentBlog {
constructor(opts, context) { constructor(opts, context) {
this.options = {...DEFAULT_OPTIONS, ...opts}; this.options = {...DEFAULT_OPTIONS, ...opts};
this.context = context; this.context = context;
@ -41,6 +41,7 @@ class DocusaurusContentBlogPlugin {
return 'docusaurus-plugin-content-blog'; return 'docusaurus-plugin-content-blog';
} }
// Fetches blog contents and returns metadata for the contents.
async loadContents() { async loadContents() {
const {pageCount, include, routeBasePath} = this.options; const {pageCount, include, routeBasePath} = this.options;
const {env, siteConfig} = this.context; const {env, siteConfig} = this.context;
@ -109,17 +110,17 @@ class DocusaurusContentBlogPlugin {
return blogMetadata; return blogMetadata;
} }
async generateRoutes({contents, actions}) { async generateRoutes({metadata, actions}) {
const {blogPageComponent, blogPostComponent} = this.options; const {blogPageComponent, blogPostComponent} = this.options;
const {addRoute} = actions; const {addRoute} = actions;
contents.forEach(metadata => { metadata.forEach(metadataItem => {
const {isBlogPage, permalink} = metadata; const {isBlogPage, permalink} = metadataItem;
if (isBlogPage) { if (isBlogPage) {
addRoute({ addRoute({
path: permalink, path: permalink,
component: blogPageComponent, component: blogPageComponent,
metadata, metadata: metadataItem,
modules: metadata.posts.map(post => post.source), modules: metadataItem.posts.map(post => post.source),
}); });
return; return;
} }
@ -127,8 +128,8 @@ class DocusaurusContentBlogPlugin {
addRoute({ addRoute({
path: permalink, path: permalink,
component: blogPostComponent, component: blogPostComponent,
metadata, metadata: metadataItem,
modules: [metadata.source], modules: [metadataItem.source],
}); });
}); });
} }
@ -138,4 +139,4 @@ class DocusaurusContentBlogPlugin {
} }
} }
module.exports = DocusaurusContentBlogPlugin; module.exports = DocusaurusPluginContentBlog;

View file

@ -3027,6 +3027,11 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
ejs@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
electron-to-chromium@^1.3.113: electron-to-chromium@^1.3.113:
version "1.3.113" version "1.3.113"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9"