-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(gatsby-plugin-mdx): add ToC and excerpt support (#35873)
Co-authored-by: LekoArts <[email protected]>
- Loading branch information
Showing
18 changed files
with
528 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,115 @@ | ||
import deepmerge from "deepmerge" | ||
import type { NodePluginArgs } from "gatsby" | ||
import type { ProcessorOptions } from "@mdx-js/mdx" | ||
import type { IFileNode, IMdxNode } from "./types" | ||
import type { IFileNode, IMdxMetadata, IMdxNode } from "./types" | ||
|
||
import { enhanceMdxOptions, IMdxPluginOptions } from "./plugin-options" | ||
import { ERROR_CODES } from "./error-utils" | ||
|
||
// Compiles MDX into JS | ||
// This could be replaced by @mdx-js/mdx if MDX compile would | ||
// accept custom data passed to the unified pipeline via processor.data() | ||
export default async function compileMDX( | ||
source: string, | ||
// Differences to original @mdx-js/loader: | ||
// * We pass the MDX node and a metadata object to the processor | ||
// * We inject the path to the original mdx file into the VFile which is used by the processor | ||
export async function compileMDX( | ||
mdxNode: IMdxNode, | ||
fileNode: IFileNode, | ||
options: ProcessorOptions | ||
): Promise<string> { | ||
const { createProcessor } = await import(`@mdx-js/mdx`) | ||
const { VFile } = await import(`vfile`) | ||
options: ProcessorOptions, | ||
reporter: NodePluginArgs["reporter"] | ||
): Promise<{ processedMDX: string; metadata: IMdxMetadata } | null> { | ||
try { | ||
const { createProcessor } = await import(`@mdx-js/mdx`) | ||
const { VFile } = await import(`vfile`) | ||
|
||
const processor = createProcessor(options) | ||
|
||
// Pass required custom data into the processor | ||
const metadata: IMdxMetadata = {} | ||
processor.data(`mdxNode`, mdxNode) | ||
processor.data(`mdxMetadata`, metadata) | ||
|
||
const result = await processor.process( | ||
// Inject path to original file for remark plugins. See: https://github.com/gatsbyjs/gatsby/issues/26914 | ||
new VFile({ value: mdxNode.body, path: fileNode.absolutePath }) | ||
) | ||
|
||
const processor = createProcessor(options) | ||
// Clone metadata so ensure it won't be overridden by later processings | ||
const clonedMetadata = Object.assign( | ||
{}, | ||
processor.data(`mdxMetadata`) as IMdxMetadata | ||
) | ||
const processedMDX = result.toString() | ||
|
||
// If we could pass this via MDX loader config, this whole custom loader might be obsolete. | ||
processor.data(`mdxNode`, mdxNode) | ||
return { processedMDX, metadata: clonedMetadata } | ||
} catch (err) { | ||
const errorMeta = [ | ||
mdxNode.title && `Title: ${mdxNode.title}`, | ||
mdxNode.slug && `Slug: ${mdxNode.slug}`, | ||
fileNode.relativePath && `Path: ${fileNode.relativePath}`, | ||
] | ||
.filter(Boolean) | ||
.join(`\n`) | ||
|
||
const result = await processor.process( | ||
// Inject path to original file for remark plugins. See: https://github.com/gatsbyjs/gatsby/issues/26914 | ||
new VFile({ value: source, path: fileNode.absolutePath }) | ||
reporter.panicOnBuild( | ||
{ | ||
id: ERROR_CODES.MdxCompilation, | ||
context: { | ||
errorMeta, | ||
}, | ||
}, | ||
err | ||
) | ||
return null | ||
} | ||
} | ||
|
||
/** | ||
* This helper function allows you to inject additional plugins and configuration into the MDX | ||
* compilation pipeline. Very useful to create your own resolvers that return custom metadata. | ||
* Internally used to generate the tables of contents and the excerpts. | ||
*/ | ||
export const compileMDXWithCustomOptions = async ({ | ||
pluginOptions, | ||
customOptions, | ||
getNode, | ||
getNodesByType, | ||
pathPrefix, | ||
reporter, | ||
cache, | ||
mdxNode, | ||
}: { | ||
pluginOptions: IMdxPluginOptions | ||
customOptions: Partial<IMdxPluginOptions> | ||
getNode: NodePluginArgs["getNode"] | ||
getNodesByType: NodePluginArgs["getNodesByType"] | ||
pathPrefix: string | ||
reporter: NodePluginArgs["reporter"] | ||
cache: NodePluginArgs["cache"] | ||
mdxNode: IMdxNode | ||
}): Promise<{ | ||
processedMDX: string | ||
metadata: IMdxMetadata | ||
} | null> => { | ||
const customPluginOptions = deepmerge( | ||
Object.assign({}, pluginOptions), | ||
customOptions | ||
) | ||
|
||
return result.toString() | ||
// Prepare MDX compile | ||
const mdxOptions = await enhanceMdxOptions(customPluginOptions, { | ||
getNode, | ||
getNodesByType, | ||
pathPrefix, | ||
reporter, | ||
cache, | ||
}) | ||
if (!mdxNode.parent) { | ||
return null | ||
} | ||
const fileNode = getNode(mdxNode.parent) | ||
if (!fileNode) { | ||
return null | ||
} | ||
|
||
// Compile MDX and extract metadata | ||
return compileMDX(mdxNode, fileNode, mdxOptions, reporter) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export const ERROR_CODES = { | ||
MdxCompilation: `10001`, | ||
} | ||
|
||
export const ERROR_MAP = { | ||
[ERROR_CODES.MdxCompilation]: { | ||
text: (context: { errorMeta: string }): string => | ||
`Failed to compile MDX. Information about the file:\n${context.errorMeta}`, | ||
level: `ERROR`, | ||
type: `PLUGIN`, | ||
category: `USER`, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.