@astrojs/mdx 0.12.0 → 0.12.1

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.
@@ -1,5 +1,5 @@
1
- @astrojs/mdx:build: cache hit, replaying output dac9ca86e0959274
2
- @astrojs/mdx:build: 
3
- @astrojs/mdx:build: > @astrojs/mdx@0.12.0 build /home/runner/work/astro/astro/packages/integrations/mdx
4
- @astrojs/mdx:build: > astro-scripts build "src/**/*.ts" && tsc
5
- @astrojs/mdx:build: 
1
+ @astrojs/mdx:build: cache hit, replaying output 55f97c81b614ef9c
2
+ @astrojs/mdx:build: 
3
+ @astrojs/mdx:build: > @astrojs/mdx@0.12.1 build /home/runner/work/astro/astro/packages/integrations/mdx
4
+ @astrojs/mdx:build: > astro-scripts build "src/**/*.ts" && tsc
5
+ @astrojs/mdx:build: 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @astrojs/mdx
2
2
 
3
+ ## 0.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5522](https://github.com/withastro/astro/pull/5522) [`efc4363e0`](https://github.com/withastro/astro/commit/efc4363e0baf7f92900e20af339811bb3df42b0e) Thanks [@delucis](https://github.com/delucis)! - Support use of `<Fragment>` in MDX files rendered with `<Content />` component
8
+
3
9
  ## 0.12.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.js CHANGED
