diff --git a/packages/docusaurus-mdx-loader/src/index.ts b/packages/docusaurus-mdx-loader/src/index.ts index 0dae5617b506..a237dbfefaae 100644 --- a/packages/docusaurus-mdx-loader/src/index.ts +++ b/packages/docusaurus-mdx-loader/src/index.ts @@ -128,7 +128,6 @@ export default async function mdxLoader( transformImage, { staticDirs: reqOptions.staticDirs, - filePath, siteDir: reqOptions.siteDir, }, ], @@ -136,7 +135,6 @@ export default async function mdxLoader( transformLinks, { staticDirs: reqOptions.staticDirs, - filePath, siteDir: reqOptions.siteDir, }, ], diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts index efffbeb76f8a..7644080ae43a 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts @@ -27,11 +27,14 @@ const { loaders: {inlineMarkdownImageFileLoader}, } = getFileLoaderUtils(); -interface PluginOptions { - filePath: string; +type PluginOptions = { staticDirs: string[]; siteDir: string; -} +}; + +type Context = PluginOptions & { + filePath: string; +}; async function toImageRequireNode( node: Image, @@ -89,7 +92,7 @@ async function ensureImageFileExist(imagePath: string, sourceFilePath: string) { async function getImageAbsolutePath( imagePath: string, - {siteDir, filePath, staticDirs}: PluginOptions, + {siteDir, filePath, staticDirs}: Context, ) { if (imagePath.startsWith('@site/')) { const imageFilePath = path.join(siteDir, imagePath.replace('@site/', '')); @@ -123,11 +126,11 @@ async function getImageAbsolutePath( } } -async function processImageNode(node: Image, options: PluginOptions) { +async function processImageNode(node: Image, context: Context) { if (!node.url) { throw new Error( `Markdown image URL is mandatory in "${toMessageRelativeFilePath( - options.filePath, + context.filePath, )}" file`, ); } @@ -144,15 +147,17 @@ async function processImageNode(node: Image, options: PluginOptions) { return; } - const imagePath = await getImageAbsolutePath(parsedUrl.pathname, options); - await toImageRequireNode(node, imagePath, options.filePath); + const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context); + await toImageRequireNode(node, imagePath, context.filePath); } const plugin: Plugin<[PluginOptions]> = (options) => { - const transformer: Transformer = async (root) => { + const transformer: Transformer = async (root, vfile) => { const promises: Promise[] = []; visit(root, 'image', (node: Image) => { - promises.push(processImageNode(node, options)); + promises.push( + processImageNode(node, {...options, filePath: vfile.path!}), + ); }); await Promise.all(promises); }; diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts index 8d4a9dc386ef..80a85a5c8dca 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts @@ -25,11 +25,14 @@ const { loaders: {inlineMarkdownLinkFileLoader}, } = getFileLoaderUtils(); -interface PluginOptions { - filePath: string; +type PluginOptions = { staticDirs: string[]; siteDir: string; -} +}; + +type Context = PluginOptions & { + filePath: string; +}; // transform the link node to a jsx link with a require() call function toAssetRequireNode(node: Link, assetPath: string, filePath: string) { @@ -71,7 +74,7 @@ async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) { async function getAssetAbsolutePath( assetPath: string, - {siteDir, filePath, staticDirs}: PluginOptions, + {siteDir, filePath, staticDirs}: Context, ) { if (assetPath.startsWith('@site/')) { const assetFilePath = path.join(siteDir, assetPath.replace('@site/', '')); @@ -96,7 +99,7 @@ async function getAssetAbsolutePath( return null; } -async function processLinkNode(node: Link, options: PluginOptions) { +async function processLinkNode(node: Link, context: Context) { if (!node.url) { // try to improve error feedback // see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675 @@ -104,7 +107,7 @@ async function processLinkNode(node: Link, options: PluginOptions) { const line = node?.position?.start?.line || '?'; throw new Error( `Markdown link URL is mandatory in "${toMessageRelativeFilePath( - options.filePath, + context.filePath, )}" file (title: ${title}, line: ${line}).`, ); } @@ -122,17 +125,17 @@ async function processLinkNode(node: Link, options: PluginOptions) { return; } - const assetPath = await getAssetAbsolutePath(parsedUrl.pathname, options); + const assetPath = await getAssetAbsolutePath(parsedUrl.pathname, context); if (assetPath) { - toAssetRequireNode(node, assetPath, options.filePath); + toAssetRequireNode(node, assetPath, context.filePath); } } const plugin: Plugin<[PluginOptions]> = (options) => { - const transformer: Transformer = async (root) => { + const transformer: Transformer = async (root, vfile) => { const promises: Promise[] = []; visit(root, 'link', (node: Link) => { - promises.push(processLinkNode(node, options)); + promises.push(processLinkNode(node, {...options, filePath: vfile.path!})); }); await Promise.all(promises); }; diff --git a/website/docs/guides/markdown-features/markdown-features-plugins.mdx b/website/docs/guides/markdown-features/markdown-features-plugins.mdx index 8fb39c128566..031f2db4fe97 100644 --- a/website/docs/guides/markdown-features/markdown-features-plugins.mdx +++ b/website/docs/guides/markdown-features/markdown-features-plugins.mdx @@ -169,6 +169,26 @@ module.exports = { }; ``` +:::tip + +The `transformer` has a second parameter [`vfile`](https://github.com/vfile/vfile) which is useful if you need to access the current Markdown file's path. + +```js +const plugin = (options) => { + const transformer = async (ast, vfile) => { + ast.children.unshift({ + type: 'text', + value: `The current file path is ${vfile.path}`, + }); + }; + return transformer; +}; +``` + +Our `transformImage` plugin uses this parameter, for example, to transform relative image references to `require()` calls. + +::: + :::note The default plugins of Docusaurus would operate before the custom remark plugins, and that means the images or links have been converted to JSX with `require()` calls already. For example, in the example above, the table of contents generated is still the same even when all `h2` headings are now prefixed by `Section X.`, because the TOC-generating plugin is called before our custom plugin. If you need to process the MDAST before the default plugins do, use the `beforeDefaultRemarkPlugins` and `beforeDefaultRehypePlugins`.