From 78159f6dd506bc56147a19f0186d5e78d635c633 Mon Sep 17 00:00:00 2001 From: Dongwoo Gim Date: Tue, 17 Sep 2019 00:46:57 +0900 Subject: [PATCH] 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 --- .eslintignore | 2 +- .gitignore | 3 +- .prettierignore | 1 + jest.config.js | 1 + .../package.json | 5 +- .../{index.test.js => index.test.ts} | 3 +- .../src/{index.js => index.ts} | 102 ++++++++++++------ .../{markdownLoader.js => markdownLoader.ts} | 7 +- .../src/types.ts | 93 ++++++++++++++++ .../src/typesDocusaurus.ts | 63 +++++++++++ .../tsconfig.json | 9 ++ .../docusaurus/src/server/plugins/index.ts | 2 +- .../docusaurus/src/server/presets/index.ts | 5 +- 13 files changed, 253 insertions(+), 43 deletions(-) rename packages/docusaurus-plugin-content-blog/src/__tests__/{index.test.js => index.test.ts} (95%) rename packages/docusaurus-plugin-content-blog/src/{index.js => index.ts} (84%) rename packages/docusaurus-plugin-content-blog/src/{markdownLoader.js => markdownLoader.ts} (83%) create mode 100644 packages/docusaurus-plugin-content-blog/src/types.ts create mode 100644 packages/docusaurus-plugin-content-blog/src/typesDocusaurus.ts create mode 100644 packages/docusaurus-plugin-content-blog/tsconfig.json diff --git a/.eslintignore b/.eslintignore index 0a03c96479..a39dad65d3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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/ diff --git a/.gitignore b/.gitignore index 4cfa022c2b..150e586567 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,4 @@ types packages/docusaurus-utils/lib/ packages/docusaurus/lib/ packages/docusaurus-init/lib/ - - +packages/docusaurus-plugin-content-blog/lib/ diff --git a/.prettierignore b/.prettierignore index 51145612eb..1277f781cf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ build packages/docusaurus-utils/lib/ packages/docusaurus/lib/ packages/docusaurus-init/lib/ +packages/docusaurus-plugin-content-blog/lib/ diff --git a/jest.config.js b/jest.config.js index e04e533755..e6a11db381 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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', diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index 759bce3a5b..9c7a11714c 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -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" }, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.js b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts similarity index 95% rename from packages/docusaurus-plugin-content-blog/src/__tests__/index.test.js rename to packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index afeb123c14..cf0945799a 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.js +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -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', diff --git a/packages/docusaurus-plugin-content-blog/src/index.js b/packages/docusaurus-plugin-content-blog/src/index.ts similarity index 84% rename from packages/docusaurus-plugin-content-blog/src/index.js rename to packages/docusaurus-plugin-content-blog/src/index.ts index ff7320a25e..3059c624ea 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.js +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -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: //, // string or regex }; -module.exports = function(context, opts) { +export default function pluginContentBlog( + context: LoadContext, + opts: Partial, +) { 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,22 +181,26 @@ module.exports = function(context, opts) { // 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: [], + if (typeof tag === 'string') { + 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, - }; + } 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) { 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) { }; }, }; -}; +} diff --git a/packages/docusaurus-plugin-content-blog/src/markdownLoader.js b/packages/docusaurus-plugin-content-blog/src/markdownLoader.ts similarity index 83% rename from packages/docusaurus-plugin-content-blog/src/markdownLoader.js rename to packages/docusaurus-plugin-content-blog/src/markdownLoader.ts index ac530f33cf..041061d050 100644 --- a/packages/docusaurus-plugin-content-blog/src/markdownLoader.js +++ b/packages/docusaurus-plugin-content-blog/src/markdownLoader.ts @@ -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; diff --git a/packages/docusaurus-plugin-content-blog/src/types.ts b/packages/docusaurus-plugin-content-blog/src/types.ts new file mode 100644 index 0000000000..5b011a5dd6 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/types.ts @@ -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; +} diff --git a/packages/docusaurus-plugin-content-blog/src/typesDocusaurus.ts b/packages/docusaurus-plugin-content-blog/src/typesDocusaurus.ts new file mode 100644 index 0000000000..383cde0e2c --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/typesDocusaurus.ts @@ -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; +} + +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; +} diff --git a/packages/docusaurus-plugin-content-blog/tsconfig.json b/packages/docusaurus-plugin-content-blog/tsconfig.json new file mode 100644 index 0000000000..f50aa9ee6d --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "outDir": "lib", + } +} diff --git a/packages/docusaurus/src/server/plugins/index.ts b/packages/docusaurus/src/server/plugins/index.ts index ff8215c0f8..39aef6b5d2 100644 --- a/packages/docusaurus/src/server/plugins/index.ts +++ b/packages/docusaurus/src/server/plugins/index.ts @@ -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); }), ); diff --git a/packages/docusaurus/src/server/presets/index.ts b/packages/docusaurus/src/server/presets/index.ts index a6050bf298..d74d1a2da7 100644 --- a/packages/docusaurus/src/server/presets/index.ts +++ b/packages/docusaurus/src/server/presets/index.ts @@ -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);