From e5b7daef339fae71a8db15206f0374649604b72d Mon Sep 17 00:00:00 2001 From: Yangshun Tay Date: Sun, 24 Feb 2019 23:11:15 -0800 Subject: [PATCH] feat(v2): create docusaurus-content-blog plugin draft --- v2-website/docusaurus.config.js | 8 +++ v2/lib/load/config.js | 11 ++-- v2/lib/load/index.js | 33 +++++++---- v2/lib/load/routes.js | 8 ++- v2/lib/theme/BlogPage/index.js | 1 + v2/lib/webpack/server.js | 14 +++-- .../docusaurus-content-blog.js} | 59 +++++++++++++------ v2/test/load/config.test.js | 2 +- 8 files changed, 95 insertions(+), 41 deletions(-) rename v2/{lib/load/blog.js => plugins/docusaurus-content-blog.js} (54%) diff --git a/v2-website/docusaurus.config.js b/v2-website/docusaurus.config.js index b2155d610c..76f4f29816 100644 --- a/v2-website/docusaurus.config.js +++ b/v2-website/docusaurus.config.js @@ -29,4 +29,12 @@ module.exports = { indexName: 'docusaurus', algoliaOptions: {}, }, + plugins: [ + { + name: 'docusaurus-content-blog', + options: { + include: ['*.md', '*.mdx'], + }, + }, + ], }; diff --git a/v2/lib/load/config.js b/v2/lib/load/config.js index a6a73b2073..d6ca704f16 100644 --- a/v2/lib/load/config.js +++ b/v2/lib/load/config.js @@ -18,8 +18,8 @@ const REQUIRED_FIELDS = [ 'headerIcon', 'organizationName', 'projectName', - 'title', 'tagline', + 'title', 'url', ]; @@ -34,6 +34,7 @@ const OPTIONAL_FIELDS = [ 'githubHost', 'highlight', 'markdownPlugins', + 'plugins', ]; const DEFAULT_CONFIG = { @@ -94,12 +95,12 @@ function loadConfig(siteDir, deleteCache = true) { } config.headerLinks = headerLinks; - /* - User's own array of custom fields, - e.g: if they want to include some field so they can access it later from `props.siteConfig` - */ + // User's own array of custom fields/ + // e.g: if they want to include some.field so they can access it later from `props.siteConfig`. const {customFields = []} = config; + // TODO: Check that plugins mentioned exist. + // Don't allow unrecognized fields. const allowedFields = [ ...REQUIRED_FIELDS, diff --git a/v2/lib/load/index.js b/v2/lib/load/index.js index b602b0c035..77b6c5f8c2 100644 --- a/v2/lib/load/index.js +++ b/v2/lib/load/index.js @@ -7,7 +7,6 @@ const fs = require('fs-extra'); const path = require('path'); -const loadBlog = require('./blog'); const loadConfig = require('./config'); const loadDocs = require('./docs'); const loadEnv = require('./env'); @@ -80,14 +79,27 @@ module.exports = async function load(siteDir) { `export default ${JSON.stringify(pagesMetadatas, null, 2)};`, ); - // Blog. - const blogDir = path.resolve(siteDir, 'blog'); - const blogMetadatas = await loadBlog({blogDir, env, siteConfig}); - await generate( - generatedFilesDir, - 'blogMetadatas.js', - `export default ${JSON.stringify(blogMetadatas, null, 2)};`, - ); + const contentsStore = {}; + + // Process plugins. + if (siteConfig.plugins) { + const context = {env, siteDir, siteConfig}; + 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 pluginContent = await plugin.onLoadContent(opts, context); + const {options, contents} = pluginContent; + contentsStore[options.contentKey] = pluginContent; + await generate( + generatedFilesDir, + options.cachePath, + `export default ${JSON.stringify(contents, null, 2)};`, + ); + }), + ); + } // Resolve outDir. const outDir = path.resolve(siteDir, 'build'); @@ -102,8 +114,6 @@ module.exports = async function load(siteDir) { const props = { siteConfig, siteDir, - blogDir, - blogMetadatas, docsDir, docsMetadatas, docsSidebars, @@ -117,6 +127,7 @@ module.exports = async function load(siteDir) { versionedDir, translatedDir, generatedFilesDir, + contentsStore, }; // Generate React Router Config. diff --git a/v2/lib/load/routes.js b/v2/lib/load/routes.js index 97b3013a64..99f64de1d1 100644 --- a/v2/lib/load/routes.js +++ b/v2/lib/load/routes.js @@ -11,7 +11,7 @@ async function genRoutesConfig({ siteConfig = {}, docsMetadatas = {}, pagesMetadatas = [], - blogMetadatas = [], + contentsStore = {}, }) { const {docsUrl, baseUrl} = siteConfig; function genDocsRoute(metadata) { @@ -138,7 +138,11 @@ async function genRoutesConfig({ `const routes = [ ${pagesMetadatas.map(genPagesRoute).join(',')}, ${docsRoutes}, - ${blogMetadatas.map(genBlogRoute).join(',')}, + ${ + contentsStore.blog + ? contentsStore.blog.contents.map(genBlogRoute).join(',') + : '' + }, ${notFoundRoute}\n];\n` + `export default routes;\n` ); diff --git a/v2/lib/theme/BlogPage/index.js b/v2/lib/theme/BlogPage/index.js index ee370e4c14..3bca7374e4 100644 --- a/v2/lib/theme/BlogPage/index.js +++ b/v2/lib/theme/BlogPage/index.js @@ -15,6 +15,7 @@ import DocusaurusContext from '@docusaurus/context'; function BlogPage(props) { const context = useContext(DocusaurusContext); + console.log(context); const {blogMetadatas, language, siteConfig = {}} = context; const {baseUrl, favicon} = siteConfig; diff --git a/v2/lib/webpack/server.js b/v2/lib/webpack/server.js index e4075efdf4..dc1ccfab5d 100644 --- a/v2/lib/webpack/server.js +++ b/v2/lib/webpack/server.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +const _ = require('lodash'); const path = require('path'); const staticSiteGenerator = require('static-site-generator-webpack-plugin'); const webpackNiceLog = require('webpack-nicelog'); @@ -21,13 +22,18 @@ module.exports = function createServerConfig(props) { // Workaround for Webpack 4 Bug (https://github.com/webpack/webpack/issues/6522) config.output.globalObject('this'); - const {siteConfig, blogMetadatas, docsMetadatas, pagesMetadatas} = props; + const {siteConfig, docsMetadatas, pagesMetadatas, contentsStore} = props; // Static site generator webpack plugin. const docsFlatMetadatas = Object.values(docsMetadatas); - const paths = [...blogMetadatas, ...docsFlatMetadatas, ...pagesMetadatas].map( - data => data.permalink, - ); + + // TODO: Generalize this into blog plugin. + const blogPermalinks = _.get(contentsStore, ['blog', 'contents'], []); + const paths = [ + ...blogPermalinks, + ...docsFlatMetadatas, + ...pagesMetadatas, + ].map(data => data.permalink); config.plugin('siteGenerator').use(staticSiteGenerator, [ { entry: 'main', diff --git a/v2/lib/load/blog.js b/v2/plugins/docusaurus-content-blog.js similarity index 54% rename from v2/lib/load/blog.js rename to v2/plugins/docusaurus-content-blog.js index b38087dc09..f34ec290b8 100644 --- a/v2/lib/load/blog.js +++ b/v2/plugins/docusaurus-content-blog.js @@ -8,7 +8,8 @@ const globby = require('globby'); const path = require('path'); const fs = require('fs-extra'); -const {parse, idx, normalizeUrl} = require('./utils'); +// TODO: Do not make it relative because plugins can be from node_modules. +const {parse, idx, normalizeUrl} = require('../lib/load/utils'); function fileToUrl(fileName) { return fileName @@ -18,15 +19,29 @@ function fileToUrl(fileName) { .replace(/\.md$/, ''); } -async function loadBlog({blogDir, env, siteConfig}) { - const blogFiles = await globby(['*.md'], { +const DEFAULT_OPTIONS = { + contentKey: 'blog', + path: 'blog', // Path to data on filesystem. + routeBasePath: 'blog', // URL Route. + include: ['*.md'], // Extensions to include. + pageCount: 10, // How many entries per page. + cachePath: 'blogMetadata.js', +}; + +async function onLoadContent(opts, context) { + const options = {...DEFAULT_OPTIONS, ...opts}; + + const {env, siteConfig, siteDir} = context; + const {pageCount, path: filePath, routeBasePath} = options; + const blogDir = path.resolve(siteDir, filePath); + const {baseUrl} = siteConfig; + + const blogFiles = await globby(options.include, { cwd: blogDir, }); - const {baseUrl} = siteConfig; - // Prepare metadata container. - const blogMetadatas = []; + const blogMetadata = []; // Language for each blog page. const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']); @@ -36,7 +51,7 @@ async function loadBlog({blogDir, env, siteConfig}) { const source = path.join(blogDir, relativeSource); const blogFileName = path.basename(relativeSource); - // Extract, YYYY, MM, DD from the file name + // Extract, YYYY, MM, DD from the file name. const filePathDateArr = blogFileName.split('-'); const date = new Date( `${filePathDateArr[0]}-${filePathDateArr[1]}-${ @@ -47,37 +62,45 @@ async function loadBlog({blogDir, env, siteConfig}) { const fileString = await fs.readFile(source, 'utf-8'); const {metadata: rawMetadata} = parse(fileString); const metadata = { - permalink: normalizeUrl([baseUrl, `blog`, fileToUrl(blogFileName)]), + permalink: normalizeUrl([ + baseUrl, + routeBasePath, + fileToUrl(blogFileName), + ]), source, ...rawMetadata, date, language: defaultLangTag, }; - blogMetadatas.push(metadata); + blogMetadata.push(metadata); }), ); - blogMetadatas.sort((a, b) => a.date - b.date); + blogMetadata.sort((a, b) => a.date - b.date); // Blog page handling. Example: `/blog`, `/blog/page1`, `/blog/page2` - const perPage = 10; - const numOfBlog = blogMetadatas.length; - const numberOfPage = Math.ceil(numOfBlog / perPage); - const basePageUrl = path.join(baseUrl, 'blog'); + const numOfBlog = blogMetadata.length; + const numberOfPage = Math.ceil(numOfBlog / pageCount); + const basePageUrl = path.join(baseUrl, routeBasePath); // eslint-disable-next-line for (let page = 0; page < numberOfPage; page++) { - blogMetadatas.push({ + blogMetadata.push({ permalink: normalizeUrl([ basePageUrl, `${page > 0 ? `page${page + 1}` : ''}`, ]), language: defaultLangTag, isBlogPage: true, - posts: blogMetadatas.slice(page * perPage, (page + 1) * perPage), + posts: blogMetadata.slice(page * pageCount, (page + 1) * pageCount), }); } - return blogMetadatas; + return { + contents: blogMetadata, + options, + }; } -module.exports = loadBlog; +module.exports = { + onLoadContent, +}; diff --git a/v2/test/load/config.test.js b/v2/test/load/config.test.js index a3fce966d8..1d04e73174 100644 --- a/v2/test/load/config.test.js +++ b/v2/test/load/config.test.js @@ -66,7 +66,7 @@ Object { expect(() => { loadConfig(siteDir); }).toThrowErrorMatchingInlineSnapshot( - `"The required field(s) 'baseUrl', 'favicon', 'headerLinks', 'headerIcon', 'organizationName', 'projectName', 'title', 'tagline', 'url' are missing from docusaurus.config.js"`, + `"The required field(s) 'baseUrl', 'favicon', 'headerLinks', 'headerIcon', 'organizationName', 'projectName', 'tagline', 'title', 'url' are missing from docusaurus.config.js"`, ); }); });