diff options
author | 2022-08-30 13:38:35 -0400 | |
---|---|---|
committer | 2022-08-30 13:38:35 -0400 | |
commit | 8f8dff4d339a3a12ee155d81a97132032ef3b622 (patch) | |
tree | 0581df292a1003288b0dbd7a3f9246f25b5a3cce /packages/integrations/mdx/src | |
parent | e905784bf12ef45093078404d3d07f01e32638ca (diff) | |
download | astro-8f8dff4d339a3a12ee155d81a97132032ef3b622.tar.gz astro-8f8dff4d339a3a12ee155d81a97132032ef3b622.tar.zst astro-8f8dff4d339a3a12ee155d81a97132032ef3b622.zip |
[MDX] Extend Markdown plugin config, with customization options (#4504)
* test: new combined remark / rehype suite
* fix: use with-plugins fixture
* chore: remove old mdx plugin tests
* docs: add JS docs
* docs: update README with thorough example
* chore: changeset
* fix: add "extends" error message
* fix: ignore string-based plugins in md
* feat: add warning log for string plugins
* docs: highlight `extendPlugins`
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
* nit: highlight "extendPlugins"
* fix: md plugins type check
* chore: "defaults" -> "astroDefaults"
* nit: info log when inheriting markdown plugins
* refactor: one big log on new behavior
* dan: dan nit
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Diffstat (limited to 'packages/integrations/mdx/src')
-rw-r--r-- | packages/integrations/mdx/src/index.ts | 94 | ||||
-rw-r--r-- | packages/integrations/mdx/src/utils.ts | 127 |
2 files changed, 157 insertions, 64 deletions
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 6516e4b12..0a2ddc8d3 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -1,30 +1,19 @@ -import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx'; +import { compile as mdxCompile } from '@mdx-js/mdx'; import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup'; -import type { AstroConfig, AstroIntegration } from 'astro'; +import type { AstroIntegration } from 'astro'; import { parse as parseESM } from 'es-module-lexer'; -import rehypeRaw from 'rehype-raw'; -import remarkGfm from 'remark-gfm'; -import remarkSmartypants from 'remark-smartypants'; import { VFile } from 'vfile'; import type { Plugin as VitePlugin } from 'vite'; -import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from './astro-data-utils.js'; -import rehypeCollectHeadings from './rehype-collect-headings.js'; -import remarkPrism from './remark-prism.js'; -import remarkShiki from './remark-shiki.js'; -import { getFileInfo, parseFrontmatter } from './utils.js'; - -type WithExtends<T> = T | { extends: T }; - -type MdxOptions = { - remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>; - rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>; -}; - -const DEFAULT_REMARK_PLUGINS: MdxRollupPluginOptions['remarkPlugins'] = [ - remarkGfm, - remarkSmartypants, -]; -const DEFAULT_REHYPE_PLUGINS: MdxRollupPluginOptions['rehypePlugins'] = []; +import { bold, blue } from 'kleur/colors'; +import { rehypeApplyFrontmatterExport } from './astro-data-utils.js'; +import { + getFileInfo, + parseFrontmatter, + handleExtendsNotSupported, + getRehypePlugins, + getRemarkPlugins, +} from './utils.js'; +import type { MdxOptions } from './utils.js'; const RAW_CONTENT_ERROR = 'MDX does not support rawContent()! If you need to read the Markdown contents to calculate values (ex. reading time), we suggest injecting frontmatter via remark plugins. Learn more on our docs: https://docs.astro.build/en/guides/integrations-guide/mdx/#inject-frontmatter-via-remark-or-rehype-plugins'; @@ -32,51 +21,32 @@ const RAW_CONTENT_ERROR = const COMPILED_CONTENT_ERROR = 'MDX does not support compiledContent()! If you need to read the HTML contents to calculate values (ex. reading time), we suggest injecting frontmatter via rehype plugins. Learn more on our docs: https://docs.astro.build/en/guides/integrations-guide/mdx/#inject-frontmatter-via-remark-or-rehype-plugins'; -function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] { - if (Array.isArray(config)) return config; - - return [...defaults, ...(config?.extends ?? [])]; -} - -async function getRemarkPlugins( - mdxOptions: MdxOptions, - config: AstroConfig -): Promise<MdxRollupPluginOptions['remarkPlugins']> { - let remarkPlugins = [ - // Initialize vfile.data.astroExports before all plugins are run - remarkInitializeAstroData, - ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS), - ]; - if (config.markdown.syntaxHighlight === 'shiki') { - remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]); - } - if (config.markdown.syntaxHighlight === 'prism') { - remarkPlugins.push(remarkPrism); - } - return remarkPlugins; -} - -function getRehypePlugins( - mdxOptions: MdxOptions, - config: AstroConfig -): MdxRollupPluginOptions['rehypePlugins'] { - let rehypePlugins = [ - [rehypeRaw, { passThrough: nodeTypes }] as any, - ...handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS), - ]; - - // getHeadings() is guaranteed by TS, so we can't allow user to override - rehypePlugins.unshift(rehypeCollectHeadings); - - return rehypePlugins; -} - export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { return { name: '@astrojs/mdx', hooks: { 'astro:config:setup': async ({ updateConfig, config, addPageExtension, command }: any) => { addPageExtension('.mdx'); + mdxOptions.extendPlugins ??= 'markdown'; + + handleExtendsNotSupported(mdxOptions.remarkPlugins); + handleExtendsNotSupported(mdxOptions.rehypePlugins); + + // TODO: remove for 1.0. Shipping to ease migration to new minor + if ( + mdxOptions.extendPlugins === 'markdown' && + (config.markdown.rehypePlugins?.length || config.markdown.remarkPlugins?.length) + ) { + console.log( + blue(`[MDX] Now inheriting remark and rehype plugins from "markdown" config.`) + ); + console.log( + `If you applied a plugin to both your Markdown and MDX configs, we suggest ${bold( + 'removing the duplicate MDX entry.' + )}` + ); + console.log(`See "extendPlugins" option to configure this behavior.`); + } const mdxPluginOpts: MdxRollupPluginOptions = { remarkPlugins: await getRemarkPlugins(mdxOptions, config), diff --git a/packages/integrations/mdx/src/utils.ts b/packages/integrations/mdx/src/utils.ts index f5135ebc2..dc7879dd8 100644 --- a/packages/integrations/mdx/src/utils.ts +++ b/packages/integrations/mdx/src/utils.ts @@ -1,10 +1,33 @@ import type { Options as AcornOpts } from 'acorn'; -import { parse } from 'acorn'; import type { AstroConfig, SSRError } from 'astro'; import type { MdxjsEsm } from 'mdast-util-mdx'; - +import type { PluggableList } from '@mdx-js/mdx/lib/core.js'; +import type { Options as MdxRollupPluginOptions } from '@mdx-js/rollup'; +import { bold, yellow } from 'kleur/colors'; +import { nodeTypes } from '@mdx-js/mdx'; +import { parse } from 'acorn'; +import rehypeRaw from 'rehype-raw'; +import remarkGfm from 'remark-gfm'; +import remarkSmartypants from 'remark-smartypants'; +import { remarkInitializeAstroData } from './astro-data-utils.js'; +import rehypeCollectHeadings from './rehype-collect-headings.js'; +import remarkPrism from './remark-prism.js'; +import remarkShiki from './remark-shiki.js'; import matter from 'gray-matter'; +export type MdxOptions = { + remarkPlugins?: PluggableList; + rehypePlugins?: PluggableList; + /** + * Choose which remark and rehype plugins to inherit, if any. + * + * - "markdown" (default) - inherit your project’s markdown plugin config ([see Markdown docs](https://docs.astro.build/en/guides/markdown-content/#configuring-markdown)) + * - "astroDefaults" - inherit Astro’s default plugins only ([see defaults](https://docs.astro.build/en/reference/configuration-reference/#markdownextenddefaultplugins)) + * - false - do not inherit any plugins + */ + extendPlugins?: 'markdown' | 'astroDefaults' | false; +}; + function appendForwardSlash(path: string) { return path.endsWith('/') ? path : path + '/'; } @@ -14,6 +37,9 @@ interface FileInfo { fileUrl: string; } +const DEFAULT_REMARK_PLUGINS: PluggableList = [remarkGfm, remarkSmartypants]; +const DEFAULT_REHYPE_PLUGINS: PluggableList = []; + /** @see 'vite-plugin-utils' for source */ export function getFileInfo(id: string, config: AstroConfig): FileInfo { const sitePathname = appendForwardSlash( @@ -83,3 +109,100 @@ export function jsToTreeNode( }, }; } + +export async function getRemarkPlugins( + mdxOptions: MdxOptions, + config: AstroConfig +): Promise<MdxRollupPluginOptions['remarkPlugins']> { + let remarkPlugins: PluggableList = [ + // Set "vfile.data.astro" for plugins to inject frontmatter + remarkInitializeAstroData, + ]; + switch (mdxOptions.extendPlugins) { + case false: + break; + case 'astroDefaults': + remarkPlugins = [...remarkPlugins, ...DEFAULT_REMARK_PLUGINS]; + break; + default: + remarkPlugins = [ + ...remarkPlugins, + ...(config.markdown.extendDefaultPlugins ? DEFAULT_REMARK_PLUGINS : []), + ...ignoreStringPlugins(config.markdown.remarkPlugins ?? []), + ]; + break; + } + if (config.markdown.syntaxHighlight === 'shiki') { + remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]); + } + if (config.markdown.syntaxHighlight === 'prism') { + remarkPlugins.push(remarkPrism); + } + + remarkPlugins = [...remarkPlugins, ...(mdxOptions.remarkPlugins ?? [])]; + return remarkPlugins; +} + +export function getRehypePlugins( + mdxOptions: MdxOptions, + config: AstroConfig +): MdxRollupPluginOptions['rehypePlugins'] { + let rehypePlugins: PluggableList = [ + // getHeadings() is guaranteed by TS, so we can't allow user to override + rehypeCollectHeadings, + // rehypeRaw allows custom syntax highlighters to work without added config + [rehypeRaw, { passThrough: nodeTypes }] as any, + ]; + switch (mdxOptions.extendPlugins) { + case false: + break; + case 'astroDefaults': + rehypePlugins = [...rehypePlugins, ...DEFAULT_REHYPE_PLUGINS]; + break; + default: + rehypePlugins = [ + ...rehypePlugins, + ...(config.markdown.extendDefaultPlugins ? DEFAULT_REHYPE_PLUGINS : []), + ...ignoreStringPlugins(config.markdown.rehypePlugins ?? []), + ]; + break; + } + + rehypePlugins = [...rehypePlugins, ...(mdxOptions.rehypePlugins ?? [])]; + return rehypePlugins; +} + +function ignoreStringPlugins(plugins: any[]) { + let validPlugins: PluggableList = []; + let hasInvalidPlugin = false; + for (const plugin of plugins) { + if (typeof plugin === 'string') { + console.warn(yellow(`[MDX] ${bold(plugin)} not applied.`)); + hasInvalidPlugin = true; + } else if (Array.isArray(plugin) && typeof plugin[0] === 'string') { + console.warn(yellow(`[MDX] ${bold(plugin[0])} not applied.`)); + hasInvalidPlugin = true; + } else { + validPlugins.push(plugin); + } + } + if (hasInvalidPlugin) { + console.warn( + `To inherit Markdown plugins in MDX, please use explicit imports in your config instead of "strings." See Markdown docs: https://docs.astro.build/en/guides/markdown-content/#markdown-plugins` + ); + } + return validPlugins; +} + +// TODO: remove for 1.0 +export function handleExtendsNotSupported(pluginConfig: any) { + if ( + typeof pluginConfig === 'object' && + pluginConfig !== null && + (pluginConfig as any).hasOwnProperty('extends') + ) { + throw new Error( + `[MDX] The "extends" plugin option is no longer supported! Astro now extends your project's \`markdown\` plugin configuration by default. To customize this behavior, see the \`extendPlugins\` option instead: https://docs.astro.build/en/guides/integrations-guide/mdx/#extendplugins` + ); + } +} |