mirror of
https://github.com/withastro/astro.git
synced 2024-12-23 21:53:55 -05:00
f1a52c18af
* freat: twoslash -> Astro shiki parser * test: update shiki style check * feat: always apply rehypeRaw * deps: move remark-shiki-twoslash to dev * test: add shiki-twoslash test * docs: update readme with twoslash example * chore: changeset * nit: remove "describe('disabled')"
160 lines
5.6 KiB
TypeScript
160 lines
5.6 KiB
TypeScript
import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx';
|
|
import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
|
|
import type { AstroConfig, 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'] = [];
|
|
|
|
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';
|
|
|
|
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');
|
|
|
|
const mdxPluginOpts: MdxRollupPluginOptions = {
|
|
remarkPlugins: await getRemarkPlugins(mdxOptions, config),
|
|
rehypePlugins: getRehypePlugins(mdxOptions, config),
|
|
jsx: true,
|
|
jsxImportSource: 'astro',
|
|
// Note: disable `.md` support
|
|
format: 'mdx',
|
|
mdExtensions: [],
|
|
};
|
|
|
|
updateConfig({
|
|
vite: {
|
|
plugins: [
|
|
{
|
|
enforce: 'pre',
|
|
...mdxPlugin(mdxPluginOpts),
|
|
// Override transform to alter code before MDX compilation
|
|
// ex. inject layouts
|
|
async transform(code, id) {
|
|
if (!id.endsWith('mdx')) return;
|
|
|
|
const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
|
|
const compiled = await mdxCompile(new VFile({ value: pageContent, path: id }), {
|
|
...mdxPluginOpts,
|
|
rehypePlugins: [
|
|
...(mdxPluginOpts.rehypePlugins ?? []),
|
|
() => rehypeApplyFrontmatterExport(frontmatter),
|
|
],
|
|
});
|
|
|
|
return {
|
|
code: String(compiled.value),
|
|
map: compiled.map,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
name: '@astrojs/mdx-postprocess',
|
|
// These transforms must happen *after* JSX runtime transformations
|
|
transform(code, id) {
|
|
if (!id.endsWith('.mdx')) return;
|
|
const [, moduleExports] = parseESM(code);
|
|
|
|
const { fileUrl, fileId } = getFileInfo(id, config);
|
|
if (!moduleExports.includes('url')) {
|
|
code += `\nexport const url = ${JSON.stringify(fileUrl)};`;
|
|
}
|
|
if (!moduleExports.includes('file')) {
|
|
code += `\nexport const file = ${JSON.stringify(fileId)};`;
|
|
}
|
|
if (!moduleExports.includes('rawContent')) {
|
|
code += `\nexport function rawContent() { throw new Error(${JSON.stringify(
|
|
RAW_CONTENT_ERROR
|
|
)}) };`;
|
|
}
|
|
if (!moduleExports.includes('compiledContent')) {
|
|
code += `\nexport function compiledContent() { throw new Error(${JSON.stringify(
|
|
COMPILED_CONTENT_ERROR
|
|
)}) };`;
|
|
}
|
|
if (!moduleExports.includes('Content')) {
|
|
code += `\nexport const Content = MDXContent;`;
|
|
}
|
|
|
|
if (command === 'dev') {
|
|
// TODO: decline HMR updates until we have a stable approach
|
|
code += `\nif (import.meta.hot) {
|
|
import.meta.hot.decline();
|
|
}`;
|
|
}
|
|
return code;
|
|
},
|
|
},
|
|
] as VitePlugin[],
|
|
},
|
|
});
|
|
},
|
|
},
|
|
};
|
|
}
|