@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.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +6 -0
- package/dist/index.js +16 -4
- package/package.json +2 -2
- package/src/index.ts +23 -6
- package/test/fixtures/mdx-component/src/components/WithFragment.mdx +3 -0
- package/test/fixtures/mdx-component/src/pages/glob.astro +11 -2
- package/test/fixtures/mdx-component/src/pages/w-fragment.astro +5 -0
- package/test/fixtures/mdx-slots/src/components/Slotted.astro +4 -0
- package/test/fixtures/mdx-slots/src/components/Test.mdx +15 -0
- package/test/fixtures/mdx-slots/src/pages/glob.astro +11 -0
- package/test/fixtures/mdx-slots/src/pages/index.astro +5 -0
- package/test/mdx-component.test.js +79 -0
- package/test/mdx-slots.js +124 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[
|
|
2
|
-
[
|
|
3
|
-
[
|
|
4
|
-
[
|
|
5
|
-
[
|
|
1
|
+
[36m@astrojs/mdx:build: [0mcache hit, replaying output [2m55f97c81b614ef9c[0m
|
|
2
|
+
[36m@astrojs/mdx:build: [0m
|
|
3
|
+
[36m@astrojs/mdx:build: [0m> @astrojs/mdx@0.12.1 build /home/runner/work/astro/astro/packages/integrations/mdx
|
|
4
|
+
[36m@astrojs/mdx:build: [0m> astro-scripts build "src/**/*.ts" && tsc
|
|
5
|
+
[36m@astrojs/mdx:build: [0m
|
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
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
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.
|
|
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.
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -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 =>
|
|
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 }) =>
|
|
15
|
+
{components.map(({ Content, file }) => (
|
|
16
|
+
<div data-file={parse(file).base}>
|
|
17
|
+
<Content />
|
|
18
|
+
</div>
|
|
19
|
+
))}
|
|
11
20
|
</div>
|
|
@@ -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
|
+
});
|