refactor(v2): change plugin api (#1547)

* misc(v2): new plugin format example

* refactor(v2): make all plugins a function returning objects

* misc: add CHANGELOG

* misc(v2): update CHANGELOG

* misc(v2): fix tests

* misc(v2): convert swizzle command

* misc(v2): convert sitemap back to commonjs
This commit is contained in:
Yangshun Tay 2019-06-02 20:37:22 -07:00 committed by GitHub
parent 9feb7b2c64
commit 6a814ac64a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 709 additions and 725 deletions

View file

@ -31,336 +31,334 @@ const DEFAULT_OPTIONS = {
blogTagsPostsComponent: '@theme/BlogTagsPostsPage',
};
class DocusaurusPluginContentBlog {
constructor(context, opts) {
this.options = {...DEFAULT_OPTIONS, ...opts};
this.context = context;
this.contentPath = path.resolve(this.context.siteDir, this.options.path);
}
module.exports = function(context, opts) {
const options = {...DEFAULT_OPTIONS, ...opts};
const contentPath = path.resolve(context.siteDir, options.path);
getName() {
return 'docusaurus-plugin-content-blog';
}
return {
name: 'docusaurus-plugin-content-blog',
getPathsToWatch() {
const {include = []} = this.options;
const globPattern = include.map(
pattern => `${this.contentPath}/${pattern}`,
);
return [...globPattern];
}
getPathsToWatch() {
const {include = []} = options;
const globPattern = include.map(pattern => `${contentPath}/${pattern}`);
return [...globPattern];
},
// Fetches blog contents and returns metadata for the necessary routes.
async loadContent() {
const {postsPerPage, include, routeBasePath} = this.options;
const {siteConfig} = this.context;
const blogDir = this.contentPath;
// Fetches blog contents and returns metadata for the necessary routes.
async loadContent() {
const {postsPerPage, include, routeBasePath} = options;
const {siteConfig} = context;
const blogDir = contentPath;
if (!fs.existsSync(blogDir)) {
return null;
}
if (!fs.existsSync(blogDir)) {
return null;
}
const {baseUrl} = siteConfig;
const blogFiles = await globby(include, {
cwd: blogDir,
});
const blogPosts = [];
await Promise.all(
blogFiles.map(async relativeSource => {
const source = path.join(blogDir, relativeSource);
const blogFileName = path.basename(relativeSource);
// Extract, YYYY, MM, DD from the file name.
const filePathDateArr = blogFileName.split('-');
const date = new Date(
`${filePathDateArr[0]}-${filePathDateArr[1]}-${
filePathDateArr[2]
}T06:00:00.000Z`,
);
const fileString = await fs.readFile(source, 'utf-8');
const {frontMatter, excerpt} = parse(fileString);
blogPosts.push({
id: blogFileName,
metadata: {
permalink: normalizeUrl([
baseUrl,
routeBasePath,
fileToUrl(blogFileName),
]),
source,
description: frontMatter.description || excerpt,
date,
tags: frontMatter.tags,
title: frontMatter.title || blogFileName,
},
});
}),
);
blogPosts.sort((a, b) => b.metadata.date - a.metadata.date);
// Blog pagination routes.
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
const totalCount = blogPosts.length;
const numberOfPages = Math.ceil(totalCount / postsPerPage);
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogListPaginated = [];
function blogPaginationPermalink(page) {
return page > 0
? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl;
}
for (let page = 0; page < numberOfPages; page += 1) {
blogListPaginated.push({
metadata: {
permalink: blogPaginationPermalink(page),
page: page + 1,
postsPerPage,
totalPages: numberOfPages,
totalCount,
previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
nextPage:
page < numberOfPages - 1 ? blogPaginationPermalink(page + 1) : null,
},
items: blogPosts
.slice(page * postsPerPage, (page + 1) * postsPerPage)
.map(item => item.id),
const {baseUrl} = siteConfig;
const blogFiles = await globby(include, {
cwd: blogDir,
});
}
const blogTags = {};
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
blogPosts.forEach(blogPost => {
const {tags} = blogPost.metadata;
if (!tags || tags.length === 0) {
// TODO: Extract tags out into a separate plugin.
const blogPosts = [];
await Promise.all(
blogFiles.map(async relativeSource => {
const source = path.join(blogDir, relativeSource);
const blogFileName = path.basename(relativeSource);
// Extract, YYYY, MM, DD from the file name.
const filePathDateArr = blogFileName.split('-');
const date = new Date(
`${filePathDateArr[0]}-${filePathDateArr[1]}-${
filePathDateArr[2]
}T06:00:00.000Z`,
);
const fileString = await fs.readFile(source, 'utf-8');
const {frontMatter, excerpt} = parse(fileString);
blogPosts.push({
id: blogFileName,
metadata: {
permalink: normalizeUrl([
baseUrl,
routeBasePath,
fileToUrl(blogFileName),
]),
source,
description: frontMatter.description || excerpt,
date,
tags: frontMatter.tags,
title: frontMatter.title || blogFileName,
},
});
}),
);
blogPosts.sort((a, b) => b.metadata.date - a.metadata.date);
// Blog pagination routes.
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
const totalCount = blogPosts.length;
const numberOfPages = Math.ceil(totalCount / postsPerPage);
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogListPaginated = [];
function blogPaginationPermalink(page) {
return page > 0
? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl;
}
for (let page = 0; page < numberOfPages; page += 1) {
blogListPaginated.push({
metadata: {
permalink: blogPaginationPermalink(page),
page: page + 1,
postsPerPage,
totalPages: numberOfPages,
totalCount,
previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
nextPage:
page < numberOfPages - 1
? blogPaginationPermalink(page + 1)
: null,
},
items: blogPosts
.slice(page * postsPerPage, (page + 1) * postsPerPage)
.map(item => item.id),
});
}
const blogTags = {};
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
blogPosts.forEach(blogPost => {
const {tags} = blogPost.metadata;
if (!tags || tags.length === 0) {
// TODO: Extract tags out into a separate plugin.
// eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = [];
return;
}
// eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = [];
blogPost.metadata.tags = tags.map(tag => {
const normalizedTag = _.kebabCase(tag);
const permalink = normalizeUrl([tagsPath, normalizedTag]);
if (!blogTags[normalizedTag]) {
blogTags[normalizedTag] = {
name: tag.toLowerCase(), // Will only use the name of the first occurrence of the tag.
items: [],
permalink,
};
}
blogTags[normalizedTag].items.push(blogPost.id);
return {
label: tag,
permalink,
};
});
});
const blogTagsListPath =
Object.keys(blogTags).length > 0 ? tagsPath : null;
return {
blogPosts,
blogListPaginated,
blogTags,
blogTagsListPath,
};
},
async contentLoaded({content: blogContents, actions}) {
if (!blogContents) {
return;
}
// eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = tags.map(tag => {
const normalizedTag = _.kebabCase(tag);
const permalink = normalizeUrl([tagsPath, normalizedTag]);
if (!blogTags[normalizedTag]) {
blogTags[normalizedTag] = {
name: tag.toLowerCase(), // Will only use the name of the first occurrence of the tag.
items: [],
permalink,
const {
blogListComponent,
blogPostComponent,
blogTagsListComponent,
blogTagsPostsComponent,
} = options;
const {addRoute, createData} = actions;
const {
blogPosts,
blogListPaginated,
blogTags,
blogTagsListPath,
} = blogContents;
const blogItemsToModules = {};
// Create routes for blog entries.
const blogItems = await Promise.all(
blogPosts.map(async blogPost => {
const {id, metadata} = blogPost;
const {permalink} = metadata;
const metadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(metadata, null, 2),
);
const temp = {
metadata,
metadataPath,
};
}
blogTags[normalizedTag].items.push(blogPost.id);
return {
label: tag,
permalink,
};
});
});
const blogTagsListPath = Object.keys(blogTags).length > 0 ? tagsPath : null;
return {
blogPosts,
blogListPaginated,
blogTags,
blogTagsListPath,
};
}
async contentLoaded({content: blogContents, actions}) {
if (!blogContents) {
return;
}
const {
blogListComponent,
blogPostComponent,
blogTagsListComponent,
blogTagsPostsComponent,
} = this.options;
const {addRoute, createData} = actions;
const {
blogPosts,
blogListPaginated,
blogTags,
blogTagsListPath,
} = blogContents;
const blogItemsToModules = {};
// Create routes for blog entries.
const blogItems = await Promise.all(
blogPosts.map(async blogPost => {
const {id, metadata} = blogPost;
const {permalink} = metadata;
const metadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(metadata, null, 2),
);
const temp = {
metadata,
metadataPath,
};
blogItemsToModules[id] = temp;
return temp;
}),
);
blogItems.forEach((blogItem, index) => {
const prevItem = index > 0 ? blogItems[index - 1] : null;
const nextItem =
index < blogItems.length - 1 ? blogItems[index + 1] : null;
const {metadata, metadataPath} = blogItem;
const {source, permalink} = metadata;
addRoute({
path: permalink,
component: blogPostComponent,
exact: true,
modules: {
content: source,
metadata: metadataPath,
prevItem: prevItem && prevItem.metadataPath,
nextItem: nextItem && nextItem.metadataPath,
},
});
});
// Create routes for blog's paginated list entries.
await Promise.all(
blogListPaginated.map(async listPage => {
const {metadata, items} = listPage;
const {permalink} = metadata;
const pageMetadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(metadata, null, 2),
);
addRoute({
path: permalink,
component: blogListComponent,
exact: true,
modules: {
items: items.map(postID => {
const {metadata: postMetadata, metadataPath} = blogItemsToModules[
postID
];
// To tell routes.js this is an import and not a nested object to recurse.
return {
content: {
__import: true,
path: postMetadata.source,
query: {
truncated: true,
},
},
metadata: metadataPath,
};
}),
metadata: pageMetadataPath,
},
});
}),
);
// Tags.
const tagsModule = {};
await Promise.all(
Object.keys(blogTags).map(async tag => {
const {name, items, permalink} = blogTags[tag];
tagsModule[tag] = {
allTagsPath: blogTagsListPath,
slug: tag,
name,
count: items.length,
permalink,
};
const tagsMetadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(tagsModule[tag], null, 2),
);
addRoute({
path: permalink,
component: blogTagsPostsComponent,
exact: true,
modules: {
items: items.map(postID => {
const {metadata: postMetadata, metadataPath} = blogItemsToModules[
postID
];
return {
content: {
__import: true,
path: postMetadata.source,
query: {
truncated: true,
},
},
metadata: metadataPath,
};
}),
metadata: tagsMetadataPath,
},
});
}),
);
// Only create /tags page if there are tags.
if (Object.keys(blogTags).length > 0) {
const tagsListPath = await createData(
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
JSON.stringify(tagsModule, null, 2),
blogItemsToModules[id] = temp;
return temp;
}),
);
addRoute({
path: blogTagsListPath,
component: blogTagsListComponent,
exact: true,
modules: {
tags: tagsListPath,
},
});
}
}
blogItems.forEach((blogItem, index) => {
const prevItem = index > 0 ? blogItems[index - 1] : null;
const nextItem =
index < blogItems.length - 1 ? blogItems[index + 1] : null;
const {metadata, metadataPath} = blogItem;
const {source, permalink} = metadata;
getThemePath() {
return path.resolve(__dirname, './theme');
}
configureWebpack(config, isServer, {getBabelLoader, getCacheLoader}) {
return {
module: {
rules: [
{
test: /(\.mdx?)$/,
include: [this.contentPath],
use: [
getCacheLoader(isServer),
getBabelLoader(isServer),
'@docusaurus/mdx-loader',
{
loader: path.resolve(__dirname, './markdownLoader.js'),
},
],
addRoute({
path: permalink,
component: blogPostComponent,
exact: true,
modules: {
content: source,
metadata: metadataPath,
prevItem: prevItem && prevItem.metadataPath,
nextItem: nextItem && nextItem.metadataPath,
},
],
},
};
}
}
});
});
module.exports = DocusaurusPluginContentBlog;
// Create routes for blog's paginated list entries.
await Promise.all(
blogListPaginated.map(async listPage => {
const {metadata, items} = listPage;
const {permalink} = metadata;
const pageMetadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(metadata, null, 2),
);
addRoute({
path: permalink,
component: blogListComponent,
exact: true,
modules: {
items: items.map(postID => {
const {
metadata: postMetadata,
metadataPath,
} = blogItemsToModules[postID];
// To tell routes.js this is an import and not a nested object to recurse.
return {
content: {
__import: true,
path: postMetadata.source,
query: {
truncated: true,
},
},
metadata: metadataPath,
};
}),
metadata: pageMetadataPath,
},
});
}),
);
// Tags.
const tagsModule = {};
await Promise.all(
Object.keys(blogTags).map(async tag => {
const {name, items, permalink} = blogTags[tag];
tagsModule[tag] = {
allTagsPath: blogTagsListPath,
slug: tag,
name,
count: items.length,
permalink,
};
const tagsMetadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(tagsModule[tag], null, 2),
);
addRoute({
path: permalink,
component: blogTagsPostsComponent,
exact: true,
modules: {
items: items.map(postID => {
const {
metadata: postMetadata,
metadataPath,
} = blogItemsToModules[postID];
return {
content: {
__import: true,
path: postMetadata.source,
query: {
truncated: true,
},
},
metadata: metadataPath,
};
}),
metadata: tagsMetadataPath,
},
});
}),
);
// Only create /tags page if there are tags.
if (Object.keys(blogTags).length > 0) {
const tagsListPath = await createData(
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
JSON.stringify(tagsModule, null, 2),
);
addRoute({
path: blogTagsListPath,
component: blogTagsListComponent,
exact: true,
modules: {
tags: tagsListPath,
},
});
}
},
getThemePath() {
return path.resolve(__dirname, './theme');
},
configureWebpack(config, isServer, {getBabelLoader, getCacheLoader}) {
return {
module: {
rules: [
{
test: /(\.mdx?)$/,
include: [contentPath],
use: [
getCacheLoader(isServer),
getBabelLoader(isServer),
'@docusaurus/mdx-loader',
{
loader: path.resolve(__dirname, './markdownLoader.js'),
},
],
},
],
},
};
},
};
};