refactor(mdx-loader): re-export metadata module instead of serializing it (#10470)

This commit is contained in:
Sébastien Lorber 2024-09-02 17:54:29 +02:00 committed by GitHub
parent 3d69ff3d47
commit a47e8dda2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 33 additions and 145 deletions

View file

@ -7,6 +7,7 @@
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import { import {
aliasedSitePath,
DEFAULT_PARSE_FRONT_MATTER, DEFAULT_PARSE_FRONT_MATTER,
getFileLoaderUtils, getFileLoaderUtils,
getWebpackLoaderCompilerName, getWebpackLoaderCompilerName,
@ -16,7 +17,6 @@ import {
compileToJSX, compileToJSX,
createAssetsExportCode, createAssetsExportCode,
extractContentTitleData, extractContentTitleData,
readMetadataPath,
} from './utils'; } from './utils';
import type { import type {
SimpleProcessors, SimpleProcessors,
@ -35,12 +35,6 @@ type Pluggable = any; // TODO fix this asap
export type MDXPlugin = Pluggable; export type MDXPlugin = Pluggable;
// This represents the path to the mdx metadata bundle path + its loaded content
export type LoadedMetadata = {
metadataPath: string;
metadataContent: unknown;
};
export type Options = Partial<MDXOptions> & { export type Options = Partial<MDXOptions> & {
markdownConfig: MarkdownConfig; markdownConfig: MarkdownConfig;
staticDirs: string[]; staticDirs: string[];
@ -48,13 +42,10 @@ export type Options = Partial<MDXOptions> & {
isMDXPartial?: (filePath: string) => boolean; isMDXPartial?: (filePath: string) => boolean;
isMDXPartialFrontMatterWarningDisabled?: boolean; isMDXPartialFrontMatterWarningDisabled?: boolean;
removeContentTitle?: boolean; removeContentTitle?: boolean;
metadataPath?: (filePath: string) => string;
// TODO Docusaurus v4: rename to just "metadata"?
// We kept retro-compatibility in v3 in case plugins/sites use mdx loader
metadataPath?: string | ((filePath: string) => string | LoadedMetadata);
createAssets?: (metadata: { createAssets?: (metadata: {
filePath: string;
frontMatter: {[key: string]: unknown}; frontMatter: {[key: string]: unknown};
metadata: unknown;
}) => {[key: string]: unknown}; }) => {[key: string]: unknown};
resolveMarkdownLink?: ResolveMarkdownLink; resolveMarkdownLink?: ResolveMarkdownLink;
@ -112,40 +103,16 @@ ${JSON.stringify(frontMatter, null, 2)}`;
} }
} }
async function loadMetadata(): Promise<LoadedMetadata | undefined> { const metadataPath = (function getMetadataPath() {
if (!isMDXPartial) { if (!isMDXPartial) {
// Read metadata for this MDX and export it. return options.metadataPath?.(filePath);
if (options.metadataPath && typeof options.metadataPath === 'function') {
const metadata = options.metadataPath(filePath);
if (!metadata) {
return undefined;
}
if (typeof metadata === 'string') {
return {
metadataPath: metadata,
metadataContent: await readMetadataPath(metadata),
};
}
if (!metadata.metadataPath) {
throw new Error(`Metadata path missing for file ${filePath}`);
}
if (!metadata.metadataContent) {
throw new Error(`Metadata content missing for file ${filePath}`);
}
return metadata;
}
} }
return undefined; return undefined;
} })();
const metadata = await loadMetadata();
if (metadata) {
this.addDependency(metadata.metadataPath);
}
const assets = const assets =
options.createAssets && metadata options.createAssets && !isMDXPartial
? options.createAssets({frontMatter, metadata: metadata.metadataContent}) ? options.createAssets({filePath, frontMatter})
: undefined; : undefined;
const fileLoaderUtils = getFileLoaderUtils(compilerName === 'server'); const fileLoaderUtils = getFileLoaderUtils(compilerName === 'server');
@ -156,8 +123,11 @@ ${JSON.stringify(frontMatter, null, 2)}`;
export const frontMatter = ${stringifyObject(frontMatter)}; export const frontMatter = ${stringifyObject(frontMatter)};
export const contentTitle = ${stringifyObject(contentTitle)}; export const contentTitle = ${stringifyObject(contentTitle)};
${ ${
metadata metadataPath
? `export const metadata = ${JSON.stringify(metadata.metadataContent)};` ? `export {default as metadata} from '${aliasedSitePath(
metadataPath,
options.siteDir,
)}'`
: '' : ''
} }
${ ${

View file

@ -5,7 +5,6 @@
* 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 logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import {escapePath, type WebpackCompilerName} from '@docusaurus/utils'; import {escapePath, type WebpackCompilerName} from '@docusaurus/utils';
import {getProcessor, type SimpleProcessorResult} from './processor'; import {getProcessor, type SimpleProcessorResult} from './processor';
@ -13,23 +12,6 @@ import {validateMDXFrontMatter} from './frontMatter';
import preprocessor from './preprocessor'; import preprocessor from './preprocessor';
import type {Options} from './loader'; import type {Options} 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
*/
export async function readMetadataPath(metadataPath: string): Promise<unknown> {
try {
return await fs.readJSON(metadataPath, 'utf8');
} catch (error) {
throw new Error(
logger.interpolate`MDX loader can't read MDX metadata file path=${metadataPath}. Maybe the isMDXPartial option function was not provided?`,
{cause: error as Error},
);
}
}
/** /**
* Converts assets an object with Webpack require calls code. * Converts assets an object with Webpack require calls code.
* This is useful for mdx files to reference co-located assets using relative * This is useful for mdx files to reference co-located assets using relative

View file

@ -44,8 +44,6 @@ import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
import type {LoadContext, Plugin} from '@docusaurus/types'; import type {LoadContext, Plugin} from '@docusaurus/types';
import type { import type {
PluginOptions, PluginOptions,
BlogPostFrontMatter,
BlogPostMetadata,
Assets, Assets,
BlogTags, BlogTags,
BlogContent, BlogContent,
@ -135,33 +133,26 @@ export default async function pluginContentBlog(
// Note that metadataPath must be the same/in-sync as // Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX. // the path from createData for each MDX.
const aliasedPath = aliasedSitePath(mdxPath, siteDir); const aliasedPath = aliasedSitePath(mdxPath, siteDir);
const metadataPath = path.join( return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
dataDir,
`${docuHash(aliasedPath)}.json`,
);
const metadataContent =
contentHelpers.sourceToBlogPost.get(aliasedPath)!.metadata;
return {
metadataPath,
metadataContent,
};
}, },
// For blog posts a title in markdown is always removed // For blog posts a title in markdown is always removed
// Blog posts title are rendered separately // Blog posts title are rendered separately
removeContentTitle: true, removeContentTitle: true,
// Assets allow to convert some relative images paths to // createAssets converts relative paths to require() calls
// require() calls createAssets: ({filePath}: {filePath: string}): Assets => {
// @ts-expect-error: TODO fix typing issue const blogPost = contentHelpers.sourceToBlogPost.get(
createAssets: ({ aliasedSitePath(filePath, siteDir),
frontMatter, )!;
metadata, if (!blogPost) {
}: { throw new Error(`Blog post not found for filePath=${filePath}`);
frontMatter: BlogPostFrontMatter; }
metadata: BlogPostMetadata; return {
}): Assets => ({ image: blogPost.metadata.frontMatter.image as string,
image: frontMatter.image, authorsImageUrls: blogPost.metadata.authors.map(
authorsImageUrls: metadata.authors.map((author) => author.imageURL), (author) => author.imageURL,
}), ),
};
},
markdownConfig: siteConfig.markdown, markdownConfig: siteConfig.markdown,
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => { resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
const permalink = resolveMarkdownLinkPathname(linkPathname, { const permalink = resolveMarkdownLinkPathname(linkPathname, {

View file

@ -123,18 +123,9 @@ export default async function pluginContentDocs(
// Note that metadataPath must be the same/in-sync as // Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX. // the path from createData for each MDX.
const aliasedPath = aliasedSitePath(mdxPath, siteDir); const aliasedPath = aliasedSitePath(mdxPath, siteDir);
const metadataPath = path.join( return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
dataDir,
`${docuHash(aliasedPath)}.json`,
);
const metadataContent = contentHelpers.sourceToDoc.get(aliasedPath);
return {
metadataPath,
metadataContent,
};
}, },
// Assets allow to convert some relative images paths to // createAssets converts relative paths to require() calls
// require(...) calls
createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({ createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({
image: frontMatter.image, image: frontMatter.image,
}), }),

View file

@ -1,33 +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 {LoadedContent, Metadata} from '@docusaurus/plugin-content-pages';
function indexPagesBySource(content: LoadedContent): Map<string, Metadata> {
return new Map(content.map((page) => [page.source, page]));
}
// TODO this is bad, we should have a better way to do this (new lifecycle?)
// The source to page/permalink is a mutable map passed to the mdx loader
// See https://github.com/facebook/docusaurus/pull/10457
// See https://github.com/facebook/docusaurus/pull/10185
export function createContentHelpers() {
const sourceToPage = new Map<string, Metadata>();
// const sourceToPermalink = new Map<string, string>();
// Mutable map update :/
function updateContent(content: LoadedContent): void {
sourceToPage.clear();
// sourceToPermalink.clear();
indexPagesBySource(content).forEach((value, key) => {
sourceToPage.set(key, value);
// sourceToPermalink.set(key, value.metadata.permalink);
});
}
return {updateContent, sourceToPage};
}

View file

@ -24,7 +24,6 @@ import {
getContentPathList, getContentPathList,
loadPagesContent, loadPagesContent,
} from './content'; } from './content';
import {createContentHelpers} from './contentHelpers';
import type {LoadContext, Plugin} from '@docusaurus/types'; import type {LoadContext, Plugin} from '@docusaurus/types';
import type { import type {
PluginOptions, PluginOptions,
@ -47,8 +46,6 @@ export default async function pluginContentPages(
); );
const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID); const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID);
const contentHelpers = createContentHelpers();
async function createPagesMDXLoaderRule(): Promise<RuleSetRule> { async function createPagesMDXLoaderRule(): Promise<RuleSetRule> {
const { const {
admonitions, admonitions,
@ -76,18 +73,9 @@ export default async function pluginContentPages(
// Note that metadataPath must be the same/in-sync as // Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX. // the path from createData for each MDX.
const aliasedSource = aliasedSitePath(mdxPath, siteDir); const aliasedSource = aliasedSitePath(mdxPath, siteDir);
const metadataPath = path.join( return path.join(dataDir, `${docuHash(aliasedSource)}.json`);
dataDir,
`${docuHash(aliasedSource)}.json`,
);
const metadataContent = contentHelpers.sourceToPage.get(aliasedSource);
return {
metadataPath,
metadataContent,
};
}, },
// Assets allow to convert some relative images paths to // createAssets converts relative paths to require() calls
// require(...) calls
createAssets: ({frontMatter}: {frontMatter: PageFrontMatter}) => ({ createAssets: ({frontMatter}: {frontMatter: PageFrontMatter}) => ({
image: frontMatter.image, image: frontMatter.image,
}), }),
@ -125,7 +113,6 @@ export default async function pluginContentPages(
if (!content) { if (!content) {
return; return;
} }
contentHelpers.updateContent(content);
await createAllRoutes({content, options, actions}); await createAllRoutes({content, options, actions});
}, },