aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/mdx/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/mdx/src/index.ts')
-rw-r--r--packages/integrations/mdx/src/index.ts152
1 files changed, 152 insertions, 0 deletions
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts
new file mode 100644
index 000000000..fe2cbde0b
--- /dev/null
+++ b/packages/integrations/mdx/src/index.ts
@@ -0,0 +1,152 @@
+import fs from 'node:fs/promises';
+import { fileURLToPath } from 'node:url';
+import { markdownConfigDefaults } from '@astrojs/markdown-remark';
+import type {
+ AstroIntegration,
+ AstroIntegrationLogger,
+ ContainerRenderer,
+ ContentEntryType,
+ HookParameters,
+} from 'astro';
+import type { Options as RemarkRehypeOptions } from 'remark-rehype';
+import type { PluggableList } from 'unified';
+import type { OptimizeOptions } from './rehype-optimize-static.js';
+import { ignoreStringPlugins, safeParseFrontmatter } from './utils.js';
+import { vitePluginMdxPostprocess } from './vite-plugin-mdx-postprocess.js';
+import { type VitePluginMdxOptions, vitePluginMdx } from './vite-plugin-mdx.js';
+
+export type MdxOptions = Omit<typeof markdownConfigDefaults, 'remarkPlugins' | 'rehypePlugins'> & {
+ extendMarkdownConfig: boolean;
+ recmaPlugins: PluggableList;
+ // Markdown allows strings as remark and rehype plugins.
+ // This is not supported by the MDX compiler, so override types here.
+ remarkPlugins: PluggableList;
+ rehypePlugins: PluggableList;
+ remarkRehype: RemarkRehypeOptions;
+ optimize: boolean | OptimizeOptions;
+};
+
+type SetupHookParams = HookParameters<'astro:config:setup'> & {
+ // `addPageExtension` and `contentEntryType` are not a public APIs
+ // Add type defs here
+ addPageExtension: (extension: string) => void;
+ addContentEntryType: (contentEntryType: ContentEntryType) => void;
+};
+
+export function getContainerRenderer(): ContainerRenderer {
+ return {
+ name: 'astro:jsx',
+ serverEntrypoint: '@astrojs/mdx/server.js',
+ };
+}
+
+export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroIntegration {
+ // @ts-expect-error Temporarily assign an empty object here, which will be re-assigned by the
+ // `astro:config:done` hook later. This is so that `vitePluginMdx` can get hold of a reference earlier.
+ let vitePluginMdxOptions: VitePluginMdxOptions = {};
+
+ return {
+ name: '@astrojs/mdx',
+ hooks: {
+ 'astro:config:setup': async (params) => {
+ const { updateConfig, config, addPageExtension, addContentEntryType, addRenderer } =
+ params as SetupHookParams;
+
+ addRenderer({
+ name: 'astro:jsx',
+ serverEntrypoint: new URL('../dist/server.js', import.meta.url),
+ });
+ addPageExtension('.mdx');
+ addContentEntryType({
+ extensions: ['.mdx'],
+ async getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
+ const parsed = safeParseFrontmatter(contents, fileURLToPath(fileUrl));
+ return {
+ data: parsed.frontmatter,
+ body: parsed.content.trim(),
+ slug: parsed.frontmatter.slug,
+ rawData: parsed.rawFrontmatter,
+ };
+ },
+ contentModuleTypes: await fs.readFile(
+ new URL('../template/content-module-types.d.ts', import.meta.url),
+ 'utf-8',
+ ),
+ // MDX can import scripts and styles,
+ // so wrap all MDX files with script / style propagation checks
+ handlePropagation: true,
+ });
+
+ updateConfig({
+ vite: {
+ plugins: [vitePluginMdx(vitePluginMdxOptions), vitePluginMdxPostprocess(config)],
+ },
+ });
+ },
+ 'astro:config:done': ({ config, logger }) => {
+ // We resolve the final MDX options here so that other integrations have a chance to modify
+ // `config.markdown` before we access it
+ const extendMarkdownConfig =
+ partialMdxOptions.extendMarkdownConfig ?? defaultMdxOptions.extendMarkdownConfig;
+
+ const resolvedMdxOptions = applyDefaultOptions({
+ options: partialMdxOptions,
+ defaults: markdownConfigToMdxOptions(
+ extendMarkdownConfig ? config.markdown : markdownConfigDefaults,
+ logger,
+ ),
+ });
+
+ // Mutate `mdxOptions` so that `vitePluginMdx` can reference the actual options
+ Object.assign(vitePluginMdxOptions, {
+ mdxOptions: resolvedMdxOptions,
+ srcDir: config.srcDir,
+ experimentalHeadingIdCompat: config.experimental.headingIdCompat,
+ });
+ // @ts-expect-error After we assign, we don't need to reference `mdxOptions` in this context anymore.
+ // Re-assign it so that the garbage can be collected later.
+ vitePluginMdxOptions = {};
+ },
+ },
+ };
+}
+
+const defaultMdxOptions = {
+ extendMarkdownConfig: true,
+ recmaPlugins: [],
+ optimize: false,
+} satisfies Partial<MdxOptions>;
+
+function markdownConfigToMdxOptions(
+ markdownConfig: typeof markdownConfigDefaults,
+ logger: AstroIntegrationLogger,
+): MdxOptions {
+ return {
+ ...defaultMdxOptions,
+ ...markdownConfig,
+ remarkPlugins: ignoreStringPlugins(markdownConfig.remarkPlugins, logger),
+ rehypePlugins: ignoreStringPlugins(markdownConfig.rehypePlugins, logger),
+ remarkRehype: (markdownConfig.remarkRehype as any) ?? {},
+ };
+}
+
+function applyDefaultOptions({
+ options,
+ defaults,
+}: {
+ options: Partial<MdxOptions>;
+ defaults: MdxOptions;
+}): MdxOptions {
+ return {
+ syntaxHighlight: options.syntaxHighlight ?? defaults.syntaxHighlight,
+ extendMarkdownConfig: options.extendMarkdownConfig ?? defaults.extendMarkdownConfig,
+ recmaPlugins: options.recmaPlugins ?? defaults.recmaPlugins,
+ remarkRehype: options.remarkRehype ?? defaults.remarkRehype,
+ gfm: options.gfm ?? defaults.gfm,
+ smartypants: options.smartypants ?? defaults.smartypants,
+ remarkPlugins: options.remarkPlugins ?? defaults.remarkPlugins,
+ rehypePlugins: options.rehypePlugins ?? defaults.rehypePlugins,
+ shikiConfig: options.shikiConfig ?? defaults.shikiConfig,
+ optimize: options.optimize ?? defaults.optimize,
+ };
+}