@astrojs/mdx 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,12 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { fileURLToPath } from "node:url";
3
- import { markdownConfigDefaults, setVfileFrontmatter } from "@astrojs/markdown-remark";
3
+ import { markdownConfigDefaults } from "@astrojs/markdown-remark";
4
4
  import astroJSXRenderer from "astro/jsx/renderer.js";
5
- import { parse as parseESM } from "es-module-lexer";
6
- import { VFile } from "vfile";
7
- import { createMdxProcessor } from "./plugins.js";
8
- import {
9
- ASTRO_IMAGE_ELEMENT,
10
- ASTRO_IMAGE_IMPORT,
11
- USES_ASTRO_IMAGE_FLAG
12
- } from "./remark-images-to-component.js";
13
- import { getFileInfo, ignoreStringPlugins, parseFrontmatter } from "./utils.js";
5
+ import { ignoreStringPlugins, parseFrontmatter } from "./utils.js";
6
+ import { vitePluginMdxPostprocess } from "./vite-plugin-mdx-postprocess.js";
7
+ import { vitePluginMdx } from "./vite-plugin-mdx.js";
14
8
  function mdx(partialMdxOptions = {}) {
9
+ let mdxOptions = {};
15
10
  return {
16
11
  name: "@astrojs/mdx",
17
12
  hooks: {
@@ -38,124 +33,30 @@ function mdx(partialMdxOptions = {}) {
38
33
  // so wrap all MDX files with script / style propagation checks
39
34
  handlePropagation: true
40
35
  });
36
+ updateConfig({
37
+ vite: {
38
+ plugins: [vitePluginMdx(mdxOptions), vitePluginMdxPostprocess(config)]
39
+ }
40
+ });
41
+ },
42
+ "astro:config:done": ({ config }) => {
41
43
  const extendMarkdownConfig = partialMdxOptions.extendMarkdownConfig ?? defaultMdxOptions.extendMarkdownConfig;
42
- const mdxOptions = applyDefaultOptions({
44
+ const resolvedMdxOptions = applyDefaultOptions({
43
45
  options: partialMdxOptions,
44
46
  defaults: markdownConfigToMdxOptions(
45
47
  extendMarkdownConfig ? config.markdown : markdownConfigDefaults
46
48
  )
47
49
  });
48
- let processor;
49
- updateConfig({
50
- vite: {
51
- plugins: [
52
- {
53
- name: "@mdx-js/rollup",
54
- enforce: "pre",
55
- configResolved(resolved) {
56
- processor = createMdxProcessor(mdxOptions, {
57
- sourcemap: !!resolved.build.sourcemap
58
- });
59
- const jsxPluginIndex = resolved.plugins.findIndex((p) => p.name === "astro:jsx");
60
- if (jsxPluginIndex !== -1) {
61
- const myPluginIndex = resolved.plugins.findIndex(
62
- (p) => p.name === "@mdx-js/rollup"
63
- );
64
- if (myPluginIndex !== -1) {
65
- const myPlugin = resolved.plugins[myPluginIndex];
66
- resolved.plugins.splice(myPluginIndex, 1);
67
- resolved.plugins.splice(jsxPluginIndex, 0, myPlugin);
68
- }
69
- }
70
- },
71
- // Override transform to alter code before MDX compilation
72
- // ex. inject layouts
73
- async transform(_, id) {
74
- if (!id.endsWith(".mdx"))
75
- return;
76
- const { fileId } = getFileInfo(id, config);
77
- const code = await fs.readFile(fileId, "utf-8");
78
- const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
79
- const vfile = new VFile({ value: pageContent, path: id });
80
- setVfileFrontmatter(vfile, frontmatter);
81
- try {
82
- const compiled = await processor.process(vfile);
83
- return {
84
- code: String(compiled.value),
85
- map: compiled.map
86
- };
87
- } catch (e) {
88
- const err = e;
89
- err.name = "MDXError";
90
- err.loc = { file: fileId, line: e.line, column: e.column };
91
- Error.captureStackTrace(err);
92
- throw err;
93
- }
94
- }
95
- },
96
- {
97
- name: "@astrojs/mdx-postprocess",
98
- // These transforms must happen *after* JSX runtime transformations
99
- transform(code, id) {
100
- if (!id.endsWith(".mdx"))
101
- return;
102
- const [moduleImports, moduleExports] = parseESM(code);
103
- const importsFromJSXRuntime = moduleImports.filter(({ n }) => n === "astro/jsx-runtime").map(({ ss, se }) => code.substring(ss, se));
104
- const hasFragmentImport = importsFromJSXRuntime.some(
105
- (statement) => /[\s,{](?:Fragment,|Fragment\s*\})/.test(statement)
106
- );
107
- if (!hasFragmentImport) {
108
- code = 'import { Fragment } from "astro/jsx-runtime"\n' + code;
109
- }
110
- const { fileUrl, fileId } = getFileInfo(id, config);
111
- if (!moduleExports.find(({ n }) => n === "url")) {
112
- code += `
113
- export const url = ${JSON.stringify(fileUrl)};`;
114
- }
115
- if (!moduleExports.find(({ n }) => n === "file")) {
116
- code += `
117
- export const file = ${JSON.stringify(fileId)};`;
118
- }
119
- if (!moduleExports.find(({ n }) => n === "Content")) {
120
- const hasComponents = moduleExports.find(({ n }) => n === "components");
121
- const usesAstroImage = moduleExports.find(
122
- ({ n }) => n === USES_ASTRO_IMAGE_FLAG
123
- );
124
- let componentsCode = `{ Fragment${hasComponents ? ", ...components" : ""}, ...props.components,`;
125
- if (usesAstroImage) {
126
- componentsCode += ` ${JSON.stringify(ASTRO_IMAGE_ELEMENT)}: ${hasComponents ? "components.img ?? " : ""} props.components?.img ?? ${ASTRO_IMAGE_IMPORT}`;
127
- }
128
- componentsCode += " }";
129
- code = code.replace(
130
- "export default function MDXContent",
131
- "function MDXContent"
132
- );
133
- code += `
134
- export const Content = (props = {}) => MDXContent({
135
- ...props,
136
- components: ${componentsCode},
137
- });
138
- export default Content;`;
139
- }
140
- code += `
141
- Content[Symbol.for('mdx-component')] = true`;
142
- code += `
143
- Content[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
144
- code += `
145
- Content.moduleId = ${JSON.stringify(id)};`;
146
- return { code, map: null };
147
- }
148
- }
149
- ]
150
- }
151
- });
50
+ Object.assign(mdxOptions, resolvedMdxOptions);
51
+ mdxOptions = {};
152
52
  }
153
53
  }
154
54
  };
155
55
  }
156
56
  const defaultMdxOptions = {
157
57
  extendMarkdownConfig: true,
158
- recmaPlugins: []
58
+ recmaPlugins: [],
59
+ optimize: false
159
60
  };
160
61
  function markdownConfigToMdxOptions(markdownConfig) {
161
62
  return {
@@ -163,8 +64,7 @@ function markdownConfigToMdxOptions(markdownConfig) {
163
64
  ...markdownConfig,
164
65
  remarkPlugins: ignoreStringPlugins(markdownConfig.remarkPlugins),
165
66
  rehypePlugins: ignoreStringPlugins(markdownConfig.rehypePlugins),
166
- remarkRehype: markdownConfig.remarkRehype ?? {},
167
- optimize: false
67
+ remarkRehype: markdownConfig.remarkRehype ?? {}
168
68
  };
169
69
  }
170
70
  function applyDefaultOptions({
package/dist/plugins.js CHANGED
@@ -5,15 +5,16 @@ import {
5
5
  remarkCollectImages
6
6
  } from "@astrojs/markdown-remark";
7
7
  import { createProcessor, nodeTypes } from "@mdx-js/mdx";
8
+ import { rehypeAnalyzeAstroMetadata } from "astro/jsx/rehype.js";
8
9
  import rehypeRaw from "rehype-raw";
9
10
  import remarkGfm from "remark-gfm";
10
11
  import remarkSmartypants from "remark-smartypants";
11
12
  import { SourceMapGenerator } from "source-map";
12
13
  import { rehypeApplyFrontmatterExport } from "./rehype-apply-frontmatter-export.js";
13
14
  import { rehypeInjectHeadingsExport } from "./rehype-collect-headings.js";
15
+ import { rehypeImageToComponent } from "./rehype-images-to-component.js";
14
16
  import rehypeMetaString from "./rehype-meta-string.js";
15
17
  import { rehypeOptimizeStatic } from "./rehype-optimize-static.js";
16
- import { remarkImageToComponent } from "./remark-images-to-component.js";
17
18
  const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
18
19
  function createMdxProcessor(mdxOptions, extraOptions) {
19
20
  return createProcessor({
@@ -21,7 +22,6 @@ function createMdxProcessor(mdxOptions, extraOptions) {
21
22
  rehypePlugins: getRehypePlugins(mdxOptions),
22
23
  recmaPlugins: mdxOptions.recmaPlugins,
23
24
  remarkRehypeOptions: mdxOptions.remarkRehype,
24
- jsx: true,
25
25
  jsxImportSource: "astro",
26
26
  // Note: disable `.md` (and other alternative extensions for markdown files like `.markdown`) support
27
27
  format: "mdx",
@@ -40,7 +40,7 @@ function getRemarkPlugins(mdxOptions) {
40
40
  remarkPlugins.push(remarkSmartypants);
41
41
  }
42
42
  }
43
- remarkPlugins.push(...mdxOptions.remarkPlugins, remarkCollectImages, remarkImageToComponent);
43
+ remarkPlugins.push(...mdxOptions.remarkPlugins, remarkCollectImages);
44
44
  return remarkPlugins;
45
45
  }
46
46
  function getRehypePlugins(mdxOptions) {
@@ -57,11 +57,16 @@ function getRehypePlugins(mdxOptions) {
57
57
  rehypePlugins.push(rehypePrism);
58
58
  }
59
59
  }
60
- rehypePlugins.push(...mdxOptions.rehypePlugins);
60
+ rehypePlugins.push(...mdxOptions.rehypePlugins, rehypeImageToComponent);
61
61
  if (!isPerformanceBenchmark) {
62
62
  rehypePlugins.push(rehypeHeadingIds, rehypeInjectHeadingsExport);
63
63
  }
64
- rehypePlugins.push(rehypeApplyFrontmatterExport);
64
+ rehypePlugins.push(
65
+ // Render info from `vfile.data.astro.data.frontmatter` as JS
66
+ rehypeApplyFrontmatterExport,
67
+ // Analyze MDX nodes and attach to `vfile.data.__astroMetadata`
68
+ rehypeAnalyzeAstroMetadata
69
+ );
65
70
  if (mdxOptions.optimize) {
66
71
  const options = mdxOptions.optimize === true ? void 0 : mdxOptions.optimize;
67
72
  rehypePlugins.push([rehypeOptimizeStatic, options]);
@@ -1,5 +1,6 @@
1
1
  import type { MarkdownVFile } from '@astrojs/markdown-remark';
2
+ import type { Root } from 'hast';
2
3
  export declare const ASTRO_IMAGE_ELEMENT = "astro-image";
3
4
  export declare const ASTRO_IMAGE_IMPORT = "__AstroImage__";
4
5
  export declare const USES_ASTRO_IMAGE_FLAG = "__usesAstroImage";
5
- export declare function remarkImageToComponent(): (tree: any, file: MarkdownVFile) => void;
6
+ export declare function rehypeImageToComponent(): (tree: Root, file: MarkdownVFile) => void;
@@ -0,0 +1,135 @@
1
+ import { visit } from "unist-util-visit";
2
+ import { jsToTreeNode } from "./utils.js";
3
+ const ASTRO_IMAGE_ELEMENT = "astro-image";
4
+ const ASTRO_IMAGE_IMPORT = "__AstroImage__";
5
+ const USES_ASTRO_IMAGE_FLAG = "__usesAstroImage";
6
+ function createArrayAttribute(name, values) {
7
+ return {
8
+ type: "mdxJsxAttribute",
9
+ name,
10
+ value: {
11
+ type: "mdxJsxAttributeValueExpression",
12
+ value: name,
13
+ data: {
14
+ estree: {
15
+ type: "Program",
16
+ body: [
17
+ {
18
+ type: "ExpressionStatement",
19
+ expression: {
20
+ type: "ArrayExpression",
21
+ elements: values.map((value) => ({
22
+ type: "Literal",
23
+ value,
24
+ raw: String(value)
25
+ }))
26
+ }
27
+ }
28
+ ],
29
+ sourceType: "module",
30
+ comments: []
31
+ }
32
+ }
33
+ }
34
+ };
35
+ }
36
+ function getImageComponentAttributes(props) {
37
+ const attrs = [];
38
+ for (const [prop, value] of Object.entries(props)) {
39
+ if (prop === "src") continue;
40
+ if (prop === "widths" || prop === "densities") {
41
+ attrs.push(createArrayAttribute(prop, String(value).split(" ")));
42
+ } else {
43
+ attrs.push({
44
+ name: prop,
45
+ type: "mdxJsxAttribute",
46
+ value: String(value)
47
+ });
48
+ }
49
+ }
50
+ return attrs;
51
+ }
52
+ function rehypeImageToComponent() {
53
+ return function(tree, file) {
54
+ if (!file.data.imagePaths) return;
55
+ const importsStatements = [];
56
+ const importedImages = /* @__PURE__ */ new Map();
57
+ visit(tree, "element", (node, index, parent) => {
58
+ if (!file.data.imagePaths || node.tagName !== "img" || !node.properties.src) return;
59
+ const src = decodeURI(String(node.properties.src));
60
+ if (!file.data.imagePaths.has(src)) return;
61
+ let importName = importedImages.get(src);
62
+ if (!importName) {
63
+ importName = `__${importedImages.size}_${src.replace(/\W/g, "_")}__`;
64
+ importsStatements.push({
65
+ type: "mdxjsEsm",
66
+ value: "",
67
+ data: {
68
+ estree: {
69
+ type: "Program",
70
+ sourceType: "module",
71
+ body: [
72
+ {
73
+ type: "ImportDeclaration",
74
+ source: {
75
+ type: "Literal",
76
+ value: src,
77
+ raw: JSON.stringify(src)
78
+ },
79
+ specifiers: [
80
+ {
81
+ type: "ImportDefaultSpecifier",
82
+ local: { type: "Identifier", name: importName }
83
+ }
84
+ ]
85
+ }
86
+ ]
87
+ }
88
+ }
89
+ });
90
+ importedImages.set(src, importName);
91
+ }
92
+ const componentElement = {
93
+ name: ASTRO_IMAGE_ELEMENT,
94
+ type: "mdxJsxFlowElement",
95
+ attributes: [
96
+ ...getImageComponentAttributes(node.properties),
97
+ {
98
+ name: "src",
99
+ type: "mdxJsxAttribute",
100
+ value: {
101
+ type: "mdxJsxAttributeValueExpression",
102
+ value: importName,
103
+ data: {
104
+ estree: {
105
+ type: "Program",
106
+ sourceType: "module",
107
+ comments: [],
108
+ body: [
109
+ {
110
+ type: "ExpressionStatement",
111
+ expression: { type: "Identifier", name: importName }
112
+ }
113
+ ]
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ],
119
+ children: []
120
+ };
121
+ parent.children.splice(index, 1, componentElement);
122
+ });
123
+ tree.children.unshift(...importsStatements);
124
+ tree.children.unshift(
125
+ jsToTreeNode(`import { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`)
126
+ );
127
+ tree.children.push(jsToTreeNode(`export const ${USES_ASTRO_IMAGE_FLAG} = true`));
128
+ };
129
+ }
130
+ export {
131
+ ASTRO_IMAGE_ELEMENT,
132
+ ASTRO_IMAGE_IMPORT,
133
+ USES_ASTRO_IMAGE_FLAG,
134
+ rehypeImageToComponent
135
+ };
@@ -1,5 +1,6 @@
1
+ import type { RehypePlugin } from '@astrojs/markdown-remark';
1
2
  export interface OptimizeOptions {
2
- customComponentNames?: string[];
3
+ ignoreElementNames?: string[];
3
4
  }
4
5
  /**
5
6
  * For MDX only, collapse static subtrees of the hast into `set:html`. Subtrees
@@ -8,4 +9,4 @@ export interface OptimizeOptions {
8
9
  * This optimization reduces the JS output as more content are represented as a
9
10
  * string instead, which also reduces the AST size that Rollup holds in memory.
10
11
  */
11
- export declare function rehypeOptimizeStatic(options?: OptimizeOptions): (tree: any) => void;
12
+ export declare const rehypeOptimizeStatic: RehypePlugin<[OptimizeOptions?]>;
@@ -1,40 +1,49 @@
1
- import { visit } from "estree-util-visit";
1
+ import { SKIP, visit } from "estree-util-visit";
2
2
  import { toHtml } from "hast-util-to-html";
3
3
  const exportConstComponentsRe = /export\s+const\s+components\s*=/;
4
- function rehypeOptimizeStatic(options) {
4
+ const rehypeOptimizeStatic = (options) => {
5
5
  return (tree) => {
6
- const customComponentNames = new Set(options?.customComponentNames);
6
+ const ignoreElementNames = new Set(options?.ignoreElementNames);
7
7
  for (const child of tree.children) {
8
8
  if (child.type === "mdxjsEsm" && exportConstComponentsRe.test(child.value)) {
9
- const objectPropertyNodes = child.data.estree.body[0]?.declarations?.[0]?.init?.properties;
10
- if (objectPropertyNodes) {
11
- for (const objectPropertyNode of objectPropertyNodes) {
12
- const componentName = objectPropertyNode.key?.name ?? objectPropertyNode.key?.value;
13
- if (componentName) {
14
- customComponentNames.add(componentName);
15
- }
9
+ const keys = getExportConstComponentObjectKeys(child);
10
+ if (keys) {
11
+ for (const key of keys) {
12
+ ignoreElementNames.add(key);
16
13
  }
17
14
  }
15
+ break;
18
16
  }
19
17
  }
20
18
  const allPossibleElements = /* @__PURE__ */ new Set();
21
19
  const elementStack = [];
20
+ const elementMetadatas = /* @__PURE__ */ new WeakMap();
21
+ const isNodeNonStatic = (node) => {
22
+ return node.type.startsWith("mdx") || ignoreElementNames.has(node.tagName);
23
+ };
22
24
  visit(tree, {
23
- enter(node) {
24
- const isCustomComponent = node.tagName && customComponentNames.has(node.tagName);
25
- if (node.type.startsWith("mdx") || isCustomComponent) {
25
+ // @ts-expect-error Force coerce node as hast node
26
+ enter(node, key, index, parents) {
27
+ if (key != null && key !== "children") return SKIP;
28
+ simplifyPlainMdxComponentNode(node, ignoreElementNames);
29
+ if (isNodeNonStatic(node)) {
26
30
  for (const el of elementStack) {
27
31
  allPossibleElements.delete(el);
28
32
  }
29
33
  elementStack.length = 0;
30
34
  }
31
- if (node.type === "element" || node.type === "mdxJsxFlowElement") {
35
+ if (node.type === "element" || isMdxComponentNode(node)) {
32
36
  elementStack.push(node);
33
37
  allPossibleElements.add(node);
38
+ if (index != null && node.type === "element") {
39
+ elementMetadatas.set(node, { parent: parents[parents.length - 1], index });
40
+ }
34
41
  }
35
42
  },
36
- leave(node, _, __, parents) {
37
- if (node.type === "element" || node.type === "mdxJsxFlowElement") {
43
+ // @ts-expect-error Force coerce node as hast node
44
+ leave(node, key, _, parents) {
45
+ if (key != null && key !== "children") return SKIP;
46
+ if (node.type === "element" || isMdxComponentNode(node)) {
38
47
  elementStack.pop();
39
48
  const parent = parents[parents.length - 1];
40
49
  if (allPossibleElements.has(parent)) {
@@ -43,8 +52,10 @@ function rehypeOptimizeStatic(options) {
43
52
  }
44
53
  }
45
54
  });
55
+ const elementGroups = findElementGroups(allPossibleElements, elementMetadatas, isNodeNonStatic);
46
56
  for (const el of allPossibleElements) {
47
- if (el.type === "mdxJsxFlowElement") {
57
+ if (el.children.length === 0) continue;
58
+ if (isMdxComponentNode(el)) {
48
59
  el.attributes.push({
49
60
  type: "mdxJsxAttribute",
50
61
  name: "set:html",
@@ -55,7 +66,87 @@ function rehypeOptimizeStatic(options) {
55
66
  }
56
67
  el.children = [];
57
68
  }
69
+ for (let i = elementGroups.length - 1; i >= 0; i--) {
70
+ const group = elementGroups[i];
71
+ const fragmentNode = {
72
+ type: "mdxJsxFlowElement",
73
+ name: "Fragment",
74
+ attributes: [
75
+ {
76
+ type: "mdxJsxAttribute",
77
+ name: "set:html",
78
+ value: toHtml(group.children)
79
+ }
80
+ ],
81
+ children: []
82
+ };
83
+ group.parent.children.splice(group.startIndex, group.children.length, fragmentNode);
84
+ }
58
85
  };
86
+ };
87
+ function findElementGroups(allPossibleElements, elementMetadatas, isNodeNonStatic) {
88
+ const elementGroups = [];
89
+ for (const el of allPossibleElements) {
90
+ if (isNodeNonStatic(el)) continue;
91
+ const metadata = elementMetadatas.get(el);
92
+ if (!metadata) {
93
+ throw new Error(
94
+ "Internal MDX error: rehype-optimize-static should have metadata for element node"
95
+ );
96
+ }
97
+ const groupableElements = [el];
98
+ for (let i = metadata.index + 1; i < metadata.parent.children.length; i++) {
99
+ const node = metadata.parent.children[i];
100
+ if (isNodeNonStatic(node)) break;
101
+ if (node.type === "element") {
102
+ const existed = allPossibleElements.delete(node);
103
+ if (!existed) break;
104
+ }
105
+ groupableElements.push(node);
106
+ }
107
+ if (groupableElements.length > 1) {
108
+ elementGroups.push({
109
+ parent: metadata.parent,
110
+ startIndex: metadata.index,
111
+ children: groupableElements
112
+ });
113
+ allPossibleElements.delete(el);
114
+ }
115
+ }
116
+ return elementGroups;
117
+ }
118
+ function isMdxComponentNode(node) {
119
+ return node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
120
+ }
121
+ function getExportConstComponentObjectKeys(node) {
122
+ const exportNamedDeclaration = node.data?.estree?.body[0];
123
+ if (exportNamedDeclaration?.type !== "ExportNamedDeclaration") return;
124
+ const variableDeclaration = exportNamedDeclaration.declaration;
125
+ if (variableDeclaration?.type !== "VariableDeclaration") return;
126
+ const variableInit = variableDeclaration.declarations[0]?.init;
127
+ if (variableInit?.type !== "ObjectExpression") return;
128
+ const keys = [];
129
+ for (const propertyNode of variableInit.properties) {
130
+ if (propertyNode.type === "Property" && propertyNode.key.type === "Identifier") {
131
+ keys.push(propertyNode.key.name);
132
+ }
133
+ }
134
+ return keys;
135
+ }
136
+ function simplifyPlainMdxComponentNode(node, ignoreElementNames) {
137
+ if (!isMdxComponentNode(node) || // Attributes could be dynamic, so bail if so.
138
+ node.attributes.length > 0 || // Fragments are also dynamic
139
+ !node.name || // Ignore if the node name is in the ignore list
140
+ ignoreElementNames.has(node.name) || // If the node name has uppercase characters, it's likely an actual MDX component
141
+ node.name.toLowerCase() !== node.name) {
142
+ return;
143
+ }
144
+ const newNode = node;
145
+ newNode.type = "element";
146
+ newNode.tagName = node.name;
147
+ newNode.properties = {};
148
+ node.attributes = void 0;
149
+ node.data = void 0;
59
150
  }
60
151
  export {
61
152
  rehypeOptimizeStatic
package/dist/utils.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { AstroConfig } from 'astro';
3
3
  import matter from 'gray-matter';
4
4
  import type { MdxjsEsm } from 'mdast-util-mdx';
5
5
  import type { PluggableList } from 'unified';
6
- interface FileInfo {
6
+ export interface FileInfo {
7
7
  fileId: string;
8
8
  fileUrl: string;
9
9
  }
@@ -16,4 +16,3 @@ export declare function getFileInfo(id: string, config: AstroConfig): FileInfo;
16
16
  export declare function parseFrontmatter(code: string, id: string): matter.GrayMatterFile<string>;
17
17
  export declare function jsToTreeNode(jsString: string, acornOpts?: AcornOpts): MdxjsEsm;
18
18
  export declare function ignoreStringPlugins(plugins: any[]): PluggableList;
19
- export {};
@@ -0,0 +1,3 @@
1
+ import type { AstroConfig } from 'astro';
2
+ import type { Plugin } from 'vite';
3
+ export declare function vitePluginMdxPostprocess(astroConfig: AstroConfig): Plugin;
@@ -0,0 +1,93 @@
1
+ import { parse } from "es-module-lexer";
2
+ import {
3
+ ASTRO_IMAGE_ELEMENT,
4
+ ASTRO_IMAGE_IMPORT,
5
+ USES_ASTRO_IMAGE_FLAG
6
+ } from "./rehype-images-to-component.js";
7
+ import { getFileInfo } from "./utils.js";
8
+ const underscoreFragmentImportRegex = /[\s,{]_Fragment[\s,}]/;
9
+ const astroTagComponentImportRegex = /[\s,{]__astro_tag_component__[\s,}]/;
10
+ function vitePluginMdxPostprocess(astroConfig) {
11
+ return {
12
+ name: "@astrojs/mdx-postprocess",
13
+ transform(code, id, opts) {
14
+ if (!id.endsWith(".mdx")) return;
15
+ const fileInfo = getFileInfo(id, astroConfig);
16
+ const [imports, exports] = parse(code);
17
+ code = injectUnderscoreFragmentImport(code, imports);
18
+ code = injectMetadataExports(code, exports, fileInfo);
19
+ code = transformContentExport(code, exports);
20
+ code = annotateContentExport(code, id, !!opts?.ssr, imports);
21
+ return { code, map: null };
22
+ }
23
+ };
24
+ }
25
+ function injectUnderscoreFragmentImport(code, imports) {
26
+ if (!isSpecifierImported(code, imports, underscoreFragmentImportRegex, "astro/jsx-runtime")) {
27
+ code += `
28
+ import { Fragment as _Fragment } from 'astro/jsx-runtime';`;
29
+ }
30
+ return code;
31
+ }
32
+ function injectMetadataExports(code, exports, fileInfo) {
33
+ if (!exports.some(({ n }) => n === "url")) {
34
+ code += `
35
+ export const url = ${JSON.stringify(fileInfo.fileUrl)};`;
36
+ }
37
+ if (!exports.some(({ n }) => n === "file")) {
38
+ code += `
39
+ export const file = ${JSON.stringify(fileInfo.fileId)};`;
40
+ }
41
+ return code;
42
+ }
43
+ function transformContentExport(code, exports) {
44
+ if (exports.find(({ n }) => n === "Content")) return code;
45
+ const hasComponents = exports.find(({ n }) => n === "components");
46
+ const usesAstroImage = exports.find(({ n }) => n === USES_ASTRO_IMAGE_FLAG);
47
+ let componentsCode = `{ Fragment: _Fragment${hasComponents ? ", ...components" : ""}, ...props.components,`;
48
+ if (usesAstroImage) {
49
+ componentsCode += ` ${JSON.stringify(ASTRO_IMAGE_ELEMENT)}: ${hasComponents ? "components.img ?? " : ""} props.components?.img ?? ${ASTRO_IMAGE_IMPORT}`;
50
+ }
51
+ componentsCode += " }";
52
+ code = code.replace("export default function MDXContent", "function MDXContent");
53
+ code += `
54
+ export const Content = (props = {}) => MDXContent({
55
+ ...props,
56
+ components: ${componentsCode},
57
+ });
58
+ export default Content;`;
59
+ return code;
60
+ }
61
+ function annotateContentExport(code, id, ssr, imports) {
62
+ code += `
63
+ Content[Symbol.for('mdx-component')] = true`;
64
+ code += `
65
+ Content[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
66
+ code += `
67
+ Content.moduleId = ${JSON.stringify(id)};`;
68
+ if (ssr) {
69
+ if (!isSpecifierImported(
70
+ code,
71
+ imports,
72
+ astroTagComponentImportRegex,
73
+ "astro/runtime/server/index.js"
74
+ )) {
75
+ code += `
76
+ import { __astro_tag_component__ } from 'astro/runtime/server/index.js';`;
77
+ }
78
+ code += `
79
+ __astro_tag_component__(Content, 'astro:jsx');`;
80
+ }
81
+ return code;
82
+ }
83
+ function isSpecifierImported(code, imports, specifierRegex, source) {
84
+ for (const imp of imports) {
85
+ if (imp.n !== source) continue;
86
+ const importStatement = code.slice(imp.ss, imp.se);
87
+ if (specifierRegex.test(importStatement)) return true;
88
+ }
89
+ return false;
90
+ }
91
+ export {
92
+ vitePluginMdxPostprocess
93
+ };
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { MdxOptions } from './index.js';
3
+ export declare function vitePluginMdx(mdxOptions: MdxOptions): Plugin;
@@ -0,0 +1,78 @@
1
+ import { setVfileFrontmatter } from "@astrojs/markdown-remark";
2
+ import { getAstroMetadata } from "astro/jsx/rehype.js";
3
+ import { VFile } from "vfile";
4
+ import { createMdxProcessor } from "./plugins.js";
5
+ import { parseFrontmatter } from "./utils.js";
6
+ function vitePluginMdx(mdxOptions) {
7
+ let processor;
8
+ return {
9
+ name: "@mdx-js/rollup",
10
+ enforce: "pre",
11
+ buildEnd() {
12
+ processor = void 0;
13
+ },
14
+ configResolved(resolved) {
15
+ if (Object.keys(mdxOptions).length === 0) return;
16
+ processor = createMdxProcessor(mdxOptions, {
17
+ sourcemap: !!resolved.build.sourcemap
18
+ });
19
+ const jsxPluginIndex = resolved.plugins.findIndex((p) => p.name === "astro:jsx");
20
+ if (jsxPluginIndex !== -1) {
21
+ resolved.plugins.splice(jsxPluginIndex, 1);
22
+ }
23
+ },
24
+ async resolveId(source, importer, options) {
25
+ if (importer?.endsWith(".mdx") && source[0] !== "/") {
26
+ let resolved = await this.resolve(source, importer, options);
27
+ if (!resolved) resolved = await this.resolve("./" + source, importer, options);
28
+ return resolved;
29
+ }
30
+ },
31
+ // Override transform to alter code before MDX compilation
32
+ // ex. inject layouts
33
+ async transform(code, id) {
34
+ if (!id.endsWith(".mdx")) return;
35
+ const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
36
+ const vfile = new VFile({ value: pageContent, path: id });
37
+ setVfileFrontmatter(vfile, frontmatter);
38
+ if (!processor) {
39
+ return this.error(
40
+ "MDX processor is not initialized. This is an internal error. Please file an issue."
41
+ );
42
+ }
43
+ try {
44
+ const compiled = await processor.process(vfile);
45
+ return {
46
+ code: String(compiled.value),
47
+ map: compiled.map,
48
+ meta: getMdxMeta(vfile)
49
+ };
50
+ } catch (e) {
51
+ const err = e;
52
+ err.name = "MDXError";
53
+ err.loc = { file: id, line: e.line, column: e.column };
54
+ Error.captureStackTrace(err);
55
+ throw err;
56
+ }
57
+ }
58
+ };
59
+ }
60
+ function getMdxMeta(vfile) {
61
+ const astroMetadata = getAstroMetadata(vfile);
62
+ if (!astroMetadata) {
63
+ throw new Error(
64
+ "Internal MDX error: Astro metadata is not set by rehype-analyze-astro-metadata"
65
+ );
66
+ }
67
+ return {
68
+ astro: astroMetadata,
69
+ vite: {
70
+ // Setting this vite metadata to `ts` causes Vite to resolve .js
71
+ // extensions to .ts files.
72
+ lang: "ts"
73
+ }
74
+ };
75
+ }
76
+ export {
77
+ vitePluginMdx
78
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/mdx",
3
3
  "description": "Add support for MDX pages in your Astro site",
4
- "version": "2.3.0",
4
+ "version": "3.0.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -27,45 +27,45 @@
27
27
  "template"
28
28
  ],
29
29
  "dependencies": {
30
- "@mdx-js/mdx": "^3.0.0",
31
- "acorn": "^8.11.2",
32
- "es-module-lexer": "^1.4.1",
30
+ "@mdx-js/mdx": "^3.0.1",
31
+ "acorn": "^8.11.3",
32
+ "es-module-lexer": "^1.5.2",
33
33
  "estree-util-visit": "^2.0.0",
34
34
  "github-slugger": "^2.0.0",
35
35
  "gray-matter": "^4.0.3",
36
- "hast-util-to-html": "^9.0.0",
37
- "kleur": "^4.1.4",
36
+ "hast-util-to-html": "^9.0.1",
37
+ "kleur": "^4.1.5",
38
38
  "rehype-raw": "^7.0.0",
39
39
  "remark-gfm": "^4.0.0",
40
- "remark-smartypants": "^2.0.0",
40
+ "remark-smartypants": "^3.0.1",
41
41
  "source-map": "^0.7.4",
42
42
  "unist-util-visit": "^5.0.0",
43
43
  "vfile": "^6.0.1",
44
44
  "@astrojs/markdown-remark": "5.1.0"
45
45
  },
46
46
  "peerDependencies": {
47
- "astro": "^4.0.0"
47
+ "astro": "^4.8.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/chai": "^4.3.10",
51
50
  "@types/estree": "^1.0.5",
51
+ "@types/hast": "^3.0.4",
52
52
  "@types/mdast": "^4.0.3",
53
- "@types/mocha": "^10.0.4",
54
53
  "@types/yargs-parser": "^21.0.3",
55
54
  "cheerio": "1.0.0-rc.12",
56
- "linkedom": "^0.16.4",
55
+ "linkedom": "^0.16.11",
57
56
  "mdast-util-mdx": "^3.0.0",
57
+ "mdast-util-mdx-jsx": "^3.1.2",
58
58
  "mdast-util-to-string": "^4.0.0",
59
59
  "reading-time": "^1.5.0",
60
- "rehype-mathjax": "^5.0.0",
61
- "rehype-pretty-code": "^0.13.0",
60
+ "rehype-mathjax": "^6.0.0",
61
+ "rehype-pretty-code": "^0.13.1",
62
62
  "remark-math": "^6.0.0",
63
- "remark-rehype": "^11.0.0",
63
+ "remark-rehype": "^11.1.0",
64
64
  "remark-shiki-twoslash": "^3.1.3",
65
65
  "remark-toc": "^9.0.0",
66
66
  "unified": "^11.0.4",
67
- "vite": "^5.1.4",
68
- "astro": "4.6.0",
67
+ "vite": "^5.2.11",
68
+ "astro": "4.8.0",
69
69
  "astro-scripts": "0.0.14"
70
70
  },
71
71
  "engines": {
@@ -1,137 +0,0 @@
1
- import { visit } from "unist-util-visit";
2
- import { jsToTreeNode } from "./utils.js";
3
- const ASTRO_IMAGE_ELEMENT = "astro-image";
4
- const ASTRO_IMAGE_IMPORT = "__AstroImage__";
5
- const USES_ASTRO_IMAGE_FLAG = "__usesAstroImage";
6
- function remarkImageToComponent() {
7
- return function(tree, file) {
8
- if (!file.data.imagePaths)
9
- return;
10
- const importsStatements = [];
11
- const importedImages = /* @__PURE__ */ new Map();
12
- visit(tree, "image", (node, index, parent) => {
13
- if (file.data.imagePaths?.has(node.url)) {
14
- let importName = importedImages.get(node.url);
15
- if (!importName) {
16
- importName = `__${importedImages.size}_${node.url.replace(/\W/g, "_")}__`;
17
- importsStatements.push({
18
- type: "mdxjsEsm",
19
- value: "",
20
- data: {
21
- estree: {
22
- type: "Program",
23
- sourceType: "module",
24
- body: [
25
- {
26
- type: "ImportDeclaration",
27
- source: { type: "Literal", value: node.url, raw: JSON.stringify(node.url) },
28
- specifiers: [
29
- {
30
- type: "ImportDefaultSpecifier",
31
- local: { type: "Identifier", name: importName }
32
- }
33
- ]
34
- }
35
- ]
36
- }
37
- }
38
- });
39
- importedImages.set(node.url, importName);
40
- }
41
- const componentElement = {
42
- name: ASTRO_IMAGE_ELEMENT,
43
- type: "mdxJsxFlowElement",
44
- attributes: [
45
- {
46
- name: "src",
47
- type: "mdxJsxAttribute",
48
- value: {
49
- type: "mdxJsxAttributeValueExpression",
50
- value: importName,
51
- data: {
52
- estree: {
53
- type: "Program",
54
- sourceType: "module",
55
- comments: [],
56
- body: [
57
- {
58
- type: "ExpressionStatement",
59
- expression: { type: "Identifier", name: importName }
60
- }
61
- ]
62
- }
63
- }
64
- }
65
- },
66
- { name: "alt", type: "mdxJsxAttribute", value: node.alt || "" }
67
- ],
68
- children: []
69
- };
70
- if (node.title) {
71
- componentElement.attributes.push({
72
- type: "mdxJsxAttribute",
73
- name: "title",
74
- value: node.title
75
- });
76
- }
77
- if (node.data && node.data.hProperties) {
78
- const createArrayAttribute = (name, values) => {
79
- return {
80
- type: "mdxJsxAttribute",
81
- name,
82
- value: {
83
- type: "mdxJsxAttributeValueExpression",
84
- value: name,
85
- data: {
86
- estree: {
87
- type: "Program",
88
- body: [
89
- {
90
- type: "ExpressionStatement",
91
- expression: {
92
- type: "ArrayExpression",
93
- elements: values.map((value) => ({
94
- type: "Literal",
95
- value,
96
- raw: String(value)
97
- }))
98
- }
99
- }
100
- ],
101
- sourceType: "module",
102
- comments: []
103
- }
104
- }
105
- }
106
- };
107
- };
108
- Object.entries(node.data.hProperties).forEach(
109
- ([key, value]) => {
110
- if (Array.isArray(value)) {
111
- componentElement.attributes.push(createArrayAttribute(key, value));
112
- } else {
113
- componentElement.attributes.push({
114
- name: key,
115
- type: "mdxJsxAttribute",
116
- value: String(value)
117
- });
118
- }
119
- }
120
- );
121
- }
122
- parent.children.splice(index, 1, componentElement);
123
- }
124
- });
125
- tree.children.unshift(...importsStatements);
126
- tree.children.unshift(
127
- jsToTreeNode(`import { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`)
128
- );
129
- tree.children.push(jsToTreeNode(`export const ${USES_ASTRO_IMAGE_FLAG} = true`));
130
- };
131
- }
132
- export {
133
- ASTRO_IMAGE_ELEMENT,
134
- ASTRO_IMAGE_IMPORT,
135
- USES_ASTRO_IMAGE_FLAG,
136
- remarkImageToComponent
137
- };