mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 10:17:55 +02:00
feat(core, mdx-loader): deduplicate MDX compilation - siteConfig.future.experimental_faster.mdxCrossCompilerCache
(#10479)
This commit is contained in:
parent
897ebbe3ca
commit
5bab0b5432
18 changed files with 333 additions and 151 deletions
|
@ -6,30 +6,49 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createProcessors} from './processor';
|
import {createProcessors} from './processor';
|
||||||
import type {Options} from './loader';
|
import type {Options} from './options';
|
||||||
import type {RuleSetRule, RuleSetUseItem} from 'webpack';
|
import type {RuleSetRule, RuleSetUseItem} from 'webpack';
|
||||||
|
|
||||||
async function enhancedOptions(options: Options): Promise<Options> {
|
type CreateOptions = {
|
||||||
|
useCrossCompilerCache?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function normalizeOptions(
|
||||||
|
optionsInput: Options & CreateOptions,
|
||||||
|
): Promise<Options> {
|
||||||
// Because Jest doesn't like ESM / createProcessors()
|
// Because Jest doesn't like ESM / createProcessors()
|
||||||
if (process.env.N0DE_ENV === 'test' || process.env.JEST_WORKER_ID) {
|
if (process.env.N0DE_ENV === 'test' || process.env.JEST_WORKER_ID) {
|
||||||
return options;
|
return optionsInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let options = optionsInput;
|
||||||
|
|
||||||
// We create the processor earlier here, to avoid the lazy processor creating
|
// We create the processor earlier here, to avoid the lazy processor creating
|
||||||
// Lazy creation messes-up with Rsdoctor ability to measure mdx-loader perf
|
// Lazy creation messes-up with Rsdoctor ability to measure mdx-loader perf
|
||||||
const newOptions: Options = options.processors
|
if (!options.processors) {
|
||||||
? options
|
options = {...options, processors: await createProcessors({options})};
|
||||||
: {...options, processors: await createProcessors({options})};
|
}
|
||||||
|
|
||||||
return newOptions;
|
// Cross-compiler cache permits to compile client/server MDX only once
|
||||||
|
// We don't want to cache in dev mode (docusaurus start)
|
||||||
|
// We only have multiple compilers in production mode (docusaurus build)
|
||||||
|
// TODO wrong but good enough for now (example: "docusaurus build --dev")
|
||||||
|
if (options.useCrossCompilerCache && process.env.NODE_ENV === 'production') {
|
||||||
|
options = {
|
||||||
|
...options,
|
||||||
|
crossCompilerCache: new Map(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createMDXLoaderItem(
|
export async function createMDXLoaderItem(
|
||||||
options: Options,
|
options: Options & CreateOptions,
|
||||||
): Promise<RuleSetUseItem> {
|
): Promise<RuleSetUseItem> {
|
||||||
return {
|
return {
|
||||||
loader: require.resolve('@docusaurus/mdx-loader'),
|
loader: require.resolve('@docusaurus/mdx-loader'),
|
||||||
options: await enhancedOptions(options),
|
options: await normalizeOptions(options),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +57,7 @@ export async function createMDXLoaderRule({
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
include: RuleSetRule['include'];
|
include: RuleSetRule['include'];
|
||||||
options: Options;
|
options: Options & CreateOptions;
|
||||||
}): Promise<RuleSetRule> {
|
}): Promise<RuleSetRule> {
|
||||||
return {
|
return {
|
||||||
test: /\.mdx?$/i,
|
test: /\.mdx?$/i,
|
||||||
|
|
|
@ -37,5 +37,6 @@ export type LoadedMDXContent<FrontMatter, Metadata, Assets = undefined> = {
|
||||||
(): JSX.Element;
|
(): JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type {Options, MDXPlugin} from './loader';
|
export type {MDXPlugin} from './loader';
|
||||||
export type {MDXOptions} from './processor';
|
export type {MDXOptions} from './processor';
|
||||||
|
export type {Options} from './options';
|
||||||
|
|
|
@ -18,14 +18,8 @@ import {
|
||||||
createAssetsExportCode,
|
createAssetsExportCode,
|
||||||
extractContentTitleData,
|
extractContentTitleData,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import type {
|
import type {WebpackCompilerName} from '@docusaurus/utils';
|
||||||
SimpleProcessors,
|
import type {Options} from './options';
|
||||||
MDXOptions,
|
|
||||||
SimpleProcessorResult,
|
|
||||||
} from './processor';
|
|
||||||
import type {ResolveMarkdownLink} from './remark/resolveMarkdownLinks';
|
|
||||||
|
|
||||||
import type {MarkdownConfig} from '@docusaurus/types';
|
|
||||||
import type {LoaderContext} from 'webpack';
|
import type {LoaderContext} from 'webpack';
|
||||||
|
|
||||||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
||||||
|
@ -35,33 +29,17 @@ type Pluggable = any; // TODO fix this asap
|
||||||
|
|
||||||
export type MDXPlugin = Pluggable;
|
export type MDXPlugin = Pluggable;
|
||||||
|
|
||||||
export type Options = Partial<MDXOptions> & {
|
async function loadMDX({
|
||||||
markdownConfig: MarkdownConfig;
|
fileContent,
|
||||||
staticDirs: string[];
|
filePath,
|
||||||
siteDir: string;
|
options,
|
||||||
isMDXPartial?: (filePath: string) => boolean;
|
compilerName,
|
||||||
isMDXPartialFrontMatterWarningDisabled?: boolean;
|
}: {
|
||||||
removeContentTitle?: boolean;
|
fileContent: string;
|
||||||
metadataPath?: (filePath: string) => string;
|
filePath: string;
|
||||||
createAssets?: (metadata: {
|
options: Options;
|
||||||
filePath: string;
|
compilerName: WebpackCompilerName;
|
||||||
frontMatter: {[key: string]: unknown};
|
}): Promise<string> {
|
||||||
}) => {[key: string]: unknown};
|
|
||||||
resolveMarkdownLink?: ResolveMarkdownLink;
|
|
||||||
|
|
||||||
// Will usually be created by "createMDXLoaderItem"
|
|
||||||
processors?: SimpleProcessors;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function mdxLoader(
|
|
||||||
this: LoaderContext<Options>,
|
|
||||||
fileContent: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const compilerName = getWebpackLoaderCompilerName(this);
|
|
||||||
const callback = this.async();
|
|
||||||
const filePath = this.resourcePath;
|
|
||||||
const options: Options = this.getOptions();
|
|
||||||
|
|
||||||
const {frontMatter} = await options.markdownConfig.parseFrontMatter({
|
const {frontMatter} = await options.markdownConfig.parseFrontMatter({
|
||||||
filePath,
|
filePath,
|
||||||
fileContent,
|
fileContent,
|
||||||
|
@ -70,18 +48,13 @@ export async function mdxLoader(
|
||||||
|
|
||||||
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
||||||
|
|
||||||
let result: SimpleProcessorResult;
|
const result = await compileToJSX({
|
||||||
try {
|
fileContent,
|
||||||
result = await compileToJSX({
|
filePath,
|
||||||
fileContent,
|
frontMatter,
|
||||||
filePath,
|
options,
|
||||||
frontMatter,
|
compilerName,
|
||||||
options,
|
});
|
||||||
compilerName,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return callback(error as Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentTitle = extractContentTitleData(result.data);
|
const contentTitle = extractContentTitleData(result.data);
|
||||||
|
|
||||||
|
@ -97,7 +70,7 @@ ${JSON.stringify(frontMatter, null, 2)}`;
|
||||||
if (!options.isMDXPartialFrontMatterWarningDisabled) {
|
if (!options.isMDXPartialFrontMatterWarningDisabled) {
|
||||||
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
|
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
|
||||||
if (shouldError) {
|
if (shouldError) {
|
||||||
return callback(new Error(errorMessage));
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
logger.warn(errorMessage);
|
logger.warn(errorMessage);
|
||||||
}
|
}
|
||||||
|
@ -146,5 +119,68 @@ ${exportsCode}
|
||||||
${result.content}
|
${result.content}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return callback(null, code);
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we cache promises instead of strings
|
||||||
|
// This is because client/server compilations might be triggered in parallel
|
||||||
|
// When this happens for the same file, we don't want to compile it twice
|
||||||
|
async function loadMDXWithCaching({
|
||||||
|
resource,
|
||||||
|
fileContent,
|
||||||
|
filePath,
|
||||||
|
options,
|
||||||
|
compilerName,
|
||||||
|
}: {
|
||||||
|
resource: string; // path?query#hash
|
||||||
|
filePath: string; // path
|
||||||
|
fileContent: string;
|
||||||
|
options: Options;
|
||||||
|
compilerName: WebpackCompilerName;
|
||||||
|
}): Promise<string> {
|
||||||
|
// Note we "resource" as cache key, not "filePath" nor "fileContent"
|
||||||
|
// This is because:
|
||||||
|
// - the same file can be compiled in different variants (blog.mdx?truncated)
|
||||||
|
// - the same content can be processed differently (versioned docs links)
|
||||||
|
const cacheKey = resource;
|
||||||
|
|
||||||
|
const cachedPromise = options.crossCompilerCache?.get(cacheKey);
|
||||||
|
if (cachedPromise) {
|
||||||
|
// We can clean up the cache and free memory here
|
||||||
|
// We know there are only 2 compilations for the same file
|
||||||
|
// Note: once we introduce RSCs we'll probably have 3 compilations
|
||||||
|
// Note: we can't use string keys in WeakMap
|
||||||
|
// But we could eventually use WeakRef for the values
|
||||||
|
options.crossCompilerCache?.delete(cacheKey);
|
||||||
|
return cachedPromise;
|
||||||
|
}
|
||||||
|
const promise = loadMDX({
|
||||||
|
fileContent,
|
||||||
|
filePath,
|
||||||
|
options,
|
||||||
|
compilerName,
|
||||||
|
});
|
||||||
|
options.crossCompilerCache?.set(cacheKey, promise);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mdxLoader(
|
||||||
|
this: LoaderContext<Options>,
|
||||||
|
fileContent: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const compilerName = getWebpackLoaderCompilerName(this);
|
||||||
|
const callback = this.async();
|
||||||
|
const options: Options = this.getOptions();
|
||||||
|
try {
|
||||||
|
const result = await loadMDXWithCaching({
|
||||||
|
resource: this.resource,
|
||||||
|
filePath: this.resourcePath,
|
||||||
|
fileContent,
|
||||||
|
options,
|
||||||
|
compilerName,
|
||||||
|
});
|
||||||
|
return callback(null, result);
|
||||||
|
} catch (error) {
|
||||||
|
return callback(error as Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
29
packages/docusaurus-mdx-loader/src/options.ts
Normal file
29
packages/docusaurus-mdx-loader/src/options.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* 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 {MDXOptions, SimpleProcessors} from './processor';
|
||||||
|
import type {MarkdownConfig} from '@docusaurus/types';
|
||||||
|
import type {ResolveMarkdownLink} from './remark/resolveMarkdownLinks';
|
||||||
|
|
||||||
|
export type Options = Partial<MDXOptions> & {
|
||||||
|
markdownConfig: MarkdownConfig;
|
||||||
|
staticDirs: string[];
|
||||||
|
siteDir: string;
|
||||||
|
isMDXPartial?: (filePath: string) => boolean;
|
||||||
|
isMDXPartialFrontMatterWarningDisabled?: boolean;
|
||||||
|
removeContentTitle?: boolean;
|
||||||
|
metadataPath?: (filePath: string) => string;
|
||||||
|
createAssets?: (metadata: {
|
||||||
|
filePath: string;
|
||||||
|
frontMatter: {[key: string]: unknown};
|
||||||
|
}) => {[key: string]: unknown};
|
||||||
|
resolveMarkdownLink?: ResolveMarkdownLink;
|
||||||
|
|
||||||
|
// Will usually be created by "createMDXLoaderItem"
|
||||||
|
processors?: SimpleProcessors;
|
||||||
|
crossCompilerCache?: Map<string, Promise<string>>; // MDX => Promise<JSX> cache
|
||||||
|
};
|
|
@ -11,7 +11,7 @@ import {
|
||||||
admonitionTitleToDirectiveLabel,
|
admonitionTitleToDirectiveLabel,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {normalizeAdmonitionOptions} from './remark/admonitions';
|
import {normalizeAdmonitionOptions} from './remark/admonitions';
|
||||||
import type {Options} from './loader';
|
import type {Options} from './options';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preprocess the string before passing it to MDX
|
* Preprocess the string before passing it to MDX
|
||||||
|
|
|
@ -20,7 +20,7 @@ import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
|
||||||
import {getFormat} from './format';
|
import {getFormat} from './format';
|
||||||
import type {WebpackCompilerName} from '@docusaurus/utils';
|
import type {WebpackCompilerName} from '@docusaurus/utils';
|
||||||
import type {MDXFrontMatter} from './frontMatter';
|
import type {MDXFrontMatter} from './frontMatter';
|
||||||
import type {Options} from './loader';
|
import type {Options} from './options';
|
||||||
import type {AdmonitionOptions} from './remark/admonitions';
|
import type {AdmonitionOptions} from './remark/admonitions';
|
||||||
|
|
||||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {escapePath, type WebpackCompilerName} from '@docusaurus/utils';
|
||||||
import {getProcessor, type SimpleProcessorResult} from './processor';
|
import {getProcessor, type SimpleProcessorResult} from './processor';
|
||||||
import {validateMDXFrontMatter} from './frontMatter';
|
import {validateMDXFrontMatter} from './frontMatter';
|
||||||
import preprocessor from './preprocessor';
|
import preprocessor from './preprocessor';
|
||||||
import type {Options} from './loader';
|
import type {Options} from './options';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts assets an object with Webpack require calls code.
|
* Converts assets an object with Webpack require calls code.
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
getFileCommitDate,
|
getFileCommitDate,
|
||||||
LAST_UPDATE_FALLBACK,
|
LAST_UPDATE_FALLBACK,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
|
import {DEFAULT_FUTURE_CONFIG} from '@docusaurus/core/src/server/configValidation';
|
||||||
import pluginContentBlog from '../index';
|
import pluginContentBlog from '../index';
|
||||||
import {validateOptions} from '../options';
|
import {validateOptions} from '../options';
|
||||||
import type {
|
import type {
|
||||||
|
@ -106,7 +107,7 @@ const getPlugin = async (
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
url: 'https://docusaurus.io',
|
url: 'https://docusaurus.io',
|
||||||
markdown,
|
markdown,
|
||||||
future: {},
|
future: DEFAULT_FUTURE_CONFIG,
|
||||||
staticDirectories: ['static'],
|
staticDirectories: ['static'],
|
||||||
} as DocusaurusConfig;
|
} as DocusaurusConfig;
|
||||||
return pluginContentBlog(
|
return pluginContentBlog(
|
||||||
|
|
|
@ -21,10 +21,7 @@ import {
|
||||||
resolveMarkdownLinkPathname,
|
resolveMarkdownLinkPathname,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
||||||
import {
|
import {createMDXLoaderItem} from '@docusaurus/mdx-loader';
|
||||||
createMDXLoaderItem,
|
|
||||||
type Options as MDXLoaderOptions,
|
|
||||||
} from '@docusaurus/mdx-loader';
|
|
||||||
import {
|
import {
|
||||||
getBlogTags,
|
getBlogTags,
|
||||||
paginateBlogPosts,
|
paginateBlogPosts,
|
||||||
|
@ -114,7 +111,9 @@ export default async function pluginContentBlog(
|
||||||
|
|
||||||
const contentDirs = getContentPathList(contentPaths);
|
const contentDirs = getContentPathList(contentPaths);
|
||||||
|
|
||||||
const loaderOptions: MDXLoaderOptions = {
|
const mdxLoaderItem = await createMDXLoaderItem({
|
||||||
|
useCrossCompilerCache:
|
||||||
|
siteConfig.future.experimental_faster.mdxCrossCompilerCache,
|
||||||
admonitions,
|
admonitions,
|
||||||
remarkPlugins,
|
remarkPlugins,
|
||||||
rehypePlugins,
|
rehypePlugins,
|
||||||
|
@ -168,7 +167,7 @@ export default async function pluginContentBlog(
|
||||||
}
|
}
|
||||||
return permalink;
|
return permalink;
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
function createBlogMarkdownLoader(): RuleSetUseItem {
|
function createBlogMarkdownLoader(): RuleSetUseItem {
|
||||||
const markdownLoaderOptions: BlogMarkdownLoaderOptions = {
|
const markdownLoaderOptions: BlogMarkdownLoaderOptions = {
|
||||||
|
@ -185,10 +184,7 @@ export default async function pluginContentBlog(
|
||||||
include: contentDirs
|
include: contentDirs
|
||||||
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
||||||
.map(addTrailingPathSeparator),
|
.map(addTrailingPathSeparator),
|
||||||
use: [
|
use: [mdxLoaderItem, createBlogMarkdownLoader()],
|
||||||
await createMDXLoaderItem(loaderOptions),
|
|
||||||
createBlogMarkdownLoader(),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,7 @@ import {
|
||||||
getTagsFile,
|
getTagsFile,
|
||||||
getTagsFilePathsToWatch,
|
getTagsFilePathsToWatch,
|
||||||
} from '@docusaurus/utils-validation';
|
} from '@docusaurus/utils-validation';
|
||||||
import {
|
import {createMDXLoaderRule} from '@docusaurus/mdx-loader';
|
||||||
createMDXLoaderRule,
|
|
||||||
type Options as MDXLoaderOptions,
|
|
||||||
} from '@docusaurus/mdx-loader';
|
|
||||||
import {loadSidebars, resolveSidebarPathOption} from './sidebars';
|
import {loadSidebars, resolveSidebarPathOption} from './sidebars';
|
||||||
import {CategoryMetadataFilenamePattern} from './sidebars/generator';
|
import {CategoryMetadataFilenamePattern} from './sidebars/generator';
|
||||||
import {
|
import {
|
||||||
|
@ -107,50 +104,56 @@ export default async function pluginContentDocs(
|
||||||
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
||||||
.map(addTrailingPathSeparator);
|
.map(addTrailingPathSeparator);
|
||||||
|
|
||||||
const loaderOptions: MDXLoaderOptions = {
|
return createMDXLoaderRule({
|
||||||
admonitions: options.admonitions,
|
include: contentDirs,
|
||||||
remarkPlugins,
|
options: {
|
||||||
rehypePlugins,
|
useCrossCompilerCache:
|
||||||
recmaPlugins,
|
siteConfig.future.experimental_faster.mdxCrossCompilerCache,
|
||||||
beforeDefaultRehypePlugins,
|
admonitions: options.admonitions,
|
||||||
beforeDefaultRemarkPlugins,
|
remarkPlugins,
|
||||||
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
rehypePlugins,
|
||||||
path.resolve(siteDir, dir),
|
recmaPlugins,
|
||||||
),
|
beforeDefaultRehypePlugins,
|
||||||
siteDir,
|
beforeDefaultRemarkPlugins,
|
||||||
isMDXPartial: createAbsoluteFilePathMatcher(options.exclude, contentDirs),
|
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
||||||
metadataPath: (mdxPath: string) => {
|
path.resolve(siteDir, dir),
|
||||||
// Note that metadataPath must be the same/in-sync as
|
),
|
||||||
// the path from createData for each MDX.
|
siteDir,
|
||||||
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
|
isMDXPartial: createAbsoluteFilePathMatcher(
|
||||||
return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
|
options.exclude,
|
||||||
|
contentDirs,
|
||||||
|
),
|
||||||
|
metadataPath: (mdxPath: string) => {
|
||||||
|
// Note that metadataPath must be the same/in-sync as
|
||||||
|
// the path from createData for each MDX.
|
||||||
|
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
|
||||||
|
return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
|
||||||
|
},
|
||||||
|
// createAssets converts relative paths to require() calls
|
||||||
|
createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({
|
||||||
|
image: frontMatter.image,
|
||||||
|
}),
|
||||||
|
markdownConfig: siteConfig.markdown,
|
||||||
|
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
||||||
|
const version = getVersionFromSourceFilePath(
|
||||||
|
sourceFilePath,
|
||||||
|
versionsMetadata,
|
||||||
|
);
|
||||||
|
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
||||||
|
sourceFilePath,
|
||||||
|
sourceToPermalink: contentHelpers.sourceToPermalink,
|
||||||
|
siteDir,
|
||||||
|
contentPaths: version,
|
||||||
|
});
|
||||||
|
if (permalink === null) {
|
||||||
|
logger.report(
|
||||||
|
siteConfig.onBrokenMarkdownLinks,
|
||||||
|
)`Docs markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath} for version number=${version.versionName}`;
|
||||||
|
}
|
||||||
|
return permalink;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// createAssets converts relative paths to require() calls
|
});
|
||||||
createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({
|
|
||||||
image: frontMatter.image,
|
|
||||||
}),
|
|
||||||
markdownConfig: siteConfig.markdown,
|
|
||||||
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
|
||||||
const version = getVersionFromSourceFilePath(
|
|
||||||
sourceFilePath,
|
|
||||||
versionsMetadata,
|
|
||||||
);
|
|
||||||
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
|
||||||
sourceFilePath,
|
|
||||||
sourceToPermalink: contentHelpers.sourceToPermalink,
|
|
||||||
siteDir,
|
|
||||||
contentPaths: version,
|
|
||||||
});
|
|
||||||
if (permalink === null) {
|
|
||||||
logger.report(
|
|
||||||
siteConfig.onBrokenMarkdownLinks,
|
|
||||||
)`Docs markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath} for version number=${version.versionName}`;
|
|
||||||
}
|
|
||||||
return permalink;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return createMDXLoaderRule({include: contentDirs, options: loaderOptions});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsMDXLoaderRule = await createDocsMDXLoaderRule();
|
const docsMDXLoaderRule = await createDocsMDXLoaderRule();
|
||||||
|
|
|
@ -14,10 +14,7 @@ import {
|
||||||
createAbsoluteFilePathMatcher,
|
createAbsoluteFilePathMatcher,
|
||||||
DEFAULT_PLUGIN_ID,
|
DEFAULT_PLUGIN_ID,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {
|
import {createMDXLoaderRule} from '@docusaurus/mdx-loader';
|
||||||
createMDXLoaderRule,
|
|
||||||
type Options as MDXLoaderOptions,
|
|
||||||
} from '@docusaurus/mdx-loader';
|
|
||||||
import {createAllRoutes} from './routes';
|
import {createAllRoutes} from './routes';
|
||||||
import {
|
import {
|
||||||
createPagesContentPaths,
|
createPagesContentPaths,
|
||||||
|
@ -57,36 +54,39 @@ export default async function pluginContentPages(
|
||||||
} = options;
|
} = options;
|
||||||
const contentDirs = getContentPathList(contentPaths);
|
const contentDirs = getContentPathList(contentPaths);
|
||||||
|
|
||||||
const loaderOptions: MDXLoaderOptions = {
|
|
||||||
admonitions,
|
|
||||||
remarkPlugins,
|
|
||||||
rehypePlugins,
|
|
||||||
recmaPlugins,
|
|
||||||
beforeDefaultRehypePlugins,
|
|
||||||
beforeDefaultRemarkPlugins,
|
|
||||||
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
|
||||||
path.resolve(siteDir, dir),
|
|
||||||
),
|
|
||||||
siteDir,
|
|
||||||
isMDXPartial: createAbsoluteFilePathMatcher(options.exclude, contentDirs),
|
|
||||||
metadataPath: (mdxPath: string) => {
|
|
||||||
// Note that metadataPath must be the same/in-sync as
|
|
||||||
// the path from createData for each MDX.
|
|
||||||
const aliasedSource = aliasedSitePath(mdxPath, siteDir);
|
|
||||||
return path.join(dataDir, `${docuHash(aliasedSource)}.json`);
|
|
||||||
},
|
|
||||||
// createAssets converts relative paths to require() calls
|
|
||||||
createAssets: ({frontMatter}: {frontMatter: PageFrontMatter}) => ({
|
|
||||||
image: frontMatter.image,
|
|
||||||
}),
|
|
||||||
markdownConfig: siteConfig.markdown,
|
|
||||||
};
|
|
||||||
|
|
||||||
return createMDXLoaderRule({
|
return createMDXLoaderRule({
|
||||||
include: contentDirs
|
include: contentDirs
|
||||||
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
||||||
.map(addTrailingPathSeparator),
|
.map(addTrailingPathSeparator),
|
||||||
options: loaderOptions,
|
options: {
|
||||||
|
useCrossCompilerCache:
|
||||||
|
siteConfig.future.experimental_faster.mdxCrossCompilerCache,
|
||||||
|
admonitions,
|
||||||
|
remarkPlugins,
|
||||||
|
rehypePlugins,
|
||||||
|
recmaPlugins,
|
||||||
|
beforeDefaultRehypePlugins,
|
||||||
|
beforeDefaultRemarkPlugins,
|
||||||
|
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
||||||
|
path.resolve(siteDir, dir),
|
||||||
|
),
|
||||||
|
siteDir,
|
||||||
|
isMDXPartial: createAbsoluteFilePathMatcher(
|
||||||
|
options.exclude,
|
||||||
|
contentDirs,
|
||||||
|
),
|
||||||
|
metadataPath: (mdxPath: string) => {
|
||||||
|
// Note that metadataPath must be the same/in-sync as
|
||||||
|
// the path from createData for each MDX.
|
||||||
|
const aliasedSource = aliasedSitePath(mdxPath, siteDir);
|
||||||
|
return path.join(dataDir, `${docuHash(aliasedSource)}.json`);
|
||||||
|
},
|
||||||
|
// createAssets converts relative paths to require() calls
|
||||||
|
createAssets: ({frontMatter}: {frontMatter: PageFrontMatter}) => ({
|
||||||
|
image: frontMatter.image,
|
||||||
|
}),
|
||||||
|
markdownConfig: siteConfig.markdown,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
packages/docusaurus-types/src/config.d.ts
vendored
1
packages/docusaurus-types/src/config.d.ts
vendored
|
@ -126,6 +126,7 @@ export type StorageConfig = {
|
||||||
export type FasterConfig = {
|
export type FasterConfig = {
|
||||||
swcJsLoader: boolean;
|
swcJsLoader: boolean;
|
||||||
swcJsMinimizer: boolean;
|
swcJsMinimizer: boolean;
|
||||||
|
mdxCrossCompilerCache: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FutureConfig = {
|
export type FutureConfig = {
|
||||||
|
|
|
@ -9,6 +9,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -74,6 +75,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -139,6 +141,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -204,6 +207,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -269,6 +273,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -334,6 +339,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -399,6 +405,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -466,6 +473,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -533,6 +541,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
@ -603,6 +612,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
|
||||||
"favicon": "img/docusaurus.ico",
|
"favicon": "img/docusaurus.ico",
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -79,6 +79,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
"future": {
|
"future": {
|
||||||
"experimental_faster": {
|
"experimental_faster": {
|
||||||
|
"mdxCrossCompilerCache": false,
|
||||||
"swcJsLoader": false,
|
"swcJsLoader": false,
|
||||||
"swcJsMinimizer": false,
|
"swcJsMinimizer": false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -48,6 +48,7 @@ describe('normalizeConfig', () => {
|
||||||
experimental_faster: {
|
experimental_faster: {
|
||||||
swcJsLoader: true,
|
swcJsLoader: true,
|
||||||
swcJsMinimizer: true,
|
swcJsMinimizer: true,
|
||||||
|
mdxCrossCompilerCache: true,
|
||||||
},
|
},
|
||||||
experimental_storage: {
|
experimental_storage: {
|
||||||
type: 'sessionStorage',
|
type: 'sessionStorage',
|
||||||
|
@ -743,6 +744,7 @@ describe('future', () => {
|
||||||
experimental_faster: {
|
experimental_faster: {
|
||||||
swcJsLoader: true,
|
swcJsLoader: true,
|
||||||
swcJsMinimizer: true,
|
swcJsMinimizer: true,
|
||||||
|
mdxCrossCompilerCache: true,
|
||||||
},
|
},
|
||||||
experimental_storage: {
|
experimental_storage: {
|
||||||
type: 'sessionStorage',
|
type: 'sessionStorage',
|
||||||
|
@ -1091,6 +1093,8 @@ describe('future', () => {
|
||||||
it('accepts faster - full', () => {
|
it('accepts faster - full', () => {
|
||||||
const faster: FasterConfig = {
|
const faster: FasterConfig = {
|
||||||
swcJsLoader: true,
|
swcJsLoader: true,
|
||||||
|
swcJsMinimizer: true,
|
||||||
|
mdxCrossCompilerCache: true,
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
normalizeConfig({
|
normalizeConfig({
|
||||||
|
@ -1202,6 +1206,7 @@ describe('future', () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('swcJsMinimizer', () => {
|
describe('swcJsMinimizer', () => {
|
||||||
it('accepts - undefined', () => {
|
it('accepts - undefined', () => {
|
||||||
const faster: Partial<FasterConfig> = {
|
const faster: Partial<FasterConfig> = {
|
||||||
|
@ -1272,5 +1277,76 @@ describe('future', () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('mdxCrossCompilerCache', () => {
|
||||||
|
it('accepts - undefined', () => {
|
||||||
|
const faster: Partial<FasterConfig> = {
|
||||||
|
mdxCrossCompilerCache: undefined,
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
normalizeConfig({
|
||||||
|
future: {
|
||||||
|
experimental_faster: faster,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(fasterContaining({mdxCrossCompilerCache: false}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts - true', () => {
|
||||||
|
const faster: Partial<FasterConfig> = {
|
||||||
|
mdxCrossCompilerCache: true,
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
normalizeConfig({
|
||||||
|
future: {
|
||||||
|
experimental_faster: faster,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(fasterContaining({mdxCrossCompilerCache: true}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts - false', () => {
|
||||||
|
const faster: Partial<FasterConfig> = {
|
||||||
|
mdxCrossCompilerCache: false,
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
normalizeConfig({
|
||||||
|
future: {
|
||||||
|
experimental_faster: faster,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(fasterContaining({mdxCrossCompilerCache: false}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects - null', () => {
|
||||||
|
// @ts-expect-error: invalid
|
||||||
|
const faster: Partial<FasterConfig> = {mdxCrossCompilerCache: 42};
|
||||||
|
expect(() =>
|
||||||
|
normalizeConfig({
|
||||||
|
future: {
|
||||||
|
experimental_faster: faster,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
""future.experimental_faster.mdxCrossCompilerCache" must be a boolean
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects - number', () => {
|
||||||
|
// @ts-expect-error: invalid
|
||||||
|
const faster: Partial<FasterConfig> = {mdxCrossCompilerCache: 42};
|
||||||
|
expect(() =>
|
||||||
|
normalizeConfig({
|
||||||
|
future: {
|
||||||
|
experimental_faster: faster,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
""future.experimental_faster.mdxCrossCompilerCache" must be a boolean
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,12 +44,14 @@ export const DEFAULT_STORAGE_CONFIG: StorageConfig = {
|
||||||
export const DEFAULT_FASTER_CONFIG: FasterConfig = {
|
export const DEFAULT_FASTER_CONFIG: FasterConfig = {
|
||||||
swcJsLoader: false,
|
swcJsLoader: false,
|
||||||
swcJsMinimizer: false,
|
swcJsMinimizer: false,
|
||||||
|
mdxCrossCompilerCache: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// When using the "faster: true" shortcut
|
// When using the "faster: true" shortcut
|
||||||
export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = {
|
export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = {
|
||||||
swcJsLoader: true,
|
swcJsLoader: true,
|
||||||
swcJsMinimizer: true,
|
swcJsMinimizer: true,
|
||||||
|
mdxCrossCompilerCache: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_FUTURE_CONFIG: FutureConfig = {
|
export const DEFAULT_FUTURE_CONFIG: FutureConfig = {
|
||||||
|
@ -217,6 +219,9 @@ const FASTER_CONFIG_SCHEMA = Joi.alternatives()
|
||||||
swcJsMinimizer: Joi.boolean().default(
|
swcJsMinimizer: Joi.boolean().default(
|
||||||
DEFAULT_FASTER_CONFIG.swcJsMinimizer,
|
DEFAULT_FASTER_CONFIG.swcJsMinimizer,
|
||||||
),
|
),
|
||||||
|
mdxCrossCompilerCache: Joi.boolean().default(
|
||||||
|
DEFAULT_FASTER_CONFIG.mdxCrossCompilerCache,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
Joi.boolean()
|
Joi.boolean()
|
||||||
.required()
|
.required()
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {fromPartial} from '@total-typescript/shoehorn';
|
import {fromPartial} from '@total-typescript/shoehorn';
|
||||||
import {loadPlugins, reloadPlugin} from '../plugins';
|
import {loadPlugins, reloadPlugin} from '../plugins';
|
||||||
|
import {DEFAULT_FUTURE_CONFIG} from '../../configValidation';
|
||||||
import type {LoadContext, Plugin, PluginConfig} from '@docusaurus/types';
|
import type {LoadContext, Plugin, PluginConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
async function testLoad({
|
async function testLoad({
|
||||||
|
@ -27,6 +28,7 @@ async function testLoad({
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
trailingSlash: true,
|
trailingSlash: true,
|
||||||
|
future: DEFAULT_FUTURE_CONFIG,
|
||||||
themeConfig: {},
|
themeConfig: {},
|
||||||
staticDirectories: [],
|
staticDirectories: [],
|
||||||
presets: [],
|
presets: [],
|
||||||
|
|
|
@ -80,6 +80,8 @@ export async function createMDXFallbackPlugin({
|
||||||
siteConfig,
|
siteConfig,
|
||||||
}: LoadContext): Promise<InitializedPlugin> {
|
}: LoadContext): Promise<InitializedPlugin> {
|
||||||
const mdxLoaderItem = await createMDXLoaderItem({
|
const mdxLoaderItem = await createMDXLoaderItem({
|
||||||
|
useCrossCompilerCache:
|
||||||
|
siteConfig.future.experimental_faster.mdxCrossCompilerCache,
|
||||||
admonitions: true,
|
admonitions: true,
|
||||||
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
||||||
path.resolve(siteDir, dir),
|
path.resolve(siteDir, dir),
|
||||||
|
|
Loading…
Add table
Reference in a new issue