feat(core, mdx-loader): deduplicate MDX compilation - siteConfig.future.experimental_faster.mdxCrossCompilerCache (#10479)

This commit is contained in:
Sébastien Lorber 2024-09-06 16:07:09 +02:00 committed by GitHub
parent 897ebbe3ca
commit 5bab0b5432
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 333 additions and 151 deletions

View file

@ -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,

View file

@ -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';

View file

@ -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);
}
} }

View 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
};

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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(

View file

@ -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(),
],
}; };
} }

View file

@ -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();

View file

@ -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,
},
}); });
} }

View file

@ -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 = {

View file

@ -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,
}, },

View file

@ -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,
}, },

View file

@ -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
"
`);
});
});
}); });
}); });

View file

@ -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()

View file

@ -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: [],

View file

@ -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),