refactor: replace non-prop interface with type; allow plugin lifecycles to have sync type (#7080)

* refactor: replace non-prop interface with type; allow plugin lifecycles to have sync type

* fix
This commit is contained in:
Joshua Chen 2022-03-31 19:16:07 +08:00 committed by GitHub
parent ce2b631455
commit 24c205a835
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 145 additions and 138 deletions

View file

@ -10,13 +10,13 @@ declare module '@mdx-js/mdx' {
import type {Processor} from 'unified';
import type {RemarkOrRehypePlugin} from '@docusaurus/mdx-loader';
export interface Options {
export type Options = {
filepath?: string;
skipExport?: boolean;
wrapExport?: string;
remarkPlugins?: RemarkOrRehypePlugin[];
rehypePlugins?: RemarkOrRehypePlugin[];
}
};
export function sync(content: string, options?: Options): string;
export function createMdxAstCompiler(options?: Options): Processor;

View file

@ -182,9 +182,9 @@ export default async function mdxLoader(
// Partial are not expected to have associated metadata files or front matter
const isMDXPartial = options.isMDXPartial?.(filePath);
if (isMDXPartial && hasFrontMatter) {
const errorMessage = `Docusaurus MDX partial files should not contain FrontMatter.
const errorMessage = `Docusaurus MDX partial files should not contain front matter.
Those partial files use the _ prefix as a convention by default, but this is configurable.
File at ${filePath} contains FrontMatter that will be ignored:
File at ${filePath} contains front matter that will be ignored:
${JSON.stringify(frontMatter, null, 2)}`;
if (!options.isMDXPartialFrontMatterWarningDisabled) {

View file

@ -9,9 +9,8 @@
import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils';
import visit from 'unist-util-visit';
import toString from 'mdast-util-to-string';
import mdastToString from 'mdast-util-to-string';
import type {Transformer} from 'unified';
import type {Parent} from 'unist';
import type {Heading, Text} from 'mdast';
export default function plugin(): Transformer {
@ -30,10 +29,8 @@ export default function plugin(): Transformer {
const headingTextNodes = headingNode.children.filter(
({type}) => !['html', 'jsx'].includes(type),
);
const heading = toString(
headingTextNodes.length > 0
? ({children: headingTextNodes} as Parent)
: headingNode,
const heading = mdastToString(
headingTextNodes.length > 0 ? headingTextNodes : headingNode,
);
// Support explicit heading IDs

View file

@ -27,9 +27,9 @@ const isImport = (child: Node): child is Literal => child.type === 'import';
const hasImports = (index: number) => index > -1;
const isExport = (child: Node): child is Literal => child.type === 'export';
interface PluginOptions {
type PluginOptions = {
name?: string;
}
};
const isTarget = (child: Literal, name: string) => {
let found = false;

View file

@ -37,7 +37,7 @@ export type SidebarEntries = {
| Array<{[key: string]: unknown} | string>;
};
export interface VersionTwoConfig {
export type VersionTwoConfig = {
baseUrl: string;
favicon: string;
tagline?: string;
@ -93,7 +93,7 @@ export interface VersionTwoConfig {
[key: string]: unknown;
}
)[];
}
};
export type VersionOneConfig = {
title?: string;

View file

@ -140,8 +140,9 @@ declare module '@docusaurus/Head' {
declare module '@docusaurus/Link' {
import type {CSSProperties, ComponentProps} from 'react';
import type {NavLinkProps as RRNavLinkProps} from 'react-router-dom';
type NavLinkProps = Partial<import('react-router-dom').NavLinkProps>;
type NavLinkProps = Partial<RRNavLinkProps>;
export type Props = NavLinkProps &
ComponentProps<'a'> & {
readonly className?: string;

View file

@ -314,7 +314,10 @@ describe('validateBlogPostFrontMatter tags', () => {
{tags: ['hello', {label: 'tagLabel', permalink: '/tagPermalink'}]},
],
invalidFrontMatters: [
[{tags: ''}, '"tags" does not look like a valid FrontMatter Yaml array.'],
[
{tags: ''},
'"tags" does not look like a valid front matter Yaml array.',
],
[{tags: ['']}, 'not allowed to be empty'],
],
// See https://github.com/facebook/docusaurus/issues/4642

View file

@ -102,7 +102,7 @@ export default async function pluginContentBlog(
) as string[];
},
async getTranslationFiles() {
getTranslationFiles() {
return getTranslationFiles(options);
},

View file

@ -10,7 +10,7 @@ declare module '@docusaurus/plugin-content-blog' {
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {Overwrite} from 'utility-types';
export interface Assets {
export type Assets = {
/**
* If `metadata.image` is a collocated image path, this entry will be the
* bundler-generated image path. Otherwise, it's empty, and the image URL
@ -25,13 +25,9 @@ declare module '@docusaurus/plugin-content-blog' {
* should be accessed through `authors.imageURL`.
*/
authorsImageUrls: (string | undefined)[];
}
};
/**
* Unknown keys are allowed, so that we can pass custom fields to authors,
* e.g., `twitter`.
*/
export interface Author extends Record<string, unknown> {
export type Author = {
/**
* If `name` doesn't exist, an `imageURL` is expected.
*/
@ -55,7 +51,12 @@ declare module '@docusaurus/plugin-content-blog' {
* to generate a fallback `mailto:` URL.
*/
email?: string;
}
/**
* Unknown keys are allowed, so that we can pass custom fields to authors,
* e.g., `twitter`.
*/
[key: string]: unknown;
};
/**
* Everything is partial/unnormalized, because front matter is always
@ -443,8 +444,6 @@ declare module '@theme/BlogPostPage' {
>;
export type Content = {
// TODO remove this. `metadata.frontMatter` is preferred because it can be
// accessed in enhanced plugins
/** Same as `metadata.frontMatter` */
readonly frontMatter: FrontMatter;
/**

View file

@ -11,39 +11,41 @@ import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage';
export type BlogContentPaths = ContentPaths;
export interface BlogContent {
export type BlogContent = {
blogSidebarTitle: string;
blogPosts: BlogPost[];
blogListPaginated: BlogPaginated[];
blogTags: BlogTags;
blogTagsListPath: string | null;
}
};
export interface BlogTags {
export type BlogTags = {
// TODO, the key is the tag slug/permalink
// This is due to legacy frontmatter: tags:
// [{label: "xyz", permalink: "/1"}, {label: "xyz", permalink: "/2"}]
// Soon we should forbid declaring permalink through frontmatter
[tagKey: string]: BlogTag;
}
};
export interface BlogTag {
export type BlogTag = {
name: string;
items: string[]; // blog post permalinks
/** Blog post permalinks. */
items: string[];
permalink: string;
pages: BlogPaginated[];
}
};
export interface BlogPost {
export type BlogPost = {
id: string;
metadata: BlogPostMetadata;
content: string;
}
};
export interface BlogPaginated {
export type BlogPaginated = {
metadata: BlogPaginatedMetadata;
items: string[]; // blog post permalinks
}
/** Blog post permalinks. */
items: string[];
};
export type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
export type BlogMarkdownLoaderOptions = {

View file

@ -265,10 +265,13 @@ describe('validateDocFrontMatter tags', () => {
validFrontMatters: [{}, {tags: undefined}, {tags: ['tag1', 'tag2']}],
convertibleFrontMatter: [[{tags: ['tag1', 42]}, {tags: ['tag1', '42']}]],
invalidFrontMatters: [
[{tags: 42}, '"tags" does not look like a valid FrontMatter Yaml array.'],
[
{tags: 42},
'"tags" does not look like a valid front matter Yaml array.',
],
[
{tags: 'tag1, tag2'},
'"tags" does not look like a valid FrontMatter Yaml array.',
'"tags" does not look like a valid front matter Yaml array.',
],
[{tags: [{}]}, '"tags[0]" does not look like a valid tag'],
[{tags: [true]}, '"tags[0]" does not look like a valid tag'],

View file

@ -106,7 +106,7 @@ export default async function pluginContentDocs(
});
},
async getTranslationFiles({content}) {
getTranslationFiles({content}) {
return getLoadedContentTranslationFiles(content);
},

View file

@ -10,9 +10,9 @@ declare module '@docusaurus/plugin-content-docs' {
import type {ContentPaths, Tag, FrontMatterTag} from '@docusaurus/utils';
import type {Required} from 'utility-types';
export interface Assets {
export type Assets = {
image?: string;
}
};
/**
* Custom callback for parsing number prefixes from file/folder names.

View file

@ -254,18 +254,15 @@ export type SidebarItemsGenerator = (
generatorArgs: SidebarItemsGeneratorArgs,
) => Promise<NormalizedSidebar>;
// Also inject the default generator to conveniently wrap/enhance/sort the
// default sidebar gen logic
// see https://github.com/facebook/docusaurus/issues/4640#issuecomment-822292320
export type SidebarItemsGeneratorOptionArgs = {
export type SidebarItemsGeneratorOption = (
generatorArgs: {
/**
* Useful to re-use/enhance the default sidebar generation logic from
* Docusaurus.
* @see https://github.com/facebook/docusaurus/issues/4640#issuecomment-822292320
*/
defaultSidebarItemsGenerator: SidebarItemsGenerator;
} & SidebarItemsGeneratorArgs;
export type SidebarItemsGeneratorOption = (
generatorArgs: SidebarItemsGeneratorOptionArgs,
} & SidebarItemsGeneratorArgs,
) => Promise<NormalizedSidebarItem[]>;
export type SidebarProcessorParams = {

View file

@ -33,12 +33,12 @@ declare module '@endiliey/react-ideal-image' {
| 'noicon'
| 'offline';
export interface SrcType {
export type SrcType = {
width: number;
src?: string;
size?: number;
format?: 'webp' | 'jpeg' | 'png' | 'gif';
}
};
type ThemeKey = 'placeholder' | 'img' | 'icon' | 'noscript';

View file

@ -11,9 +11,9 @@ import type {Node, Parent} from 'unist';
import visit from 'unist-util-visit';
import npmToYarn from 'npm-to-yarn';
interface PluginOptions {
type PluginOptions = {
sync?: boolean;
}
};
// E.g. global install: 'npm i' -> 'yarn'
const convertNpmToYarn = (npmCode: string) => npmToYarn(npmCode, 'yarn');

View file

@ -118,7 +118,7 @@ export default function docusaurusThemeClassic(
return '../src/theme';
},
getTranslationFiles: async () => getTranslationFiles({themeConfig}),
getTranslationFiles: () => getTranslationFiles({themeConfig}),
translateThemeConfig: (params) =>
translateThemeConfig({

View file

@ -13,11 +13,11 @@ import {ReactContextError} from '../utils/reactUtils';
// Inspired by https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx
const EmptyContext: unique symbol = Symbol('EmptyContext');
type SidebarContextValue = {name: string; items: PropSidebar};
type ContextValue = {name: string; items: PropSidebar};
const Context = React.createContext<
SidebarContextValue | null | typeof EmptyContext
>(EmptyContext);
const Context = React.createContext<ContextValue | null | typeof EmptyContext>(
EmptyContext,
);
/**
* Provide the current sidebar to your children.
@ -31,7 +31,7 @@ export function DocsSidebarProvider({
name: string | undefined;
items: PropSidebar | undefined;
}): JSX.Element {
const stableValue: SidebarContextValue | null = useMemo(
const stableValue: ContextValue | null = useMemo(
() =>
name && items
? {
@ -45,9 +45,9 @@ export function DocsSidebarProvider({
}
/**
* Gets the sidebar data that's currently displayed, or `null` if there isn't one
* Gets the sidebar that's currently displayed, or `null` if there isn't one
*/
export function useDocsSidebar(): SidebarContextValue | null {
export function useDocsSidebar(): ContextValue | null {
const value = useContext(Context);
if (value === EmptyContext) {
throw new ReactContextError('DocsSidebarProvider');

View file

@ -220,8 +220,9 @@ export function useDocsVersionCandidates(
/**
* The layout components, like navbar items, must be able to work on all pages,
* even on non-doc ones. This hook would always return a sidebar to be linked
* to. See also {@link useDocsVersionCandidates} for how this selection is done.
* even on non-doc ones where there's no version context, so a sidebar ID could
* be ambiguous. This hook would always return a sidebar to be linked to. See
* also {@link useDocsVersionCandidates} for how this selection is done.
*
* @throws This hook throws if a sidebar with said ID is not found.
*/
@ -252,8 +253,9 @@ export function useLayoutDocsSidebar(
/**
* The layout components, like navbar items, must be able to work on all pages,
* even on non-doc ones. This hook would always return a doc to be linked
* to. See also {@link useDocsVersionCandidates} for how this selection is done.
* even on non-doc ones where there's no version context, so a doc ID could be
* ambiguous. This hook would always return a doc to be linked to. See also
* {@link useDocsVersionCandidates} for how this selection is done.
*
* @throws This hook throws if a doc with said ID is not found.
*/

View file

@ -12,13 +12,13 @@ import useRouteContext from '@docusaurus/useRouteContext';
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
import {useTitleFormatter} from './generalUtils';
interface PageMetadataProps {
type PageMetadataProps = {
readonly title?: string;
readonly description?: string;
readonly keywords?: readonly string[] | string;
readonly image?: string;
readonly children?: ReactNode;
}
};
/**
* Helper component to manipulate page metadata and override site defaults.

View file

@ -54,11 +54,11 @@ Possible reasons: running Docusaurus in an iframe, in an incognito browser sessi
}
// Convenient storage interface for a single storage key
export interface StorageSlot {
export type StorageSlot = {
get: () => string | null;
set: (value: string) => void;
del: () => void;
}
};
const NoopStorageSlot: StorageSlot = {
get: () => null,

View file

@ -101,7 +101,7 @@ function filterTOC({
* to ensure that weird TOC structures preserve their semantics. For example, an
* h3-h2-h4 sequence should not be treeified as an "h3 > h4" hierarchy with
* min=3, max=4, but should rather be "[h3, h4]" (since the h2 heading has split
* the two headings and they are not parents)
* the two headings and they are not parent-children)
*/
export function useFilteredAndTreeifiedTOC({
toc,

View file

@ -10,10 +10,10 @@ declare module '@philpl/buble' {
// eslint-disable-next-line import/no-extraneous-dependencies, no-restricted-syntax
export * from 'buble';
export const features: string[];
export interface TransformOptions extends OriginalTransformOptions {
export type TransformOptions = OriginalTransformOptions & {
transforms?: OriginalTransformOptions['transforms'] & {
asyncAwait?: boolean;
getterSetter?: boolean;
};
}
};
}

View file

@ -23,9 +23,9 @@ declare module '@theme/Playground' {
}
declare module '@theme/ReactLiveScope' {
interface Scope {
type Scope = {
[key: string]: unknown;
}
};
const ReactLiveScope: Scope;
export default ReactLiveScope;

View file

@ -86,7 +86,7 @@ export type DocusaurusConfig = {
| string
| {
src: string;
[key: string]: unknown;
[key: string]: string | boolean | undefined;
}
)[];
clientModules: string[];
@ -96,7 +96,7 @@ export type DocusaurusConfig = {
| string
| {
href: string;
[key: string]: unknown;
[key: string]: string | boolean | undefined;
}
)[];
titleDelimiter?: string;
@ -304,16 +304,16 @@ export type ThemeConfigValidationContext<T> = {
export type Plugin<Content = unknown> = {
name: string;
loadContent?: () => Promise<Content>;
loadContent?: () => Promise<Content> | Content;
contentLoaded?: (args: {
/** the content loaded by this plugin instance */
content: Content; //
/** content loaded by ALL the plugins */
allContent: AllContent;
actions: PluginContentLoadedActions;
}) => Promise<void>;
}) => Promise<void> | void;
routesLoaded?: (routes: RouteConfig[]) => void; // TODO remove soon, deprecated (alpha-60)
postBuild?: (props: Props & {content: Content}) => Promise<void>;
postBuild?: (props: Props & {content: Content}) => Promise<void> | void;
// TODO refactor the configureWebpack API surface: use an object instead of
// multiple params (requires breaking change)
configureWebpack?: (
@ -342,8 +342,10 @@ export type Plugin<Content = unknown> = {
// translations
getTranslationFiles?: (args: {
content: Content;
}) => Promise<TranslationFile[]>;
getDefaultCodeTranslationMessages?: () => Promise<{[id: string]: string}>;
}) => Promise<TranslationFile[]> | TranslationFile[];
getDefaultCodeTranslationMessages?: () =>
| Promise<{[id: string]: string}>
| {[id: string]: string};
translateContent?: (args: {
/** The content loaded by this plugin instance. */
content: Content;

View file

@ -83,7 +83,7 @@ export const FrontMatterTagsSchema = JoiFrontMatter.array()
.items(FrontMatterTagSchema)
.messages({
'array.base':
'{{#label}} does not look like a valid FrontMatter Yaml array.',
'{{#label}} does not look like a valid front matter Yaml array.',
});
export const FrontMatterTOCHeadingLevels = {

View file

@ -19,18 +19,18 @@ import './nprogress.css';
nprogress.configure({showSpinner: false});
interface Props extends RouteComponentProps {
type Props = RouteComponentProps & {
readonly routes: RouteConfig[];
readonly delay: number;
readonly location: Location;
}
interface State {
};
type State = {
nextRouteHasLoaded: boolean;
}
};
class PendingNavigation extends React.Component<Props, State> {
previousLocation: Location | null;
progressBarTimeout: NodeJS.Timeout | null;
private previousLocation: Location | null;
private progressBarTimeout: NodeJS.Timeout | null;
constructor(props: Props) {
super(props);

View file

@ -11,9 +11,9 @@ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
import type {Props} from '@docusaurus/ErrorBoundary';
import DefaultFallback from '@theme/Error';
interface State {
type State = {
error: Error | null;
}
};
export default class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {

View file

@ -26,9 +26,9 @@ declare module 'react-loadable-ssr-addon-v5-slorber' {
modulesToBeLoaded: string[],
): {js: Asset[]; css: Asset[]};
interface ReactLoadableSSRAddon {
type ReactLoadableSSRAddon = {
new (props: {filename: string});
}
};
const plugin: ReactLoadableSSRAddon;
export default plugin;
@ -47,7 +47,7 @@ declare module '@slorber/static-site-generator-webpack-plugin' {
noIndex: boolean;
};
interface StaticSiteGeneratorPlugin {
type StaticSiteGeneratorPlugin = {
new (props: {
entry: string;
locals: Locals;
@ -55,7 +55,7 @@ declare module '@slorber/static-site-generator-webpack-plugin' {
preferFoldersOutput?: boolean;
globals: {[key: string]: unknown};
});
}
};
const plugin: StaticSiteGeneratorPlugin;
export default plugin;

View file

@ -36,26 +36,27 @@ export function createBootstrapPlugin({
return siteConfigClientModules;
},
injectHtmlTags: () => {
const stylesheetsTags = stylesheets.map((source) =>
const stylesheetsTags = stylesheets.map(
(source): string | HtmlTagObject =>
typeof source === 'string'
? `<link rel="stylesheet" href="${source}">`
: ({
: {
tagName: 'link',
attributes: {
rel: 'stylesheet',
...source,
},
} as HtmlTagObject),
},
);
const scriptsTags = scripts.map((source) =>
const scriptsTags = scripts.map((source): string | HtmlTagObject =>
typeof source === 'string'
? `<script src="${source}"></script>`
: ({
: {
tagName: 'script',
attributes: {
...source,
},
} as HtmlTagObject),
},
);
return {
headTags: [...stylesheetsTags, ...scriptsTags],

View file

@ -33,7 +33,7 @@ import type {Compiler, Stats} from 'webpack';
import path from 'path';
import {sync as delSync} from 'del';
export interface Options {
export type Options = {
/**
* Write Logs to Console
* (Always enabled when dry is true)
@ -65,7 +65,7 @@ export interface Options {
* default: ['**\/*']
*/
cleanOnceBeforeBuildPatterns?: string[];
}
};
export default class CleanWebpackPlugin {
private readonly verbose: boolean;

View file

@ -10,9 +10,9 @@ import fs from 'fs-extra';
import waitOn from 'wait-on';
import type {Compiler} from 'webpack';
interface WaitPluginOptions {
type WaitPluginOptions = {
filepath: string;
}
};
export default class WaitPlugin {
filepath: string;

View file

@ -24,13 +24,13 @@ The plugin module's default export is a constructor function with the signature
`context` is plugin-agnostic, and the same object will be passed into all plugins used for a Docusaurus website. The `context` object contains the following fields:
```ts
interface LoadContext {
type LoadContext = {
siteDir: string;
generatedFilesDir: string;
siteConfig: DocusaurusConfig;
outDir: string;
baseUrl: string;
}
};
```
### `options` {#options}

View file

@ -43,17 +43,17 @@ The data that was loaded in `loadContent` will be consumed in `contentLoaded`. I
Create a route to add to the website.
```ts
interface RouteConfig {
type RouteConfig = {
path: string;
component: string;
modules?: RouteModules;
routes?: RouteConfig[];
exact?: boolean;
priority?: number;
}
interface RouteModules {
};
type RouteModules = {
[module: string]: Module | RouteModules | RouteModules[];
}
};
type Module =
| {
path: string;
@ -339,7 +339,7 @@ function injectHtmlTags(): {
type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[];
interface HtmlTagObject {
type HtmlTagObject = {
/**
* Attributes of the HTML tag
* E.g. `{'disabled': true, 'value': 'demo', 'rel': 'preconnect'}`
@ -355,7 +355,7 @@ interface HtmlTagObject {
* The inner HTML
*/
innerHTML?: string;
}
};
```
Example:

View file

@ -341,31 +341,31 @@ type PluginVersionInformation =
| {readonly type: 'local'}
| {readonly type: 'synthetic'};
interface SiteMetadata {
type SiteMetadata = {
readonly docusaurusVersion: string;
readonly siteVersion?: string;
readonly pluginVersions: Record<string, PluginVersionInformation>;
}
};
interface I18nLocaleConfig {
type I18nLocaleConfig = {
label: string;
direction: string;
}
};
interface I18n {
type I18n = {
defaultLocale: string;
locales: [string, ...string[]];
currentLocale: string;
localeConfigs: Record<string, I18nLocaleConfig>;
}
};
interface DocusaurusContext {
type DocusaurusContext = {
siteConfig: DocusaurusConfig;
siteMetadata: SiteMetadata;
globalData: Record<string, unknown>;
i18n: I18n;
codeTranslations: Record<string, string>;
}
};
```
Usage example:

View file

@ -9,14 +9,14 @@ import React, {type ReactNode, type ComponentProps} from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
export interface SvgIconProps extends ComponentProps<'svg'> {
export type SvgIconProps = ComponentProps<'svg'> & {
viewBox?: string;
size?: 'inherit' | 'small' | 'medium' | 'large';
color?: 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'warning';
svgClass?: string; // Class attribute on the child
colorAttr?: string; // Applies a color attribute to the SVG element.
children: ReactNode; // Node passed into the SVG element.
}
};
export default function Svg(props: SvgIconProps): JSX.Element {
const {

View file

@ -19,13 +19,13 @@ function WebsiteLink({to, children}: {to: string; children?: ReactNode}) {
);
}
interface ProfileProps {
type ProfileProps = {
className?: string;
name: string;
children: ReactNode;
githubUrl?: string;
twitterUrl?: string;
}
};
function TeamProfileCard({
className,

View file

@ -9,9 +9,9 @@ import React from 'react';
import type {Props as Tweet} from '../components/Tweet';
export interface TweetItem extends Tweet {
export type TweetItem = Tweet & {
showOnHomepage: boolean;
}
};
const TWEETS: TweetItem[] = [
{