@astrojs/mdx 0.3.1 → 0.4.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.
@@ -1,5 +1,5 @@
1
- @astrojs/mdx:build: cache hit, replaying output 0a3d73b5c732032a
2
- @astrojs/mdx:build: 
3
- @astrojs/mdx:build: > @astrojs/mdx@0.3.1 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 c4afb89e8a455a28
2
+ @astrojs/mdx:build: 
3
+ @astrojs/mdx:build: > @astrojs/mdx@0.4.0 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.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4088](https://github.com/withastro/astro/pull/4088) [`1743fe140`](https://github.com/withastro/astro/commit/1743fe140eb58d60e26cbd11a066bb60de046e0c) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support "layout" frontmatter property
8
+
3
9
  ## 0.3.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -136,15 +136,37 @@ const posts = await Astro.glob('./*.mdx');
136
136
 
137
137
  ### Layouts
138
138
 
139
- You can use the [MDX layout component](https://mdxjs.com/docs/using-mdx/#layout) to specify a layout component to wrap all page content. This is done with a default export statement at the end of your `.mdx` file:
139
+ Layouts can be applied [in the same way as standard Astro Markdown](https://docs.astro.build/en/guides/markdown-content/#markdown-layouts). You can add a `layout` to [your frontmatter](#frontmatter) like so:
140
140
 
141
- ```mdx
142
- // src/pages/my-page.mdx
141
+ ```yaml
142
+ ---
143
+ layout: '../layouts/BaseLayout.astro'
144
+ title: 'My Blog Post'
145
+ ---
146
+ ```
147
+
148
+ Then, you can retrieve all other frontmatter properties from your layout via the `content` property, and render your MDX using the default [`<slot />`](https://docs.astro.build/en/core-concepts/astro-components/#slots):
143
149
 
144
- export {default} from '../../layouts/BaseLayout.astro';
150
+ ```astro
151
+ ---
152
+ // src/layouts/BaseLayout.astro
153
+ const { content } = Astro.props;
154
+ ---
155
+ <html>
156
+ <head>
157
+ <title>{content.title}</title>
158
+ </head>
159
+ <body>
160
+ <h1>{content.title}</h1>
161
+ <!-- Rendered MDX will be passed into the default slot. -->
162
+ <slot />
163
+ </body>
164
+ </html>
145
165
  ```
146
166
 
147
- You can also import and use a [`<Layout />` component](/en/core-concepts/layouts/) for your MDX page content, and pass all the variables declared in frontmatter as props.
167
+ #### Importing layouts manually
168
+
169
+ You may need to pass information to your layouts that does not (or cannot) exist in your frontmatter. In this case, you can import and use a [`<Layout />` component](https://docs.astro.build/en/core-concepts/layouts/) like any other component:
148
170
 
149
171
  ```mdx
150
172
  ---
@@ -155,9 +177,11 @@ publishDate: '21 September 2022'
155
177
  ---
156
178
  import BaseLayout from '../layouts/BaseLayout.astro';
157
179
 
158
- <BaseLayout {...frontmatter}>
159
- # {frontmatter.title}
180
+ function fancyJsHelper() {
181
+ return "Try doing that with YAML!";
182
+ }
160
183
 
184
+ <BaseLayout title={frontmatter.title} fancyJsHelper={fancyJsHelper}>
161
185
  Welcome to my new Astro blog, using MDX!
162
186
  </BaseLayout>
163
187
  ```
@@ -166,12 +190,12 @@ Then, your values are available to you through `Astro.props` in your layout, and
166
190
  ```astro
167
191
  ---
168
192
  // src/layouts/BaseLayout.astro
169
- const { title, publishDate } = Astro.props;
193
+ const { title, fancyJsHelper } = Astro.props;
170
194
  ---
171
195
  <!-- -->
172
196
  <h1>{title}</h1>
173
197
  <slot />
174
- <p>Published on {publishDate}</p>
198
+ <p>{fancyJsHelper()}</p>
175
199
  <!-- -->
176
200
  ```
177
201
 
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import remarkMdxFrontmatter from "remark-mdx-frontmatter";
8
8
  import remarkShikiTwoslash from "remark-shiki-twoslash";
9
9
  import remarkSmartypants from "remark-smartypants";
10
10
  import remarkPrism from "./remark-prism.js";
11
- import { getFileInfo } from "./utils.js";
11
+ import { getFileInfo, getFrontmatter } from "./utils.js";
12
12
  const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
13
13
  function handleExtends(config, defaults = []) {
14
14
  if (Array.isArray(config))
@@ -42,19 +42,42 @@ function mdx(mdxOptions = {}) {
42
42
  ...mdxOptions.frontmatterOptions
43
43
  }
44
44
  ]);
45
+ const configuredMdxPlugin = mdxPlugin({
46
+ remarkPlugins,
47
+ rehypePlugins,
48
+ jsx: true,
49
+ jsxImportSource: "astro",
50
+ format: "mdx",
51
+ mdExtensions: []
52
+ });
45
53
  updateConfig({
46
54
  vite: {
47
55
  plugins: [
48
56
  {
49
57
  enforce: "pre",
50
- ...mdxPlugin({
51
- remarkPlugins,
52
- rehypePlugins,
53
- jsx: true,
54
- jsxImportSource: "astro",
55
- format: "mdx",
56
- mdExtensions: []
57
- })
58
+ ...configuredMdxPlugin,
59
+ async transform(code, id) {
60
+ var _a, _b;
61
+ if (!id.endsWith(".mdx"))
62
+ return;
63
+ const mdxPluginTransform = (_a = configuredMdxPlugin.transform) == null ? void 0 : _a.bind(this);
64
+ if ((_b = mdxOptions.frontmatterOptions) == null ? void 0 : _b.parsers) {
65
+ return mdxPluginTransform == null ? void 0 : mdxPluginTransform(code, id);
66
+ }
67
+ const frontmatter = getFrontmatter(code, id);
68
+ if (frontmatter.layout) {
69
+ const { layout, ...content } = frontmatter;
70
+ code += `
71
+ export default async function({ children }) {
72
+ const Layout = (await import(${JSON.stringify(
73
+ frontmatter.layout
74
+ )})).default;
75
+ return <Layout content={${JSON.stringify(
76
+ content
77
+ )}}>{children}</Layout> }`;
78
+ }
79
+ return mdxPluginTransform == null ? void 0 : mdxPluginTransform(code, id);
80
+ }
58
81
  },
59
82
  {
60
83
  name: "@astrojs/mdx",
package/dist/utils.d.ts CHANGED
@@ -5,4 +5,11 @@ interface FileInfo {
5
5
  }
6
6
  /** @see 'vite-plugin-utils' for source */
7
7
  export declare function getFileInfo(id: string, config: AstroConfig): FileInfo;
8
+ /**
9
+ * Match YAML exception handling from Astro core errors
10
+ * @see 'astro/src/core/errors.ts'
11
+ */
12
+ export declare function getFrontmatter(code: string, id: string): {
13
+ [key: string]: any;
14
+ };
8
15
  export {};
package/dist/utils.js CHANGED
@@ -1,3 +1,4 @@
1
+ import matter from "gray-matter";
1
2
  function appendForwardSlash(path) {
2
3
  return path.endsWith("/") ? path : path + "/";
3
4
  }
@@ -25,6 +26,22 @@ function getFileInfo(id, config) {
25
26
  }
26
27
  return { fileId, fileUrl };
27
28
  }
29
+ function getFrontmatter(code, id) {
30
+ try {
31
+ return matter(code).data;
32
+ } catch (e) {
33
+ if (e.name === "YAMLException") {
34
+ const err = e;
35
+ err.id = id;
36
+ err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
37
+ err.message = e.reason;
38
+ throw err;
39
+ } else {
40
+ throw e;
41
+ }
42
+ }
43
+ }
28
44
  export {
29
- getFileInfo
45
+ getFileInfo,
46
+ getFrontmatter
30
47
  };
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.3.1",
4
+ "version": "0.4.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -28,21 +28,22 @@
28
28
  "@mdx-js/mdx": "^2.1.2",
29
29
  "@mdx-js/rollup": "^2.1.1",
30
30
  "es-module-lexer": "^0.10.5",
31
+ "gray-matter": "^4.0.3",
31
32
  "prismjs": "^1.28.0",
32
33
  "rehype-raw": "^6.1.1",
34
+ "remark-frontmatter": "^4.0.1",
33
35
  "remark-gfm": "^3.0.1",
36
+ "remark-mdx-frontmatter": "^2.0.2",
34
37
  "remark-shiki-twoslash": "^3.1.0",
35
38
  "remark-smartypants": "^2.0.0",
36
39
  "shiki": "^0.10.1",
37
- "unist-util-visit": "^4.1.0",
38
- "remark-frontmatter": "^4.0.1",
39
- "remark-mdx-frontmatter": "^2.0.2"
40
+ "unist-util-visit": "^4.1.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/chai": "^4.3.1",
43
44
  "@types/mocha": "^9.1.1",
44
45
  "@types/yargs-parser": "^21.0.0",
45
- "astro": "1.0.0-rc.2",
46
+ "astro": "1.0.0-rc.3",
46
47
  "astro-scripts": "0.0.6",
47
48
  "chai": "^4.3.6",
48
49
  "linkedom": "^0.14.12",
package/src/index.ts CHANGED
@@ -9,8 +9,9 @@ import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
9
9
  import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
10
10
  import remarkShikiTwoslash from 'remark-shiki-twoslash';
11
11
  import remarkSmartypants from 'remark-smartypants';
12
+ import type { Plugin as VitePlugin } from 'vite';
12
13
  import remarkPrism from './remark-prism.js';
13
- import { getFileInfo } from './utils.js';
14
+ import { getFileInfo, getFrontmatter } from './utils.js';
14
15
 
15
16
  type WithExtends<T> = T | { extends: T };
16
17
 
@@ -68,24 +69,47 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
68
69
  },
69
70
  ]);
70
71
 
72
+ const configuredMdxPlugin = mdxPlugin({
73
+ remarkPlugins,
74
+ rehypePlugins,
75
+ jsx: true,
76
+ jsxImportSource: 'astro',
77
+ // Note: disable `.md` support
78
+ format: 'mdx',
79
+ mdExtensions: [],
80
+ });
81
+
71
82
  updateConfig({
72
83
  vite: {
73
84
  plugins: [
74
85
  {
75
86
  enforce: 'pre',
76
- ...mdxPlugin({
77
- remarkPlugins,
78
- rehypePlugins,
79
- jsx: true,
80
- jsxImportSource: 'astro',
81
- // Note: disable `.md` support
82
- format: 'mdx',
83
- mdExtensions: [],
84
- }),
87
+ ...configuredMdxPlugin,
88
+ // Override transform to inject layouts before MDX compilation
89
+ async transform(this, code, id) {
90
+ if (!id.endsWith('.mdx')) return;
91
+
92
+ const mdxPluginTransform = configuredMdxPlugin.transform?.bind(this);
93
+ // If user overrides our default YAML parser,
94
+ // do not attempt to parse the `layout` via gray-matter
95
+ if (mdxOptions.frontmatterOptions?.parsers) {
96
+ return mdxPluginTransform?.(code, id);
97
+ }
98
+ const frontmatter = getFrontmatter(code, id);
99
+ if (frontmatter.layout) {
100
+ const { layout, ...content } = frontmatter;
101
+ code += `\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
102
+ frontmatter.layout
103
+ )})).default;\nreturn <Layout content={${JSON.stringify(
104
+ content
105
+ )}}>{children}</Layout> }`;
106
+ }
107
+ return mdxPluginTransform?.(code, id);
108
+ },
85
109
  },
86
110
  {
87
111
  name: '@astrojs/mdx',
88
- transform(code: string, id: string) {
112
+ transform(code, id) {
89
113
  if (!id.endsWith('.mdx')) return;
90
114
  const [, moduleExports] = parseESM(code);
91
115
 
@@ -113,7 +137,7 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
113
137
  return code;
114
138
  },
115
139
  },
116
- ],
140
+ ] as VitePlugin[],
117
141
  },
118
142
  });
119
143
  },
package/src/utils.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { AstroConfig } from 'astro';
1
+ import type { AstroConfig, SSRError } from 'astro';
2
+ import matter from 'gray-matter';
2
3
 
3
4
  function appendForwardSlash(path: string) {
4
5
  return path.endsWith('/') ? path : path + '/';
@@ -37,3 +38,23 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo {
37
38
  }
38
39
  return { fileId, fileUrl };
39
40
  }
41
+
42
+ /**
43
+ * Match YAML exception handling from Astro core errors
44
+ * @see 'astro/src/core/errors.ts'
45
+ */
46
+ export function getFrontmatter(code: string, id: string) {
47
+ try {
48
+ return matter(code).data;
49
+ } catch (e: any) {
50
+ if (e.name === 'YAMLException') {
51
+ const err: SSRError = e;
52
+ err.id = id;
53
+ err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
54
+ err.message = e.reason;
55
+ throw err;
56
+ } else {
57
+ throw e;
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ const { content = { title: "Didn't work" } } = Astro.props;
3
+ ---
4
+
5
+ <!DOCTYPE html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <title>{content.title}</title>
12
+ </head>
13
+ <body>
14
+ <h1>{content.title}</h1>
15
+ <p data-layout-rendered>Layout rendered!</p>
16
+ <slot />
17
+ </body>
18
+ </html>
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: 'Using YAML frontmatter'
3
+ layout: '../layouts/Base.astro'
3
4
  illThrowIfIDontExist: "Oh no, that's scary!"
4
5
  ---
5
6
 
6
- # {frontmatter.illThrowIfIDontExist}
7
+ {frontmatter.illThrowIfIDontExist}
@@ -1,6 +1,7 @@
1
1
  import mdx from '@astrojs/mdx';
2
2
 
3
3
  import { expect } from 'chai';
4
+ import { parseHTML } from 'linkedom';
4
5
  import { loadFixture } from '../../../astro/test/test-utils.js';
5
6
 
6
7
  const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter/', import.meta.url);
@@ -26,6 +27,36 @@ describe('MDX frontmatter', () => {
26
27
  expect(titles).to.include('Using YAML frontmatter');
27
28
  });
28
29
 
30
+ it('renders layout from "layout" frontmatter property', async () => {
31
+ const fixture = await loadFixture({
32
+ root: FIXTURE_ROOT,
33
+ integrations: [mdx()],
34
+ });
35
+ await fixture.build();
36
+
37
+ const html = await fixture.readFile('/index.html');
38
+ const { document } = parseHTML(html);
39
+
40
+ const layoutParagraph = document.querySelector('[data-layout-rendered]');
41
+
42
+ expect(layoutParagraph).to.not.be.null;
43
+ });
44
+
45
+ it('passes frontmatter to layout via "content" prop', async () => {
46
+ const fixture = await loadFixture({
47
+ root: FIXTURE_ROOT,
48
+ integrations: [mdx()],
49
+ });
50
+ await fixture.build();
51
+
52
+ const html = await fixture.readFile('/index.html');
53
+ const { document } = parseHTML(html);
54
+
55
+ const h1 = document.querySelector('h1');
56
+
57
+ expect(h1.textContent).to.equal('Using YAML frontmatter');
58
+ });
59
+
29
60
  it('extracts frontmatter to "customFrontmatter" export when configured', async () => {
30
61
  const fixture = await loadFixture({
31
62
  root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url),