refactor(types): move non-core, non-public types out of the types package (#7293)

This commit is contained in:
Joshua Chen 2022-05-03 17:15:48 +08:00 committed by GitHub
parent c7a5af7c4d
commit b49ae67521
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 397 additions and 404 deletions

View file

@ -3,7 +3,7 @@
"version": "2.0.0-beta.18", "version": "2.0.0-beta.18",
"description": "Docusaurus Loader for MDX", "description": "Docusaurus Loader for MDX",
"main": "lib/index.js", "main": "lib/index.js",
"types": "src/mdx-loader.d.ts", "types": "lib/index.d.ts",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },

View file

@ -7,10 +7,13 @@
// TODO Types provided by MDX 2.0 https://github.com/mdx-js/mdx/blob/main/packages/mdx/types/index.d.ts // TODO Types provided by MDX 2.0 https://github.com/mdx-js/mdx/blob/main/packages/mdx/types/index.d.ts
declare module '@mdx-js/mdx' { declare module '@mdx-js/mdx' {
import type {Processor} from 'unified'; import type {Processor, Plugin} from 'unified';
import type {MDXPlugin} from '@docusaurus/mdx-loader';
export type Options = { type MDXPlugin =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[Plugin<any[]>, any] | Plugin<any[]>;
type Options = {
filepath?: string; filepath?: string;
skipExport?: boolean; skipExport?: boolean;
wrapExport?: string; wrapExport?: string;

View file

@ -5,240 +5,31 @@
* 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 {mdxLoader} from './loader';
import {createCompiler} from '@mdx-js/mdx';
import logger from '@docusaurus/logger';
import emoji from 'remark-emoji';
import {
parseFrontMatter,
parseMarkdownContentTitle,
escapePath,
getFileLoaderUtils,
} from '@docusaurus/utils';
import stringifyObject from 'stringify-object';
import headings from './remark/headings';
import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {LoaderContext} from 'webpack';
import type {Processor} from 'unified';
const { export default mdxLoader;
loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils();
const pragma = ` export type TOCItem = {
/* @jsxRuntime classic */ readonly value: string;
/* @jsx mdx */ readonly id: string;
/* @jsxFrag mdx.Fragment */ readonly level: number;
`;
const DEFAULT_OPTIONS: MDXOptions = {
rehypePlugins: [],
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc],
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [],
}; };
const compilerCache = new Map<string | Options, [Processor, Options]>(); export type LoadedMDXContent<FrontMatter, Metadata, Assets = undefined> = {
/** As verbatim declared in the MDX document. */
type Options = MDXOptions & { readonly frontMatter: FrontMatter;
staticDirs: string[]; /** As provided by the content plugin. */
siteDir: string; readonly metadata: Metadata;
isMDXPartial?: (filePath: string) => boolean; /** A list of TOC items (headings). */
isMDXPartialFrontMatterWarningDisabled?: boolean; readonly toc: readonly TOCItem[];
removeContentTitle?: boolean; /** First h1 title before any content. */
metadataPath?: string | ((filePath: string) => string); readonly contentTitle: string | undefined;
createAssets?: (metadata: { /**
frontMatter: {[key: string]: unknown}; * Usually image assets that may be collocated like `./img/thumbnail.png`.
metadata: {[key: string]: unknown}; * The loader would also bundle these assets and the client should use these
}) => {[key: string]: unknown}; * in priority.
filepath: string; */
readonly assets: Assets;
(): JSX.Element;
}; };
export type {Options, MDXPlugin, MDXOptions} from './loader';
/**
* When this throws, it generally means that there's no metadata file associated
* with this MDX document. It can happen when using MDX partials (usually
* starting with _). That's why it's important to provide the `isMDXPartial`
* function in config
*/
async function readMetadataPath(metadataPath: string) {
try {
return await fs.readFile(metadataPath, 'utf8');
} catch (err) {
logger.error`MDX loader can't read MDX metadata file path=${metadataPath}. Maybe the isMDXPartial option function was not provided?`;
throw err;
}
}
/**
* Converts assets an object with Webpack require calls code.
* This is useful for mdx files to reference co-located assets using relative
* paths. Those assets should enter the Webpack assets pipeline and be hashed.
* For now, we only handle that for images and paths starting with `./`:
*
* `{image: "./myImage.png"}` => `{image: require("./myImage.png")}`
*/
function createAssetsExportCode(assets: {[key: string]: unknown}) {
if (Object.keys(assets).length === 0) {
return 'undefined';
}
// TODO implementation can be completed/enhanced
function createAssetValueCode(assetValue: unknown): string | undefined {
if (Array.isArray(assetValue)) {
const arrayItemCodes = assetValue.map(
(item) => createAssetValueCode(item) ?? 'undefined',
);
return `[${arrayItemCodes.join(', ')}]`;
}
// Only process string values starting with ./
// We could enhance this logic and check if file exists on disc?
if (typeof assetValue === 'string' && assetValue.startsWith('./')) {
// TODO do we have other use-cases than image assets?
// Probably not worth adding more support, as we want to move to Webpack 5 new asset system (https://github.com/facebook/docusaurus/pull/4708)
const inlineLoader = inlineMarkdownImageFileLoader;
return `require("${inlineLoader}${escapePath(assetValue)}").default`;
}
return undefined;
}
const assetEntries = Object.entries(assets);
const codeLines = assetEntries
.map(([key, value]) => {
const assetRequireCode = createAssetValueCode(value);
return assetRequireCode ? `"${key}": ${assetRequireCode},` : undefined;
})
.filter(Boolean);
return `{\n${codeLines.join('\n')}\n}`;
}
export default async function mdxLoader(
this: LoaderContext<Options>,
fileString: string,
): Promise<void> {
const callback = this.async();
const filePath = this.resourcePath;
const reqOptions = this.getOptions() ?? {};
const {frontMatter, content: contentWithTitle} = parseFrontMatter(fileString);
const {content, contentTitle} = parseMarkdownContentTitle(contentWithTitle, {
removeContentTitle: reqOptions.removeContentTitle,
});
const hasFrontMatter = Object.keys(frontMatter).length > 0;
if (!compilerCache.has(this.query)) {
const options: Options = {
...reqOptions,
remarkPlugins: [
...(reqOptions.beforeDefaultRemarkPlugins ?? []),
...DEFAULT_OPTIONS.remarkPlugins,
[
transformImage,
{
staticDirs: reqOptions.staticDirs,
siteDir: reqOptions.siteDir,
},
],
[
transformLinks,
{
staticDirs: reqOptions.staticDirs,
siteDir: reqOptions.siteDir,
},
],
...(reqOptions.remarkPlugins ?? []),
],
rehypePlugins: [
...(reqOptions.beforeDefaultRehypePlugins ?? []),
...DEFAULT_OPTIONS.rehypePlugins,
...(reqOptions.rehypePlugins ?? []),
],
};
compilerCache.set(this.query, [createCompiler(options), options]);
}
const [compiler, options] = compilerCache.get(this.query)!;
let result: string;
try {
result = await compiler
.process({
contents: content,
path: this.resourcePath,
})
.then((res) => res.toString());
} catch (err) {
return callback(err as Error);
}
// MDX partials are MDX files starting with _ or in a folder starting with _
// 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 front matter.
Those partial files use the _ prefix as a convention by default, but this is configurable.
File at ${filePath} contains front matter that will be ignored:
${JSON.stringify(frontMatter, null, 2)}`;
if (!options.isMDXPartialFrontMatterWarningDisabled) {
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
if (shouldError) {
return callback(new Error(errorMessage));
}
logger.warn(errorMessage);
}
}
function getMetadataPath(): string | undefined {
if (!isMDXPartial) {
// Read metadata for this MDX and export it.
if (options.metadataPath && typeof options.metadataPath === 'function') {
return options.metadataPath(filePath);
}
}
return undefined;
}
const metadataPath = getMetadataPath();
if (metadataPath) {
this.addDependency(metadataPath);
}
const metadataJsonString = metadataPath
? await readMetadataPath(metadataPath)
: undefined;
const metadata = metadataJsonString
? JSON.parse(metadataJsonString)
: undefined;
const assets =
reqOptions.createAssets && metadata
? reqOptions.createAssets({frontMatter, metadata})
: undefined;
const exportsCode = `
export const frontMatter = ${stringifyObject(frontMatter)};
export const contentTitle = ${stringifyObject(contentTitle)};
${metadataJsonString ? `export const metadata = ${metadataJsonString};` : ''}
${assets ? `export const assets = ${createAssetsExportCode(assets)};` : ''}
`;
const code = `
${pragma}
import React from 'react';
import { mdx } from '@mdx-js/react';
${exportsCode}
${result}
`;
return callback(null, code);
}

View file

@ -0,0 +1,253 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 {createCompiler} from '@mdx-js/mdx';
import logger from '@docusaurus/logger';
import emoji from 'remark-emoji';
import {
parseFrontMatter,
parseMarkdownContentTitle,
escapePath,
getFileLoaderUtils,
} from '@docusaurus/utils';
import stringifyObject from 'stringify-object';
import headings from './remark/headings';
import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import type {LoaderContext} from 'webpack';
import type {Processor, Plugin} from 'unified';
const {
loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils();
const pragma = `
/* @jsxRuntime classic */
/* @jsx mdx */
/* @jsxFrag mdx.Fragment */
`;
const DEFAULT_OPTIONS: MDXOptions = {
rehypePlugins: [],
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc],
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [],
};
const compilerCache = new Map<string | Options, [Processor, Options]>();
export type MDXPlugin =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[Plugin<any[]>, any] | Plugin<any[]>;
export type MDXOptions = {
remarkPlugins: MDXPlugin[];
rehypePlugins: MDXPlugin[];
beforeDefaultRemarkPlugins: MDXPlugin[];
beforeDefaultRehypePlugins: MDXPlugin[];
};
export type Options = MDXOptions & {
staticDirs: string[];
siteDir: string;
isMDXPartial?: (filePath: string) => boolean;
isMDXPartialFrontMatterWarningDisabled?: boolean;
removeContentTitle?: boolean;
metadataPath?: string | ((filePath: string) => string);
createAssets?: (metadata: {
frontMatter: {[key: string]: unknown};
metadata: {[key: string]: unknown};
}) => {[key: string]: unknown};
filepath: string;
};
/**
* When this throws, it generally means that there's no metadata file associated
* with this MDX document. It can happen when using MDX partials (usually
* starting with _). That's why it's important to provide the `isMDXPartial`
* function in config
*/
async function readMetadataPath(metadataPath: string) {
try {
return await fs.readFile(metadataPath, 'utf8');
} catch (err) {
logger.error`MDX loader can't read MDX metadata file path=${metadataPath}. Maybe the isMDXPartial option function was not provided?`;
throw err;
}
}
/**
* Converts assets an object with Webpack require calls code.
* This is useful for mdx files to reference co-located assets using relative
* paths. Those assets should enter the Webpack assets pipeline and be hashed.
* For now, we only handle that for images and paths starting with `./`:
*
* `{image: "./myImage.png"}` => `{image: require("./myImage.png")}`
*/
function createAssetsExportCode(assets: {[key: string]: unknown}) {
if (Object.keys(assets).length === 0) {
return 'undefined';
}
// TODO implementation can be completed/enhanced
function createAssetValueCode(assetValue: unknown): string | undefined {
if (Array.isArray(assetValue)) {
const arrayItemCodes = assetValue.map(
(item) => createAssetValueCode(item) ?? 'undefined',
);
return `[${arrayItemCodes.join(', ')}]`;
}
// Only process string values starting with ./
// We could enhance this logic and check if file exists on disc?
if (typeof assetValue === 'string' && assetValue.startsWith('./')) {
// TODO do we have other use-cases than image assets?
// Probably not worth adding more support, as we want to move to Webpack 5 new asset system (https://github.com/facebook/docusaurus/pull/4708)
const inlineLoader = inlineMarkdownImageFileLoader;
return `require("${inlineLoader}${escapePath(assetValue)}").default`;
}
return undefined;
}
const assetEntries = Object.entries(assets);
const codeLines = assetEntries
.map(([key, value]) => {
const assetRequireCode = createAssetValueCode(value);
return assetRequireCode ? `"${key}": ${assetRequireCode},` : undefined;
})
.filter(Boolean);
return `{\n${codeLines.join('\n')}\n}`;
}
export async function mdxLoader(
this: LoaderContext<Options>,
fileString: string,
): Promise<void> {
const callback = this.async();
const filePath = this.resourcePath;
const reqOptions = this.getOptions() ?? {};
const {frontMatter, content: contentWithTitle} = parseFrontMatter(fileString);
const {content, contentTitle} = parseMarkdownContentTitle(contentWithTitle, {
removeContentTitle: reqOptions.removeContentTitle,
});
const hasFrontMatter = Object.keys(frontMatter).length > 0;
if (!compilerCache.has(this.query)) {
const options: Options = {
...reqOptions,
remarkPlugins: [
...(reqOptions.beforeDefaultRemarkPlugins ?? []),
...DEFAULT_OPTIONS.remarkPlugins,
[
transformImage,
{
staticDirs: reqOptions.staticDirs,
siteDir: reqOptions.siteDir,
},
],
[
transformLinks,
{
staticDirs: reqOptions.staticDirs,
siteDir: reqOptions.siteDir,
},
],
...(reqOptions.remarkPlugins ?? []),
],
rehypePlugins: [
...(reqOptions.beforeDefaultRehypePlugins ?? []),
...DEFAULT_OPTIONS.rehypePlugins,
...(reqOptions.rehypePlugins ?? []),
],
};
compilerCache.set(this.query, [createCompiler(options), options]);
}
const [compiler, options] = compilerCache.get(this.query)!;
let result: string;
try {
result = await compiler
.process({
contents: content,
path: this.resourcePath,
})
.then((res) => res.toString());
} catch (err) {
return callback(err as Error);
}
// MDX partials are MDX files starting with _ or in a folder starting with _
// 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 front matter.
Those partial files use the _ prefix as a convention by default, but this is configurable.
File at ${filePath} contains front matter that will be ignored:
${JSON.stringify(frontMatter, null, 2)}`;
if (!options.isMDXPartialFrontMatterWarningDisabled) {
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
if (shouldError) {
return callback(new Error(errorMessage));
}
logger.warn(errorMessage);
}
}
function getMetadataPath(): string | undefined {
if (!isMDXPartial) {
// Read metadata for this MDX and export it.
if (options.metadataPath && typeof options.metadataPath === 'function') {
return options.metadataPath(filePath);
}
}
return undefined;
}
const metadataPath = getMetadataPath();
if (metadataPath) {
this.addDependency(metadataPath);
}
const metadataJsonString = metadataPath
? await readMetadataPath(metadataPath)
: undefined;
const metadata = metadataJsonString
? JSON.parse(metadataJsonString)
: undefined;
const assets =
reqOptions.createAssets && metadata
? reqOptions.createAssets({frontMatter, metadata})
: undefined;
const exportsCode = `
export const frontMatter = ${stringifyObject(frontMatter)};
export const contentTitle = ${stringifyObject(contentTitle)};
${metadataJsonString ? `export const metadata = ${metadataJsonString};` : ''}
${assets ? `export const assets = ${createAssetsExportCode(assets)};` : ''}
`;
const code = `
${pragma}
import React from 'react';
import { mdx } from '@mdx-js/react';
${exportsCode}
${result}
`;
return callback(null, code);
}

View file

@ -1,37 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type {Plugin} from 'unified';
import type {TOCItem} from '@docusaurus/types';
export type MDXPlugin =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[Plugin<any[]>, any] | Plugin<any[]>;
export type MDXOptions = {
remarkPlugins: MDXPlugin[];
rehypePlugins: MDXPlugin[];
beforeDefaultRemarkPlugins: MDXPlugin[];
beforeDefaultRehypePlugins: MDXPlugin[];
};
export type LoadedMDXContent<FrontMatter, Metadata, Assets = undefined> = {
/** As verbatim declared in the MDX document. */
readonly frontMatter: FrontMatter;
/** As provided by the content plugin. */
readonly metadata: Metadata;
/** A list of TOC items (headings). */
readonly toc: readonly TOCItem[];
/** First h1 title before any content. */
readonly contentTitle: string | undefined;
/**
* Usually image assets that may be collocated like `./img/thumbnail.png`.
* The loader would also bundle these assets and the client should use these
* in priority.
*/
readonly assets: Assets;
(): JSX.Element;
};

View file

@ -13,7 +13,7 @@ import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit'; import visit from 'unist-util-visit';
import {toValue} from '../utils'; import {toValue} from '../utils';
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '../..';
import type {Node, Parent} from 'unist'; import type {Node, Parent} from 'unist';
import type {Heading, Literal} from 'mdast'; import type {Heading, Literal} from 'mdast';
import type {Transformer} from 'unified'; import type {Transformer} from 'unified';

View file

@ -7,5 +7,6 @@
"declarationMap": true, "declarationMap": true,
"rootDir": "src", "rootDir": "src",
"outDir": "lib" "outDir": "lib"
} },
"include": ["src"]
} }

View file

@ -20,6 +20,8 @@ import {
getContentPathList, getContentPathList,
getDataFilePath, getDataFilePath,
DEFAULT_PLUGIN_ID, DEFAULT_PLUGIN_ID,
type TagsListItem,
type TagModule,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {translateContent, getTranslationFiles} from './translations'; import {translateContent, getTranslationFiles} from './translations';
@ -31,13 +33,7 @@ import type {
BlogContentPaths, BlogContentPaths,
BlogMarkdownLoaderOptions, BlogMarkdownLoaderOptions,
} from './types'; } from './types';
import type { import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
LoadContext,
Plugin,
HtmlTags,
TagsListItem,
TagModule,
} from '@docusaurus/types';
import { import {
generateBlogPosts, generateBlogPosts,
getSourceToPermalink, getSourceToPermalink,

View file

@ -7,9 +7,8 @@
declare module '@docusaurus/plugin-content-blog' { declare module '@docusaurus/plugin-content-blog' {
import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag} from '@docusaurus/utils'; import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {Overwrite} from 'utility-types'; import type {Overwrite} from 'utility-types';
import type {Tag} from '@docusaurus/types';
export type Assets = { export type Assets = {
/** /**
@ -487,7 +486,7 @@ declare module '@theme/BlogListPage' {
declare module '@theme/BlogTagsListPage' { declare module '@theme/BlogTagsListPage' {
import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
import type {TagsListItem} from '@docusaurus/types'; import type {TagsListItem} from '@docusaurus/utils';
export interface Props { export interface Props {
/** Blog sidebar. */ /** Blog sidebar. */
@ -503,7 +502,7 @@ declare module '@theme/BlogTagsPostsPage' {
import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
import type {Content} from '@theme/BlogPostPage'; import type {Content} from '@theme/BlogPostPage';
import type {Metadata} from '@theme/BlogListPage'; import type {Metadata} from '@theme/BlogListPage';
import type {TagModule} from '@docusaurus/types'; import type {TagModule} from '@docusaurus/utils';
export interface Props { export interface Props {
/** Blog sidebar. */ /** Blog sidebar. */

View file

@ -5,8 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import type {BrokenMarkdownLink, ContentPaths} from '@docusaurus/utils'; import type {BrokenMarkdownLink, ContentPaths, Tag} from '@docusaurus/utils';
import type {Tag} from '@docusaurus/types';
import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog'; import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog';
import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage'; import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage';

View file

@ -7,8 +7,13 @@
declare module '@docusaurus/plugin-content-docs' { declare module '@docusaurus/plugin-content-docs' {
import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {ContentPaths, FrontMatterTag} from '@docusaurus/utils'; import type {
import type {TagsListItem, TagModule, Tag} from '@docusaurus/types'; ContentPaths,
FrontMatterTag,
TagsListItem,
TagModule,
Tag,
} from '@docusaurus/utils';
import type {Required} from 'utility-types'; import type {Required} from 'utility-types';
export type Assets = { export type Assets = {

View file

@ -8,14 +8,13 @@
/// <reference types="@docusaurus/module-type-aliases" /> /// <reference types="@docusaurus/module-type-aliases" />
import type {Sidebars} from './sidebars/types'; import type {Sidebars} from './sidebars/types';
import type {BrokenMarkdownLink} from '@docusaurus/utils'; import type {BrokenMarkdownLink, Tag} from '@docusaurus/utils';
import type { import type {
VersionMetadata, VersionMetadata,
LastUpdateData, LastUpdateData,
DocMetadata, DocMetadata,
CategoryGeneratedIndexMetadata, CategoryGeneratedIndexMetadata,
} from '@docusaurus/plugin-content-docs'; } from '@docusaurus/plugin-content-docs';
import type {Tag} from '@docusaurus/types';
import type {SidebarsUtils} from './sidebars/utils'; import type {SidebarsUtils} from './sidebars/utils';
export type DocFile = { export type DocFile = {

View file

@ -1057,7 +1057,7 @@ declare module '@theme/Details' {
} }
declare module '@theme/TOCItems' { declare module '@theme/TOCItems' {
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/mdx-loader';
export interface Props { export interface Props {
readonly toc: readonly TOCItem[]; readonly toc: readonly TOCItem[];
@ -1085,7 +1085,7 @@ declare module '@theme/TOCItems/Tree' {
} }
declare module '@theme/TOC' { declare module '@theme/TOC' {
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/mdx-loader';
// `minHeadingLevel` only comes from doc/post front matter, and won't have a // `minHeadingLevel` only comes from doc/post front matter, and won't have a
// default set by Joi. See TOC, TOCInline, TOCCollapsible for examples. // default set by Joi. See TOC, TOCInline, TOCCollapsible for examples.
@ -1100,7 +1100,7 @@ declare module '@theme/TOC' {
} }
declare module '@theme/TOCInline' { declare module '@theme/TOCInline' {
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/mdx-loader';
export interface Props { export interface Props {
readonly toc: readonly TOCItem[]; readonly toc: readonly TOCItem[];
@ -1112,7 +1112,7 @@ declare module '@theme/TOCInline' {
} }
declare module '@theme/TOCCollapsible' { declare module '@theme/TOCCollapsible' {
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/mdx-loader';
export interface Props { export interface Props {
readonly className?: string; readonly className?: string;
@ -1236,7 +1236,7 @@ declare module '@theme/IconExternalLink' {
} }
declare module '@theme/TagsListByLetter' { declare module '@theme/TagsListByLetter' {
import type {TagsListItem} from '@docusaurus/types'; import type {TagsListItem} from '@docusaurus/utils';
export interface Props { export interface Props {
readonly tags: readonly TagsListItem[]; readonly tags: readonly TagsListItem[];
@ -1245,7 +1245,7 @@ declare module '@theme/TagsListByLetter' {
} }
declare module '@theme/TagsListInline' { declare module '@theme/TagsListInline' {
import type {Tag} from '@docusaurus/types'; import type {Tag} from '@docusaurus/utils';
export interface Props { export interface Props {
readonly tags: readonly Tag[]; readonly tags: readonly Tag[];
@ -1254,7 +1254,7 @@ declare module '@theme/TagsListInline' {
} }
declare module '@theme/Tag' { declare module '@theme/Tag' {
import type {TagsListItem} from '@docusaurus/types'; import type {TagsListItem} from '@docusaurus/utils';
import type {Optional} from 'utility-types'; import type {Optional} from 'utility-types';
export interface Props extends Optional<TagsListItem, 'count'> {} export interface Props extends Optional<TagsListItem, 'count'> {}

View file

@ -6,7 +6,7 @@
*/ */
import {translate} from '@docusaurus/Translate'; import {translate} from '@docusaurus/Translate';
import type {TagsListItem} from '@docusaurus/types'; import type {TagsListItem} from '@docusaurus/utils';
export const translateTagsPageTitle = (): string => export const translateTagsPageTitle = (): string =>
translate({ translate({

View file

@ -6,7 +6,7 @@
*/ */
import {useMemo} from 'react'; import {useMemo} from 'react';
import type {TOCItem} from '@docusaurus/types'; import type {TOCItem} from '@docusaurus/mdx-loader';
export type TOCTreeNode = { export type TOCTreeNode = {
readonly value: string; readonly value: string;

View file

@ -427,13 +427,13 @@ export type LoadContext = {
}; };
export type Props = LoadContext & { export type Props = LoadContext & {
readonly headTags: string; headTags: string;
readonly preBodyTags: string; preBodyTags: string;
readonly postBodyTags: string; postBodyTags: string;
readonly siteMetadata: SiteMetadata; siteMetadata: SiteMetadata;
readonly routes: RouteConfig[]; routes: RouteConfig[];
readonly routesPaths: string[]; routesPaths: string[];
readonly plugins: LoadedPlugin[]; plugins: LoadedPlugin[];
}; };
// === Plugin === // === Plugin ===
@ -447,9 +447,7 @@ export type PluginContentLoadedActions = {
export type ConfigureWebpackUtils = { export type ConfigureWebpackUtils = {
getStyleLoaders: ( getStyleLoaders: (
isServer: boolean, isServer: boolean,
cssOptions: { cssOptions: {[key: string]: unknown},
[key: string]: unknown;
},
) => RuleSetRule[]; ) => RuleSetRule[];
getJSLoader: (options: { getJSLoader: (options: {
isServer: boolean; isServer: boolean;
@ -464,7 +462,7 @@ export type AllContent = {
}; };
// TODO improve type (not exposed by postcss-loader) // TODO improve type (not exposed by postcss-loader)
export type PostCssOptions = {[key: string]: unknown} & {plugins: unknown[]}; export type PostCssOptions = {plugins: unknown[]; [key: string]: unknown};
type HtmlTagObject = { type HtmlTagObject = {
/** /**
@ -557,40 +555,6 @@ export type Plugin<Content = unknown> = {
}) => ThemeConfig; }) => ThemeConfig;
}; };
export type NormalizedPluginConfig = {
/**
* The default export of the plugin module, or alternatively, what's provided
* in the config file as inline plugins. Note that if a file is like:
*
* ```ts
* export default plugin() {...}
* export validateOptions() {...}
* ```
*
* Then the static methods may not exist here. `pluginModule.module` will
* always take priority.
*/
plugin: PluginModule;
/** Options as they are provided in the config, not validated yet. */
options: PluginOptions;
/** Only available when a string is provided in config. */
pluginModule?: {
/**
* Raw module name as provided in the config. Shorthands have been resolved,
* so at least it's directly `require.resolve`able.
*/
path: string;
/** Whatever gets imported with `require`. */
module: ImportedPluginModule;
};
/**
* Different from `pluginModule.path`, this one is always an absolute path,
* used to resolve relative paths returned from lifecycles. If it's an inline
* plugin, it will be path to the config file.
*/
entryPath: string;
};
export type InitializedPlugin = Plugin & { export type InitializedPlugin = Plugin & {
readonly options: Required<PluginOptions>; readonly options: Required<PluginOptions>;
readonly version: PluginVersionInformation; readonly version: PluginVersionInformation;
@ -612,9 +576,8 @@ export type SwizzleComponentConfig = {
export type SwizzleConfig = { export type SwizzleConfig = {
components: {[componentName: string]: SwizzleComponentConfig}; components: {[componentName: string]: SwizzleComponentConfig};
// Other settings could be added here, // Other settings could be added here, like the ability to declare the config
// For example: the ability to declare the config as exhaustive // as exhaustive so that we can emit errors
// so that we can emit errors
}; };
export type PluginModule = { export type PluginModule = {
@ -626,21 +589,13 @@ export type PluginModule = {
getSwizzleConfig?: () => SwizzleConfig | undefined; getSwizzleConfig?: () => SwizzleConfig | undefined;
}; };
export type ImportedPluginModule = PluginModule & {
default?: PluginModule;
};
export type Preset = { export type Preset = {
plugins?: PluginConfig[]; plugins?: PluginConfig[];
themes?: PluginConfig[]; themes?: PluginConfig[];
}; };
export type PresetModule = { export type PresetModule = {
<T>(context: LoadContext, presetOptions: T): Preset; (context: LoadContext, presetOptions: unknown): Preset;
};
export type ImportedPresetModule = PresetModule & {
default?: PresetModule;
}; };
// === Route registry === // === Route registry ===
@ -775,19 +730,6 @@ export type Registry = {
]; ];
}; };
/**
* Aliases used for Webpack resolution (useful for implementing swizzling)
*/
export type ThemeAliases = {
[alias: string]: string;
};
export type TOCItem = {
readonly value: string;
readonly id: string;
readonly level: number;
};
export type ClientModule = { export type ClientModule = {
onRouteDidUpdate?: (args: { onRouteDidUpdate?: (args: {
previousLocation: Location | null; previousLocation: Location | null;
@ -799,25 +741,6 @@ export type ClientModule = {
}) => (() => void) | void; }) => (() => void) | void;
}; };
/** What the user configures. */
export type Tag = {
label: string;
/** Permalink to this tag's page, without the `/tags/` base path. */
permalink: string;
};
/** What the tags list page should know about each tag. */
export type TagsListItem = Tag & {
/** Number of posts/docs with this tag. */
count: number;
};
/** What the tag's own page should know about the tag. */
export type TagModule = TagsListItem & {
/** The tags list page's permalink. */
allTagsPath: string;
};
export type UseDataOptions = { export type UseDataOptions = {
/** /**
* Throw an error, or simply return undefined if the data cannot be found. Use * Throw an error, or simply return undefined if the data cannot be found. Use

View file

@ -6,8 +6,7 @@
*/ */
import Joi from './Joi'; import Joi from './Joi';
import {isValidPathname, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils';
import type {Tag} from '@docusaurus/types';
import {JoiFrontMatter} from './JoiFrontMatter'; import {JoiFrontMatter} from './JoiFrontMatter';
export const PluginIdSchema = Joi.string() export const PluginIdSchema = Joi.string()

View file

@ -57,6 +57,9 @@ export {
buildSshUrl, buildSshUrl,
} from './urlUtils'; } from './urlUtils';
export { export {
type Tag,
type TagsListItem,
type TagModule,
type FrontMatterTag, type FrontMatterTag,
normalizeFrontMatterTags, normalizeFrontMatterTags,
groupTaggedItems, groupTaggedItems,

View file

@ -7,7 +7,25 @@
import _ from 'lodash'; import _ from 'lodash';
import {normalizeUrl} from './urlUtils'; import {normalizeUrl} from './urlUtils';
import type {Tag} from '@docusaurus/types';
/** What the user configures. */
export type Tag = {
label: string;
/** Permalink to this tag's page, without the `/tags/` base path. */
permalink: string;
};
/** What the tags list page should know about each tag. */
export type TagsListItem = Tag & {
/** Number of posts/docs with this tag. */
count: number;
};
/** What the tag's own page should know about the tag. */
export type TagModule = TagsListItem & {
/** The tags list page's permalink. */
allTagsPath: string;
};
export type FrontMatterTag = string | Tag; export type FrontMatterTag = string | Tag;

View file

@ -8,11 +8,11 @@
import leven from 'leven'; import leven from 'leven';
import _ from 'lodash'; import _ from 'lodash';
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import type {NormalizedPluginConfig} from '../../server/plugins/configs';
import type { import type {
InitializedPlugin, InitializedPlugin,
SwizzleAction, SwizzleAction,
SwizzleActionStatus, SwizzleActionStatus,
NormalizedPluginConfig,
} from '@docusaurus/types'; } from '@docusaurus/types';
export const SwizzleActions: SwizzleAction[] = ['wrap', 'eject']; export const SwizzleActions: SwizzleAction[] = ['wrap', 'eject'];

View file

@ -12,10 +12,46 @@ import {resolveModuleName} from './moduleShorthand';
import type { import type {
LoadContext, LoadContext,
PluginConfig, PluginConfig,
ImportedPluginModule, PluginModule,
NormalizedPluginConfig, PluginOptions,
} from '@docusaurus/types'; } from '@docusaurus/types';
type ImportedPluginModule = PluginModule & {default?: PluginModule};
export type NormalizedPluginConfig = {
/**
* The default export of the plugin module, or alternatively, what's provided
* in the config file as inline plugins. Note that if a file is like:
*
* ```ts
* export default plugin() {...}
* export validateOptions() {...}
* ```
*
* Then the static methods may not exist here. `pluginModule.module` will
* always take priority.
*/
plugin: PluginModule;
/** Options as they are provided in the config, not validated yet. */
options: PluginOptions;
/** Only available when a string is provided in config. */
pluginModule?: {
/**
* Raw module name as provided in the config. Shorthands have been resolved,
* so at least it's directly `require.resolve`able.
*/
path: string;
/** Whatever gets imported with `require`. */
module: ImportedPluginModule;
};
/**
* Different from `pluginModule.path`, this one is always an absolute path,
* used to resolve relative paths returned from lifecycles. If it's an inline
* plugin, it will be path to the config file.
*/
entryPath: string;
};
async function normalizePluginConfig( async function normalizePluginConfig(
pluginConfig: Exclude<PluginConfig, false | null>, pluginConfig: Exclude<PluginConfig, false | null>,
configPath: string, configPath: string,

View file

@ -13,7 +13,6 @@ import type {
PluginModule, PluginModule,
PluginOptions, PluginOptions,
InitializedPlugin, InitializedPlugin,
NormalizedPluginConfig,
} from '@docusaurus/types'; } from '@docusaurus/types';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {getPluginVersion} from '../siteMetadata'; import {getPluginVersion} from '../siteMetadata';
@ -22,7 +21,7 @@ import {
normalizePluginOptions, normalizePluginOptions,
normalizeThemeConfig, normalizeThemeConfig,
} from '@docusaurus/utils-validation'; } from '@docusaurus/utils-validation';
import {loadPluginConfigs} from './configs'; import {loadPluginConfigs, type NormalizedPluginConfig} from './configs';
function getOptionValidationFunction( function getOptionValidationFunction(
normalizedPluginConfig: NormalizedPluginConfig, normalizedPluginConfig: NormalizedPluginConfig,

View file

@ -10,11 +10,13 @@ import importFresh from 'import-fresh';
import type { import type {
LoadContext, LoadContext,
PluginConfig, PluginConfig,
ImportedPresetModule, PresetModule,
DocusaurusConfig, DocusaurusConfig,
} from '@docusaurus/types'; } from '@docusaurus/types';
import {resolveModuleName} from './moduleShorthand'; import {resolveModuleName} from './moduleShorthand';
type ImportedPresetModule = PresetModule & {default?: PresetModule};
/** /**
* Calls preset functions, aggregates each of their return values, and returns * Calls preset functions, aggregates each of their return values, and returns
* the plugin and theme configs. * the plugin and theme configs.

View file

@ -12,7 +12,7 @@ import {excludeJS, clientDir, createBaseConfig} from '../base';
import * as utils from '@docusaurus/utils/lib/webpackUtils'; import * as utils from '@docusaurus/utils/lib/webpackUtils';
import {posixPath} from '@docusaurus/utils'; import {posixPath} from '@docusaurus/utils';
import _ from 'lodash'; import _ from 'lodash';
import type {Props, ThemeAliases} from '@docusaurus/types'; import type {Props} from '@docusaurus/types';
describe('babel transpilation exclude logic', () => { describe('babel transpilation exclude logic', () => {
it('always transpiles client dir files', () => { it('always transpiles client dir files', () => {
@ -106,9 +106,8 @@ describe('base webpack config', () => {
}); });
it('creates webpack aliases', async () => { it('creates webpack aliases', async () => {
// @ts-expect-error: Docusaurus webpack alias is always an object const aliases = ((await createBaseConfig(props, true)).resolve?.alias ??
const aliases: ThemeAliases = {}) as {[alias: string]: string};
(await createBaseConfig(props, true)).resolve?.alias ?? {};
// Make aliases relative so that test work on all computers // Make aliases relative so that test work on all computers
const relativeAliases = _.mapValues(aliases, (a) => const relativeAliases = _.mapValues(aliases, (a) =>
posixPath(path.relative(props.siteDir, a)), posixPath(path.relative(props.siteDir, a)),

View file

@ -15,7 +15,12 @@ import {
Globby, Globby,
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import _ from 'lodash'; import _ from 'lodash';
import type {ThemeAliases, LoadedPlugin} from '@docusaurus/types'; import type {LoadedPlugin} from '@docusaurus/types';
/**
* Aliases used for Webpack resolution (useful for implementing swizzling)
*/
type ThemeAliases = {[alias: string]: string};
const ThemeFallbackDir = path.join(__dirname, '../../client/theme-fallback'); const ThemeFallbackDir = path.join(__dirname, '../../client/theme-fallback');