@@ -91,9 +91,14 @@ function mdx(mdxOptions = {}) {
91
91
  transform(code, id) {
92
92
  if (!id.endsWith(".mdx"))
93
93
  return;
94
- code += `
95
- MDXContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
96
- const [, moduleExports] = parseESM(code);
94
+ const [moduleImports, moduleExports] = parseESM(code);
95
+ const importsFromJSXRuntime = moduleImports.filter(({ n }) => n === "astro/jsx-runtime").map(({ ss, se }) => code.substring(ss, se));
96
+ const hasFragmentImport = importsFromJSXRuntime.some(
97
+ (statement) => /[\s,{](Fragment,|Fragment\s*})/.test(statement)
98
+ );
99
+ if (!hasFragmentImport) {
100
+ code = 'import { Fragment } from "astro/jsx-runtime"\n' + code;
101
+ }
97
102
  const { fileUrl, fileId } = getFileInfo(id, config);
98
103
  if (!moduleExports.includes("url")) {
99
104
  code += `
@@ -116,9 +121,16 @@ export function compiledContent() { throw new Error(${JSON.stringify(
116
121
  )}) };`;
117
122
  }
118
123
  if (!moduleExports.includes("Content")) {
124
+ code = code.replace("export default MDXContent;", "");
119
125
  code += `
120
- export const Content = MDXContent;`;
126
+ export const Content = (props = {}) => MDXContent({
127
+ ...props,
128
+ components: { Fragment, ...props.components },
129
+ });
130
+ export default Content;`;
121
131
  }
132
+ code += `
133
+ Content[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
122
134
  if (command === "dev") {
123
135
  code += `
124
136
  if (import.meta.hot) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/mdx",
3
3
  "description": "Use MDX within Astro",
4
- "version": "0.12.0",
4
+ "version": "0.12.1",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -46,7 +46,7 @@
46
46
  "@types/github-slugger": "^1.3.0",
47
47
  "@types/mocha": "^9.1.1",
48
48
  "@types/yargs-parser": "^21.0.0",
49
- "astro": "1.6.11",
49
+ "astro": "1.6.13",
50
50
  "astro-scripts": "0.0.9",
51
51
  "chai": "^4.3.6",
52
52
  "cheerio": "^1.0.0-rc.11",
package/src/index.ts CHANGED
@@ -132,11 +132,18 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
132
132
  transform(code, id) {
133
133
  if (!id.endsWith('.mdx')) return;
134
134
 
135
- // Ensures styles and scripts are injected into a `<head>`
136
- // When a layout is not applied
137
- code += `\nMDXContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
138
-
139
- const [, moduleExports] = parseESM(code);
135
+ const [moduleImports, moduleExports] = parseESM(code);
136
+
137
+ // Fragment import should already be injected, but check just to be safe.
138
+ const importsFromJSXRuntime = moduleImports
139
+ .filter(({ n }) => n === 'astro/jsx-runtime')
140
+ .map(({ ss, se }) => code.substring(ss, se));
141
+ const hasFragmentImport = importsFromJSXRuntime.some((statement) =>
142
+ /[\s,{](Fragment,|Fragment\s*})/.test(statement)
143
+ );
144
+ if (!hasFragmentImport) {
145
+ code = 'import { Fragment } from "astro/jsx-runtime"\n' + code;
146
+ }
140
147
 
141
148
  const { fileUrl, fileId } = getFileInfo(id, config);
142
149
  if (!moduleExports.includes('url')) {
@@ -156,9 +163,19 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
156
163
  )}) };`;
157
164
  }
158
165
  if (!moduleExports.includes('Content')) {
159
- code += `\nexport const Content = MDXContent;`;
166
+ // Make `Content` the default export so we can wrap `MDXContent` and pass in `Fragment`
167
+ code = code.replace('export default MDXContent;', '');
168
+ code += `\nexport const Content = (props = {}) => MDXContent({
169
+ ...props,
170
+ components: { Fragment, ...props.components },
171
+ });
172
+ export default Content;`;
160
173
  }
161
174
 
175
+ // Ensures styles and scripts are injected into a `<head>`
176
+ // When a layout is not applied
177
+ code += `\nContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
178
+
162
179
  if (command === 'dev') {
163
180
  // TODO: decline HMR updates until we have a stable approach
164
181
  code += `\nif (import.meta.hot) {
@@ -0,0 +1,3 @@
1
+ # MDX containing `<Fragment />`
2
+
3
+ <p><Fragment>bar</Fragment></p>
@@ -1,11 +1,20 @@
1
1
  ---
2
+ import { parse } from 'node:path';
2
3
  const components = await Astro.glob('../components/*.mdx');
3
4
  ---
4
5
 
5
6
  <div data-default-export>
6
- {components.map(Component => <Component.default />)}
7
+ {components.map(Component => (
8
+ <div data-file={parse(Component.file).base}>
9
+ <Component.default />
10
+ </div>
11
+ ))}
7
12
  </div>
8
13
 
9
14
  <div data-content-export>
10
- {components.map(({ Content }) => <Content />)}
15
+ {components.map(({ Content, file }) => (
16
+ <div data-file={parse(file).base}>
17
+ <Content />
18
+ </div>
19
+ ))}
11
20
  </div>
@@ -0,0 +1,5 @@
1
+ ---
2
+ import WithFragment from '../components/WithFragment.mdx';
3
+ ---
4
+
5
+ <WithFragment />
@@ -0,0 +1,4 @@
1
+ <div class="slotted">
2
+ <div data-default-slot><slot /></div>
3
+ <div data-named-slot><slot name="named" /></div>
4
+ </div>
@@ -0,0 +1,15 @@
1
+ import Slotted from './Slotted.astro'
2
+
3
+ # Hello slotted component!
4
+
5
+ <Slotted>
6
+
7
+ Default content.
8
+
9
+ <Fragment slot="named">
10
+
11
+ Content for named slot.
12
+
13
+ </Fragment>
14
+
15
+ </Slotted>
@@ -0,0 +1,11 @@
1
+ ---
2
+ const components = await Astro.glob('../components/*.mdx');
3
+ ---
4
+
5
+ <div data-default-export>
6
+ {components.map(Component => <Component.default />)}
7
+ </div>
8
+
9
+ <div data-content-export>
10
+ {components.map(({ Content }) => <Content />)}
11
+ </div>
@@ -0,0 +1,5 @@
1
+ ---
2
+ import Test from '../components/Test.mdx';
3
+ ---
4
+
5
+ <Test />
@@ -51,6 +51,41 @@ describe('MDX Component', () => {
51
51
  expect(h1.textContent).to.equal('Hello component!');
52
52
  expect(foo.textContent).to.equal('bar');
53
53
  });
54
+
55
+ describe('with <Fragment>', () => {
56
+ it('supports top-level imports', async () => {
57
+ const html = await fixture.readFile('/w-fragment/index.html');
58
+ const { document } = parseHTML(html);
59
+
60
+ const h1 = document.querySelector('h1');
61
+ const p = document.querySelector('p');
62
+
63
+ expect(h1.textContent).to.equal('MDX containing <Fragment />');
64
+ expect(p.textContent).to.equal('bar');
65
+ });
66
+
67
+ it('supports glob imports - <Component.default />', async () => {
68
+ const html = await fixture.readFile('/glob/index.html');
69
+ const { document } = parseHTML(html);
70
+
71
+ const h = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] h1');
72
+ const p = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] p');
73
+
74
+ expect(h.textContent).to.equal('MDX containing <Fragment />');
75
+ expect(p.textContent).to.equal('bar');
76
+ });
77
+
78
+ it('supports glob imports - <Content />', async () => {
79
+ const html = await fixture.readFile('/glob/index.html');
80
+ const { document } = parseHTML(html);
81
+
82
+ const h = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] h1');
83
+ const p = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] p');
84
+
85
+ expect(h.textContent).to.equal('MDX containing <Fragment />');
86
+ expect(p.textContent).to.equal('bar');
87
+ });
88
+ });
54
89
  });
