@astrojs/mdx 0.6.0 → 0.8.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.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +57 -76
  4. package/dist/astro-data-utils.d.ts +9 -0
  5. package/dist/astro-data-utils.js +47 -0
  6. package/dist/index.d.ts +0 -7
  7. package/dist/index.js +47 -43
  8. package/dist/utils.d.ts +2 -3
  9. package/dist/utils.js +4 -4
  10. package/package.json +6 -6
  11. package/src/astro-data-utils.ts +59 -0
  12. package/src/index.ts +63 -59
  13. package/src/utils.ts +2 -2
  14. package/test/fixtures/mdx-escape/src/components/Em.astro +7 -0
  15. package/test/fixtures/mdx-escape/src/components/P.astro +1 -0
  16. package/test/fixtures/mdx-escape/src/components/Title.astro +1 -0
  17. package/test/fixtures/mdx-escape/src/pages/html-tag.mdx +5 -0
  18. package/test/fixtures/mdx-escape/src/pages/index.mdx +13 -0
  19. package/test/fixtures/mdx-frontmatter/src/pages/index.mdx +3 -0
  20. package/test/fixtures/mdx-frontmatter-injection/astro.config.mjs +12 -0
  21. package/test/fixtures/mdx-frontmatter-injection/node_modules/.bin/astro +17 -0
  22. package/test/fixtures/mdx-frontmatter-injection/package.json +12 -0
  23. package/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs +20 -0
  24. package/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js +6 -0
  25. package/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx +3 -0
  26. package/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx +19 -0
  27. package/test/fixtures/mdx-frontmatter-injection/src/pages/with-overrides.mdx +7 -0
  28. package/test/fixtures/mdx-plus-react/astro.config.mjs +6 -0
  29. package/test/fixtures/mdx-plus-react/node_modules/.bin/astro +17 -0
  30. package/test/fixtures/mdx-plus-react/package.json +8 -0
  31. package/test/fixtures/mdx-plus-react/src/components/Component.jsx +5 -0
  32. package/test/fixtures/mdx-plus-react/src/pages/index.astro +11 -0
  33. package/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js +2 -2
  34. package/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js +6 -0
  35. package/test/mdx-escape.test.js +32 -0
  36. package/test/mdx-frontmatter-injection.test.js +44 -0
  37. package/test/mdx-frontmatter.test.js +0 -17
  38. package/test/mdx-plus-react.test.js +25 -0
  39. package/test/mdx-rehype-plugins.test.js +6 -17
  40. package/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js +0 -9
  41. package/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx +0 -6
