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/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/lib/
packages/docusaurus-init/lib/
packages/docusaurus-plugin-content-blog/lib/

View file

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

View file

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

View file

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

View file

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

View file

@ -4,25 +4,42 @@
* This source code is licensed under the MIT license found in the
* 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');
const globby = require('globby');
const _ = require('lodash');
const path = require('path');
const {parse, normalizeUrl, docuHash} = require('@docusaurus/utils');
import {
DateLink,
PluginOptions,
BlogTags,
BlogPost,
Tag,
BlogContent,
BlogItemsToModules,
TagsModule,
ConfigureWebpackUtils,
} from './types';
import {
LoadContext,
PluginContentLoadedActions,
RouteModule,
} from './typesDocusaurus';
import {Configuration} from 'webpack';
// YYYY-MM-DD-{name}.mdx?
// prefer named capture, but old node version do not support
const FILENAME_PATTERN = /^(\d{4}-\d{1,2}-\d{1,2})-?(.*?).mdx?$/;
function toUrl({date, link}) {
function toUrl({date, link}: DateLink) {
return `${date
.toISOString()
.substring(0, '2019-01-01'.length)
.replace(/-/g, '/')}/${link}`;
}
const DEFAULT_OPTIONS = {
const DEFAULT_OPTIONS: PluginOptions = {
path: 'blog', // Path to data on filesystem, relative to site dir.
routeBasePath: 'blog', // URL Route.
include: ['*.md', '*.mdx'], // Extensions to include.
@ -36,7 +53,10 @@ const DEFAULT_OPTIONS = {
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 contentPath = path.resolve(context.siteDir, options.path);
@ -64,10 +84,10 @@ module.exports = function(context, opts) {
cwd: blogDir,
});
const blogPosts = [];
const blogPosts: BlogPost[] = [];
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.
const source = path.join(blogDir, relativeSource);
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.
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
@ -120,7 +142,7 @@ module.exports = function(context, opts) {
const blogListPaginated = [];
function blogPaginationPermalink(page) {
function blogPaginationPermalink(page: number) {
return page > 0
? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl;
@ -146,7 +168,7 @@ module.exports = function(context, opts) {
});
}
const blogTags = {};
const blogTags: BlogTags = {};
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
blogPosts.forEach(blogPost => {
const {tags} = blogPost.metadata;
@ -159,6 +181,7 @@ module.exports = function(context, opts) {
// eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = tags.map(tag => {
if (typeof tag === 'string') {
const normalizedTag = _.kebabCase(tag);
const permalink = normalizeUrl([tagsPath, normalizedTag]);
if (!blogTags[normalizedTag]) {
@ -174,7 +197,10 @@ module.exports = function(context, opts) {
return {
label: tag,
permalink,
};
} as Tag;
} else {
return tag;
}
});
});
@ -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) {
return;
}
@ -209,7 +241,7 @@ module.exports = function(context, opts) {
blogTagsListPath,
} = blogContents;
const blogItemsToModules = {};
const blogItemsToModules: BlogItemsToModules = {};
// Create routes for blog entries.
const blogItems = await Promise.all(
blogPosts.map(async blogPost => {
@ -245,7 +277,7 @@ module.exports = function(context, opts) {
metadata: metadataPath,
prevItem: prevItem && prevItem.metadataPath,
nextItem: nextItem && nextItem.metadataPath,
},
} as RouteModule,
});
});
@ -288,7 +320,7 @@ module.exports = function(context, opts) {
);
// Tags.
const tagsModule = {};
const tagsModule: TagsModule = {};
await Promise.all(
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;
return {
module: {
@ -383,4 +419,4 @@ module.exports = function(context, opts) {
};
},
};
};
}

View file

@ -6,8 +6,9 @@
*/
const {parseQuery, getOptions} = require('loader-utils');
import {loader} from 'webpack';
module.exports = async function(fileString) {
export = function(fileString: string) {
const callback = this.async();
const {truncateMarker} = getOptions(this);
@ -25,5 +26,5 @@ module.exports = async function(fileString) {
// eslint-disable-next-line
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.
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 preset: Preset = presetModule(context, presetOptions);
const preset: Preset = (presetModule.default || presetModule)(
context,
presetOptions,
);
preset.plugins && unflatPlugins.push(preset.plugins);
preset.themes && unflatThemes.push(preset.themes);