1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
import type { SSRError } from 'astro';
import { getAstroMetadata } from 'astro/jsx/rehype.js';
import { VFile } from 'vfile';
import type { Plugin } from 'vite';
import type { MdxOptions } from './index.js';
import { createMdxProcessor } from './plugins.js';
import { safeParseFrontmatter } from './utils.js';
export interface VitePluginMdxOptions {
mdxOptions: MdxOptions;
srcDir: URL;
experimentalHeadingIdCompat: boolean;
}
// NOTE: Do not destructure `opts` as we're assigning a reference that will be mutated later
export function vitePluginMdx(opts: VitePluginMdxOptions): Plugin {
let processor: ReturnType<typeof createMdxProcessor> | undefined;
let sourcemapEnabled: boolean;
return {
name: '@mdx-js/rollup',
enforce: 'pre',
buildEnd() {
processor = undefined;
},
configResolved(resolved) {
sourcemapEnabled = !!resolved.build.sourcemap;
// HACK: Remove the `astro:jsx` plugin if defined as we handle the JSX transformation ourselves
const jsxPluginIndex = resolved.plugins.findIndex((p) => p.name === 'astro:jsx');
if (jsxPluginIndex !== -1) {
// @ts-ignore-error ignore readonly annotation
resolved.plugins.splice(jsxPluginIndex, 1);
}
},
async resolveId(source, importer, options) {
if (importer?.endsWith('.mdx') && source[0] !== '/') {
let resolved = await this.resolve(source, importer, options);
if (!resolved) resolved = await this.resolve('./' + source, importer, options);
return resolved;
}
},
// Override transform to alter code before MDX compilation
// ex. inject layouts
async transform(code, id) {
if (!id.endsWith('.mdx')) return;
const { frontmatter, content } = safeParseFrontmatter(code, id);
const vfile = new VFile({
value: content,
path: id,
data: {
astro: {
frontmatter,
},
applyFrontmatterExport: {
srcDir: opts.srcDir,
},
},
});
// Lazily initialize the MDX processor
if (!processor) {
processor = createMdxProcessor(opts.mdxOptions, {
sourcemap: sourcemapEnabled,
experimentalHeadingIdCompat: opts.experimentalHeadingIdCompat,
});
}
try {
const compiled = await processor.process(vfile);
return {
code: String(compiled.value),
map: compiled.map,
meta: getMdxMeta(vfile),
};
} catch (e: any) {
const err: SSRError = e;
// For some reason MDX puts the error location in the error's name, not very useful for us.
err.name = 'MDXError';
err.loc = { file: id, line: e.line, column: e.column };
// For another some reason, MDX doesn't include a stack trace. Weird
Error.captureStackTrace(err);
throw err;
}
},
};
}
function getMdxMeta(vfile: VFile): Record<string, any> {
const astroMetadata = getAstroMetadata(vfile);
if (!astroMetadata) {
throw new Error(
'Internal MDX error: Astro metadata is not set by rehype-analyze-astro-metadata',
);
}
return {
astro: astroMetadata,
vite: {
// Setting this vite metadata to `ts` causes Vite to resolve .js
// extensions to .ts files.
lang: 'ts',
},
};
}
|