@@ -1,5 +1,5 @@
1
- @astrojs/mdx:build: cache hit, replaying output db9da5c9ed100942
2
- @astrojs/mdx:build: 
3
- @astrojs/mdx:build: > @astrojs/mdx@0.6.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 628f5c9b87587320
2
+ @astrojs/mdx:build: 
3
+ @astrojs/mdx:build: > @astrojs/mdx@0.8.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,36 @@
1
1
  # @astrojs/mdx
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`04ad44563`](https://github.com/withastro/astro/commit/04ad445632c67bdd60c1704e1e0dcbcaa27b9308)]:
8
+ - @astrojs/prism@1.0.0
9
+
10
+ ## 0.8.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#4204](https://github.com/withastro/astro/pull/4204) [`4c2ca5352`](https://github.com/withastro/astro/commit/4c2ca5352d0c4119ed2a9e5e0b78ce71eb1b414a) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Remove `frontmatterOptions` from MDX config
15
+
16
+ ### Patch Changes
17
+
18
+ - [#4205](https://github.com/withastro/astro/pull/4205) [`6c9736cbc`](https://github.com/withastro/astro/commit/6c9736cbc90162f1de3ebccd7cfe98332749b639) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add frontmatter injection instructions to README
19
+
20
+ ## 0.7.0
21
+
22
+ ### Minor Changes
23
+
24
+ - [#4176](https://github.com/withastro/astro/pull/4176) [`2675b8633`](https://github.com/withastro/astro/commit/2675b8633c5d5c45b237ec87940d5eaf1bfb1b4b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support frontmatter injection for MD and MDX using remark and rehype plugins
25
+
26
+ ### Patch Changes
27
+
28
+ - [#4181](https://github.com/withastro/astro/pull/4181) [`77cede720`](https://github.com/withastro/astro/commit/77cede720b09bce34f29c3d2d8b505311ce876b1) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Make collect-headings rehype plugin non-overridable
29
+
30
+ * [#4174](https://github.com/withastro/astro/pull/4174) [`8eb3a8c6d`](https://github.com/withastro/astro/commit/8eb3a8c6d9554707963c3a3bc36ed8b68d3cf0fb) Thanks [@matthewp](https://github.com/matthewp)! - Allows using React with automatic imports alongside MDX
31
+
32
+ - [#4145](https://github.com/withastro/astro/pull/4145) [`c7efcf57e`](https://github.com/withastro/astro/commit/c7efcf57e00a0fcde3bc9f813e3cc59902bd484c) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Fix a missing newline bug when `layout` was set.
33
+
3
34
  ## 0.6.0
4
35
 
5
36
  ### Minor Changes
package/README.md CHANGED
@@ -20,33 +20,30 @@ Check out [“What is MDX?”](https://mdxjs.com/docs/what-is-mdx/), a deep-dive
20
20
 
21
21
  ## Installation
22
22
 
23
- <details>
24
- <summary>Quick Install</summary>
23
+ ### Quick Install
25
24
 
26
25
  The `astro add` command-line tool automates the installation for you. Run one of the following commands in a new terminal window. (If you aren't sure which package manager you're using, run the first command.) Then, follow the prompts, and type "y" in the terminal (meaning "yes") for each one.
27
26
 
28
- ```sh
29
- # Using NPM
30
- npx astro add mdx
31
- # Using Yarn
32
- yarn astro add mdx
33
- # Using PNPM
34
- pnpx astro add mdx
35
- ```
27
+ ```sh
28
+ # Using NPM
29
+ npx astro add mdx
30
+ # Using Yarn
31
+ yarn astro add mdx
32
+ # Using PNPM
33
+ pnpx astro add mdx
34
+ ```
36
35
 
37
36
  Then, restart the dev server by typing `CTRL-C` and then `npm run astro dev` in the terminal window that was running Astro.
38
37
 
39
38
  Because this command is new, it might not properly set things up. If that happens, [feel free to log an issue on our GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
40
- </details>
41
39
 
42
- <details>
43
- <summary>Manual Install</summary>
40
+ ### Manual Install
44
41
 
45
42
  First, install the `@astrojs/mdx` package using your package manager. If you're using npm or aren't sure, run this in the terminal:
46
43
 
47
- ```sh
48
- npm install @astrojs/mdx
49
- ```
44
+ ```sh
45
+ npm install @astrojs/mdx
46
+ ```
50
47
 
51
48
  Then, apply this integration to your `astro.config.*` file using the `integrations` property:
52
49
 
@@ -63,11 +60,14 @@ export default defineConfig({
63
60
  ```
64
61
 
65
62
  Finally, restart the dev server.
66
- </details>
67
63
 
68
64
  ## Usage
69
65
 
70
- To write your first MDX page in Astro, head to our [UI framework documentation][astro-ui-frameworks]. You'll explore:
66
+ You can [add MDX pages to your project](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages) by adding `.mdx` files within your `src/pages/` directory.
67
+
68
+ ### Components
69
+
70
+ To use components in your MDX pages in Astro, head to our [UI framework documentation][astro-ui-frameworks]. You'll explore:
71
71
  - 📦 how framework components are loaded,
72
72
  - 💧 client-side hydration options, and
73
73
  - 🪆 opportunities to mix and nest frameworks together
@@ -76,8 +76,6 @@ To write your first MDX page in Astro, head to our [UI framework documentation][
76
76
 
77
77
  > **Note**: `.mdx` files adhere to strict JSX syntax rather than Astro's HTML-like syntax.
78
78
 
79
- Also check our [Astro Integration Documentation][astro-integration] for more on integrations.
80
-
81
79
  ### Variables
82
80
 
83
81
  MDX supports `export` statements to add variables to your templates. These variables are accessible both from the template itself _and_ as named properties when importing the template somewhere else.
@@ -123,7 +121,7 @@ A function that returns an array of all headings (i.e. `h1 -> h6` elements) in t
123
121
 
124
122
  ### Frontmatter
125
123
 
126
- Astro also supports YAML-based frontmatter out-of-the-box using the [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) plugin. By default, all variables declared in a frontmatter fence (`---`) will be accessible via the `frontmatter` export. See the `frontmatterOptions` configuration to customize this behavior.
124
+ Astro also supports YAML-based frontmatter out-of-the-box. By default, all variables declared in a frontmatter fence (`---`) will be accessible via the `frontmatter` export.
127
125
 
128
126
  For example, we can add a `title` and `publishDate` to an MDX page or component like so:
129
127
 
@@ -152,6 +150,40 @@ const posts = await Astro.glob('./*.mdx');
152
150
  ))}
153
151
  ```
154
152
 
153
+ ### Inject frontmatter via remark or rehype plugins
154
+
155
+ You may want to inject frontmatter properties across all of your MDX files. By using a [remark](#remarkPlugins) or [rehype](#remarkplugins) plugin, you can generate these properties based on a file’s contents.
156
+
157
+ You can append to the `data.astro.frontmatter` property from your plugin’s `file` argument like so:
158
+
159
+ ```js
160
+ // example-remark-plugin.mjs
161
+ export function exampleRemarkPlugin() {
162
+ // All remark and rehype plugins return a separate function
163
+ return function (tree, file) {
164
+ file.data.astro.frontmatter.customProperty = 'Generated property';
165
+ }
166
+ }
167
+ ```
168
+
169
+ After applying this plugin to your MDX integration config:
170
+
171
+ ```js
172
+ // astro.config.mjs
173
+ import mdx from '@astrojs/mdx';
174
+ import { exampleRemarkPlugin } from './example-remark-plugin.mjs';
175
+
176
+ export default {
177
+ integrations: [
178
+ mdx({
179
+ remarkPlugins: [exampleRemarkPlugin],
180
+ }),
181
+ ],
182
+ };
183
+ ```
184
+
185
+ …every MDX file will have `customProperty` in its frontmatter! See [our Markdown documentation](https://docs.astro.build/en/guides/markdown-content/#injecting-frontmatter) for more usage instructions and a [reading time plugin example](https://docs.astro.build/en/guides/markdown-content/#example-calculate-reading-time).
186
+
155
187
  ### Layouts
156
188
 
157
189
  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:
@@ -255,8 +287,7 @@ This applies a minimal Prism renderer with added support for `astro` code blocks
255
287
 
256
288
  ## Configuration
257
289
 
258
- <details>
259
- <summary><strong>remarkPlugins</strong></summary>
290
+ ### remarkPlugins
260
291
 
261
292
  **Default plugins:** [remark-gfm](https://github.com/remarkjs/remark-gfm), [remark-smartypants](https://github.com/silvenon/remark-smartypants)
262
293
 
@@ -292,31 +323,13 @@ export default {
292
323
  }
293
324
  ```
294
325
 
295
- </details>
296
-
297
- <details>
298
- <summary><strong>rehypePlugins</strong></summary>
299
-
300
- **Default plugins:** [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts)
326
+ ### rehypePlugins
301
327
 
302
328
  [Rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) allow you to transform the HTML that your Markdown generates. We recommend checking the [Remark plugin](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) catalog first _before_ considering rehype plugins, since most users want to transform their Markdown syntax instead. If HTML transforms are what you need, we encourage you to browse [awesome-rehype](https://github.com/rehypejs/awesome-rehype) for a full curated list of plugins!
303
329
 
304
- We apply our own [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin by default. This applies IDs to all headings (i.e. `h1 -> h6`) in your MDX files to [link to headings via anchor tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page).
330
+ We apply our own (non-overridable) [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin. This applies IDs to all headings (i.e. `h1 -> h6`) in your MDX files to [link to headings via anchor tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page).
305
331
 
306
- To apply rehype plugins _while preserving_ Astro's default plugins, use a nested `extends` object like so:
307
-
308
- ```js
309
- // astro.config.mjs
310
- import rehypeMinifyHtml from 'rehype-minify';
311
-
312
- export default {
313
- integrations: [mdx({
314
- rehypePlugins: { extends: [rehypeMinifyHtml] },
315
- })],
316
- }
317
- ```
318
-
319
- To apply plugins _without_ Astro's defaults, you can apply a plain array:
332
+ To apply additional rehype plugins, pass an array to the `rehypePlugins` option like so:
320
333
 
321
334
  ```js
322
335
  // astro.config.mjs
@@ -328,38 +341,6 @@ export default {
328
341
  })],
329
342
  }
330
343
  ```
331
- </details>
332
-
333
- <details>
334
- <summary><strong>frontmatterOptions</strong></summary>
335
-
336
- **Default:** `{ name: 'frontmatter' }`
337
-
338
- We use [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) to parse YAML-based frontmatter in your MDX files. If you want to override our default configuration or extend remark-mdx-frontmatter (ex. to [apply a custom frontmatter parser](https://github.com/remcohaszing/remark-mdx-frontmatter#parsers)), you can supply a `frontmatterOptions` configuration.
339
-
340
- For example, say you want to access frontmatter as root-level variables without a nested `frontmatter` object. You can override the [`name` configuration option](https://github.com/remcohaszing/remark-mdx-frontmatter#name) like so:
341
-
342
- ```js
343
- // astro.config.mjs
344
- export default {
345
- integrations: [mdx({
346
- frontmatterOptions: {
347
- name: '',
348
- }
349
- })],
350
- }
351
- ```
352
-
353
- ```mdx
354
- ---
355
- title: I'm just a variable now!
356
- ---
357
-
358
- # {title}
359
- ```
360
-
361
- See the [remark-mdx-frontmatter README](https://github.com/remcohaszing/remark-mdx-frontmatter#options) for a complete list of options.
362
- </details>
363
344
 
364
345
  ## Examples
365
346
 
@@ -0,0 +1,9 @@
1
+ import type { MarkdownAstroData } from 'astro';
2
+ import type { Data, VFile } from 'vfile';
3
+ export declare function remarkInitializeAstroData(): (tree: any, vfile: VFile) => void;
4
+ export declare function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any>): (tree: any, vfile: VFile) => void;
5
+ /**
6
+ * Copied from markdown utils
7
+ * @see "vite-plugin-utils"
8
+ */
9
+ export declare function safelyGetAstroData(vfileData: Data): MarkdownAstroData;
@@ -0,0 +1,47 @@
1
+ import { jsToTreeNode } from "./utils.js";
2
+ function remarkInitializeAstroData() {
3
+ return function(tree, vfile) {
4
+ if (!vfile.data.astro) {
5
+ vfile.data.astro = { frontmatter: {} };
6
+ }
7
+ };
8
+ }
9
+ const EXPORT_NAME = "frontmatter";
10
+ function rehypeApplyFrontmatterExport(pageFrontmatter) {
11
+ return function(tree, vfile) {
12
+ const { frontmatter: injectedFrontmatter } = safelyGetAstroData(vfile.data);
13
+ const frontmatter = { ...injectedFrontmatter, ...pageFrontmatter };
14
+ const exportNodes = [
15
+ jsToTreeNode(`export const ${EXPORT_NAME} = ${JSON.stringify(frontmatter)};`)
16
+ ];
17
+ tree.children = exportNodes.concat(tree.children);
18
+ };
19
+ }
20
+ function isValidAstroData(obj) {
21
+ if (typeof obj === "object" && obj !== null && obj.hasOwnProperty("frontmatter")) {
22
+ const { frontmatter } = obj;
23
+ try {
24
+ JSON.stringify(frontmatter);
25
+ } catch {
26
+ return false;
27
+ }
28
+ return typeof frontmatter === "object" && frontmatter !== null;
29
+ }
30
+ return false;
31
+ }
32
+ function safelyGetAstroData(vfileData) {
33
+ const { astro } = vfileData;
34
+ if (!astro)
35
+ return { frontmatter: {} };
36
+ if (!isValidAstroData(astro)) {
37
+ throw Error(
38
+ `[MDX] A remark or rehype plugin tried to add invalid frontmatter. Ensure "astro.frontmatter" is a JSON object!`
39
+ );
40
+ }
41
+ return astro;
42
+ }
43
+ export {
44
+ rehypeApplyFrontmatterExport,
45
+ remarkInitializeAstroData,
46
+ safelyGetAstroData
47
+ };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,11 @@
1
1
  import { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
2
2
  import type { AstroIntegration } from 'astro';
3
- import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
4
3
  declare type WithExtends<T> = T | {
5
4
  extends: T;
6
5
  };
7
6
  declare type MdxOptions = {
8
7
  remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>;
9
8
  rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>;
10
- /**
11
- * Configure the remark-mdx-frontmatter plugin
12
- * @see https://github.com/remcohaszing/remark-mdx-frontmatter#options for a full list of options
13
- * @default {{ name: 'frontmatter' }}
14
- */
15
- frontmatterOptions?: RemarkMdxFrontmatterOptions;
16
9
  };
17
10
  export default function mdx(mdxOptions?: MdxOptions): AstroIntegration;
18
11
  export {};
package/dist/index.js CHANGED
@@ -2,52 +2,55 @@ import { compile as mdxCompile, nodeTypes } from "@mdx-js/mdx";
2
2
  import mdxPlugin from "@mdx-js/rollup";
3
3
  import { parse as parseESM } from "es-module-lexer";
4
4
  import rehypeRaw from "rehype-raw";
5
- import remarkFrontmatter from "remark-frontmatter";
6
5
  import remarkGfm from "remark-gfm";
7
- import remarkMdxFrontmatter from "remark-mdx-frontmatter";
8
6
  import remarkShikiTwoslash from "remark-shiki-twoslash";
9
7
  import remarkSmartypants from "remark-smartypants";
10
8
  import { VFile } from "vfile";
9
+ import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from "./astro-data-utils.js";
11
10
  import rehypeCollectHeadings from "./rehype-collect-headings.js";
12
11
  import remarkPrism from "./remark-prism.js";
13
- import { getFileInfo, getFrontmatter } from "./utils.js";
14
- const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
15
- const DEFAULT_REHYPE_PLUGINS = [rehypeCollectHeadings];
12
+ import { getFileInfo, parseFrontmatter } from "./utils.js";
13
+ const DEFAULT_REMARK_PLUGINS = [
14
+ remarkGfm,
15
+ remarkSmartypants
16
+ ];
17
+ const DEFAULT_REHYPE_PLUGINS = [];
16
18
  function handleExtends(config, defaults = []) {
17
19
  if (Array.isArray(config))
18
20
  return config;
19
21
  return [...defaults, ...(config == null ? void 0 : config.extends) ?? []];
20
22
  }
23
+ function getRemarkPlugins(mdxOptions, config) {
24
+ let remarkPlugins = [
25
+ remarkInitializeAstroData,
26
+ ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS)
27
+ ];
28
+ if (config.markdown.syntaxHighlight === "shiki") {
29
+ const shikiTwoslash = remarkShikiTwoslash.default ?? remarkShikiTwoslash;
30
+ remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
31
+ }
32
+ if (config.markdown.syntaxHighlight === "prism") {
33
+ remarkPlugins.push(remarkPrism);
34
+ }
35
+ return remarkPlugins;
36
+ }
37
+ function getRehypePlugins(mdxOptions, config) {
38
+ let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
39
+ if (config.markdown.syntaxHighlight === "shiki" || config.markdown.syntaxHighlight === "prism") {
40
+ rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
41
+ }
42
+ rehypePlugins.push(rehypeCollectHeadings);
43
+ return rehypePlugins;
44
+ }
21
45
  function mdx(mdxOptions = {}) {
22
46
  return {
23
47
  name: "@astrojs/mdx",
24
48
  hooks: {
25
49
  "astro:config:setup": ({ updateConfig, config, addPageExtension, command }) => {
26
50
  addPageExtension(".mdx");
27
- let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
28
- let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
29
- if (config.markdown.syntaxHighlight === "shiki") {
30
- remarkPlugins.push([
31
- remarkShikiTwoslash.default ?? remarkShikiTwoslash,
32
- config.markdown.shikiConfig
33
- ]);
34
- rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
35
- }
36
- if (config.markdown.syntaxHighlight === "prism") {
37
- remarkPlugins.push(remarkPrism);
38
- rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
39
- }
40
- remarkPlugins.push(remarkFrontmatter);
41
- remarkPlugins.push([
42
- remarkMdxFrontmatter,
43
- {
44
- name: "frontmatter",
45
- ...mdxOptions.frontmatterOptions
46
- }
47
- ]);
48
51
  const mdxPluginOpts = {
49
- remarkPlugins,
50
- rehypePlugins,
52
+ remarkPlugins: getRemarkPlugins(mdxOptions, config),
53
+ rehypePlugins: getRehypePlugins(mdxOptions, config),
51
54
  jsx: true,
52
55
  jsxImportSource: "astro",
53
56
  format: "mdx",
@@ -60,28 +63,29 @@ function mdx(mdxOptions = {}) {
60
63
  enforce: "pre",
61
64
  ...mdxPlugin(mdxPluginOpts),
62
65
  async transform(code, id) {
63
- var _a;
64
66
  if (!id.endsWith("mdx"))
65
67
  return;
66
- if (!((_a = mdxOptions.frontmatterOptions) == null ? void 0 : _a.parsers)) {
67
- const frontmatter = getFrontmatter(code, id);
68
- if (frontmatter.layout) {
69
- const { layout, ...content } = frontmatter;
70
- code += `
68
+ let { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
69
+ if (frontmatter.layout) {
70
+ const { layout, ...contentProp } = frontmatter;
71
+ pageContent += `
72
+
71
73
  export default async function({ children }) {
72
74
  const Layout = (await import(${JSON.stringify(
73
- frontmatter.layout
74
- )})).default;
75
+ frontmatter.layout
76
+ )})).default;
75
77
  const frontmatter=${JSON.stringify(
76
- content
77
- )};
78
+ contentProp
79
+ )};
78
80
  return <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
79
- }
80
81
  }
81
- const compiled = await mdxCompile(
82
- new VFile({ value: code, path: id }),
83
- mdxPluginOpts
84
- );
82
+ const compiled = await mdxCompile(new VFile({ value: pageContent, path: id }), {
83
+ ...mdxPluginOpts,
84
+ rehypePlugins: [
85
+ ...mdxPluginOpts.rehypePlugins ?? [],
86
+ () => rehypeApplyFrontmatterExport(frontmatter)
87
+ ]
88
+ });
85
89
  return {
86
90
  code: String(compiled.value),
87
91
  map: compiled.map
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Options as AcornOpts } from 'acorn';
2
2
  import type { AstroConfig } from 'astro';
3
3
  import type { MdxjsEsm } from 'mdast-util-mdx';
4
+ import matter from 'gray-matter';
4
5
  interface FileInfo {
5
6
  fileId: string;
6
7
  fileUrl: string;
@@ -11,8 +12,6 @@ export declare function getFileInfo(id: string, config: AstroConfig): FileInfo;
11
12
  * Match YAML exception handling from Astro core errors
12
13
  * @see 'astro/src/core/errors.ts'
13
14
  */
14
- export declare function getFrontmatter(code: string, id: string): {
15
- [key: string]: any;
16
- };
15
+ export declare function parseFrontmatter(code: string, id: string): matter.GrayMatterFile<string>;
17
16
  export declare function jsToTreeNode(jsString: string, acornOpts?: AcornOpts): MdxjsEsm;
18
17
  export {};
package/dist/utils.js CHANGED
@@ -27,9 +27,9 @@ function getFileInfo(id, config) {
27
27
  }
28
28
  return { fileId, fileUrl };
29
29
  }
30
- function getFrontmatter(code, id) {
30
+ function parseFrontmatter(code, id) {
31
31
  try {
32
- return matter(code).data;
32
+ return matter(code);
33
33
  } catch (e) {
34
34
  if (e.name === "YAMLException") {
35
35
  const err = e;
@@ -61,6 +61,6 @@ function jsToTreeNode(jsString, acornOpts = {
61
61
  }
62
62
  export {
63
63
  getFileInfo,
64
- getFrontmatter,
65
- jsToTreeNode
64
+ jsToTreeNode,
65
+ parseFrontmatter
66
66
  };
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.6.0",
4
+ "version": "0.8.1",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -23,7 +23,7 @@
23
23
  "./package.json": "./package.json"
24
24
  },
25
25
  "dependencies": {
26
- "@astrojs/prism": "^0.7.0",
26
+ "@astrojs/prism": "^1.0.0",
27
27
  "@mdx-js/mdx": "^2.1.2",
28
28
  "@mdx-js/rollup": "^2.1.1",
29
29
  "acorn": "^8.8.0",
@@ -33,18 +33,18 @@
33
33
  "rehype-raw": "^6.1.1",
34
34
  "remark-frontmatter": "^4.0.1",
35
35
  "remark-gfm": "^3.0.1",
36
- "remark-mdx-frontmatter": "^2.0.2",
37
36
  "remark-shiki-twoslash": "^3.1.0",
38
37
  "remark-smartypants": "^2.0.0",
39
38
  "shiki": "^0.10.1",
40
- "unist-util-visit": "^4.1.0"
39
+ "unist-util-visit": "^4.1.0",
40
+ "vfile": "^5.3.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/chai": "^4.3.1",
44
44
  "@types/mocha": "^9.1.1",
45
45
  "@types/yargs-parser": "^21.0.0",
46
- "astro": "1.0.0-rc.5",
47
- "astro-scripts": "0.0.6",
46
+ "astro": "1.0.0",
47
+ "astro-scripts": "0.0.7",
48
48
  "chai": "^4.3.6",
49
49
  "linkedom": "^0.14.12",
50
50
  "mdast-util-to-string": "^3.1.0",
@@ -0,0 +1,59 @@
1
+ import type { MarkdownAstroData } from 'astro';
2
+ import type { Data, VFile } from 'vfile';
3
+ import { jsToTreeNode } from './utils.js';
4
+
5
+ export function remarkInitializeAstroData() {
6
+ return function (tree: any, vfile: VFile) {
7
+ if (!vfile.data.astro) {
8
+ vfile.data.astro = { frontmatter: {} };
9
+ }
10
+ };
11
+ }
12
+
13
+ const EXPORT_NAME = 'frontmatter';
14
+
15
+ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any>) {
16
+ return function (tree: any, vfile: VFile) {
17
+ const { frontmatter: injectedFrontmatter } = safelyGetAstroData(vfile.data);
18
+ const frontmatter = { ...injectedFrontmatter, ...pageFrontmatter };
19
+ const exportNodes = [
20
+ jsToTreeNode(`export const ${EXPORT_NAME} = ${JSON.stringify(frontmatter)};`),
21
+ ];
22
+ tree.children = exportNodes.concat(tree.children);
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Copied from markdown utils
28
+ * @see "vite-plugin-utils"
29
+ */
30
+ function isValidAstroData(obj: unknown): obj is MarkdownAstroData {
31
+ if (typeof obj === 'object' && obj !== null && obj.hasOwnProperty('frontmatter')) {
32
+ const { frontmatter } = obj as any;
33
+ try {
34
+ // ensure frontmatter is JSON-serializable
35
+ JSON.stringify(frontmatter);
36
+ } catch {
37
+ return false;
38
+ }
39
+ return typeof frontmatter === 'object' && frontmatter !== null;
40
+ }
41
+ return false;
42
+ }
43
+
44
+ /**
45
+ * Copied from markdown utils
46
+ * @see "vite-plugin-utils"
47
+ */
48
+ export function safelyGetAstroData(vfileData: Data): MarkdownAstroData {
49
+ const { astro } = vfileData;
50
+
51
+ if (!astro) return { frontmatter: {} };
52
+ if (!isValidAstroData(astro)) {
53
+ throw Error(
54
+ `[MDX] A remark or rehype plugin tried to add invalid frontmatter. Ensure "astro.frontmatter" is a JSON object!`
55
+ );
56
+ }
57
+
58
+ return astro;
59
+ }
package/src/index.ts CHANGED
@@ -1,35 +1,30 @@
1
1
  import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx';
2
2
  import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
3
- import type { AstroIntegration } from 'astro';
3
+ import type { AstroConfig, AstroIntegration } from 'astro';
4
4
  import { parse as parseESM } from 'es-module-lexer';
5
5
  import rehypeRaw from 'rehype-raw';
6
- import remarkFrontmatter from 'remark-frontmatter';
7
6
  import remarkGfm from 'remark-gfm';
8
- import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
9
- import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
10
7
  import remarkShikiTwoslash from 'remark-shiki-twoslash';
11
8
  import remarkSmartypants from 'remark-smartypants';
12
9
  import { VFile } from 'vfile';
13
10
  import type { Plugin as VitePlugin } from 'vite';
11
+ import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from './astro-data-utils.js';
14
12
  import rehypeCollectHeadings from './rehype-collect-headings.js';
15
13
  import remarkPrism from './remark-prism.js';
16
- import { getFileInfo, getFrontmatter } from './utils.js';
14
+ import { getFileInfo, parseFrontmatter } from './utils.js';
17
15
 
18
16
  type WithExtends<T> = T | { extends: T };
19
17
 
20
18
  type MdxOptions = {
21
19
  remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>;
22
20
  rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>;
23
- /**
24
- * Configure the remark-mdx-frontmatter plugin
25
- * @see https://github.com/remcohaszing/remark-mdx-frontmatter#options for a full list of options
26
- * @default {{ name: 'frontmatter' }}
27
- */
28
- frontmatterOptions?: RemarkMdxFrontmatterOptions;
29
21
  };
30
22
 
31
- const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
32
- const DEFAULT_REHYPE_PLUGINS = [rehypeCollectHeadings];
23
+ const DEFAULT_REMARK_PLUGINS: MdxRollupPluginOptions['remarkPlugins'] = [
24
+ remarkGfm,
25
+ remarkSmartypants,
26
+ ];
27
+ const DEFAULT_REHYPE_PLUGINS: MdxRollupPluginOptions['rehypePlugins'] = [];
33
28
 
34
29
  function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] {
35
30
  if (Array.isArray(config)) return config;
@@ -37,44 +32,54 @@ function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] =
37
32
  return [...defaults, ...(config?.extends ?? [])];
38
33
  }
39
34
 
35
+ function getRemarkPlugins(
36
+ mdxOptions: MdxOptions,
37
+ config: AstroConfig
38
+ ): MdxRollupPluginOptions['remarkPlugins'] {
39
+ let remarkPlugins = [
40
+ // Initialize vfile.data.astroExports before all plugins are run
41
+ remarkInitializeAstroData,
42
+ ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
43
+ ];
44
+ if (config.markdown.syntaxHighlight === 'shiki') {
45
+ // Default export still requires ".default" chaining for some reason
46
+ // Workarounds tried:
47
+ // - "import * as remarkShikiTwoslash"
48
+ // - "import { default as remarkShikiTwoslash }"
49
+ const shikiTwoslash = (remarkShikiTwoslash as any).default ?? remarkShikiTwoslash;
50
+ remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
51
+ }
52
+ if (config.markdown.syntaxHighlight === 'prism') {
53
+ remarkPlugins.push(remarkPrism);
54
+ }
55
+ return remarkPlugins;
56
+ }
57
+
58
+ function getRehypePlugins(
59
+ mdxOptions: MdxOptions,
60
+ config: AstroConfig
61
+ ): MdxRollupPluginOptions['rehypePlugins'] {
62
+ let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
63
+
64
+ if (config.markdown.syntaxHighlight === 'shiki' || config.markdown.syntaxHighlight === 'prism') {
65
+ rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
66
+ }
67
+ // getHeadings() is guaranteed by TS, so we can't allow user to override
68
+ rehypePlugins.push(rehypeCollectHeadings);
69
+
70
+ return rehypePlugins;
71
+ }
72
+
40
73
  export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
41
74
  return {
42
75
  name: '@astrojs/mdx',
43
76
  hooks: {
44
77
  'astro:config:setup': ({ updateConfig, config, addPageExtension, command }: any) => {
45
78
  addPageExtension('.mdx');
46
- let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
47
- let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
48
-
49
- if (config.markdown.syntaxHighlight === 'shiki') {
50
- remarkPlugins.push([
51
- // Default export still requires ".default" chaining for some reason
52
- // Workarounds tried:
53
- // - "import * as remarkShikiTwoslash"
54
- // - "import { default as remarkShikiTwoslash }"
55
- (remarkShikiTwoslash as any).default ?? remarkShikiTwoslash,
56
- config.markdown.shikiConfig,
57
- ]);
58
- rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
59
- }
60
-
61
- if (config.markdown.syntaxHighlight === 'prism') {
62
- remarkPlugins.push(remarkPrism);
63
- rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
64
- }
65
-
66
- remarkPlugins.push(remarkFrontmatter);
67
- remarkPlugins.push([
68
- remarkMdxFrontmatter,
69
- {
70
- name: 'frontmatter',
71
- ...mdxOptions.frontmatterOptions,
72
- },
73
- ]);
74
79
 
75
80
  const mdxPluginOpts: MdxRollupPluginOptions = {
76
- remarkPlugins,
77
- rehypePlugins,
81
+ remarkPlugins: getRemarkPlugins(mdxOptions, config),
82
+ rehypePlugins: getRehypePlugins(mdxOptions, config),
78
83
  jsx: true,
79
84
  jsxImportSource: 'astro',
80
85
  // Note: disable `.md` support
@@ -93,24 +98,23 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
93
98
  async transform(code, id) {
94
99
  if (!id.endsWith('mdx')) return;
95
100
 
96
- // If user overrides our default YAML parser,
97
- // do not attempt to parse the `layout` via gray-matter
98
- if (!mdxOptions.frontmatterOptions?.parsers) {
99
- const frontmatter = getFrontmatter(code, id);
100
- if (frontmatter.layout) {
101
- const { layout, ...content } = frontmatter;
102
- code += `\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
103
- frontmatter.layout
104
- )})).default;\nconst frontmatter=${JSON.stringify(
105
- content
106
- )};\nreturn <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
107
- }
101
+ let { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
102
+ if (frontmatter.layout) {
103
+ const { layout, ...contentProp } = frontmatter;
104
+ pageContent += `\n\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
105
+ frontmatter.layout
106
+ )})).default;\nconst frontmatter=${JSON.stringify(
107
+ contentProp
108
+ )};\nreturn <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
108
109
  }
109
110
 
110
- const compiled = await mdxCompile(
111
- new VFile({ value: code, path: id }),
112
- mdxPluginOpts
113
- );
111
+ const compiled = await mdxCompile(new VFile({ value: pageContent, path: id }), {
112
+ ...mdxPluginOpts,
113
+ rehypePlugins: [
114
+ ...(mdxPluginOpts.rehypePlugins ?? []),
115
+ () => rehypeApplyFrontmatterExport(frontmatter),
116
+ ],
117
+ });
114
118
 
115
119
  return {
116
120
  code: String(compiled.value),
package/src/utils.ts CHANGED
@@ -47,9 +47,9 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo {
47
47
  * Match YAML exception handling from Astro core errors
48
48
  * @see 'astro/src/core/errors.ts'
49
49
  */
50
- export function getFrontmatter(code: string, id: string) {
50
+ export function parseFrontmatter(code: string, id: string) {
51
51
  try {
52
- return matter(code).data;
52
+ return matter(code);
53
53
  } catch (e: any) {
54
54
  if (e.name === 'YAMLException') {
55
55
  const err: SSRError = e;
@@ -0,0 +1,7 @@
1
+ <em><slot/></em>
2
+
3
+ <style>
4
+ em {
5
+ color: red;
6
+ }
7
+ </style>
@@ -0,0 +1 @@
1
+ <p><slot /></p>
@@ -0,0 +1 @@
1
+ <h1><slot/></h1>
@@ -0,0 +1,5 @@
1
+ import P from '../components/P.astro';
2
+ import Em from '../components/Em.astro';
3
+
4
+ <P>Render <Em>Me</Em></P>
5
+ <P><Em>Me</Em></P>
@@ -0,0 +1,13 @@
1
+ import P from '../components/P.astro';
2
+ import Em from '../components/Em.astro';
3
+ import Title from '../components/Title.astro';
4
+
5
+ export const components = { p: P, em: Em, h1: Title };
6
+
7
+ # Hello _there_
8
+
9
+ # _there_
10
+
11
+ Hello _there_
12
+
13
+ _there_
@@ -5,3 +5,6 @@ illThrowIfIDontExist: "Oh no, that's scary!"
5
5
  ---
6
6
 
7
7
  {frontmatter.illThrowIfIDontExist}
8
+
9
+ > Note: newline intentionally missing from the end of this file.
10
+ > Useful since that can be the source of bugs in our compile step.
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'astro/config';
2
+ import mdx from '@astrojs/mdx';
3
+ import { rehypeReadingTime, remarkTitle } from './src/markdown-plugins.mjs';
4
+
5
+ // https://astro.build/config
6
+ export default defineConfig({
7
+ site: 'https://astro.build/',
8
+ integrations: [mdx({
9
+ remarkPlugins: [remarkTitle],
10
+ rehypePlugins: [rehypeReadingTime],
11
+ })],
12
+ });
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="$NODE_PATH:/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../../../../astro/astro.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../../../../astro/astro.js" "$@"
17
+ fi
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@test/mdx-frontmatter-injection",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "astro": "workspace:*",
7
+ "@astrojs/mdx": "workspace:*",
8
+ "mdast-util-to-string": "^3.1.0",
9
+ "reading-time": "^1.5.0",
10
+ "unist-util-visit": "^4.1.0"
11
+ }
12
+ }
@@ -0,0 +1,20 @@
1
+ import getReadingTime from 'reading-time';
2
+ import { toString } from 'mdast-util-to-string';
3
+ import { visit } from 'unist-util-visit';
4
+
5
+ export function rehypeReadingTime() {
6
+ return function (tree, { data }) {
7
+ const readingTime = getReadingTime(toString(tree));
8
+ data.astro.frontmatter.injectedReadingTime = readingTime;
9
+ };
10
+ }
11
+
12
+ export function remarkTitle() {
13
+ return function (tree, { data }) {
14
+ visit(tree, ['heading'], (node) => {
15
+ if (node.depth === 1) {
16
+ data.astro.frontmatter.title = toString(node.children);
17
+ }
18
+ });
19
+ };
20
+ }
@@ -0,0 +1,6 @@
1
+ export async function get() {
2
+ const docs = await import.meta.glob('./*.mdx', { eager: true });
3
+ return {
4
+ body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)),
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ # Page 1
2
+
3
+ Look at that!
@@ -0,0 +1,19 @@
1
+ # Page 2
2
+
3
+ ## Table of contents
4
+
5
+ ## Section 1
6
+
7
+ Some text!
8
+
9
+ ### Subsection 1
10
+
11
+ Some subsection test!
12
+
13
+ ### Subsection 2
14
+
15
+ Oh cool, more text!
16
+
17
+ ## Section 2
18
+
19
+ More content
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: 'Overridden title'
3
+ injectedReadingTime:
4
+ text: '1000 min read'
5
+ ---
6
+
7
+ # Working!
@@ -0,0 +1,6 @@
1
+ import mdx from '@astrojs/mdx';
2
+ import react from '@astrojs/react';
3
+
4
+ export default {
5
+ integrations: [react(), mdx()]
6
+ }
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="$NODE_PATH:/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../../../../astro/astro.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../../../../astro/astro.js" "$@"
17
+ fi
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@test/mdx-plus-react",
3
+ "dependencies": {
4
+ "astro": "workspace:*",
5
+ "@astrojs/mdx": "workspace:*",
6
+ "@astrojs/react": "workspace:*"
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ const Component = () => {
2
+ return <p>Hello world</p>;
3
+ };
4
+
5
+ export default Component;
@@ -0,0 +1,11 @@
1
+ ---
2
+ import Component from "../components/Component.jsx";
3
+ ---
4
+ <html>
5
+ <head>
6
+ <title>Testing</title>
7
+ </head>
8
+ <body>
9
+ <Component />
10
+ </body>
11
+ </html>
@@ -1,7 +1,7 @@
1
- import { readingTime } from './space-ipsum.mdx';
1
+ import * as exps from './space-ipsum.mdx';
2
2
 
3
3
  export function get() {
4
4
  return {
5
- body: JSON.stringify(readingTime),
5
+ body: JSON.stringify(exps),
6
6
  }
7
7
  }
@@ -0,0 +1,6 @@
1
+ export async function get() {
2
+ const docs = await import.meta.glob('./*.mdx', { eager: true });
3
+ return {
4
+ body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)),
5
+ }
6
+ }
@@ -0,0 +1,32 @@
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
+ const FIXTURE_ROOT = new URL('./fixtures/mdx-escape/', import.meta.url);
8
+
9
+ describe('MDX frontmatter', () => {
10
+ let fixture;
11
+ before(async () => {
12
+ fixture = await loadFixture({
13
+ root: FIXTURE_ROOT,
14
+ integrations: [mdx()],
15
+ });
16
+ await fixture.build();
17
+ });
18
+
19
+ it('does not have unescaped HTML at top-level', async () => {
20
+ const html = await fixture.readFile('/index.html');
21
+ const { document } = parseHTML(html);
22
+
23
+ expect(document.body.textContent).to.not.include('<em');
24
+ });
25
+
26
+ it('does not have unescaped HTML inside html tags', async () => {
27
+ const html = await fixture.readFile('/html-tag/index.html');
28
+ const { document } = parseHTML(html);
29
+
30
+ expect(document.body.textContent).to.not.include('<em');
31
+ });
32
+ });
@@ -0,0 +1,44 @@
1
+ import { expect } from 'chai';
2
+ import { loadFixture } from '../../../astro/test/test-utils.js';
3
+
4
+ const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter-injection/', import.meta.url);
5
+
6
+ describe('MDX frontmatter injection', () => {
7
+ let fixture;
8
+
9
+ before(async () => {
10
+ fixture = await loadFixture({
11
+ root: FIXTURE_ROOT,
12
+ });
13
+ await fixture.build();
14
+ });
15
+
16
+ it('remark supports custom vfile data - get title', async () => {
17
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
18
+ const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
19
+ expect(titles).to.contain('Page 1');
20
+ expect(titles).to.contain('Page 2');
21
+ });
22
+
23
+ it('rehype supports custom vfile data - reading time', async () => {
24
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
25
+ const readingTimes = frontmatterByPage.map(
26
+ (frontmatter = {}) => frontmatter.injectedReadingTime
27
+ );
28
+ expect(readingTimes.length).to.be.greaterThan(0);
29
+ for (let readingTime of readingTimes) {
30
+ expect(readingTime).to.not.be.null;
31
+ expect(readingTime.text).match(/^\d+ min read/);
32
+ }
33
+ });
34
+
35
+ it('overrides injected frontmatter with user frontmatter', async () => {
36
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
37
+ const readingTimes = frontmatterByPage.map(
38
+ (frontmatter = {}) => frontmatter.injectedReadingTime?.text
39
+ );
40
+ const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
41
+ expect(titles).to.contain('Overridden title');
42
+ expect(readingTimes).to.contain('1000 min read');
43
+ });
44
+ });
@@ -56,21 +56,4 @@ describe('MDX frontmatter', () => {
56
56
  expect(headingSlugs).to.contain('section-1');
57
57
  expect(headingSlugs).to.contain('section-2');
58
58
  });
59
-
60
- it('extracts frontmatter to "customFrontmatter" export when configured', async () => {
61
- const customFixture = await loadFixture({
62
- root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url),
63
- integrations: [
64
- mdx({
65
- frontmatterOptions: {
66
- name: 'customFrontmatter',
67
- },
68
- }),
69
- ],
70
- });
71
- await customFixture.build();
72
-
73
- const { titles } = JSON.parse(await customFixture.readFile('/glob.json'));
74
- expect(titles).to.include('Using YAML frontmatter');
75
- });
76
59
  });
@@ -0,0 +1,25 @@
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 and React', () => {
8
+ let fixture;
9
+
10
+ before(async () => {
11
+ fixture = await loadFixture({
12
+ root: new URL('./fixtures/mdx-plus-react/', import.meta.url),
13
+ });
14
+ await fixture.build();
15
+ });
16
+
17
+ it('can be used in the same project', async () => {
18
+ const html = await fixture.readFile('/index.html');
19
+ const { document } = parseHTML(html);
20
+
21
+ const p = document.querySelector('p');
22
+
23
+ expect(p.textContent).to.equal('Hello world');
24
+ });
25
+ });
@@ -1,15 +1,15 @@
1
1
  import mdx from '@astrojs/mdx';
2
- import { jsToTreeNode } from '../dist/utils.js';
3
2
 
4
- import { expect } from 'chai';
5
- import { parseHTML } from 'linkedom';
6
3
  import getReadingTime from 'reading-time';
7
4
  import { toString } from 'mdast-util-to-string';
5
+ import { expect } from 'chai';
6
+ import { parseHTML } from 'linkedom';
7
+ import { jsToTreeNode } from '../dist/utils.js';
8
8
 
9
9
  import { loadFixture } from '../../../astro/test/test-utils.js';
10
10
 
11
- export function rehypeReadingTime() {
12
- return function (tree) {
11
+ function rehypeReadingTime() {
12
+ return function (tree, { data }) {
13
13
  const readingTime = getReadingTime(toString(tree));
14
14
  tree.children.unshift(
15
15
  jsToTreeNode(`export const readingTime = ${JSON.stringify(readingTime)}`)
@@ -34,19 +34,8 @@ describe('MDX rehype plugins', () => {
34
34
  await fixture.build();
35
35
  });
36
36
 
37
- it('removes default getHeadings', async () => {
38
- const html = await fixture.readFile('/space-ipsum/index.html');
39
- const { document } = parseHTML(html);
40
-
41
- const headings = [...document.querySelectorAll('h1, h2')];
42
- expect(headings.length).to.be.greaterThan(0);
43
- for (const heading of headings) {
44
- expect(heading.id).to.be.empty;
45
- }
46
- });
47
-
48
37
  it('supports custom rehype plugins - reading time', async () => {
49
- const readingTime = JSON.parse(await fixture.readFile('/reading-time.json'));
38
+ const { readingTime } = JSON.parse(await fixture.readFile('/reading-time.json'));
50
39
 
51
40
  expect(readingTime).to.not.be.null;
52
41
  expect(readingTime.text).to.match(/^\d+ min read/);
@@ -1,9 +0,0 @@
1
- export async function get() {
2
- const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
3
-
4
- return {
5
- body: JSON.stringify({
6
- titles: Object.values(mdxPages ?? {}).map(v => v?.customFrontmatter?.title),
7
- })
8
- }
9
- }
@@ -1,6 +0,0 @@
1
- ---
2
- title: 'Using YAML frontmatter'
3
- illThrowIfIDontExist: "Oh no, that's scary!"
4
- ---
5
-
6
- # {customFrontmatter.illThrowIfIDontExist}