refactor(v2): convert @docusaurus/plugin-content-blog to TypeScript (#1785)

* convert `@docusaurus/plugin-content-blog` to typescript

remove divided plugin
convert `@docusaurus/plugin-content-blog` to typescript
convert `@docusaurus/plugin-content-blog` to typescript
convert `@docusaurus/plugin-content-blog` to typescript
add `packages/docusaurus-plugin-content-blog/lib` to ignores
linted
refactoring type definition
fix test fails
lint

* lint
This commit is contained in:
Dongwoo Gim 2019-09-17 00:46:57 +09:00 committed by Endi
parent 0584407257
commit 78159f6dd5
13 changed files with 253 additions and 43 deletions

View file

@ -13,4 +13,4 @@ packages/docusaurus-1.x/lib/core/__tests__/split-tab.test.js
packages/docusaurus-utils/lib/ packages/docusaurus-utils/lib/
packages/docusaurus/lib/ packages/docusaurus/lib/
packages/docusaurus-init/lib/ packages/docusaurus-init/lib/
packages/docusaurus-plugin-content-blog/lib/

3
.gitignore vendored
View file

@ -15,5 +15,4 @@ types
packages/docusaurus-utils/lib/ packages/docusaurus-utils/lib/
packages/docusaurus/lib/ packages/docusaurus/lib/
packages/docusaurus-init/lib/ packages/docusaurus-init/lib/
packages/docusaurus-plugin-content-blog/lib/

View file

@ -5,3 +5,4 @@ build
packages/docusaurus-utils/lib/ packages/docusaurus-utils/lib/
packages/docusaurus/lib/ packages/docusaurus/lib/
packages/docusaurus-init/lib/ packages/docusaurus-init/lib/
packages/docusaurus-plugin-content-blog/lib/

View file

@ -17,6 +17,7 @@ module.exports = {
'__fixtures__', '__fixtures__',
'/packages/docusaurus/lib', '/packages/docusaurus/lib',
'/packages/docusaurus-utils/lib', '/packages/docusaurus-utils/lib',
'/packages/docusaurus-plugin-content-blog/lib',
], ],
transform: { transform: {
'^.+\\.[jt]sx?$': 'babel-jest', '^.+\\.[jt]sx?$': 'babel-jest',

View file

@ -2,7 +2,10 @@
"name": "@docusaurus/plugin-content-blog", "name": "@docusaurus/plugin-content-blog",
"version": "2.0.0-alpha.24", "version": "2.0.0-alpha.24",
"description": "Blog plugin for Docusaurus", "description": "Blog plugin for Docusaurus",
"main": "src/index.js", "main": "lib/index.js",
"scripts": {
"tsc": "tsc"
},
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View file

@ -8,11 +8,12 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import pluginContentBlog from '../index'; import pluginContentBlog from '../index';
import {DocusaurusConfig} from '../typesDocusaurus';
describe('loadBlog', () => { describe('loadBlog', () => {
test('simple website', async () => { test('simple website', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website'); const siteDir = path.join(__dirname, '__fixtures__', 'website');
const siteConfig = { const siteConfig: DocusaurusConfig = {
title: 'Hello', title: 'Hello',
baseUrl: '/', baseUrl: '/',
url: 'https://docusaurus.io', url: 'https://docusaurus.io',

View file

@ -4,25 +4,42 @@
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import fs from 'fs-extra';
import globby from 'globby';
import _ from 'lodash';
import path from 'path';
import {parse, normalizeUrl, docuHash} from '@docusaurus/utils';
const fs = require('fs-extra'); import {
const globby = require('globby'); DateLink,
const _ = require('lodash'); PluginOptions,
const path = require('path'); BlogTags,
const {parse, normalizeUrl, docuHash} = require('@docusaurus/utils'); BlogPost,
Tag,
BlogContent,
BlogItemsToModules,
TagsModule,
ConfigureWebpackUtils,
} from './types';
import {
LoadContext,
PluginContentLoadedActions,
RouteModule,
} from './typesDocusaurus';
import {Configuration} from 'webpack';
// YYYY-MM-DD-{name}.mdx? // YYYY-MM-DD-{name}.mdx?
// prefer named capture, but old node version do not support // prefer named capture, but old node version do not support
const FILENAME_PATTERN = /^(\d{4}-\d{1,2}-\d{1,2})-?(.*?).mdx?$/; const FILENAME_PATTERN = /^(\d{4}-\d{1,2}-\d{1,2})-?(.*?).mdx?$/;
function toUrl({date, link}) { function toUrl({date, link}: DateLink) {
return `${date return `${date
.toISOString() .toISOString()
.substring(0, '2019-01-01'.length) .substring(0, '2019-01-01'.length)
.replace(/-/g, '/')}/${link}`; .replace(/-/g, '/')}/${link}`;
} }
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS: PluginOptions = {
path: 'blog', // Path to data on filesystem, relative to site dir. path: 'blog', // Path to data on filesystem, relative to site dir.
routeBasePath: 'blog', // URL Route. routeBasePath: 'blog', // URL Route.
include: ['*.md', '*.mdx'], // Extensions to include. include: ['*.md', '*.mdx'], // Extensions to include.
@ -36,7 +53,10 @@ const DEFAULT_OPTIONS = {
truncateMarker: /<!--\s*(truncate)\s*-->/, // string or regex truncateMarker: /<!--\s*(truncate)\s*-->/, // string or regex
}; };
module.exports = function(context, opts) { export default function pluginContentBlog(
context: LoadContext,
opts: Partial<PluginOptions>,
) {
const options = {...DEFAULT_OPTIONS, ...opts}; const options = {...DEFAULT_OPTIONS, ...opts};
const contentPath = path.resolve(context.siteDir, options.path); const contentPath = path.resolve(context.siteDir, options.path);
@ -64,10 +84,10 @@ module.exports = function(context, opts) {
cwd: blogDir, cwd: blogDir,
}); });
const blogPosts = []; const blogPosts: BlogPost[] = [];
await Promise.all( await Promise.all(
blogFiles.map(async relativeSource => { blogFiles.map(async (relativeSource: string) => {
// Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it. // Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it.
const source = path.join(blogDir, relativeSource); const source = path.join(blogDir, relativeSource);
const aliasedSource = `@site/${path.relative(siteDir, source)}`; const aliasedSource = `@site/${path.relative(siteDir, source)}`;
@ -110,7 +130,9 @@ module.exports = function(context, opts) {
}); });
}), }),
); );
blogPosts.sort((a, b) => b.metadata.date - a.metadata.date); blogPosts.sort(
(a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
);
// Blog pagination routes. // Blog pagination routes.
// Example: `/blog`, `/blog/page/1`, `/blog/page/2` // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
@ -120,7 +142,7 @@ module.exports = function(context, opts) {
const blogListPaginated = []; const blogListPaginated = [];
function blogPaginationPermalink(page) { function blogPaginationPermalink(page: number) {
return page > 0 return page > 0
? normalizeUrl([basePageUrl, `page/${page + 1}`]) ? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl; : basePageUrl;
@ -146,7 +168,7 @@ module.exports = function(context, opts) {
}); });
} }
const blogTags = {}; const blogTags: BlogTags = {};
const tagsPath = normalizeUrl([basePageUrl, 'tags']); const tagsPath = normalizeUrl([basePageUrl, 'tags']);
blogPosts.forEach(blogPost => { blogPosts.forEach(blogPost => {
const {tags} = blogPost.metadata; const {tags} = blogPost.metadata;
@ -159,22 +181,26 @@ module.exports = function(context, opts) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = tags.map(tag => { blogPost.metadata.tags = tags.map(tag => {
const normalizedTag = _.kebabCase(tag); if (typeof tag === 'string') {
const permalink = normalizeUrl([tagsPath, normalizedTag]); const normalizedTag = _.kebabCase(tag);
if (!blogTags[normalizedTag]) { const permalink = normalizeUrl([tagsPath, normalizedTag]);
blogTags[normalizedTag] = { if (!blogTags[normalizedTag]) {
name: tag.toLowerCase(), // Will only use the name of the first occurrence of the tag. blogTags[normalizedTag] = {
items: [], 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, permalink,
}; } as Tag;
} else {
return tag;
} }
blogTags[normalizedTag].items.push(blogPost.id);
return {
label: tag,
permalink,
};
}); });
}); });
@ -189,7 +215,13 @@ module.exports = function(context, opts) {
}; };
}, },
async contentLoaded({content: blogContents, actions}) { async contentLoaded({
content: blogContents,
actions,
}: {
content: BlogContent;
actions: PluginContentLoadedActions;
}) {
if (!blogContents) { if (!blogContents) {
return; return;
} }
@ -209,7 +241,7 @@ module.exports = function(context, opts) {
blogTagsListPath, blogTagsListPath,
} = blogContents; } = blogContents;
const blogItemsToModules = {}; const blogItemsToModules: BlogItemsToModules = {};
// Create routes for blog entries. // Create routes for blog entries.
const blogItems = await Promise.all( const blogItems = await Promise.all(
blogPosts.map(async blogPost => { blogPosts.map(async blogPost => {
@ -245,7 +277,7 @@ module.exports = function(context, opts) {
metadata: metadataPath, metadata: metadataPath,
prevItem: prevItem && prevItem.metadataPath, prevItem: prevItem && prevItem.metadataPath,
nextItem: nextItem && nextItem.metadataPath, nextItem: nextItem && nextItem.metadataPath,
}, } as RouteModule,
}); });
}); });
@ -288,7 +320,7 @@ module.exports = function(context, opts) {
); );
// Tags. // Tags.
const tagsModule = {}; const tagsModule: TagsModule = {};
await Promise.all( await Promise.all(
Object.keys(blogTags).map(async tag => { Object.keys(blogTags).map(async tag => {
@ -352,7 +384,11 @@ module.exports = function(context, opts) {
} }
}, },
configureWebpack(config, isServer, {getBabelLoader, getCacheLoader}) { configureWebpack(
_config: Configuration,
isServer: boolean,
{getBabelLoader, getCacheLoader}: ConfigureWebpackUtils,
) {
const {rehypePlugins, remarkPlugins, truncateMarker} = options; const {rehypePlugins, remarkPlugins, truncateMarker} = options;
return { return {
module: { module: {
@ -383,4 +419,4 @@ module.exports = function(context, opts) {
}; };
}, },
}; };
}; }

View file

@ -6,8 +6,9 @@
*/ */
const {parseQuery, getOptions} = require('loader-utils'); const {parseQuery, getOptions} = require('loader-utils');
import {loader} from 'webpack';
module.exports = async function(fileString) { export = function(fileString: string) {
const callback = this.async(); const callback = this.async();
const {truncateMarker} = getOptions(this); const {truncateMarker} = getOptions(this);
@ -25,5 +26,5 @@ module.exports = async function(fileString) {
// eslint-disable-next-line // eslint-disable-next-line
finalContent = fileString.split(truncateMarker)[0]; finalContent = fileString.split(truncateMarker)[0];
} }
return callback(null, finalContent); return callback && callback(null, finalContent);
}; } as loader.Loader;

View file

@ -0,0 +1,93 @@
import {Loader} from 'webpack';
export interface BlogContent {
blogPosts: BlogPost[];
blogListPaginated: BlogPaginated[];
blogTags: BlogTags;
blogTagsListPath: string;
}
export interface DateLink {
date: Date;
link: string;
}
export interface PluginOptions {
path: string;
routeBasePath: string;
include: string[];
postsPerPage: number;
blogListComponent: string;
blogPostComponent: string;
blogTagsListComponent: string;
blogTagsPostsComponent: string;
remarkPlugins: string[];
rehypePlugins: string[];
truncateMarker: RegExp | string;
}
export interface BlogTags {
[key: string]: BlogTag;
}
export interface BlogTag {
name: string;
items: string[];
permalink: string;
}
export interface BlogPost {
id: string;
metadata: MetaData;
}
export interface BlogPaginated {
metadata: MetaData;
items: string[];
}
export interface MetaData {
permalink: string;
source: string;
description: string;
date: Date;
tags: (Tag | string)[];
title: string;
}
export interface Tag {
label: string;
permalink: string;
}
export interface BlogItemsToModules {
[key: string]: MetaDataWithPath;
}
export interface MetaDataWithPath {
metadata: MetaData;
metadataPath: string;
}
export interface TagsModule {
[key: string]: TagModule;
}
export interface TagModule {
allTagsPath: string;
slug: string;
name: string;
count: number;
permalink: string;
}
export interface ConfigureWebpackUtils {
getStyleLoaders: (
isServer: boolean,
cssOptions: {
[key: string]: any;
},
) => Loader[];
getCacheLoader: (isServer: boolean, cacheOptions?: {}) => Loader | null;
getBabelLoader: (isServer: boolean, babelOptions?: {}) => Loader;
}

View file

@ -0,0 +1,63 @@
import {ParsedUrlQueryInput} from 'querystring';
export interface DocusaurusConfig {
baseUrl: string;
favicon?: string;
tagline?: string;
title: string;
url: string;
organizationName?: string;
projectName?: string;
githubHost?: string;
plugins?: PluginConfig[];
themes?: PluginConfig[];
presets?: PresetConfig[];
themeConfig?: {
[key: string]: any;
};
customFields?: {
[key: string]: any;
};
}
export type PluginConfig = [string, Object | undefined] | string;
export type PresetConfig = [string, Object | undefined] | string;
export interface CLIOptions {
[option: string]: any;
}
export interface LoadContext {
siteDir: string;
generatedFilesDir?: string;
siteConfig: DocusaurusConfig;
cliOptions?: CLIOptions;
outDir?: string;
baseUrl?: string;
}
export interface PluginContentLoadedActions {
addRoute(config: RouteConfig): void;
createData(name: string, data: Object): Promise<string>;
}
export type Module =
| {
path: string;
__import?: boolean;
query?: ParsedUrlQueryInput;
}
| string;
export interface RouteModule {
[module: string]: Module | RouteModule | RouteModule[];
}
export interface RouteConfig {
path: string;
component: string;
modules?: RouteModule;
routes?: RouteConfig[];
exact?: boolean;
}

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo",
"rootDir": "src",
"outDir": "lib",
}
}

View file

@ -50,7 +50,7 @@ export async function loadPlugins({
// module is any valid module identifier - npm package or locally-resolved path. // module is any valid module identifier - npm package or locally-resolved path.
const pluginModule = importFresh(pluginModuleImport); const pluginModule = importFresh(pluginModuleImport);
return pluginModule(context, pluginOptions); return (pluginModule.default || pluginModule)(context, pluginOptions);
}), }),
); );

View file

@ -30,7 +30,10 @@ export function loadPresets(
} }
const presetModule = importFresh(presetModuleImport); const presetModule = importFresh(presetModuleImport);
const preset: Preset = presetModule(context, presetOptions); const preset: Preset = (presetModule.default || presetModule)(
context,
presetOptions,
);
preset.plugins && unflatPlugins.push(preset.plugins); preset.plugins && unflatPlugins.push(preset.plugins);
preset.themes && unflatThemes.push(preset.themes); preset.themes && unflatThemes.push(preset.themes);