@astrojs/mdx 2.3.1 → 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,140 +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
- buildEnd() {
56
- processor = void 0;
57
- },
58
- configResolved(resolved) {
59
- processor = createMdxProcessor(mdxOptions, {
60
- sourcemap: !!resolved.build.sourcemap
61
- });
62
- const jsxPluginIndex = resolved.plugins.findIndex((p) => p.name === "astro:jsx");
63
- if (jsxPluginIndex !== -1) {
64
- const myPluginIndex = resolved.plugins.findIndex(
65
- (p) => p.name === "@mdx-js/rollup"
66
- );
67
- if (myPluginIndex !== -1) {
68
- const myPlugin = resolved.plugins[myPluginIndex];
69
- resolved.plugins.splice(myPluginIndex, 1);
70
- resolved.plugins.splice(jsxPluginIndex, 0, myPlugin);
71
- }
72
- }
73
- },
74
- async resolveId(source, importer, options) {
75
- if (importer?.endsWith(".mdx") && source[0] !== "/") {
76
- let resolved = await this.resolve(source, importer, options);
77
- if (!resolved)
78
- resolved = await this.resolve("./" + source, importer, options);
79
- return resolved;
80
- }
81
- },
82
- // Override transform to alter code before MDX compilation
83
- // ex. inject layouts
84
- async transform(_, id) {
85
- if (!id.endsWith(".mdx"))
86
- return;
87
- const { fileId } = getFileInfo(id, config);
88
- const code = await fs.readFile(fileId, "utf-8");
89
- const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
90
- const vfile = new VFile({ value: pageContent, path: id });
91
- setVfileFrontmatter(vfile, frontmatter);
92
- if (!processor) {
93
- return this.error(
94
- "MDX processor is not initialized. This is an internal error. Please file an issue."
95
- );
96
- }
97
- try {
98
- const compiled = await processor.process(vfile);
99
- return {
100
- code: String(compiled.value),
101
- map: compiled.map
102
- };
103
- } catch (e) {
104
- const err = e;
105
- err.name = "MDXError";
106
- err.loc = { file: fileId, line: e.line, column: e.column };
107
- Error.captureStackTrace(err);
108
- throw err;
109
- }
110
- }
111
- },
112
- {
113
- name: "@astrojs/mdx-postprocess",
114
- // These transforms must happen *after* JSX runtime transformations
115
- transform(code, id) {
116
- if (!id.endsWith(".mdx"))
117
- return;
118
- const [moduleImports, moduleExports] = parseESM(code);
119
- const importsFromJSXRuntime = moduleImports.filter(({ n }) => n === "astro/jsx-runtime").map(({ ss, se }) => code.substring(ss, se));
120
- const hasFragmentImport = importsFromJSXRuntime.some(
121
- (statement) => /[\s,{](?:Fragment,|Fragment\s*\})/.test(statement)
122
- );
123
- if (!hasFragmentImport) {
124
- code = 'import { Fragment } from "astro/jsx-runtime"\n' + code;
125
- }
126
- const { fileUrl, fileId } = getFileInfo(id, config);
127
- if (!moduleExports.find(({ n }) => n === "url")) {
128
- code += `
129
- export const url = ${JSON.stringify(fileUrl)};`;
130
- }
131
- if (!moduleExports.find(({ n }) => n === "file")) {
132
- code += `
133
- export const file = ${JSON.stringify(fileId)};`;
134
- }
135
- if (!moduleExports.find(({ n }) => n === "Content")) {
136
- const hasComponents = moduleExports.find(({ n }) => n === "components");
137
- const usesAstroImage = moduleExports.find(
138
- ({ n }) => n === USES_ASTRO_IMAGE_FLAG
139
- );
140
- let componentsCode = `{ Fragment${hasComponents ? ", ...components" : ""}, ...props.components,`;
141
- if (usesAstroImage) {
142
- componentsCode += ` ${JSON.stringify(ASTRO_IMAGE_ELEMENT)}: ${hasComponents ? "components.img ?? " : ""} props.components?.img ?? ${ASTRO_IMAGE_IMPORT}`;
143
- }
144
- componentsCode += " }";
145
- code = code.replace(
146
- "export default function MDXContent",
147
- "function MDXContent"
148
- );
149
- code += `
150
- export const Content = (props = {}) => MDXContent({
151
- ...props,
152
- components: ${componentsCode},
153
- });
154
- export default Content;`;
155
- }
156
- code += `
157
- Content[Symbol.for('mdx-component')] = true`;
158
- code += `
159
- Content[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
160
- code += `
161
- Content.moduleId = ${JSON.stringify(id)};`;
162
- return { code, map: null };
163
- }
164
- }
165
- ]
166
- }
167
- });
50
+ Object.assign(mdxOptions, resolvedMdxOptions);
51
+ mdxOptions = {};
168
52
  }
169
53
  }
170
54
  };
171
55
  }
172
56
  const defaultMdxOptions = {
173
57
  extendMarkdownConfig: true,
174
- recmaPlugins: []
58
+ recmaPlugins: [],
59
+ optimize: false
175
60
  };
176
61
  function markdownConfigToMdxOptions(markdownConfig) {
177
62
  return {
@@ -179,8 +64,7 @@ function markdownConfigToMdxOptions(markdownConfig) {
179
64
  ...markdownConfig,
180
65
  remarkPlugins: ignoreStringPlugins(markdownConfig.remarkPlugins),
181
66
  rehypePlugins: ignoreStringPlugins(markdownConfig.rehypePlugins),
182
- remarkRehype: markdownConfig.remarkRehype ?? {},
183
- optimize: false
67
+ remarkRehype: markdownConfig.remarkRehype ?? {}
184
68
  };
185
69
  }
186
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.1",
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.2",
67
+ "vite": "^5.2.11",
68
+ "astro": "4.8.0",
69
69
  "astro-scripts": "0.0.14"
70
70
  },
71
71
  "engines": {
@@ -1,141 +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: {
28
- type: "Literal",
29
- value: node.url,
30
- raw: JSON.stringify(node.url)
31
- },
32
- specifiers: [
33
- {
34
- type: "ImportDefaultSpecifier",
35
- local: { type: "Identifier", name: importName }
36
- }
37
- ]
38
- }
39
- ]
40
- }
41
- }
42
- });
43
- importedImages.set(node.url, importName);
44
- }
45
- const componentElement = {
46
- name: ASTRO_IMAGE_ELEMENT,
47
- type: "mdxJsxFlowElement",
48
- attributes: [
49
- {
50
- name: "src",
51
- type: "mdxJsxAttribute",
52
- value: {
53
- type: "mdxJsxAttributeValueExpression",
54
- value: importName,
55
- data: {
56
- estree: {
57
- type: "Program",
58
- sourceType: "module",
59
- comments: [],
60
- body: [
61
- {
62
- type: "ExpressionStatement",
63
- expression: { type: "Identifier", name: importName }
64
- }
65
- ]
66
- }
67
- }
68
- }
69
- },
70
- { name: "alt", type: "mdxJsxAttribute", value: node.alt || "" }
71
- ],
72
- children: []
73
- };
74
- if (node.title) {
75
- componentElement.attributes.push({
76
- type: "mdxJsxAttribute",
77
- name: "title",
78
- value: node.title
79
- });
80
- }
81
- if (node.data && node.data.hProperties) {
82
- const createArrayAttribute = (name, values) => {
83
- return {
84
- type: "mdxJsxAttribute",
85
- name,
86
- value: {
87
- type: "mdxJsxAttributeValueExpression",
88
- value: name,
89
- data: {
90
- estree: {
91
- type: "Program",
92
- body: [
93
- {
94
- type: "ExpressionStatement",
95
- expression: {
96
- type: "ArrayExpression",
97
- elements: values.map((value) => ({
98
- type: "Literal",
99
- value,
100
- raw: String(value)
101
- }))
102
- }
103
- }
104
- ],
105
- sourceType: "module",
106
- comments: []
107
- }
108
- }
109
- }
110
- };
111
- };
112
- Object.entries(node.data.hProperties).forEach(
113
- ([key, value]) => {
114
- if (Array.isArray(value)) {
115
- componentElement.attributes.push(createArrayAttribute(key, value));
116
- } else {
117
- componentElement.attributes.push({
118
- name: key,
119
- type: "mdxJsxAttribute",
120
- value: String(value)
121
- });
122
- }
123
- }
124
- );
125
- }
126
- parent.children.splice(index, 1, componentElement);
127
- }
128
- });
129
- tree.children.unshift(...importsStatements);
130
- tree.children.unshift(
131
- jsToTreeNode(`import { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`)
132
- );
133
- tree.children.push(jsToTreeNode(`export const ${USES_ASTRO_IMAGE_FLAG} = true`));
134
- };
135
- }
136
- export {
137
- ASTRO_IMAGE_ELEMENT,
138
- ASTRO_IMAGE_IMPORT,
139
- USES_ASTRO_IMAGE_FLAG,
140
- remarkImageToComponent
141
- };