feat(v2): create docusaurus-content-blog plugin draft

This commit is contained in:
Yangshun Tay 2019-02-24 23:11:15 -08:00
parent 211e04f409
commit e5b7daef33
8 changed files with 95 additions and 41 deletions

View file

@ -29,4 +29,12 @@ module.exports = {
indexName: 'docusaurus', indexName: 'docusaurus',
algoliaOptions: {}, algoliaOptions: {},
}, },
plugins: [
{
name: 'docusaurus-content-blog',
options: {
include: ['*.md', '*.mdx'],
},
},
],
}; };

View file

@ -18,8 +18,8 @@ const REQUIRED_FIELDS = [
'headerIcon', 'headerIcon',
'organizationName', 'organizationName',
'projectName', 'projectName',
'title',
'tagline', 'tagline',
'title',
'url', 'url',
]; ];
@ -34,6 +34,7 @@ const OPTIONAL_FIELDS = [
'githubHost', 'githubHost',
'highlight', 'highlight',
'markdownPlugins', 'markdownPlugins',
'plugins',
]; ];
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
@ -94,12 +95,12 @@ function loadConfig(siteDir, deleteCache = true) {
} }
config.headerLinks = headerLinks; config.headerLinks = headerLinks;
/* // User's own array of custom fields/
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`.
e.g: if they want to include some field so they can access it later from `props.siteConfig`
*/
const {customFields = []} = config; const {customFields = []} = config;
// TODO: Check that plugins mentioned exist.
// Don't allow unrecognized fields. // Don't allow unrecognized fields.
const allowedFields = [ const allowedFields = [
...REQUIRED_FIELDS, ...REQUIRED_FIELDS,

View file

@ -7,7 +7,6 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path'); const path = require('path');
const loadBlog = require('./blog');
const loadConfig = require('./config'); const loadConfig = require('./config');
const loadDocs = require('./docs'); const loadDocs = require('./docs');
const loadEnv = require('./env'); const loadEnv = require('./env');
@ -80,14 +79,27 @@ module.exports = async function load(siteDir) {
`export default ${JSON.stringify(pagesMetadatas, null, 2)};`, `export default ${JSON.stringify(pagesMetadatas, null, 2)};`,
); );
// Blog. const contentsStore = {};
const blogDir = path.resolve(siteDir, 'blog');
const blogMetadatas = await loadBlog({blogDir, env, siteConfig}); // Process plugins.
await generate( if (siteConfig.plugins) {
generatedFilesDir, const context = {env, siteDir, siteConfig};
'blogMetadatas.js', await Promise.all(
`export default ${JSON.stringify(blogMetadatas, null, 2)};`, 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. // Resolve outDir.
const outDir = path.resolve(siteDir, 'build'); const outDir = path.resolve(siteDir, 'build');
@ -102,8 +114,6 @@ module.exports = async function load(siteDir) {
const props = { const props = {
siteConfig, siteConfig,
siteDir, siteDir,
blogDir,
blogMetadatas,
docsDir, docsDir,
docsMetadatas, docsMetadatas,
docsSidebars, docsSidebars,
@ -117,6 +127,7 @@ module.exports = async function load(siteDir) {
versionedDir, versionedDir,
translatedDir, translatedDir,
generatedFilesDir, generatedFilesDir,
contentsStore,
}; };
// Generate React Router Config. // Generate React Router Config.

View file

@ -11,7 +11,7 @@ async function genRoutesConfig({
siteConfig = {}, siteConfig = {},
docsMetadatas = {}, docsMetadatas = {},
pagesMetadatas = [], pagesMetadatas = [],
blogMetadatas = [], contentsStore = {},
}) { }) {
const {docsUrl, baseUrl} = siteConfig; const {docsUrl, baseUrl} = siteConfig;
function genDocsRoute(metadata) { function genDocsRoute(metadata) {
@ -138,7 +138,11 @@ async function genRoutesConfig({
`const routes = [ `const routes = [
${pagesMetadatas.map(genPagesRoute).join(',')}, ${pagesMetadatas.map(genPagesRoute).join(',')},
${docsRoutes}, ${docsRoutes},
${blogMetadatas.map(genBlogRoute).join(',')}, ${
contentsStore.blog
? contentsStore.blog.contents.map(genBlogRoute).join(',')
: ''
},
${notFoundRoute}\n];\n` + ${notFoundRoute}\n];\n` +
`export default routes;\n` `export default routes;\n`
); );

View file

@ -15,6 +15,7 @@ import DocusaurusContext from '@docusaurus/context';
function BlogPage(props) { function BlogPage(props) {
const context = useContext(DocusaurusContext); const context = useContext(DocusaurusContext);
console.log(context);
const {blogMetadatas, language, siteConfig = {}} = context; const {blogMetadatas, language, siteConfig = {}} = context;
const {baseUrl, favicon} = siteConfig; const {baseUrl, favicon} = siteConfig;

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 _ = require('lodash');
const path = require('path'); const path = require('path');
const staticSiteGenerator = require('static-site-generator-webpack-plugin'); const staticSiteGenerator = require('static-site-generator-webpack-plugin');
const webpackNiceLog = require('webpack-nicelog'); 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) // Workaround for Webpack 4 Bug (https://github.com/webpack/webpack/issues/6522)
config.output.globalObject('this'); config.output.globalObject('this');
const {siteConfig, blogMetadatas, docsMetadatas, pagesMetadatas} = props; const {siteConfig, docsMetadatas, pagesMetadatas, contentsStore} = props;
// Static site generator webpack plugin. // Static site generator webpack plugin.
const docsFlatMetadatas = Object.values(docsMetadatas); 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, [ config.plugin('siteGenerator').use(staticSiteGenerator, [
{ {
entry: 'main', entry: 'main',

View file

@ -8,7 +8,8 @@
const globby = require('globby'); const globby = require('globby');
const path = require('path'); const path = require('path');
const fs = require('fs-extra'); 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) { function fileToUrl(fileName) {
return fileName return fileName
@ -18,15 +19,29 @@ function fileToUrl(fileName) {
.replace(/\.md$/, ''); .replace(/\.md$/, '');
} }
async function loadBlog({blogDir, env, siteConfig}) { const DEFAULT_OPTIONS = {
const blogFiles = await globby(['*.md'], { 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, cwd: blogDir,
}); });
const {baseUrl} = siteConfig;
// Prepare metadata container. // Prepare metadata container.
const blogMetadatas = []; const blogMetadata = [];
// Language for each blog page. // Language for each blog page.
const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']); const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']);
@ -36,7 +51,7 @@ async function loadBlog({blogDir, env, siteConfig}) {
const source = path.join(blogDir, relativeSource); const source = path.join(blogDir, relativeSource);
const blogFileName = path.basename(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 filePathDateArr = blogFileName.split('-');
const date = new Date( const date = new Date(
`${filePathDateArr[0]}-${filePathDateArr[1]}-${ `${filePathDateArr[0]}-${filePathDateArr[1]}-${
@ -47,37 +62,45 @@ async function loadBlog({blogDir, env, siteConfig}) {
const fileString = await fs.readFile(source, 'utf-8'); const fileString = await fs.readFile(source, 'utf-8');
const {metadata: rawMetadata} = parse(fileString); const {metadata: rawMetadata} = parse(fileString);
const metadata = { const metadata = {
permalink: normalizeUrl([baseUrl, `blog`, fileToUrl(blogFileName)]), permalink: normalizeUrl([
baseUrl,
routeBasePath,
fileToUrl(blogFileName),
]),
source, source,
...rawMetadata, ...rawMetadata,
date, date,
language: defaultLangTag, 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` // Blog page handling. Example: `/blog`, `/blog/page1`, `/blog/page2`
const perPage = 10; const numOfBlog = blogMetadata.length;
const numOfBlog = blogMetadatas.length; const numberOfPage = Math.ceil(numOfBlog / pageCount);
const numberOfPage = Math.ceil(numOfBlog / perPage); const basePageUrl = path.join(baseUrl, routeBasePath);
const basePageUrl = path.join(baseUrl, 'blog');
// eslint-disable-next-line // eslint-disable-next-line
for (let page = 0; page < numberOfPage; page++) { for (let page = 0; page < numberOfPage; page++) {
blogMetadatas.push({ blogMetadata.push({
permalink: normalizeUrl([ permalink: normalizeUrl([
basePageUrl, basePageUrl,
`${page > 0 ? `page${page + 1}` : ''}`, `${page > 0 ? `page${page + 1}` : ''}`,
]), ]),
language: defaultLangTag, language: defaultLangTag,
isBlogPage: true, 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,
};

View file

@ -66,7 +66,7 @@ Object {
expect(() => { expect(() => {
loadConfig(siteDir); loadConfig(siteDir);
}).toThrowErrorMatchingInlineSnapshot( }).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"`,
); );
}); });
}); });