55
90
 
56
91
  describe('dev', () => {
@@ -108,5 +143,49 @@ describe('MDX Component', () => {
108
143
  expect(h1.textContent).to.equal('Hello component!');
109
144
  expect(foo.textContent).to.equal('bar');
110
145
  });
146
+
147
+ describe('with <Fragment>', () => {
148
+ it('supports top-level imports', async () => {
149
+ const res = await fixture.fetch('/w-fragment');
150
+ expect(res.status).to.equal(200);
151
+
152
+ const html = await res.text();
153
+ const { document } = parseHTML(html);
154
+
155
+ const h1 = document.querySelector('h1');
156
+ const p = document.querySelector('p');
157
+
158
+ expect(h1.textContent).to.equal('MDX containing <Fragment />');
159
+ expect(p.textContent).to.equal('bar');
160
+ });
161
+
162
+ it('supports glob imports - <Component.default />', async () => {
163
+ const res = await fixture.fetch('/glob');
164
+ expect(res.status).to.equal(200);
165
+
166
+ const html = await res.text();
167
+ const { document } = parseHTML(html);
168
+
169
+ const h = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] h1');
170
+ const p = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] p');
171
+
172
+ expect(h.textContent).to.equal('MDX containing <Fragment />');
173
+ expect(p.textContent).to.equal('bar');
174
+ });
175
+
176
+ it('supports glob imports - <Content />', async () => {
177
+ const res = await fixture.fetch('/glob');
178
+ expect(res.status).to.equal(200);
179
+
180
+ const html = await res.text();
181
+ const { document } = parseHTML(html);
182
+
183
+ const h = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] h1');
184
+ const p = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] p');
185
+
186
+ expect(h.textContent).to.equal('MDX containing <Fragment />');
187
+ expect(p.textContent).to.equal('bar');
188
+ });
189
+ });
111
190
  });
112
191
  });
