mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 07:37:19 +02:00
refactor(v2): use correct plugin types (#4418)
This commit is contained in:
parent
1078341b22
commit
abae86f283
11 changed files with 69 additions and 80 deletions
|
@ -21,7 +21,6 @@ import {
|
|||
STATIC_DIR_NAME,
|
||||
DEFAULT_PLUGIN_ID,
|
||||
} from '@docusaurus/core/lib/constants';
|
||||
import {ValidationError} from 'joi';
|
||||
import {flatten, take, kebabCase} from 'lodash';
|
||||
|
||||
import {
|
||||
|
@ -56,7 +55,7 @@ import {
|
|||
export default function pluginContentBlog(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Plugin<BlogContent | null, typeof PluginOptionSchema> {
|
||||
): Plugin<BlogContent | null> {
|
||||
if (options.admonitions) {
|
||||
options.remarkPlugins = options.remarkPlugins.concat([
|
||||
[admonitions, options.admonitions],
|
||||
|
@ -561,10 +560,7 @@ export default function pluginContentBlog(
|
|||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
|
||||
PluginOptions,
|
||||
ValidationError
|
||||
> {
|
||||
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
|
||||
const validatedOptions = validate(PluginOptionSchema, options);
|
||||
return validatedOptions;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ import {PermalinkToSidebar} from '@docusaurus/plugin-content-docs-types';
|
|||
import {RuleSetRule} from 'webpack';
|
||||
import {cliDocsVersionCommand} from './cli';
|
||||
import {VERSIONS_JSON_FILE} from './constants';
|
||||
import {OptionsSchema} from './options';
|
||||
import {flatten, keyBy, compact} from 'lodash';
|
||||
import {toGlobalDataVersion} from './globalData';
|
||||
import {toVersionMetadataProp} from './props';
|
||||
|
@ -54,7 +53,7 @@ import {
|
|||
export default function pluginContentDocs(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Plugin<LoadedContent, typeof OptionsSchema> {
|
||||
): Plugin<LoadedContent> {
|
||||
const {siteDir, generatedFilesDir, baseUrl, siteConfig} = context;
|
||||
|
||||
const versionsMetadata = readVersionsMetadata({context, options});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
URISchema,
|
||||
} from '@docusaurus/utils-validation';
|
||||
import {OptionValidationContext, ValidationResult} from '@docusaurus/types';
|
||||
import {ValidationError} from 'joi';
|
||||
import chalk from 'chalk';
|
||||
import admonitions from 'remark-admonitions';
|
||||
|
||||
|
@ -89,14 +88,10 @@ export const OptionsSchema = Joi.object({
|
|||
versions: VersionsOptionsSchema,
|
||||
});
|
||||
|
||||
// TODO bad validation function types
|
||||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
|
||||
PluginOptions,
|
||||
ValidationError
|
||||
> {
|
||||
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
|
||||
// TODO remove homePageId before end of 2020
|
||||
// "slug: /" is better because the home doc can be different across versions
|
||||
if (options.homePageId) {
|
||||
|
@ -118,8 +113,7 @@ export function validateOptions({
|
|||
options.includeCurrentVersion = !options.excludeNextVersionDocs;
|
||||
}
|
||||
|
||||
// @ts-expect-error: TODO bad OptionValidationContext, need refactor
|
||||
const normalizedOptions: PluginOptions = validate(OptionsSchema, options);
|
||||
const normalizedOptions = validate(OptionsSchema, options);
|
||||
|
||||
if (normalizedOptions.admonitions) {
|
||||
normalizedOptions.remarkPlugins = normalizedOptions.remarkPlugins.concat([
|
||||
|
@ -127,6 +121,5 @@ export function validateOptions({
|
|||
]);
|
||||
}
|
||||
|
||||
// @ts-expect-error: TODO bad OptionValidationContext, need refactor
|
||||
return normalizedOptions;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import {
|
|||
import {Configuration, Loader} from 'webpack';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import {PluginOptionSchema} from './pluginOptionSchema';
|
||||
import {ValidationError} from 'joi';
|
||||
import {
|
||||
DEFAULT_PLUGIN_ID,
|
||||
STATIC_DIR_NAME,
|
||||
|
@ -53,7 +52,7 @@ const isMarkdownSource = (source: string) =>
|
|||
export default function pluginContentPages(
|
||||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Plugin<LoadedContent | null, typeof PluginOptionSchema> {
|
||||
): Plugin<LoadedContent | null> {
|
||||
if (options.admonitions) {
|
||||
options.remarkPlugins = options.remarkPlugins.concat([
|
||||
[admonitions, options.admonitions || {}],
|
||||
|
@ -260,10 +259,7 @@ export default function pluginContentPages(
|
|||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
|
||||
PluginOptions,
|
||||
ValidationError
|
||||
> {
|
||||
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
|
||||
const validatedOptions = validate(PluginOptionSchema, options);
|
||||
return validatedOptions;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
Plugin,
|
||||
} from '@docusaurus/types';
|
||||
import {PluginOptionSchema} from './pluginOptionSchema';
|
||||
import {ValidationError} from 'joi';
|
||||
|
||||
export default function pluginSitemap(
|
||||
_context: LoadContext,
|
||||
|
@ -48,10 +47,7 @@ export default function pluginSitemap(
|
|||
export function validateOptions({
|
||||
validate,
|
||||
options,
|
||||
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
|
||||
PluginOptions,
|
||||
ValidationError
|
||||
> {
|
||||
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
|
||||
const validatedOptions = validate(PluginOptionSchema, options);
|
||||
return validatedOptions;
|
||||
}
|
||||
|
|
|
@ -68,10 +68,14 @@ function getInfimaCSSFile(direction) {
|
|||
}.css`;
|
||||
}
|
||||
|
||||
type PluginOptions = {
|
||||
customCss?: string;
|
||||
};
|
||||
|
||||
export default function docusaurusThemeClassic(
|
||||
context,
|
||||
options,
|
||||
): Plugin<null, unknown> {
|
||||
context: any, // TODO: LoadContext is missing some of properties
|
||||
options: PluginOptions,
|
||||
): Plugin<void> {
|
||||
const {
|
||||
siteConfig: {themeConfig},
|
||||
i18n: {currentLocale, localeConfigs},
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@types/webpack": "^4.41.0",
|
||||
"commander": "^5.1.0",
|
||||
"querystring": "0.2.0",
|
||||
"webpack-merge": "^4.2.2"
|
||||
"webpack-merge": "^4.2.2",
|
||||
"joi": "^17.4.0"
|
||||
}
|
||||
}
|
||||
|
|
56
packages/docusaurus-types/src/index.d.ts
vendored
56
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -7,10 +7,11 @@
|
|||
|
||||
// ESLint doesn't understand types dependencies in d.ts
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import {Loader, Configuration, Stats} from 'webpack';
|
||||
import {Command} from 'commander';
|
||||
import {ParsedUrlQueryInput} from 'querystring';
|
||||
import {MergeStrategy} from 'webpack-merge';
|
||||
import type {Loader, Configuration, Stats} from 'webpack';
|
||||
import type {Command} from 'commander';
|
||||
import type {ParsedUrlQueryInput} from 'querystring';
|
||||
import type {MergeStrategy} from 'webpack-merge';
|
||||
import type Joi from 'joi';
|
||||
|
||||
export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'error' | 'throw';
|
||||
|
||||
|
@ -183,7 +184,7 @@ export type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[];
|
|||
export interface Props extends LoadContext, InjectedHtmlTags {
|
||||
routes: RouteConfig[];
|
||||
routesPaths: string[];
|
||||
plugins: Plugin<any, unknown>[];
|
||||
plugins: Plugin<any>[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -210,11 +211,9 @@ export type AllContent = Record<
|
|||
// TODO improve type (not exposed by postcss-loader)
|
||||
export type PostCssOptions = Record<string, any> & {plugins: any[]};
|
||||
|
||||
export interface Plugin<T, U = unknown> {
|
||||
export interface Plugin<T> {
|
||||
name: string;
|
||||
loadContent?(): Promise<T>;
|
||||
validateOptions?(): ValidationResult<U>;
|
||||
validateThemeConfig?(): ValidationResult<any>;
|
||||
contentLoaded?({
|
||||
content,
|
||||
actions,
|
||||
|
@ -242,7 +241,6 @@ export interface Plugin<T, U = unknown> {
|
|||
preBodyTags?: HtmlTags;
|
||||
postBodyTags?: HtmlTags;
|
||||
};
|
||||
getSwizzleComponentList?(): string[];
|
||||
// TODO before/afterDevServer implementation
|
||||
|
||||
// translations
|
||||
|
@ -269,6 +267,17 @@ export interface Plugin<T, U = unknown> {
|
|||
}): ThemeConfig;
|
||||
}
|
||||
|
||||
export type PluginModule = {
|
||||
<T, X>(context: LoadContext, options: T): Plugin<X>;
|
||||
validateOptions?<T>(data: OptionValidationContext<T>): T;
|
||||
validateThemeConfig?<T>(data: ThemeConfigValidationContext<T>): T;
|
||||
getSwizzleComponentList?(): string[];
|
||||
};
|
||||
|
||||
export type ImportedPluginModule = PluginModule & {
|
||||
default?: PluginModule;
|
||||
};
|
||||
|
||||
export type ConfigureWebpackFn = Plugin<unknown>['configureWebpack'];
|
||||
export type ConfigureWebpackFnMergeStrategy = Record<string, MergeStrategy>;
|
||||
export type ConfigurePostCssFn = Plugin<unknown>['configurePostCss'];
|
||||
|
@ -346,36 +355,25 @@ interface HtmlTagObject {
|
|||
innerHTML?: string;
|
||||
}
|
||||
|
||||
export interface ValidationResult<T, E extends Error = Error> {
|
||||
error?: E;
|
||||
value: T;
|
||||
}
|
||||
export type ValidationResult<T> = T;
|
||||
|
||||
export type Validate<T, E extends Error = Error> = (
|
||||
export type ValidationSchema<T> = Joi.ObjectSchema<T>;
|
||||
|
||||
export type Validate<T> = (
|
||||
validationSchema: ValidationSchema<T>,
|
||||
options: Partial<T>,
|
||||
) => ValidationResult<T, E>;
|
||||
) => ValidationResult<T>;
|
||||
|
||||
export interface OptionValidationContext<T, E extends Error = Error> {
|
||||
validate: Validate<T, E>;
|
||||
export interface OptionValidationContext<T> {
|
||||
validate: Validate<T>;
|
||||
options: Partial<T>;
|
||||
}
|
||||
|
||||
export interface ThemeConfigValidationContext<T, E extends Error = Error> {
|
||||
validate: Validate<T, E>;
|
||||
export interface ThemeConfigValidationContext<T> {
|
||||
validate: Validate<T>;
|
||||
themeConfig: Partial<T>;
|
||||
}
|
||||
|
||||
// TODO we should use a Joi type here
|
||||
export interface ValidationSchema<T> {
|
||||
validate(
|
||||
options: Partial<T>,
|
||||
opt: Record<string, unknown>,
|
||||
): ValidationResult<T>;
|
||||
unknown(): ValidationSchema<T>;
|
||||
append(data: any): ValidationSchema<T>;
|
||||
}
|
||||
|
||||
export interface TOCItem {
|
||||
readonly value: string;
|
||||
readonly id: string;
|
||||
|
|
|
@ -37,7 +37,7 @@ export const logValidationBugReportHint = (): void => {
|
|||
|
||||
export function normalizePluginOptions<T extends {id?: string}>(
|
||||
schema: Joi.ObjectSchema<T>,
|
||||
options: unknown,
|
||||
options: Partial<T>,
|
||||
): T {
|
||||
// All plugins can be provided an "id" option (multi-instance support)
|
||||
// we add schema validation automatically
|
||||
|
@ -61,7 +61,7 @@ export function normalizePluginOptions<T extends {id?: string}>(
|
|||
|
||||
export function normalizeThemeConfig<T>(
|
||||
schema: Joi.ObjectSchema<T>,
|
||||
themeConfig: unknown,
|
||||
themeConfig: Partial<T>,
|
||||
): T {
|
||||
// A theme should only validate his "slice" of the full themeConfig,
|
||||
// not the whole object, so we allow unknown attributes
|
||||
|
|
|
@ -9,7 +9,7 @@ import chalk = require('chalk');
|
|||
import fs from 'fs-extra';
|
||||
import importFresh from 'import-fresh';
|
||||
import path from 'path';
|
||||
import {Plugin, LoadContext, PluginConfig} from '@docusaurus/types';
|
||||
import {ImportedPluginModule, PluginConfig} from '@docusaurus/types';
|
||||
import leven from 'leven';
|
||||
import {partition} from 'lodash';
|
||||
import {THEME_PATH} from '../constants';
|
||||
|
@ -31,9 +31,8 @@ export function getPluginNames(plugins: PluginConfig[]): string[] {
|
|||
if (packagePath === '.') {
|
||||
return pluginPath;
|
||||
}
|
||||
return (importFresh(path.join(packagePath, 'package.json')) as {
|
||||
name: string;
|
||||
}).name as string;
|
||||
return importFresh<{name: string}>(path.join(packagePath, 'package.json'))
|
||||
.name;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -66,7 +65,7 @@ function readComponent(themePath: string) {
|
|||
// load components from theme based on configurations
|
||||
function getComponentName(
|
||||
themePath: string,
|
||||
plugin: any,
|
||||
plugin: ImportedPluginModule,
|
||||
danger: boolean,
|
||||
): Array<string> {
|
||||
// support both commonjs and ES style exports
|
||||
|
@ -82,7 +81,10 @@ function getComponentName(
|
|||
return readComponent(themePath);
|
||||
}
|
||||
|
||||
function themeComponents(themePath: string, plugin: Plugin<unknown>): string {
|
||||
function themeComponents(
|
||||
themePath: string,
|
||||
plugin: ImportedPluginModule,
|
||||
): string {
|
||||
const components = colorCode(themePath, plugin);
|
||||
|
||||
if (components.length === 0) {
|
||||
|
@ -103,7 +105,10 @@ function formattedThemeNames(themeNames: string[]): string {
|
|||
return `Themes available for swizzle:\n${themeNames.join('\n')}`;
|
||||
}
|
||||
|
||||
function colorCode(themePath: string, plugin: any): Array<string> {
|
||||
function colorCode(
|
||||
themePath: string,
|
||||
plugin: ImportedPluginModule,
|
||||
): Array<string> {
|
||||
// support both commonjs and ES style exports
|
||||
const getSwizzleComponentList =
|
||||
plugin.default?.getSwizzleComponentList ?? plugin.getSwizzleComponentList;
|
||||
|
@ -148,11 +153,9 @@ export default async function swizzle(
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
let pluginModule;
|
||||
let pluginModule: ImportedPluginModule;
|
||||
try {
|
||||
pluginModule = importFresh(themeName) as (
|
||||
context: LoadContext,
|
||||
) => Plugin<unknown>;
|
||||
pluginModule = importFresh(themeName);
|
||||
} catch {
|
||||
let suggestion;
|
||||
themeNames.forEach((name) => {
|
||||
|
@ -170,10 +173,7 @@ export default async function swizzle(
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const plugin = pluginModule.default ?? pluginModule;
|
||||
const validateOptions =
|
||||
pluginModule.default?.validateOptions ?? pluginModule.validateOptions;
|
||||
let pluginOptions;
|
||||
let pluginOptions = {};
|
||||
const resolvedThemeName = require.resolve(themeName);
|
||||
// find the plugin from list of plugin and get options if specified
|
||||
pluginConfigs.forEach((pluginConfig) => {
|
||||
|
@ -188,6 +188,9 @@ export default async function swizzle(
|
|||
}
|
||||
});
|
||||
|
||||
// support both commonjs and ES style exports
|
||||
const validateOptions =
|
||||
pluginModule.default?.validateOptions ?? pluginModule.validateOptions;
|
||||
if (validateOptions) {
|
||||
pluginOptions = validateOptions({
|
||||
validate: normalizePluginOptions,
|
||||
|
@ -195,6 +198,8 @@ export default async function swizzle(
|
|||
});
|
||||
}
|
||||
|
||||
// support both commonjs and ES style exports
|
||||
const plugin = pluginModule.default ?? pluginModule;
|
||||
const pluginInstance = plugin(context, pluginOptions);
|
||||
const themePath = typescript
|
||||
? pluginInstance.getTypeScriptThemePath?.()
|
||||
|
|
|
@ -9,6 +9,7 @@ import Module from 'module';
|
|||
import importFresh from 'import-fresh';
|
||||
import {
|
||||
DocusaurusPluginVersionInformation,
|
||||
ImportedPluginModule,
|
||||
LoadContext,
|
||||
Plugin,
|
||||
PluginConfig,
|
||||
|
@ -68,7 +69,7 @@ For more information, visit https://v2.docusaurus.io/docs/using-plugins.`);
|
|||
// The pluginModuleImport value is any valid
|
||||
// module identifier - npm package or locally-resolved path.
|
||||
const pluginPath = pluginRequire.resolve(pluginModuleImport);
|
||||
const pluginModule: any = importFresh(pluginPath);
|
||||
const pluginModule: ImportedPluginModule = importFresh(pluginPath);
|
||||
const pluginVersion = getPluginVersion(pluginPath, context.siteDir);
|
||||
|
||||
const plugin = pluginModule.default || pluginModule;
|
||||
|
@ -114,7 +115,7 @@ For more information, visit https://v2.docusaurus.io/docs/using-plugins.`);
|
|||
version: pluginVersion,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
.filter(<T>(item: T): item is Exclude<T, null> => Boolean(item));
|
||||
|
||||
ensureUniquePluginInstanceIds(plugins);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue