refactor(mdx-loader): use vfile.path to access Markdown file path (#6443)

This commit is contained in:
Joshua Chen 2022-01-22 22:28:50 +08:00 committed by GitHub
parent e40cafccd5
commit 3d7ba337c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 22 deletions

View file

@ -128,7 +128,6 @@ export default async function mdxLoader(
transformImage, transformImage,
{ {
staticDirs: reqOptions.staticDirs, staticDirs: reqOptions.staticDirs,
filePath,
siteDir: reqOptions.siteDir, siteDir: reqOptions.siteDir,
}, },
], ],
@ -136,7 +135,6 @@ export default async function mdxLoader(
transformLinks, transformLinks,
{ {
staticDirs: reqOptions.staticDirs, staticDirs: reqOptions.staticDirs,
filePath,
siteDir: reqOptions.siteDir, siteDir: reqOptions.siteDir,
}, },
], ],

View file

@ -27,11 +27,14 @@ const {
loaders: {inlineMarkdownImageFileLoader}, loaders: {inlineMarkdownImageFileLoader},
} = getFileLoaderUtils(); } = getFileLoaderUtils();
interface PluginOptions { type PluginOptions = {
filePath: string;
staticDirs: string[]; staticDirs: string[];
siteDir: string; siteDir: string;
} };
type Context = PluginOptions & {
filePath: string;
};
async function toImageRequireNode( async function toImageRequireNode(
node: Image, node: Image,
@ -89,7 +92,7 @@ async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
async function getImageAbsolutePath( async function getImageAbsolutePath(
imagePath: string, imagePath: string,
{siteDir, filePath, staticDirs}: PluginOptions, {siteDir, filePath, staticDirs}: Context,
) { ) {
if (imagePath.startsWith('@site/')) { if (imagePath.startsWith('@site/')) {
const imageFilePath = path.join(siteDir, imagePath.replace('@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) { if (!node.url) {
throw new Error( throw new Error(
`Markdown image URL is mandatory in "${toMessageRelativeFilePath( `Markdown image URL is mandatory in "${toMessageRelativeFilePath(
options.filePath, context.filePath,
)}" file`, )}" file`,
); );
} }
@ -144,15 +147,17 @@ async function processImageNode(node: Image, options: PluginOptions) {
return; return;
} }
const imagePath = await getImageAbsolutePath(parsedUrl.pathname, options); const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context);
await toImageRequireNode(node, imagePath, options.filePath); await toImageRequireNode(node, imagePath, context.filePath);
} }
const plugin: Plugin<[PluginOptions]> = (options) => { const plugin: Plugin<[PluginOptions]> = (options) => {
const transformer: Transformer = async (root) => { const transformer: Transformer = async (root, vfile) => {
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
visit(root, 'image', (node: Image) => { visit(root, 'image', (node: Image) => {
promises.push(processImageNode(node, options)); promises.push(
processImageNode(node, {...options, filePath: vfile.path!}),
);
}); });
await Promise.all(promises); await Promise.all(promises);
}; };

View file

@ -25,11 +25,14 @@ const {
loaders: {inlineMarkdownLinkFileLoader}, loaders: {inlineMarkdownLinkFileLoader},
} = getFileLoaderUtils(); } = getFileLoaderUtils();
interface PluginOptions { type PluginOptions = {
filePath: string;
staticDirs: string[]; staticDirs: string[];
siteDir: string; siteDir: string;
} };
type Context = PluginOptions & {
filePath: string;
};
// transform the link node to a jsx link with a require() call // transform the link node to a jsx link with a require() call
function toAssetRequireNode(node: Link, assetPath: string, filePath: string) { function toAssetRequireNode(node: Link, assetPath: string, filePath: string) {
@ -71,7 +74,7 @@ async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) {
async function getAssetAbsolutePath( async function getAssetAbsolutePath(
assetPath: string, assetPath: string,
{siteDir, filePath, staticDirs}: PluginOptions, {siteDir, filePath, staticDirs}: Context,
) { ) {
if (assetPath.startsWith('@site/')) { if (assetPath.startsWith('@site/')) {
const assetFilePath = path.join(siteDir, assetPath.replace('@site/', '')); const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
@ -96,7 +99,7 @@ async function getAssetAbsolutePath(
return null; return null;
} }
async function processLinkNode(node: Link, options: PluginOptions) { async function processLinkNode(node: Link, context: Context) {
if (!node.url) { if (!node.url) {
// try to improve error feedback // try to improve error feedback
// see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675 // 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 || '?'; const line = node?.position?.start?.line || '?';
throw new Error( throw new Error(
`Markdown link URL is mandatory in "${toMessageRelativeFilePath( `Markdown link URL is mandatory in "${toMessageRelativeFilePath(
options.filePath, context.filePath,
)}" file (title: ${title}, line: ${line}).`, )}" file (title: ${title}, line: ${line}).`,
); );
} }
@ -122,17 +125,17 @@ async function processLinkNode(node: Link, options: PluginOptions) {
return; return;
} }
const assetPath = await getAssetAbsolutePath(parsedUrl.pathname, options); const assetPath = await getAssetAbsolutePath(parsedUrl.pathname, context);
if (assetPath) { if (assetPath) {
toAssetRequireNode(node, assetPath, options.filePath); toAssetRequireNode(node, assetPath, context.filePath);
} }
} }
const plugin: Plugin<[PluginOptions]> = (options) => { const plugin: Plugin<[PluginOptions]> = (options) => {
const transformer: Transformer = async (root) => { const transformer: Transformer = async (root, vfile) => {
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
visit(root, 'link', (node: Link) => { visit(root, 'link', (node: Link) => {
promises.push(processLinkNode(node, options)); promises.push(processLinkNode(node, {...options, filePath: vfile.path!}));
}); });
await Promise.all(promises); await Promise.all(promises);
}; };

View file

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