@@ -0,0 +1,124 @@
1
+ import mdx from '@astrojs/mdx';
2
+
3
+ import { expect } from 'chai';
4
+ import { parseHTML } from 'linkedom';
5
+ import { loadFixture } from '../../../astro/test/test-utils.js';
6
+
7
+ describe('MDX slots', () => {
8
+ let fixture;
9
+
10
+ before(async () => {
11
+ fixture = await loadFixture({
12
+ root: new URL('./fixtures/mdx-slots/', import.meta.url),
13
+ integrations: [mdx()],
14
+ });
15
+ });
16
+
17
+ describe('build', () => {
18
+ before(async () => {
19
+ await fixture.build();
20
+ });
21
+
22
+ it('supports top-level imports', async () => {
23
+ const html = await fixture.readFile('/index.html');
24
+ const { document } = parseHTML(html);
25
+
26
+ const h1 = document.querySelector('h1');
27
+ const defaultSlot = document.querySelector('[data-default-slot]');
28
+ const namedSlot = document.querySelector('[data-named-slot]');
29
+
30
+ expect(h1.textContent).to.equal('Hello slotted component!');
31
+ expect(defaultSlot.textContent).to.equal('Default content.');
32
+ expect(namedSlot.textContent).to.equal('Content for named slot.');
33
+ });
34
+
35
+ it('supports glob imports - <Component.default />', async () => {
36
+ const html = await fixture.readFile('/glob/index.html');
37
+ const { document } = parseHTML(html);
38
+
39
+ const h1 = document.querySelector('[data-default-export] h1');
40
+ const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]');
41
+ const namedSlot = document.querySelector('[data-default-export] [data-named-slot]');
42
+
43
+ expect(h1.textContent).to.equal('Hello slotted component!');
44
+ expect(defaultSlot.textContent).to.equal('Default content.');
45
+ expect(namedSlot.textContent).to.equal('Content for named slot.');
46
+ });
47
+
48
+ it('supports glob imports - <Content />', async () => {
49
+ const html = await fixture.readFile('/glob/index.html');
50
+ const { document } = parseHTML(html);
51
+
52
+ const h1 = document.querySelector('[data-content-export] h1');
53
+ const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]');
54
+ const namedSlot = document.querySelector('[data-content-export] [data-named-slot]');
55
+
56
+ expect(h1.textContent).to.equal('Hello slotted component!');
57
+ expect(defaultSlot.textContent).to.equal('Default content.');
58
+ expect(namedSlot.textContent).to.equal('Content for named slot.');
59
+ });
60
+ });
61
+
62
+ describe('dev', () => {
63
+ let devServer;
64
+
65
+ before(async () => {
66
+ devServer = await fixture.startDevServer();
67
+ });
68
+
69
+ after(async () => {
70
+ await devServer.stop();
71
+ });
72
+
73
+ it('supports top-level imports', async () => {
74
+ const res = await fixture.fetch('/');
75
+
76
+ expect(res.status).to.equal(200);
77
+
78
+ const html = await res.text();
79
+ const { document } = parseHTML(html);
80
+
81
+ const h1 = document.querySelector('h1');
82
+ const defaultSlot = document.querySelector('[data-default-slot]');
83
+ const namedSlot = document.querySelector('[data-named-slot]');
84
+
85
+ expect(h1.textContent).to.equal('Hello slotted component!');
86
+ expect(defaultSlot.textContent).to.equal('Default content.');
87
+ expect(namedSlot.textContent).to.equal('Content for named slot.');
88
+ });
89
+
90
+ it('supports glob imports - <Component.default />', async () => {
91
+ const res = await fixture.fetch('/glob');
92
+
93
+ expect(res.status).to.equal(200);
94
+
95
+ const html = await res.text();
96
+ const { document } = parseHTML(html);
97
+
98
+ const h1 = document.querySelector('[data-default-export] h1');
99
+ const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]');
100
+ const namedSlot = document.querySelector('[data-default-export] [data-named-slot]');
101
+
102
+ expect(h1.textContent).to.equal('Hello slotted component!');
103
+ expect(defaultSlot.textContent).to.equal('Default content.');
104
+ expect(namedSlot.textContent).to.equal('Content for named slot.');
105
+ });
106
+
107
+ it('supports glob imports - <Content />', async () => {
108
+ const res = await fixture.fetch('/glob');
109
+
110
+ expect(res.status).to.equal(200);
111
+
112
+ const html = await res.text();
113
+ const { document } = parseHTML(html);
114
+
115
+ const h1 = document.querySelector('[data-content-export] h1');
116
+ const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]');
117
+ const namedSlot = document.querySelector('[data-content-export] [data-named-slot]');
118
+
119
+ expect(h1.textContent).to.equal('Hello slotted component!');
120
+ expect(defaultSlot.textContent).to.equal('Default content.');
121
+ expect(namedSlot.textContent).to.equal('Content for named slot.');
122
+ });
123
+ });
124
+ });