aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/mdx
diff options
context:
space:
mode:
authorGravatar github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2025-06-05 14:25:23 +0000
committerGravatar github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2025-06-05 14:25:23 +0000
commite586d7d704d475afe3373a1de6ae20d504f79d6d (patch)
tree7e3fa24807cebd48a86bd40f866d792181191ee9 /packages/integrations/mdx
downloadastro-e586d7d704d475afe3373a1de6ae20d504f79d6d.tar.gz
astro-e586d7d704d475afe3373a1de6ae20d504f79d6d.tar.zst
astro-e586d7d704d475afe3373a1de6ae20d504f79d6d.zip
Sync from a8e1c0a7402940e0fc5beef669522b315052df1blatest
Diffstat (limited to 'packages/integrations/mdx')
-rw-r--r--packages/integrations/mdx/CHANGELOG.md1525
-rw-r--r--packages/integrations/mdx/README.md38
-rw-r--r--packages/integrations/mdx/package.json80
-rw-r--r--packages/integrations/mdx/src/README.md124
-rw-r--r--packages/integrations/mdx/src/index.ts152
-rw-r--r--packages/integrations/mdx/src/plugins.ts111
-rw-r--r--packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts113
-rw-r--r--packages/integrations/mdx/src/rehype-collect-headings.ts11
-rw-r--r--packages/integrations/mdx/src/rehype-images-to-component.ts188
-rw-r--r--packages/integrations/mdx/src/rehype-meta-string.ts17
-rw-r--r--packages/integrations/mdx/src/rehype-optimize-static.ts302
-rw-r--r--packages/integrations/mdx/src/server.ts73
-rw-r--r--packages/integrations/mdx/src/utils.ts108
-rw-r--r--packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts148
-rw-r--r--packages/integrations/mdx/src/vite-plugin-mdx.ts110
-rw-r--r--packages/integrations/mdx/template/content-module-types.d.ts10
-rw-r--r--packages/integrations/mdx/test/css-head-mdx.test.js101
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/package.json10
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BaseHead.astro11
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BasicBlock.astro14
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/GenericComponent.astro1
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/HelloWorld.astro11
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/MDXWrapper.astro9
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/P.astro3
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/SmallCaps.astro3
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/UsingMdx.astro8
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/WithHoistedScripts.astro6
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/content/config.ts18
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/_styles.css3
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/using-mdx.mdx6
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/test.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/using-component.mdx13
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/ContentLayout.astro18
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/DocumentLayout.astro15
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/One.astro15
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Three.astro6
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Two.astro6
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/DirectContentUsage.astro17
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/componentwithtext.mdx12
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexOne.astro10
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexThree.astro10
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexTwo.astro10
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx22
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/posts/[post].astro18
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/remote.astro17
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testOne.mdx15
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testThree.mdx15
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testTwo.mdx15
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/styles/global.css3
-rw-r--r--packages/integrations/mdx/test/fixtures/css-head-mdx/src/test.mdx14
-rw-r--r--packages/integrations/mdx/test/fixtures/image-remark-imgattr/astro.config.mjs8
-rw-r--r--packages/integrations/mdx/test/fixtures/image-remark-imgattr/package.json12
-rw-r--r--packages/integrations/mdx/test/fixtures/image-remark-imgattr/remarkPlugin.js22
-rw-r--r--packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/assets/penguin2.jpgbin0 -> 11677 bytes
-rw-r--r--packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/pages/index.mdx1
-rw-r--r--packages/integrations/mdx/test/fixtures/invalid-mdx-component/src/pages/invalid-content.mdx7
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-astro-markdown-remarkRehype/src/pages/index.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-component/src/components/Test.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-component/src/components/WithFragment.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro20
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-component/src/pages/index.astro5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-component/src/pages/w-fragment.astro5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Em.astro7
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-escape/src/components/P.astro1
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Title.astro1
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/html-tag.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/index.mdx13
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/astro.config.mjs12
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/package.json12
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/layouts/Base.astro17
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs27
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx24
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro38
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx10
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/with-headings.mdx7
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/pages.json.js8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-frontmatter.mdx45
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-jsx-expressions.mdx8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test.mdx13
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/content/1.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/pages/[slug].astro34
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/astro.config.ts13
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/package.json10
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg9
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston in space.webpbin0 -> 3728 bytes
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston.webpbin0 -> 3728 bytes
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro25
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro19
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro16
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/houston.pngbin0 -> 160915 bytes
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/index.mdx17
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/no-image.mdx1
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/relative/houston.pngbin0 -> 160915 bytes
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx9
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-infinite-loop/astro.config.ts6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-infinite-loop/package.json11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/components/Test.js3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/doc.mdx6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/index.astro5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-math/src/pages/mathjax.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-namespace/astro.config.mjs6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-namespace/package.json11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-namespace/src/components/Component.jsx6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/object.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/star.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/astro.config.mjs37
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/package.json8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Blockquote.astro3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Strong.astro3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/_imported.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/import.astro15
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/index.mdx15
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/astro.config.ts5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/package.json10
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro1
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx7
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx12
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/src/pages/index.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-page/src/styles.css3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plugins/src/pages/with-plugins.mdx25
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/astro.config.mjs6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/components/BrokenComponent.jsx8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js12
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/files/file.mdx4
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro9
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/inline-component.mdx5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/post.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-script-style-raw/src/pages/index.mdx13
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Slotted.astro4
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Test.mdx15
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/glob.astro11
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/index.astro5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx9
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/pages.json.js6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-1.mdx1
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-2.mdx1
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/with-url-override.mdx3
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/astro.config.mjs16
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/package.json8
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/frontmatter.json.js5
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/vite-env-vars.mdx46
-rw-r--r--packages/integrations/mdx/test/invalid-mdx-component.test.js39
-rw-r--r--packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js89
-rw-r--r--packages/integrations/mdx/test/mdx-component.test.js195
-rw-r--r--packages/integrations/mdx/test/mdx-escape.test.js33
-rw-r--r--packages/integrations/mdx/test/mdx-frontmatter-injection.test.js60
-rw-r--r--packages/integrations/mdx/test/mdx-frontmatter.test.js79
-rw-r--r--packages/integrations/mdx/test/mdx-get-headings.test.js251
-rw-r--r--packages/integrations/mdx/test/mdx-get-static-paths.test.js34
-rw-r--r--packages/integrations/mdx/test/mdx-images.test.js82
-rw-r--r--packages/integrations/mdx/test/mdx-infinite-loop.test.js31
-rw-r--r--packages/integrations/mdx/test/mdx-math.test.js73
-rw-r--r--packages/integrations/mdx/test/mdx-namespace.test.js84
-rw-r--r--packages/integrations/mdx/test/mdx-optimize.test.js61
-rw-r--r--packages/integrations/mdx/test/mdx-page.test.js109
-rw-r--r--packages/integrations/mdx/test/mdx-plugins.test.js315
-rw-r--r--packages/integrations/mdx/test/mdx-plus-react-errors.test.js33
-rw-r--r--packages/integrations/mdx/test/mdx-plus-react.test.js55
-rw-r--r--packages/integrations/mdx/test/mdx-script-style-raw.test.js75
-rw-r--r--packages/integrations/mdx/test/mdx-slots.test.js125
-rw-r--r--packages/integrations/mdx/test/mdx-syntax-highlighting.test.js146
-rw-r--r--packages/integrations/mdx/test/mdx-url-export.test.js29
-rw-r--r--packages/integrations/mdx/test/mdx-vite-env-vars.test.js65
-rw-r--r--packages/integrations/mdx/test/remark-imgattr.test.js49
-rw-r--r--packages/integrations/mdx/test/units/rehype-optimize-static.test.js89
-rw-r--r--packages/integrations/mdx/tsconfig.json7
179 files changed, 6776 insertions, 0 deletions
diff --git a/packages/integrations/mdx/CHANGELOG.md b/packages/integrations/mdx/CHANGELOG.md
new file mode 100644
index 000000000..8dde66a46
--- /dev/null
+++ b/packages/integrations/mdx/CHANGELOG.md
@@ -0,0 +1,1525 @@
+# @astrojs/mdx
+
+## 4.3.0
+
+### Minor Changes
+
+- [#13809](https://github.com/withastro/astro/pull/13809) [`3c3b492`](https://github.com/withastro/astro/commit/3c3b492375bd6a63f1fb6cede3685aff999be3c9) Thanks [@ascorbic](https://github.com/ascorbic)! - Increases minimum Node.js version to 18.20.8
+
+ Node.js 18 has now reached end-of-life and should not be used. For now, Astro will continue to support Node.js 18.20.8, which is the final LTS release of Node.js 18, as well as Node.js 20 and Node.js 22 or later. We will drop support for Node.js 18 in a future release, so we recommend upgrading to Node.js 22 as soon as possible. See Astro's [Node.js support policy](https://docs.astro.build/en/upgrade-astro/#support) for more details.
+
+ :warning: **Important note for users of Cloudflare Pages**: The current build image for Cloudflare Pages uses Node.js 18.17.1 by default, which is no longer supported by Astro. If you are using Cloudflare Pages you should [override the default Node.js version](https://developers.cloudflare.com/pages/configuration/build-image/#override-default-versions) to Node.js 22. This does not affect users of Cloudflare Workers, which uses Node.js 22 by default.
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @astrojs/markdown-remark@6.3.2
+
+## 4.2.6
+
+### Patch Changes
+
+- [#13731](https://github.com/withastro/astro/pull/13731) [`c3e80c2`](https://github.com/withastro/astro/commit/c3e80c25b90c803e2798b752583a8e77cdad3146) Thanks [@jsparkdev](https://github.com/jsparkdev)! - update vite to latest version for fixing CVE
+
+## 4.2.5
+
+### Patch Changes
+
+- [#13591](https://github.com/withastro/astro/pull/13591) [`5dd2d3f`](https://github.com/withastro/astro/commit/5dd2d3fde8a138ed611dedf39ffa5dfeeed315f8) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Removes unused code
+
+## 4.2.4
+
+### Patch Changes
+
+- [#13596](https://github.com/withastro/astro/pull/13596) [`3752519`](https://github.com/withastro/astro/commit/375251966d1b28a570bff45ff0fe7e7d2fe46f72) Thanks [@jsparkdev](https://github.com/jsparkdev)! - update vite to latest version to fix CVE
+
+- [#13547](https://github.com/withastro/astro/pull/13547) [`360cb91`](https://github.com/withastro/astro/commit/360cb9199a4314f90825c5639ff4396760e9cfcc) Thanks [@jsparkdev](https://github.com/jsparkdev)! - Updates vite to the latest version
+
+## 4.2.3
+
+### Patch Changes
+
+- [#13526](https://github.com/withastro/astro/pull/13526) [`ff9d69e`](https://github.com/withastro/astro/commit/ff9d69e3443c80059c54f6296d19f66bb068ead3) Thanks [@jsparkdev](https://github.com/jsparkdev)! - update `vite` to the latest version
+
+## 4.2.2
+
+### Patch Changes
+
+- [#13505](https://github.com/withastro/astro/pull/13505) [`a98ae5b`](https://github.com/withastro/astro/commit/a98ae5b8f5c33900379012e9e253a755c0a8927e) Thanks [@ematipico](https://github.com/ematipico)! - Updates the dependency `vite` to the latest.
+
+## 4.2.1
+
+### Patch Changes
+
+- [#13448](https://github.com/withastro/astro/pull/13448) [`91c9503`](https://github.com/withastro/astro/commit/91c95034e0d0bd450170623fd8aab4b56b5b1366) Thanks [@ematipico](https://github.com/ematipico)! - Upgrade to shiki v3
+
+- Updated dependencies [[`91c9503`](https://github.com/withastro/astro/commit/91c95034e0d0bd450170623fd8aab4b56b5b1366)]:
+ - @astrojs/markdown-remark@6.3.1
+
+## 4.2.0
+
+### Minor Changes
+
+- [#13352](https://github.com/withastro/astro/pull/13352) [`cb886dc`](https://github.com/withastro/astro/commit/cb886dcde6c28acca286a66be46228a4d4cc52e7) Thanks [@delucis](https://github.com/delucis)! - Adds support for a new `experimental.headingIdCompat` flag
+
+ By default, Astro removes a trailing `-` from the end of IDs it generates for headings ending with
+ special characters. This differs from the behavior of common Markdown processors.
+
+ You can now disable this behavior with a new configuration flag:
+
+ ```js
+ // astro.config.mjs
+ import { defineConfig } from 'astro/config';
+
+ export default defineConfig({
+ experimental: {
+ headingIdCompat: true,
+ },
+ });
+ ```
+
+ This can be useful when heading IDs and anchor links need to behave consistently across your site
+ and other platforms such as GitHub and npm.
+
+ If you are [using the `rehypeHeadingIds` plugin directly](https://docs.astro.build/en/guides/markdown-content/#heading-ids-and-plugins), you can also pass this new option:
+
+ ```js
+ // astro.config.mjs
+ import { defineConfig } from 'astro/config';
+ import { rehypeHeadingIds } from '@astrojs/markdown-remark';
+ import { otherPluginThatReliesOnHeadingIDs } from 'some/plugin/source';
+
+ export default defineConfig({
+ markdown: {
+ rehypePlugins: [
+ [rehypeHeadingIds, { experimentalHeadingIdCompat: true }],
+ otherPluginThatReliesOnHeadingIDs,
+ ],
+ },
+ });
+ ```
+
+### Patch Changes
+
+- Updated dependencies [[`cb886dc`](https://github.com/withastro/astro/commit/cb886dcde6c28acca286a66be46228a4d4cc52e7), [`a3327ff`](https://github.com/withastro/astro/commit/a3327ffbe6373228339824684eaa6f340a20a32e)]:
+ - @astrojs/markdown-remark@6.3.0
+
+## 4.1.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @astrojs/markdown-remark@6.2.1
+
+## 4.1.0
+
+### Minor Changes
+
+- [#13254](https://github.com/withastro/astro/pull/13254) [`1e11f5e`](https://github.com/withastro/astro/commit/1e11f5e8b722b179e382f3c792cd961b2b51f61b) Thanks [@p0lyw0lf](https://github.com/p0lyw0lf)! - Adds the ability to process and optimize remote images in Markdown syntax in MDX files.
+
+ Previously, Astro only allowed local images to be optimized when included using `![]()` syntax. Astro's image service could only display remote images without any processing.
+
+ Now, Astro's image service can also optimize remote images written in standard Markdown syntax. This allows you to enjoy the benefits of Astro's image processing when your images are stored externally, for example in a CMS or digital asset manager.
+
+ No additional configuration is required to use this feature! Any existing remote images written in Markdown will now automatically be optimized. To opt-out of this processing, write your images in Markdown using the JSX `<img/>` tag instead. Note that images located in your `public/` folder are still never processed.
+
+### Patch Changes
+
+- Updated dependencies [[`1e11f5e`](https://github.com/withastro/astro/commit/1e11f5e8b722b179e382f3c792cd961b2b51f61b)]:
+ - @astrojs/markdown-remark@6.2.0
+
+## 4.0.8
+
+### Patch Changes
+
+- Updated dependencies [[`db252e0`](https://github.com/withastro/astro/commit/db252e0692a0addf7239bfefc0220c525d63337d)]:
+ - @astrojs/markdown-remark@6.1.0
+
+## 4.0.7
+
+### Patch Changes
+
+- [#13011](https://github.com/withastro/astro/pull/13011) [`cf30880`](https://github.com/withastro/astro/commit/cf3088060d45227dcb48e041c4ed5e0081d71398) Thanks [@ascorbic](https://github.com/ascorbic)! - Upgrades Vite
+
+## 4.0.6
+
+### Patch Changes
+
+- Updated dependencies [[`3d89e62`](https://github.com/withastro/astro/commit/3d89e6282235a8da45d9ddfe02bcf7ec78056941)]:
+ - @astrojs/markdown-remark@6.0.2
+
+## 4.0.5
+
+### Patch Changes
+
+- [#12959](https://github.com/withastro/astro/pull/12959) [`3a267f3`](https://github.com/withastro/astro/commit/3a267f33a2a2576c9065c88646ed67f5a7a8ba0b) Thanks [@bluwy](https://github.com/bluwy)! - Reverts https://github.com/withastro/astro/commit/9a3b48c5c3e8f597159454f06c5a0ce8e709bc50 which caused a regression for rendering inline MDX components and MDX files from content collections
+
+## 4.0.4
+
+### Patch Changes
+
+- [#12921](https://github.com/withastro/astro/pull/12921) [`aeb7e1a`](https://github.com/withastro/astro/commit/aeb7e1ac11ebf87847ed2fac89072aa2b4ac2aae) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes a bug that caused Image component to be imported on MDX pages that did not include images
+
+- [#12913](https://github.com/withastro/astro/pull/12913) [`9a3b48c`](https://github.com/withastro/astro/commit/9a3b48c5c3e8f597159454f06c5a0ce8e709bc50) Thanks [@bluwy](https://github.com/bluwy)! - Makes internal `check()` function a no-op to allow faster component renders and prevent React 19 component warnings
+
+## 4.0.3
+
+### Patch Changes
+
+- [#12799](https://github.com/withastro/astro/pull/12799) [`739dbfb`](https://github.com/withastro/astro/commit/739dbfba4214107cf8fc40c702834dad33eed3b0) Thanks [@ascorbic](https://github.com/ascorbic)! - Upgrades Vite to pin esbuild
+
+## 4.0.2
+
+### Patch Changes
+
+- Updated dependencies [[`f13417b`](https://github.com/withastro/astro/commit/f13417bfbf73130c224752379e2da33084f89554), [`87231b1`](https://github.com/withastro/astro/commit/87231b1168da66bb593f681206c42fa555dfcabc), [`a71e9b9`](https://github.com/withastro/astro/commit/a71e9b93b317edc0ded49d4d50f1b7841c8cd428)]:
+ - @astrojs/markdown-remark@6.0.1
+
+## 4.0.1
+
+### Patch Changes
+
+- [#12594](https://github.com/withastro/astro/pull/12594) [`4f2fd0a`](https://github.com/withastro/astro/commit/4f2fd0a0d67a748af8b611b9afc7d4c789f7c8cc) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fixes compatibility with Astro 5
+
+## 4.0.0
+
+### Major Changes
+
+- [#12231](https://github.com/withastro/astro/pull/12231) [`90ae100`](https://github.com/withastro/astro/commit/90ae100cf482529828febed591172433309bc12e) Thanks [@bluwy](https://github.com/bluwy)! - Handles the breaking change in Astro where content pages (including `.mdx` pages located within `src/pages/`) no longer respond with `charset=utf-8` in the `Content-Type` header.
+
+ For MDX pages without layouts, `@astrojs/mdx` will automatically add the `<meta charset="utf-8">` tag to the page by default. This reduces the boilerplate needed to write with non-ASCII characters. If your MDX pages have a layout, the layout component should include the `<meta charset="utf-8">` tag.
+
+ If you require `charset=utf-8` to render your page correctly, make sure that your layout components have the `<meta charset="utf-8">` tag added.
+
+- [#12008](https://github.com/withastro/astro/pull/12008) [`5608338`](https://github.com/withastro/astro/commit/560833843c6d3ce2b6c6c473ec4ae70e744bf255) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Welcome to the Astro 5 beta! This release has no changes from the latest alpha of this package, but it does bring us one step closer to the final, stable release.
+
+ Starting from this release, no breaking changes will be introduced unless absolutely necessary.
+
+ To learn how to upgrade, check out the [Astro v5.0 upgrade guide in our beta docs site](https://5-0-0-beta.docs.astro.build/en/guides/upgrade-to/v5/).
+
+### Minor Changes
+
+- [#12539](https://github.com/withastro/astro/pull/12539) [`827093e`](https://github.com/withastro/astro/commit/827093e6175549771f9d93ddf3f2be4c2c60f0b7) Thanks [@bluwy](https://github.com/bluwy)! - Drops node 21 support
+
+- [#11741](https://github.com/withastro/astro/pull/11741) [`6617491`](https://github.com/withastro/astro/commit/6617491c3bc2bde87f7867d7dec2580781852cfc) Thanks [@bluwy](https://github.com/bluwy)! - Updates adapter server entrypoint to use `@astrojs/mdx/server.js`
+
+ This is an internal change. Handling JSX in your `.mdx` files has been moved from Astro internals and is now the responsibility of this integration. You should not notice a change in your project, and no update to your code is required.
+
+### Patch Changes
+
+- [#12075](https://github.com/withastro/astro/pull/12075) [`a19530e`](https://github.com/withastro/astro/commit/a19530e377b7d7afad58a33b23c0a5df1c376819) Thanks [@bluwy](https://github.com/bluwy)! - Parses frontmatter ourselves
+
+- [#11861](https://github.com/withastro/astro/pull/11861) [`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59) Thanks [@bluwy](https://github.com/bluwy)! - Updates `@astrojs/markdown-remark` and handle its breaking changes
+
+- [#12533](https://github.com/withastro/astro/pull/12533) [`1b61fdf`](https://github.com/withastro/astro/commit/1b61fdf038d2627c6dad1a7f1426f60a4616ad93) Thanks [@ematipico](https://github.com/ematipico)! - Fixes a case where the MDX renderer couldn't be loaded when used as a direct dependency of an Astro integration.
+
+- Updated dependencies [[`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59), [`5608338`](https://github.com/withastro/astro/commit/560833843c6d3ce2b6c6c473ec4ae70e744bf255), [`560ef15`](https://github.com/withastro/astro/commit/560ef15ad23bd137b56ef1048eb2df548b99fdce), [`83a2a64`](https://github.com/withastro/astro/commit/83a2a648418ad30f4eb781d1c1b5f2d8a8ac846e), [`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59), [`a19530e`](https://github.com/withastro/astro/commit/a19530e377b7d7afad58a33b23c0a5df1c376819)]:
+ - @astrojs/markdown-remark@6.0.0
+
+## 4.0.0-beta.5
+
+### Minor Changes
+
+- [#12539](https://github.com/withastro/astro/pull/12539) [`827093e`](https://github.com/withastro/astro/commit/827093e6175549771f9d93ddf3f2be4c2c60f0b7) Thanks [@bluwy](https://github.com/bluwy)! - Drops node 21 support
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @astrojs/markdown-remark@6.0.0-beta.3
+
+## 4.0.0-beta.4
+
+### Patch Changes
+
+- [#12533](https://github.com/withastro/astro/pull/12533) [`1b61fdf`](https://github.com/withastro/astro/commit/1b61fdf038d2627c6dad1a7f1426f60a4616ad93) Thanks [@ematipico](https://github.com/ematipico)! - Fixes a case where the MDX renderer couldn't be loaded when used as a direct dependency of an Astro integration.
+
+## 4.0.0-beta.3
+
+### Major Changes
+
+- [#12231](https://github.com/withastro/astro/pull/12231) [`90ae100`](https://github.com/withastro/astro/commit/90ae100cf482529828febed591172433309bc12e) Thanks [@bluwy](https://github.com/bluwy)! - Handles the breaking change in Astro where content pages (including `.mdx` pages located within `src/pages/`) no longer respond with `charset=utf-8` in the `Content-Type` header.
+
+ For MDX pages without layouts, `@astrojs/mdx` will automatically add the `<meta charset="utf-8">` tag to the page by default. This reduces the boilerplate needed to write with non-ASCII characters. If your MDX pages have a layout, the layout component should include the `<meta charset="utf-8">` tag.
+
+ If you require `charset=utf-8` to render your page correctly, make sure that your layout components have the `<meta charset="utf-8">` tag added.
+
+## 4.0.0-alpha.2
+
+### Patch Changes
+
+- [#11861](https://github.com/withastro/astro/pull/11861) [`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59) Thanks [@bluwy](https://github.com/bluwy)! - Updates `@astrojs/markdown-remark` and handle its breaking changes
+
+- Updated dependencies [[`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59), [`560ef15`](https://github.com/withastro/astro/commit/560ef15ad23bd137b56ef1048eb2df548b99fdce), [`3ab3b4e`](https://github.com/withastro/astro/commit/3ab3b4efbcdd2aabea5f949deedf51a5acefae59)]:
+ - @astrojs/markdown-remark@6.0.0-alpha.1
+
+## 4.0.0-alpha.1
+
+### Minor Changes
+
+- [#11741](https://github.com/withastro/astro/pull/11741) [`6617491`](https://github.com/withastro/astro/commit/6617491c3bc2bde87f7867d7dec2580781852cfc) Thanks [@bluwy](https://github.com/bluwy)! - Updates adapter server entrypoint to use `@astrojs/mdx/server.js`
+
+ This is an internal change. Handling JSX in your `.mdx` files has been moved from Astro internals and is now the responsibility of this integration. You should not notice a change in your project, and no update to your code is required.
+
+## 4.0.0-alpha.0
+
+- Updated dependencies [[`b6fbdaa`](https://github.com/withastro/astro/commit/b6fbdaa94a9ecec706a99e1938fbf5cd028c72e0), [`89bab1e`](https://github.com/withastro/astro/commit/89bab1e70786123fbe933a9d7a1b80c9334dcc5f), [`d74617c`](https://github.com/withastro/astro/commit/d74617cbd3278feba05909ec83db2d73d57a153e), [`83a2a64`](https://github.com/withastro/astro/commit/83a2a648418ad30f4eb781d1c1b5f2d8a8ac846e), [`e90f559`](https://github.com/withastro/astro/commit/e90f5593d23043579611452a84b9e18ad2407ef9), [`2df49a6`](https://github.com/withastro/astro/commit/2df49a6fb4f6d92fe45f7429430abe63defeacd6), [`8a53517`](https://github.com/withastro/astro/commit/8a5351737d6a14fc55f1dafad8f3b04079e81af6)]:
+ - astro@5.0.0-alpha.0
+ - @astrojs/markdown-remark@6.0.0-alpha.0
+
+## 3.1.9
+
+### Patch Changes
+
+- [#12245](https://github.com/withastro/astro/pull/12245) [`1d4f6a4`](https://github.com/withastro/astro/commit/1d4f6a4989bc1cfd7109b1bff41503f115660e02) Thanks [@bmenant](https://github.com/bmenant)! - Add `components` property to MDXInstance type definition (RenderResult and module import)
+
+## 3.1.8
+
+### Patch Changes
+
+- Updated dependencies [[`710a1a1`](https://github.com/withastro/astro/commit/710a1a11f488ff6ed3da6d3e0723b2322ccfe27b)]:
+ - @astrojs/markdown-remark@5.3.0
+
+## 3.1.7
+
+### Patch Changes
+
+- [#12026](https://github.com/withastro/astro/pull/12026) [`40e7a1b`](https://github.com/withastro/astro/commit/40e7a1b05d9e5ea3fcda176c9663bbcff86edb63) Thanks [@bluwy](https://github.com/bluwy)! - Initializes the MDX processor only when there's `.mdx` files
+
+## 3.1.6
+
+### Patch Changes
+
+- [#11975](https://github.com/withastro/astro/pull/11975) [`c9ae7b1`](https://github.com/withastro/astro/commit/c9ae7b1b89e050900bbc111f29e8c5d95c26bf36) Thanks [@bluwy](https://github.com/bluwy)! - Handles nested root hast node when optimizing MDX
+
+## 3.1.5
+
+### Patch Changes
+
+- [#11818](https://github.com/withastro/astro/pull/11818) [`88ef1d0`](https://github.com/withastro/astro/commit/88ef1d0e774e8ab8798b9912da1b069f97736623) Thanks [@bluwy](https://github.com/bluwy)! - Fixes CSS in the layout component to be ordered first before any other components in the MDX file
+
+## 3.1.4
+
+### Patch Changes
+
+- [#11717](https://github.com/withastro/astro/pull/11717) [`423614e`](https://github.com/withastro/astro/commit/423614ebb6ddb76cc8d11f3e3b6ae111a4a82662) Thanks [@bluwy](https://github.com/bluwy)! - Fixes stack trace location when failed to parse an MDX file with frontmatter
+
+## 3.1.3
+
+### Patch Changes
+
+- Updated dependencies [[`49b5145`](https://github.com/withastro/astro/commit/49b5145158a603b9bb951bf914a6a9780c218704)]:
+ - @astrojs/markdown-remark@5.2.0
+
+## 3.1.2
+
+### Patch Changes
+
+- Updated dependencies [[`b6afe6a`](https://github.com/withastro/astro/commit/b6afe6a782f68f4a279463a144baaf99cb96b6dc)]:
+ - @astrojs/markdown-remark@5.1.1
+
+## 3.1.1
+
+### Patch Changes
+
+- [#11263](https://github.com/withastro/astro/pull/11263) [`7d59750`](https://github.com/withastro/astro/commit/7d597506615fa5a34327304e8321be7b9c4b799d) Thanks [@wackbyte](https://github.com/wackbyte)! - Refactor to use Astro's integration logger for logging
+
+## 3.1.0
+
+### Minor Changes
+
+- [#11144](https://github.com/withastro/astro/pull/11144) [`803dd80`](https://github.com/withastro/astro/commit/803dd8061df02138b4928442bcb76e77dcf6f5e7) Thanks [@ematipico](https://github.com/ematipico)! - The integration now exposes a function called `getContainerRenderer`, that can be used inside the Container APIs to load the relative renderer.
+
+ ```js
+ import { experimental_AstroContainer as AstroContainer } from 'astro/container';
+ import ReactWrapper from '../src/components/ReactWrapper.astro';
+ import { loadRenderers } from 'astro:container';
+ import { getContainerRenderer } from '@astrojs/react';
+
+ test('ReactWrapper with react renderer', async () => {
+ const renderers = await loadRenderers([getContainerRenderer()]);
+ const container = await AstroContainer.create({
+ renderers,
+ });
+ const result = await container.renderToString(ReactWrapper);
+
+ expect(result).toContain('Counter');
+ expect(result).toContain('Count: <!-- -->5');
+ });
+ ```
+
+## 3.0.1
+
+### Patch Changes
+
+- [#10813](https://github.com/withastro/astro/pull/10813) [`3cc3e2c`](https://github.com/withastro/astro/commit/3cc3e2ccba062749a6bd8469bc88ff797bea0abc) Thanks [@Xetera](https://github.com/Xetera)! - Omitting compiler-internal symbol from user components to fix breaking error messages
+
+## 3.0.0
+
+### Major Changes
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Refactors the MDX transformation to rely only on the unified pipeline. Babel and esbuild transformations are removed, which should result in faster build times. The refactor requires using Astro v4.8.0 but no other changes are necessary.
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Allows integrations after the MDX integration to update `markdown.remarkPlugins` and `markdown.rehypePlugins`, and have the plugins work in MDX too.
+
+ If your integration relies on Astro's previous behavior that prevents integrations from adding remark/rehype plugins for MDX, you will now need to configure `@astrojs/mdx` with `extendMarkdownConfig: false` and explicitly specify any `remarkPlugins` and `rehypePlugins` options instead.
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Renames the `optimize.customComponentNames` option to `optimize.ignoreElementNames` to better reflect its usecase. Its behaviour is not changed and should continue to work as before.
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Replaces the internal `remark-images-to-component` plugin with `rehype-images-to-component` to let users use additional rehype plugins for images
+
+### Patch Changes
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Simplifies plain MDX components as hast element nodes to further improve HTML string inlining for the `optimize` option
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Allows Vite plugins to transform `.mdx` files before the MDX plugin transforms it
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Updates the `optimize` option to group static sibling nodes as a `<Fragment />`. This reduces the number of AST nodes and simplifies runtime rendering of MDX pages.
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Tags the MDX component export for quicker component checks while rendering
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Fixes `export const components` keys detection for the `optimize` option
+
+- [#10935](https://github.com/withastro/astro/pull/10935) [`ddd8e49`](https://github.com/withastro/astro/commit/ddd8e49d1a179bec82310fb471f822a1567a6610) Thanks [@bluwy](https://github.com/bluwy)! - Improves `optimize` handling for MDX components with attributes and inline MDX components
+
+## 2.3.1
+
+### Patch Changes
+
+- [#10754](https://github.com/withastro/astro/pull/10754) [`3e7a12c8532411e580fcfdb8445cad8fc8499291`](https://github.com/withastro/astro/commit/3e7a12c8532411e580fcfdb8445cad8fc8499291) Thanks [@rishi-raj-jain](https://github.com/rishi-raj-jain)! - Fixes an issue where images in MDX required a relative specifier (e.g. `./`)
+
+ Now, you can use the standard `![](img.png)` syntax in MDX files for images colocated in the same folder: no relative specifier required!
+
+ There is no need to update your project; your existing images will still continue to work. However, you may wish to remove any relative specifiers from these MDX images as they are no longer necessary:
+
+ ```diff
+ - ![A cute dog](./dog.jpg)
+ + ![A cute dog](dog.jpg)
+ <!-- This dog lives in the same folder as my article! -->
+ ```
+
+- [#10770](https://github.com/withastro/astro/pull/10770) [`88ee63a3ba4488c60348cb821034e6d4a057efd0`](https://github.com/withastro/astro/commit/88ee63a3ba4488c60348cb821034e6d4a057efd0) Thanks [@bluwy](https://github.com/bluwy)! - Removes internal MDX processor on `buildEnd` to free up memory
+
+## 2.3.0
+
+### Minor Changes
+
+- [#10689](https://github.com/withastro/astro/pull/10689) [`683d51a5eecafbbfbfed3910a3f1fbf0b3531b99`](https://github.com/withastro/astro/commit/683d51a5eecafbbfbfed3910a3f1fbf0b3531b99) Thanks [@ematipico](https://github.com/ematipico)! - Deprecate support for versions of Node.js older than `v18.17.1` for Node.js 18, older than `v20.0.3` for Node.js 20, and the complete Node.js v19 release line.
+
+ This change is in line with Astro's [Node.js support policy](https://docs.astro.build/en/upgrade-astro/#support).
+
+### Patch Changes
+
+- Updated dependencies [[`ccafa8d230f65c9302421a0ce0a0adc5824bfd55`](https://github.com/withastro/astro/commit/ccafa8d230f65c9302421a0ce0a0adc5824bfd55)]:
+ - @astrojs/markdown-remark@5.1.0
+
+## 2.2.4
+
+### Patch Changes
+
+- [#10673](https://github.com/withastro/astro/pull/10673) [`db7f9c87035a0de780536d95cdd9facff00c3c08`](https://github.com/withastro/astro/commit/db7f9c87035a0de780536d95cdd9facff00c3c08) Thanks [@bluwy](https://github.com/bluwy)! - Removes unnecessary internal `recmaInjectImportMetaEnv` plugin
+
+## 2.2.3
+
+### Patch Changes
+
+- Updated dependencies [[`2cf116f80cb5e421ab5cc5eb4a654e7b78c1b8de`](https://github.com/withastro/astro/commit/2cf116f80cb5e421ab5cc5eb4a654e7b78c1b8de), [`374efcdff9625ca43309d89e3b9cfc9174351512`](https://github.com/withastro/astro/commit/374efcdff9625ca43309d89e3b9cfc9174351512)]:
+ - @astrojs/markdown-remark@5.0.0
+
+## 2.2.2
+
+### Patch Changes
+
+- Updated dependencies [[`c585528f446ccca3d4c643f4af5d550b93c18902`](https://github.com/withastro/astro/commit/c585528f446ccca3d4c643f4af5d550b93c18902)]:
+ - @astrojs/markdown-remark@4.3.2
+
+## 2.2.1
+
+### Patch Changes
+
+- Updated dependencies [[`19e42c368184013fc30d1e46753b9e9383bb2bdf`](https://github.com/withastro/astro/commit/19e42c368184013fc30d1e46753b9e9383bb2bdf)]:
+ - @astrojs/markdown-remark@4.3.1
+
+## 2.2.0
+
+### Minor Changes
+
+- [#10104](https://github.com/withastro/astro/pull/10104) [`a31bbd7ff8f3ec62ee507f72d1d25140b82ffc18`](https://github.com/withastro/astro/commit/a31bbd7ff8f3ec62ee507f72d1d25140b82ffc18) Thanks [@remcohaszing](https://github.com/remcohaszing)! - Changes Astro's internal syntax highlighting to use rehype plugins instead of remark plugins. This provides better interoperability with other [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins) that deal with code blocks, in particular with third party syntax highlighting plugins and [`rehype-mermaid`](https://github.com/remcohaszing/rehype-mermaid).
+
+ This may be a breaking change if you are currently using:
+
+ - a remark plugin that relies on nodes of type `html`
+ - a rehype plugin that depends on nodes of type `raw`.
+
+ Please review your rendered code samples carefully, and if necessary, consider using a rehype plugin that deals with the generated `element` nodes instead. You can transform the AST of raw HTML strings, or alternatively use [`hast-util-to-html`](https://github.com/syntax-tree/hast-util-to-html) to get a string from a `raw` node.
+
+### Patch Changes
+
+- Updated dependencies [[`c081adf998d30419fed97d8fccc11340cdc512e0`](https://github.com/withastro/astro/commit/c081adf998d30419fed97d8fccc11340cdc512e0), [`5a9528741fa98d017b269c7e4f013058028bdc5d`](https://github.com/withastro/astro/commit/5a9528741fa98d017b269c7e4f013058028bdc5d), [`a31bbd7ff8f3ec62ee507f72d1d25140b82ffc18`](https://github.com/withastro/astro/commit/a31bbd7ff8f3ec62ee507f72d1d25140b82ffc18)]:
+ - @astrojs/markdown-remark@4.3.0
+
+## 2.1.1
+
+### Patch Changes
+
+- Updated dependencies [[`44c957f893c6bf5f5b7c78301de7b21c5975584d`](https://github.com/withastro/astro/commit/44c957f893c6bf5f5b7c78301de7b21c5975584d)]:
+ - @astrojs/markdown-remark@4.2.1
+
+## 2.1.0
+
+### Minor Changes
+
+- [#9753](https://github.com/withastro/astro/pull/9753) [`df37366556d46f7abdf82b09e33b08bd94e631b3`](https://github.com/withastro/astro/commit/df37366556d46f7abdf82b09e33b08bd94e631b3) Thanks [@OliverSpeir](https://github.com/OliverSpeir)! - Allows remark plugins to pass options specifying how images in .mdx files will be optimized
+
+## 2.0.6
+
+### Patch Changes
+
+- Updated dependencies [[`53c69dcc82cdf4000aff13a6c11fffe19096cf45`](https://github.com/withastro/astro/commit/53c69dcc82cdf4000aff13a6c11fffe19096cf45), [`2f81cffa9da9db0e2802d303f94feaee8d2f54ec`](https://github.com/withastro/astro/commit/2f81cffa9da9db0e2802d303f94feaee8d2f54ec), [`a505190933365268d48139a5f197a3cfb5570870`](https://github.com/withastro/astro/commit/a505190933365268d48139a5f197a3cfb5570870)]:
+ - @astrojs/markdown-remark@4.2.0
+
+## 2.0.5
+
+### Patch Changes
+
+- [#9706](https://github.com/withastro/astro/pull/9706) [`1539e04a8e5865027b3a8718c6f142885e7c8d88`](https://github.com/withastro/astro/commit/1539e04a8e5865027b3a8718c6f142885e7c8d88) Thanks [@bluwy](https://github.com/bluwy)! - Removes redundant HMR handling code
+
+- Updated dependencies [[`165cfc154be477337037185c32b308616d1ed6fa`](https://github.com/withastro/astro/commit/165cfc154be477337037185c32b308616d1ed6fa), [`e9a72d9a91a3741566866bcaab11172cb0dc7d31`](https://github.com/withastro/astro/commit/e9a72d9a91a3741566866bcaab11172cb0dc7d31)]:
+ - @astrojs/markdown-remark@4.1.0
+
+## 2.0.4
+
+### Patch Changes
+
+- [#9652](https://github.com/withastro/astro/pull/9652) [`e72efd6a9a1e2a70488fd225529617ffd8418534`](https://github.com/withastro/astro/commit/e72efd6a9a1e2a70488fd225529617ffd8418534) Thanks [@bluwy](https://github.com/bluwy)! - Removes environment variables workaround that broke project builds with sourcemaps
+
+## 2.0.3
+
+### Patch Changes
+
+- [#9479](https://github.com/withastro/astro/pull/9479) [`1baf0b0d3cbd0564954c2366a7278794fad6726e`](https://github.com/withastro/astro/commit/1baf0b0d3cbd0564954c2366a7278794fad6726e) Thanks [@sarah11918](https://github.com/sarah11918)! - Updates README
+
+## 2.0.2
+
+### Patch Changes
+
+- [#9452](https://github.com/withastro/astro/pull/9452) [`e83b5095f`](https://github.com/withastro/astro/commit/e83b5095f164f48ba40fc715a805fc66a3e39dcf) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Upgrades vite to latest
+
+## 2.0.1
+
+### Patch Changes
+
+- [#9366](https://github.com/withastro/astro/pull/9366) [`1b4e91898`](https://github.com/withastro/astro/commit/1b4e91898116f75b02b66ec402385cf44e559118) Thanks [@lilnasy](https://github.com/lilnasy)! - Updates NPM package to refer to the stable Astro version instead of a beta.
+
+- Updated dependencies [[`270c6cc27`](https://github.com/withastro/astro/commit/270c6cc27f20995883fcdabbff9b56d7f041f9e4)]:
+ - @astrojs/markdown-remark@4.0.1
+
+## 2.0.0
+
+### Major Changes
+
+- [#9138](https://github.com/withastro/astro/pull/9138) [`abf601233`](https://github.com/withastro/astro/commit/abf601233f8188d118a8cb063c777478d8d9f1a3) Thanks [@bluwy](https://github.com/bluwy)! - Updates the unified, remark, and rehype dependencies to latest. Make sure to update your custom remark and rehype plugins as well to be compatible with the latest versions.
+
+ **Potentially breaking change:** The default value of `markdown.remarkRehype.footnoteBackLabel` is changed from `"Back to content"` to `"Back to reference 1"`. See the `mdast-util-to-hast` [commit](https://github.com/syntax-tree/mdast-util-to-hast/commit/56c88e45690be138fad9f0bf367b939d09816863) for more information.
+
+### Patch Changes
+
+- Updated dependencies [[`abf601233`](https://github.com/withastro/astro/commit/abf601233f8188d118a8cb063c777478d8d9f1a3), [`addb57c8e`](https://github.com/withastro/astro/commit/addb57c8e80b7b67ec61224666f3a1db5c44410c), [`c7953645e`](https://github.com/withastro/astro/commit/c7953645eeaaf9e87c6db4494b0023d2c1878ff0)]:
+ - @astrojs/markdown-remark@4.0.0
+
+## 2.0.0-beta.0
+
+### Major Changes
+
+- [#9138](https://github.com/withastro/astro/pull/9138) [`abf601233`](https://github.com/withastro/astro/commit/abf601233f8188d118a8cb063c777478d8d9f1a3) Thanks [@bluwy](https://github.com/bluwy)! - Updates the unified, remark, and rehype dependencies to latest. Make sure to update your custom remark and rehype plugins as well to be compatible with the latest versions.
+
+ **Potentially breaking change:** The default value of `markdown.remarkRehype.footnoteBackLabel` is changed from `"Back to content"` to `"Back to reference 1"`. See the `mdast-util-to-hast` [commit](https://github.com/syntax-tree/mdast-util-to-hast/commit/56c88e45690be138fad9f0bf367b939d09816863) for more information.
+
+### Patch Changes
+
+- Updated dependencies [[`abf601233`](https://github.com/withastro/astro/commit/abf601233f8188d118a8cb063c777478d8d9f1a3), [`6201bbe96`](https://github.com/withastro/astro/commit/6201bbe96c2a083fb201e4a43a9bd88499821a3e), [`cdabf6ef0`](https://github.com/withastro/astro/commit/cdabf6ef02be7220fd2b6bdcef924ceca089381e), [`addb57c8e`](https://github.com/withastro/astro/commit/addb57c8e80b7b67ec61224666f3a1db5c44410c), [`1c48ed286`](https://github.com/withastro/astro/commit/1c48ed286538ab9e354eca4e4dcd7c6385c96721), [`37697a2c5`](https://github.com/withastro/astro/commit/37697a2c5511572dc29c0a4ea46f90c2f62be8e6), [`bd0c2e9ae`](https://github.com/withastro/astro/commit/bd0c2e9ae3389a9d3085050c1e8134ae98dff299), [`c7953645e`](https://github.com/withastro/astro/commit/c7953645eeaaf9e87c6db4494b0023d2c1878ff0), [`0fe3a7ed5`](https://github.com/withastro/astro/commit/0fe3a7ed5d7bb1a9fce1623e84ba14104b51223c), [`710be505c`](https://github.com/withastro/astro/commit/710be505c9ddf416e77a75343d8cae9c497d72c6), [`153a5abb9`](https://github.com/withastro/astro/commit/153a5abb905042ac68b712514dc9ec387d3e6b17)]:
+ - @astrojs/markdown-remark@4.0.0-beta.0
+ - astro@4.0.0-beta.0
+
+## 1.1.5
+
+### Patch Changes
+
+- Updated dependencies [[`4537ecf0d`](https://github.com/withastro/astro/commit/4537ecf0d060f89cb8c000338a7fc5f4197a88c8)]:
+ - @astrojs/markdown-remark@3.5.0
+
+## 1.1.4
+
+### Patch Changes
+
+- Updated dependencies [[`c5010aad3`](https://github.com/withastro/astro/commit/c5010aad3475669648dc939e00f88bbb52489d0d)]:
+ - @astrojs/markdown-remark@3.4.0
+
+## 1.1.3
+
+### Patch Changes
+
+- [#8897](https://github.com/withastro/astro/pull/8897) [`5a3d46da1`](https://github.com/withastro/astro/commit/5a3d46da1e80f62a29eaf464eeb87c626cc5593f) Thanks [@remcohaszing](https://github.com/remcohaszing)! - Update the README to suggest that users install the [official MDX extension](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) for [VS Code](https://code.visualstudio.com/).
+
+- Updated dependencies [[`26b77b8fe`](https://github.com/withastro/astro/commit/26b77b8fef0e03bfc5550aecaa1f56a4fc1cd297)]:
+ - astro@3.3.4
+
+## 1.1.2
+
+### Patch Changes
+
+- Updated dependencies [[`2993055be`](https://github.com/withastro/astro/commit/2993055bed2764c31ff4b4f55b81ab6b1ae6b401), [`c4270e476`](https://github.com/withastro/astro/commit/c4270e47681ee2453f3fea07fed7b238645fd6ea), [`bd5aa1cd3`](https://github.com/withastro/astro/commit/bd5aa1cd35ecbd2784f30dd836ff814684fee02b), [`f369fa250`](https://github.com/withastro/astro/commit/f369fa25055a3497ebaf61c88fb0e8af56c73212), [`391729686`](https://github.com/withastro/astro/commit/391729686bcc8404a7dd48c5987ee380daf3200f), [`f999365b8`](https://github.com/withastro/astro/commit/f999365b8248b8b14f3743e68a42d450d06acff3), [`b2ae9ee0c`](https://github.com/withastro/astro/commit/b2ae9ee0c42b11ffc1d3f070d1d5ac881aef84ed), [`0abff97fe`](https://github.com/withastro/astro/commit/0abff97fed3db14be3c75ff9ece3aab67c4ba783), [`3bef32f81`](https://github.com/withastro/astro/commit/3bef32f81c56bc600ca307f1bd40787e23e625a5)]:
+ - astro@3.3.0
+ - @astrojs/markdown-remark@3.3.0
+
+## 1.1.1
+
+### Patch Changes
+
+- [#8737](https://github.com/withastro/astro/pull/8737) [`6f60da805`](https://github.com/withastro/astro/commit/6f60da805e0014bc50dd07bef972e91c73560c3c) Thanks [@ematipico](https://github.com/ematipico)! - Add provenance statement when publishing the library from CI
+
+- Updated dependencies [[`21f482657`](https://github.com/withastro/astro/commit/21f4826576c2c812a1604e18717799da3470decd), [`6f60da805`](https://github.com/withastro/astro/commit/6f60da805e0014bc50dd07bef972e91c73560c3c), [`d78806dfe`](https://github.com/withastro/astro/commit/d78806dfe0301ea7ffe6c7c1f783bd415ac7cda9), [`d1c75fe15`](https://github.com/withastro/astro/commit/d1c75fe158839699c59728cf3a83888e8c72a459), [`aa265d730`](https://github.com/withastro/astro/commit/aa265d73024422967c1b1c68ad268c419c6c798f), [`78adbc443`](https://github.com/withastro/astro/commit/78adbc4433208458291e36713909762e148e1e5d), [`21e0757ea`](https://github.com/withastro/astro/commit/21e0757ea22a57d344c934045ca19db93b684436), [`357270f2a`](https://github.com/withastro/astro/commit/357270f2a3d0bf2aa634ba7e52e9d17618eff4a7)]:
+ - @astrojs/markdown-remark@3.2.1
+ - astro@3.2.3
+
+## 1.1.0
+
+### Minor Changes
+
+- [#8468](https://github.com/withastro/astro/pull/8468) [`a8d72ceae`](https://github.com/withastro/astro/commit/a8d72ceaeed154434923b21c0ae129a72263b8ed) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support the `img` component export for optimized images. This allows you to customize how optimized images are styled and rendered.
+
+ When rendering an optimized image, Astro will pass the `ImageMetadata` object to your `img` component as the `src` prop. For unoptimized images (i.e. images using URLs or absolute paths), Astro will continue to pass the `src` as a string.
+
+ This example handles both cases and applies custom styling:
+
+ ```astro
+ ---
+ // src/components/MyImage.astro
+ import type { ImageMetadata } from 'astro';
+ import { Image } from 'astro:assets';
+
+ type Props = {
+ src: string | ImageMetadata;
+ alt: string;
+ };
+
+ const { src, alt } = Astro.props;
+ ---
+
+ {
+ typeof src === 'string' ? (
+ <img class="custom-styles" src={src} alt={alt} />
+ ) : (
+ <Image class="custom-styles" {src} {alt} />
+ )
+ }
+
+ <style>
+ .custom-styles {
+ border: 1px solid red;
+ }
+ </style>
+ ```
+
+ Now, this components can be applied to the `img` component props object or file export:
+
+ ```md
+ import MyImage from '../../components/MyImage.astro';
+
+ export const components = { img: MyImage };
+
+ # My MDX article
+ ```
+
+### Patch Changes
+
+- [#8533](https://github.com/withastro/astro/pull/8533) [`74dc3edb3`](https://github.com/withastro/astro/commit/74dc3edb305c49feec49c39082fa836485da8a92) Thanks [@bluwy](https://github.com/bluwy)! - Improve MDX rendering performance by sharing processor instance
+
+- Updated dependencies [[`7522bb491`](https://github.com/withastro/astro/commit/7522bb4914f2f9e8b8f3c743bc9c941fd3aca644), [`ecc65abbf`](https://github.com/withastro/astro/commit/ecc65abbf9e086c5bbd1973cd4a820082b4e0dc5), [`2c4fc878b`](https://github.com/withastro/astro/commit/2c4fc878bece36b7fcf1470419c7ce6f1e1e95d0), [`d93987824`](https://github.com/withastro/astro/commit/d93987824d3d6b4f58267be21ab8466ee8d5d5f8), [`c92e0acd7`](https://github.com/withastro/astro/commit/c92e0acd715171b3f4c3294099780e21576648c8), [`7522bb491`](https://github.com/withastro/astro/commit/7522bb4914f2f9e8b8f3c743bc9c941fd3aca644), [`f95febf96`](https://github.com/withastro/astro/commit/f95febf96bb97babb28d78994332f5e47f5f637d), [`b85c8a78a`](https://github.com/withastro/astro/commit/b85c8a78a116dbbddc901438bc0b7a1917dc0238), [`45364c345`](https://github.com/withastro/astro/commit/45364c345267429e400baecd1fbc290503f8b13a)]:
+ - astro@3.1.0
+ - @astrojs/markdown-remark@3.2.0
+
+## 1.0.3
+
+### Patch Changes
+
+- [#8430](https://github.com/withastro/astro/pull/8430) [`f3f62a5a2`](https://github.com/withastro/astro/commit/f3f62a5a20f4881bb04f65f192df8e1ccf7fb601) Thanks [@bluwy](https://github.com/bluwy)! - Use exported remarkShiki and remarkPrism plugins from `@astrojs/markdown-remark`
+
+- Updated dependencies [[`f3f62a5a2`](https://github.com/withastro/astro/commit/f3f62a5a20f4881bb04f65f192df8e1ccf7fb601), [`f66053a1e`](https://github.com/withastro/astro/commit/f66053a1ea0a4e3bdb0b0df12bb1bf56e1ea2618), [`0fa483283`](https://github.com/withastro/astro/commit/0fa483283e54c94f173838cd558dc0dbdd11e699)]:
+ - @astrojs/markdown-remark@3.1.0
+ - astro@3.0.11
+
+## 1.0.2
+
+### Patch Changes
+
+- [#8438](https://github.com/withastro/astro/pull/8438) [`6df4f3bd9`](https://github.com/withastro/astro/commit/6df4f3bd9d74de47dc8732e7f3b42bef42d2facf) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fix errors not having a stacktrace
+
+- Updated dependencies [[`b3cf1b327`](https://github.com/withastro/astro/commit/b3cf1b32765c76cfc90e497a68280ad52f02cb1f), [`b92d066b7`](https://github.com/withastro/astro/commit/b92d066b737f64f08a9cf293bd07c9263ef8f32d)]:
+ - astro@3.0.10
+
+## 1.0.1
+
+### Patch Changes
+
+- [#8405](https://github.com/withastro/astro/pull/8405) [`93a1231f1`](https://github.com/withastro/astro/commit/93a1231f14d97339e38d8a67cf541337960e7d5e) Thanks [@delucis](https://github.com/delucis)! - Add location data to MDX compile errors
+
+- Updated dependencies [[`7d95bd9ba`](https://github.com/withastro/astro/commit/7d95bd9baaf755239fd7d35e4813861b2dbccf42), [`1947ef7a9`](https://github.com/withastro/astro/commit/1947ef7a99ce3d1d6ea797842edd31d5edffa5de), [`61ad70fdc`](https://github.com/withastro/astro/commit/61ad70fdc52035964c43ecdb4cf7468f6c2b61e7), [`d2f2a11cd`](https://github.com/withastro/astro/commit/d2f2a11cdb42b0de79be21c798eda8e7e7b2a277), [`5126c6a40`](https://github.com/withastro/astro/commit/5126c6a40f88bff66ee5d3c3a21eea8c4a44ce7a), [`48ff7855b`](https://github.com/withastro/astro/commit/48ff7855b238536a3df17cb29335c90029fc41a4), [`923a443cb`](https://github.com/withastro/astro/commit/923a443cb060a0e936a0e1cc87c0360232f77914), [`8935b3b46`](https://github.com/withastro/astro/commit/8935b3b4672d6c54c7b79e6c4575298f75eeb9f4)]:
+ - astro@3.0.9
+
+## 1.0.0
+
+### Major Changes
+
+- [#8179](https://github.com/withastro/astro/pull/8179) [`6011d52d3`](https://github.com/withastro/astro/commit/6011d52d38e43c3e3d52bc3bc41a60e36061b7b7) Thanks [@matthewp](https://github.com/matthewp)! - Astro 3.0 Release Candidate
+
+- [#8131](https://github.com/withastro/astro/pull/8131) [`43140b87a`](https://github.com/withastro/astro/commit/43140b87abad99d9e3472faf0e263728ff5a033b) Thanks [@matthewp](https://github.com/matthewp)! - Support Astro 3 JSX format
+
+ This upgrades the MDX plugin to correctly work with the new JSX render API in Astro 3.
+
+### Minor Changes
+
+- [#8188](https://github.com/withastro/astro/pull/8188) [`d0679a666`](https://github.com/withastro/astro/commit/d0679a666f37da0fca396d42b9b32bbb25d29312) Thanks [@ematipico](https://github.com/ematipico)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+
+- [#8169](https://github.com/withastro/astro/pull/8169) [`e79e3779d`](https://github.com/withastro/astro/commit/e79e3779df0ad35253abcdb931d622847d9adb12) Thanks [@bluwy](https://github.com/bluwy)! - Remove pre-shiki v0.14 theme names for compatibility. Please rename to the new theme names to migrate:
+
+ - `material-darker` -> `material-theme-darker`
+ - `material-default` -> `material-theme`
+ - `material-lighter` -> `material-theme-lighter`
+ - `material-ocean` -> `material-theme-ocean`
+ - `material-palenight` -> `material-theme-palenight`
+
+- [#8188](https://github.com/withastro/astro/pull/8188) [`84af8ed9d`](https://github.com/withastro/astro/commit/84af8ed9d1e6401c6ebc9c60fe8cddb44d5044b0) Thanks [@ematipico](https://github.com/ematipico)! - Add `astro` as peer dependency
+
+### Patch Changes
+
+- [#8188](https://github.com/withastro/astro/pull/8188) [`84af8ed9d`](https://github.com/withastro/astro/commit/84af8ed9d1e6401c6ebc9c60fe8cddb44d5044b0) Thanks [@ematipico](https://github.com/ematipico)! - Re-orders the MDX plugin to run before Astro's JSX plugin
+
+- [#8188](https://github.com/withastro/astro/pull/8188) [`32669cd47`](https://github.com/withastro/astro/commit/32669cd47555e9c7433c3998a2b6e624dfb2d8e9) Thanks [@ematipico](https://github.com/ematipico)! - Handle `components` exports handling itself
+
+- Updated dependencies [[`d0679a666`](https://github.com/withastro/astro/commit/d0679a666f37da0fca396d42b9b32bbb25d29312), [`db39206cb`](https://github.com/withastro/astro/commit/db39206cbb85b034859ac416179f141184bb2bff), [`adf9fccfd`](https://github.com/withastro/astro/commit/adf9fccfdda107c2224558f1c2e6a77847ac0a8a), [`0c7b42dc6`](https://github.com/withastro/astro/commit/0c7b42dc6780e687e416137539f55a3a427d1d10), [`46c4c0e05`](https://github.com/withastro/astro/commit/46c4c0e053f830585b9ef229ce1c259df00a80f8), [`364d861bd`](https://github.com/withastro/astro/commit/364d861bd527b8511968e2837728148f090bedef), [`2484dc408`](https://github.com/withastro/astro/commit/2484dc4080e5cd84b9a53648a1de426d7c907be2), [`81545197a`](https://github.com/withastro/astro/commit/81545197a32fd015d763fc386c8b67e0e08b7393), [`6011d52d3`](https://github.com/withastro/astro/commit/6011d52d38e43c3e3d52bc3bc41a60e36061b7b7), [`c2c71d90c`](https://github.com/withastro/astro/commit/c2c71d90c264a2524f99e0373ab59015f23ad4b1), [`cd2d7e769`](https://github.com/withastro/astro/commit/cd2d7e76981ef9b9013453aa2629838e1e9fd422), [`80f1494cd`](https://github.com/withastro/astro/commit/80f1494cdaf72e58a420adb4f7c712d4089e1923), [`e45f30293`](https://github.com/withastro/astro/commit/e45f3029340db718b6ed7e91b5d14f5cf14cd71d), [`c0de7a7b0`](https://github.com/withastro/astro/commit/c0de7a7b0f042cd49cbea4f4ac1b2ab6f9fef644), [`65c354969`](https://github.com/withastro/astro/commit/65c354969e6fe0ef6d622e8f4c545e2f717ce8c6), [`3c3100851`](https://github.com/withastro/astro/commit/3c31008519ce68b5b1b1cb23b71fbe0a2d506882), [`34cb20021`](https://github.com/withastro/astro/commit/34cb2002161ba88df6bcb72fecfd12ed867c134b), [`a824863ab`](https://github.com/withastro/astro/commit/a824863ab1c451f4068eac54f28dd240573e1cba), [`44f7a2872`](https://github.com/withastro/astro/commit/44f7a28728c56c04ac377b6e917329f324874043), [`1048aca55`](https://github.com/withastro/astro/commit/1048aca550769415e528016e42b358ffbfd44b61), [`be6bbd2c8`](https://github.com/withastro/astro/commit/be6bbd2c86b9bf5268e765bb937dda00ff15781a), [`9e021a91c`](https://github.com/withastro/astro/commit/9e021a91c57d10809f588dd47968fc0e7f8b4d5c), [`7511a4980`](https://github.com/withastro/astro/commit/7511a4980fd36536464c317de33a5190427f430a), [`c37632a20`](https://github.com/withastro/astro/commit/c37632a20d06164fb97a4c2fc48df6d960398832), [`acf652fc1`](https://github.com/withastro/astro/commit/acf652fc1d5db166231e87e22d0d50444f5556d8), [`42785c7b7`](https://github.com/withastro/astro/commit/42785c7b784b151e6d582570e5d74482129e8eb8), [`8450379db`](https://github.com/withastro/astro/commit/8450379db854fb1eaa9f38f21d65db240bc616cd), [`dbc97b121`](https://github.com/withastro/astro/commit/dbc97b121f42583728f1cdfdbf14575fda943f5b), [`7d2f311d4`](https://github.com/withastro/astro/commit/7d2f311d428e3d1c8c13b9bf2a708d6435713fc2), [`2540feedb`](https://github.com/withastro/astro/commit/2540feedb06785d5a20eecc3668849f147d778d4), [`ea7ff5177`](https://github.com/withastro/astro/commit/ea7ff5177dbcd7b2508cb1eef1b22b8ee1f47079), [`68efd4a8b`](https://github.com/withastro/astro/commit/68efd4a8b29f248397667801465b3152dc98e9a7), [`7bd1b86f8`](https://github.com/withastro/astro/commit/7bd1b86f85c06fdde0a1ed9146d01bac69990671), [`036388f66`](https://github.com/withastro/astro/commit/036388f66dab68ad54b895ed86f9176958dd83c8), [`519a1c4e8`](https://github.com/withastro/astro/commit/519a1c4e8407c7abcb8d879b67a9f4b960652cae), [`1f58a7a1b`](https://github.com/withastro/astro/commit/1f58a7a1bea6888868b689dac94801d554319b02), [`2ae9d37f0`](https://github.com/withastro/astro/commit/2ae9d37f0a9cb21ab288d3c30aecb6d84db87788), [`a8f35777e`](https://github.com/withastro/astro/commit/a8f35777e7e322068a4e2f520c2c9e43ade19e58), [`70f34f5a3`](https://github.com/withastro/astro/commit/70f34f5a355f42526ee9e5355f3de8e510002ea2), [`5208a3c8f`](https://github.com/withastro/astro/commit/5208a3c8fefcec7694857fb344af351f4631fc34), [`84af8ed9d`](https://github.com/withastro/astro/commit/84af8ed9d1e6401c6ebc9c60fe8cddb44d5044b0), [`f003e7364`](https://github.com/withastro/astro/commit/f003e7364317cafdb8589913b26b28e928dd07c9), [`ffc9e2d3d`](https://github.com/withastro/astro/commit/ffc9e2d3de46049bf3d82140ef018f524fb03187), [`732111cdc`](https://github.com/withastro/astro/commit/732111cdce441639db31f40f621df48442d00969), [`0f637c71e`](https://github.com/withastro/astro/commit/0f637c71e511cb4c51712128d217a26c8eee4d40), [`33b8910cf`](https://github.com/withastro/astro/commit/33b8910cfdce5713891c50a84a0a8fe926311710), [`8a5b0c1f3`](https://github.com/withastro/astro/commit/8a5b0c1f3a4be6bb62db66ec70144109ff5b4c59), [`148e61d24`](https://github.com/withastro/astro/commit/148e61d2492456811f8a3c8daaab1c3429a2ffdc), [`e79e3779d`](https://github.com/withastro/astro/commit/e79e3779df0ad35253abcdb931d622847d9adb12), [`632579dc2`](https://github.com/withastro/astro/commit/632579dc2094cc342929261c89e689f0dd358284), [`3674584e0`](https://github.com/withastro/astro/commit/3674584e02b161a698b429ceb66723918fdc56ac), [`1db4e92c1`](https://github.com/withastro/astro/commit/1db4e92c12ed73681217f5cefd39f2f47542f961), [`e7f872e91`](https://github.com/withastro/astro/commit/e7f872e91e852b901cf221a5151077dec64305bf), [`16f09dfff`](https://github.com/withastro/astro/commit/16f09dfff7722fda99dd0412e3006a7a39c80829), [`4477bb41c`](https://github.com/withastro/astro/commit/4477bb41c8ed688785c545731ef5b184b629f4e5), [`55c10d1d5`](https://github.com/withastro/astro/commit/55c10d1d564e805efc3c1a7c48e0d9a1cdf0c7ed), [`3e834293d`](https://github.com/withastro/astro/commit/3e834293d47ab2761a7aa013916e8371871efb7f), [`96beb883a`](https://github.com/withastro/astro/commit/96beb883ad87f8bbf5b2f57e14a743763d2a6f58), [`997a0db8a`](https://github.com/withastro/astro/commit/997a0db8a4e3851edd69384cf5eadbb969e1d547), [`80f1494cd`](https://github.com/withastro/astro/commit/80f1494cdaf72e58a420adb4f7c712d4089e1923), [`0f0625504`](https://github.com/withastro/astro/commit/0f0625504145f18cba7dc6cf20291cb2abddc5a9), [`e1ae56e72`](https://github.com/withastro/astro/commit/e1ae56e724d0f83db1230359e06cd6bc26f5fa26), [`f32d093a2`](https://github.com/withastro/astro/commit/f32d093a280faafff024228c12bb438156ec34d7), [`f01eb585e`](https://github.com/withastro/astro/commit/f01eb585e7c972d940761309b1595f682b6922d2), [`b76c166bd`](https://github.com/withastro/astro/commit/b76c166bdd8e28683f62806aef968d1e0c3b06d9), [`a87cbe400`](https://github.com/withastro/astro/commit/a87cbe400314341d5f72abf86ea264e6b47c091f), [`866ed4098`](https://github.com/withastro/astro/commit/866ed4098edffb052239cdb26e076cf8db61b1d9), [`767eb6866`](https://github.com/withastro/astro/commit/767eb68666eb777965baa0d6ade20bbafecf95bf), [`32669cd47`](https://github.com/withastro/astro/commit/32669cd47555e9c7433c3998a2b6e624dfb2d8e9)]:
+ - @astrojs/prism@3.0.0
+ - astro@3.0.0
+ - @astrojs/markdown-remark@3.0.0
+
+## 1.0.0-rc.2
+
+### Major Changes
+
+- [#8179](https://github.com/withastro/astro/pull/8179) [`6011d52d3`](https://github.com/withastro/astro/commit/6011d52d38e43c3e3d52bc3bc41a60e36061b7b7) Thanks [@matthewp](https://github.com/matthewp)! - Astro 3.0 Release Candidate
+
+### Minor Changes
+
+- [#8169](https://github.com/withastro/astro/pull/8169) [`e79e3779d`](https://github.com/withastro/astro/commit/e79e3779df0ad35253abcdb931d622847d9adb12) Thanks [@bluwy](https://github.com/bluwy)! - Remove pre-shiki v0.14 theme names for compatibility. Please rename to the new theme names to migrate:
+
+ - `material-darker` -> `material-theme-darker`
+ - `material-default` -> `material-theme`
+ - `material-lighter` -> `material-theme-lighter`
+ - `material-ocean` -> `material-theme-ocean`
+ - `material-palenight` -> `material-theme-palenight`
+
+### Patch Changes
+
+- Updated dependencies [[`adf9fccfd`](https://github.com/withastro/astro/commit/adf9fccfdda107c2224558f1c2e6a77847ac0a8a), [`582132328`](https://github.com/withastro/astro/commit/5821323285646aee7ff9194a505f708028e4db57), [`81545197a`](https://github.com/withastro/astro/commit/81545197a32fd015d763fc386c8b67e0e08b7393), [`6011d52d3`](https://github.com/withastro/astro/commit/6011d52d38e43c3e3d52bc3bc41a60e36061b7b7), [`be6bbd2c8`](https://github.com/withastro/astro/commit/be6bbd2c86b9bf5268e765bb937dda00ff15781a), [`42785c7b7`](https://github.com/withastro/astro/commit/42785c7b784b151e6d582570e5d74482129e8eb8), [`95120efbe`](https://github.com/withastro/astro/commit/95120efbe817163663492181cbeb225849354493), [`2ae9d37f0`](https://github.com/withastro/astro/commit/2ae9d37f0a9cb21ab288d3c30aecb6d84db87788), [`f003e7364`](https://github.com/withastro/astro/commit/f003e7364317cafdb8589913b26b28e928dd07c9), [`732111cdc`](https://github.com/withastro/astro/commit/732111cdce441639db31f40f621df48442d00969), [`33b8910cf`](https://github.com/withastro/astro/commit/33b8910cfdce5713891c50a84a0a8fe926311710), [`e79e3779d`](https://github.com/withastro/astro/commit/e79e3779df0ad35253abcdb931d622847d9adb12), [`179796405`](https://github.com/withastro/astro/commit/179796405e053b559d83f84507e5a465861a029a), [`a87cbe400`](https://github.com/withastro/astro/commit/a87cbe400314341d5f72abf86ea264e6b47c091f), [`767eb6866`](https://github.com/withastro/astro/commit/767eb68666eb777965baa0d6ade20bbafecf95bf)]:
+ - astro@3.0.0-rc.5
+ - @astrojs/markdown-remark@3.0.0-rc.1
+ - @astrojs/prism@3.0.0-rc.1
+
+## 1.0.0-beta.1
+
+### Major Changes
+
+- [#8131](https://github.com/withastro/astro/pull/8131) [`43140b87a`](https://github.com/withastro/astro/commit/43140b87abad99d9e3472faf0e263728ff5a033b) Thanks [@matthewp](https://github.com/matthewp)! - Support Astro 3 JSX format
+
+ This upgrades the MDX plugin to correctly work with the new JSX render API in Astro 3.
+
+### Patch Changes
+
+- Updated dependencies [[`2484dc408`](https://github.com/withastro/astro/commit/2484dc4080e5cd84b9a53648a1de426d7c907be2), [`c2c71d90c`](https://github.com/withastro/astro/commit/c2c71d90c264a2524f99e0373ab59015f23ad4b1), [`7177f7579`](https://github.com/withastro/astro/commit/7177f7579b6e866f0fd895b3fd079d8ba330b1a9), [`097a8e4e9`](https://github.com/withastro/astro/commit/097a8e4e916c7df18eafdaa6c8d6ce2991c17ab6), [`dbc97b121`](https://github.com/withastro/astro/commit/dbc97b121f42583728f1cdfdbf14575fda943f5b), [`2540feedb`](https://github.com/withastro/astro/commit/2540feedb06785d5a20eecc3668849f147d778d4), [`ea7ff5177`](https://github.com/withastro/astro/commit/ea7ff5177dbcd7b2508cb1eef1b22b8ee1f47079), [`68efd4a8b`](https://github.com/withastro/astro/commit/68efd4a8b29f248397667801465b3152dc98e9a7), [`0e0fa605d`](https://github.com/withastro/astro/commit/0e0fa605d109cc91e08a1ae1cc560ea240fe631b), [`5208a3c8f`](https://github.com/withastro/astro/commit/5208a3c8fefcec7694857fb344af351f4631fc34), [`8a5b0c1f3`](https://github.com/withastro/astro/commit/8a5b0c1f3a4be6bb62db66ec70144109ff5b4c59), [`d6b494376`](https://github.com/withastro/astro/commit/d6b4943764989c0e89df2d6875cd19691566dfb3), [`4477bb41c`](https://github.com/withastro/astro/commit/4477bb41c8ed688785c545731ef5b184b629f4e5), [`3e834293d`](https://github.com/withastro/astro/commit/3e834293d47ab2761a7aa013916e8371871efb7f), [`b76c166bd`](https://github.com/withastro/astro/commit/b76c166bdd8e28683f62806aef968d1e0c3b06d9)]:
+ - astro@3.0.0-beta.3
+
+## 1.0.0-beta.0
+
+### Minor Changes
+
+- [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+
+- [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8) Thanks [@bluwy](https://github.com/bluwy)! - Add `astro` as peer dependency
+
+### Patch Changes
+
+- [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8) Thanks [@bluwy](https://github.com/bluwy)! - Re-orders the MDX plugin to run before Astro's JSX plugin
+
+- [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f) Thanks [@bluwy](https://github.com/bluwy)! - Handle `components` exports handling itself
+
+- Updated dependencies [[`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81), [`76ddef19c`](https://github.com/withastro/astro/commit/76ddef19ccab6e5f7d3a5740cd41acf10e334b38), [`9b4f70a62`](https://github.com/withastro/astro/commit/9b4f70a629f55e461759ba46f68af7097a2e9215), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`2f951cd40`](https://github.com/withastro/astro/commit/2f951cd403dfcc2c3ca6aae618ae3e1409516e32), [`c022a4217`](https://github.com/withastro/astro/commit/c022a4217a805d223c1494e9eda4e48bbf810388), [`67becaa58`](https://github.com/withastro/astro/commit/67becaa580b8f787df58de66b7008b7098f1209c), [`bc37331d8`](https://github.com/withastro/astro/commit/bc37331d8154e3e95a8df9131e4e014e78a7a9e7), [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8), [`3dc1ca2fa`](https://github.com/withastro/astro/commit/3dc1ca2fac8d9965cc5085a5d09e72ed87b4281a), [`1be84dfee`](https://github.com/withastro/astro/commit/1be84dfee3ce8e6f5cc624f99aec4e980f6fde37), [`35f01df79`](https://github.com/withastro/astro/commit/35f01df797d23315f2bee2fc3fd795adb0559c58), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`78de801f2`](https://github.com/withastro/astro/commit/78de801f21fd4ca1653950027d953bf08614566b), [`59d6e569f`](https://github.com/withastro/astro/commit/59d6e569f63e175c97e82e94aa7974febfb76f7c), [`7723c4cc9`](https://github.com/withastro/astro/commit/7723c4cc93298c2e6530e55da7afda048f22cf81), [`fb5cd6b56`](https://github.com/withastro/astro/commit/fb5cd6b56dc27a71366ed5e1ab8bfe9b8f96bac5), [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f)]:
+ - @astrojs/prism@3.0.0-beta.0
+ - astro@3.0.0-beta.0
+ - @astrojs/markdown-remark@3.0.0-beta.0
+
+## 0.19.7
+
+### Patch Changes
+
+- [#7307](https://github.com/withastro/astro/pull/7307) [`8034edd9e`](https://github.com/withastro/astro/commit/8034edd9ecf805073395ba7f68f73cd5fc4d2c73) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix [Object AsyncGenerator] appearing in markup for Markdoc documents
+
+## 0.19.6
+
+### Patch Changes
+
+- [#7185](https://github.com/withastro/astro/pull/7185) [`339529fc8`](https://github.com/withastro/astro/commit/339529fc820bac2d514b63198ecf54a1d88c0917) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Bring back improved style and script handling across content collection files. This addresses bugs found in a previous release to `@astrojs/markdoc`.
+
+## 0.19.5
+
+### Patch Changes
+
+- [#7151](https://github.com/withastro/astro/pull/7151) [`ea16570b1`](https://github.com/withastro/astro/commit/ea16570b1e0929678170c10b06c011dc668d7013) Thanks [@bluwy](https://github.com/bluwy)! - Add `optimize` option for faster builds and rendering
+
+- [#7192](https://github.com/withastro/astro/pull/7192) [`7851f9258`](https://github.com/withastro/astro/commit/7851f9258fae2f54795470253df9ce4bcd5f9cb0) Thanks [@ematipico](https://github.com/ematipico)! - Detect `mdx` files using their full extension
+
+- [#7191](https://github.com/withastro/astro/pull/7191) [`27c6e0182`](https://github.com/withastro/astro/commit/27c6e01826a6da525f1f811d97784accd1ebbd96) Thanks [@bluwy](https://github.com/bluwy)! - Remove `@mdx-js/rollup` dependency
+
+## 0.19.4
+
+### Patch Changes
+
+- [#7178](https://github.com/withastro/astro/pull/7178) [`57e65d247`](https://github.com/withastro/astro/commit/57e65d247f67de61bcc3a585c2254feb61ed2e74) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: revert Markdoc asset bleed changes. Production build issues were discovered that deserve a different fix.
+
+## 0.19.3
+
+### Patch Changes
+
+- [#6758](https://github.com/withastro/astro/pull/6758) [`f558a9e20`](https://github.com/withastro/astro/commit/f558a9e2056fc8f2e2d5814e74f199e398159fc4) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Improve style and script handling across content collection files. This addresses style bleed present in `@astrojs/markdoc` v0.1.0
+
+## 0.19.2
+
+### Patch Changes
+
+- [#7104](https://github.com/withastro/astro/pull/7104) [`826e02890`](https://github.com/withastro/astro/commit/826e0289005f645b902375b98d5549c6a95ccafa) Thanks [@bluwy](https://github.com/bluwy)! - Specify `"files"` field to only publish necessary files
+
+- Updated dependencies [[`826e02890`](https://github.com/withastro/astro/commit/826e0289005f645b902375b98d5549c6a95ccafa)]:
+ - @astrojs/markdown-remark@2.2.1
+ - @astrojs/prism@2.1.2
+
+## 0.19.1
+
+### Patch Changes
+
+- [#6932](https://github.com/withastro/astro/pull/6932) [`49514e4ce`](https://github.com/withastro/astro/commit/49514e4ce40fedb39bf7decd2c296258efbdafc7) Thanks [@bluwy](https://github.com/bluwy)! - Upgrade shiki to v0.14.1. This updates the shiki theme colors and adds the theme name to the `pre` tag, e.g. `<pre class="astro-code github-dark">`.
+
+- Updated dependencies [[`49514e4ce`](https://github.com/withastro/astro/commit/49514e4ce40fedb39bf7decd2c296258efbdafc7)]:
+ - @astrojs/markdown-remark@2.2.0
+
+## 0.19.0
+
+### Minor Changes
+
+- [#6824](https://github.com/withastro/astro/pull/6824) [`2511d58d5`](https://github.com/withastro/astro/commit/2511d58d586af080a78e5ef8a63020b3e17770db) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add support for using optimized and relative images in MDX files with `experimental.assets`
+
+### Patch Changes
+
+- Updated dependencies [[`2511d58d5`](https://github.com/withastro/astro/commit/2511d58d586af080a78e5ef8a63020b3e17770db)]:
+ - @astrojs/markdown-remark@2.1.4
+
+## 0.18.4
+
+### Patch Changes
+
+- [#6817](https://github.com/withastro/astro/pull/6817) [`f882bc163`](https://github.com/withastro/astro/commit/f882bc1636d5ce1c3b8faae47df36b4dc758045a) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix sourcemap warnings when using Content Collections and MDX with the `vite.build.sourcemap` option
+
+## 0.18.3
+
+### Patch Changes
+
+- [#6779](https://github.com/withastro/astro/pull/6779) [`a98f6f418`](https://github.com/withastro/astro/commit/a98f6f418c92261a06ef79624a8c86e288c21eab) Thanks [@matthewp](https://github.com/matthewp)! - Prevent body head content injection in MDX when using layout
+
+## 0.18.2
+
+### Patch Changes
+
+- [#6552](https://github.com/withastro/astro/pull/6552) [`392ba3e4d`](https://github.com/withastro/astro/commit/392ba3e4d55f73ce9194bd94a2243f1aa62af079) Thanks [@bluwy](https://github.com/bluwy)! - Fix integration return type
+
+- Updated dependencies [[`90e5f87d0`](https://github.com/withastro/astro/commit/90e5f87d03215a833bb6ac91f9548670a25ce659), [`f5fddafc2`](https://github.com/withastro/astro/commit/f5fddafc248bb1ef57b7349bfecc25539ae2b5ea)]:
+ - @astrojs/markdown-remark@2.1.1
+
+## 0.18.1
+
+### Patch Changes
+
+- [#6494](https://github.com/withastro/astro/pull/6494) [`a13e9d7e3`](https://github.com/withastro/astro/commit/a13e9d7e33baccf51e7d4815f99b481ad174bc57) Thanks [@Yan-Thomas](https://github.com/Yan-Thomas)! - Consistency improvements to several package descriptions
+
+- Updated dependencies [[`a13e9d7e3`](https://github.com/withastro/astro/commit/a13e9d7e33baccf51e7d4815f99b481ad174bc57)]:
+ - @astrojs/prism@2.1.1
+
+## 0.18.0
+
+### Minor Changes
+
+- [#6344](https://github.com/withastro/astro/pull/6344) [`694918a56`](https://github.com/withastro/astro/commit/694918a56b01104831296be0c25456135a63c784) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add a new experimental flag (`experimental.assets`) to enable our new core Assets story.
+
+ This unlocks a few features:
+
+ - A new built-in image component and JavaScript API to transform and optimize images.
+ - Relative images with automatic optimization in Markdown.
+ - Support for validating assets using content collections.
+ - and more!
+
+ See [Assets (Experimental)](https://docs.astro.build/en/guides/assets/) on our docs site for more information on how to use this feature!
+
+- [#6213](https://github.com/withastro/astro/pull/6213) [`afbbc4d5b`](https://github.com/withastro/astro/commit/afbbc4d5bfafc1779bac00b41c2a1cb1c90f2808) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Updated compilation settings to disable downlevelling for Node 14
+
+### Patch Changes
+
+- [#6209](https://github.com/withastro/astro/pull/6209) [`fec583909`](https://github.com/withastro/astro/commit/fec583909ab62829dc0c1600e2387979365f2b94) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Introduce the (experimental) `@astrojs/markdoc` integration. This unlocks Markdoc inside your Content Collections, bringing support for Astro and UI components in your content. This also improves Astro core internals to make Content Collections extensible to more file types in the future.
+
+ You can install this integration using the `astro add` command:
+
+ ```
+ astro add markdoc
+ ```
+
+ [Read the `@astrojs/markdoc` documentation](https://docs.astro.build/en/guides/integrations-guide/markdoc/) for usage instructions, and browse the [new `with-markdoc` starter](https://astro.new/with-markdoc) to try for yourself.
+
+- Updated dependencies [[`694918a56`](https://github.com/withastro/astro/commit/694918a56b01104831296be0c25456135a63c784), [`afbbc4d5b`](https://github.com/withastro/astro/commit/afbbc4d5bfafc1779bac00b41c2a1cb1c90f2808)]:
+ - @astrojs/markdown-remark@2.1.0
+ - @astrojs/prism@2.1.0
+
+## 0.17.2
+
+### Patch Changes
+
+- [#6296](https://github.com/withastro/astro/pull/6296) [`075b87e8b`](https://github.com/withastro/astro/commit/075b87e8b72a69a608cd2ff1196dc0989e2cf1e1) Thanks [@RaphaelBossek](https://github.com/RaphaelBossek)! - Update to `es-module-lexer@1.1.1`
+
+## 0.17.1
+
+- Updated to es-module-lexer@1.1.1
+
+## 0.17.0
+
+### Minor Changes
+
+- [#6253](https://github.com/withastro/astro/pull/6253) [`0049fda62`](https://github.com/withastro/astro/commit/0049fda62fa8650a0d250adb00a2c5d82679aeaf) Thanks [@bluwy](https://github.com/bluwy)! - Support rehype plugins that inject namespaced attributes. This introduces a breaking change if you use [custom components for HTML elements](https://docs.astro.build/en/guides/markdown-content/#assigning-custom-components-to-html-elements), where the prop passed to the component will be normal HTML casing, e.g. `class` instead of `className`, and `xlink:href` instead of `xlinkHref`.
+
+## 0.16.2
+
+### Patch Changes
+
+- [#6252](https://github.com/withastro/astro/pull/6252) [`0fbcf838a`](https://github.com/withastro/astro/commit/0fbcf838a775d3b07e7c2372888a8aa6c190278e) Thanks [@bluwy](https://github.com/bluwy)! - Revert previous breaking change
+
+## 0.16.1
+
+### Patch Changes
+
+- [#6243](https://github.com/withastro/astro/pull/6243) [`4f6ecba4c`](https://github.com/withastro/astro/commit/4f6ecba4c1b35bacbbc0097854ee2b7b8c878e71) Thanks [@bluwy](https://github.com/bluwy)! - Support rehype plugins that inject namespaced attributes
+
+## 0.16.0
+
+### Minor Changes
+
+- [#6050](https://github.com/withastro/astro/pull/6050) [`2ab32b59e`](https://github.com/withastro/astro/commit/2ab32b59ef0a28d34757f2c2adb9cf2baa86855e) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: load syntax highlighters after MDX remark plugins. This keeps MDX consistent with Astro's markdown behavior.
+
+### Patch Changes
+
+- [#6062](https://github.com/withastro/astro/pull/6062) [`c6cf847bd`](https://github.com/withastro/astro/commit/c6cf847bd0b6bef3c51a5710fba5ca43b11e46f9) Thanks [@delucis](https://github.com/delucis)! - Update MDX README
+
+## 0.15.2
+
+### Patch Changes
+
+- [#5478](https://github.com/withastro/astro/pull/5478) [`1c7eef308`](https://github.com/withastro/astro/commit/1c7eef308e808aa5ed4662b53e67ec8d1b814d1f) Thanks [@nemo0](https://github.com/nemo0)! - Update READMEs for consistency
+
+## 0.15.1
+
+### Patch Changes
+
+- [#5978](https://github.com/withastro/astro/pull/5978) [`7abb1e905`](https://github.com/withastro/astro/commit/7abb1e9056c4b4fd0abfced347df32a41cdfbf28) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fix MDX heading IDs generation when using a frontmatter reference
+
+- Updated dependencies [[`7abb1e905`](https://github.com/withastro/astro/commit/7abb1e9056c4b4fd0abfced347df32a41cdfbf28)]:
+ - @astrojs/markdown-remark@2.0.1
+
+## 0.15.0
+
+### Minor Changes
+
+- [#5684](https://github.com/withastro/astro/pull/5684) [`a9c292026`](https://github.com/withastro/astro/commit/a9c2920264e36cc5dc05f4adc1912187979edb0d) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Refine Markdown and MDX configuration options for ease-of-use. & [#5769](https://github.com/withastro/astro/pull/5769) [`93e633922`](https://github.com/withastro/astro/commit/93e633922c2e449df3bb2357b3683af1d3c0e07b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Introduce a `smartypants` flag to opt-out of Astro's default SmartyPants plugin.
+
+ - **Markdown**
+
+ - **Replace the `extendDefaultPlugins` option** with a `gfm` boolean and a `smartypants` boolean. These are enabled by default, and can be disabled to remove GitHub-Flavored Markdown and SmartyPants.
+
+ - Ensure GitHub-Flavored Markdown and SmartyPants are applied whether or not custom `remarkPlugins` or `rehypePlugins` are configured. If you want to apply custom plugins _and_ remove Astro's default plugins, manually set `gfm: false` and `smartypants: false` in your config.
+
+ - **Migrate `extendDefaultPlugins` to `gfm` and `smartypants`**
+
+ You may have disabled Astro's built-in plugins (GitHub-Flavored Markdown and Smartypants) with the `extendDefaultPlugins` option. This has now been split into 2 flags to disable each plugin individually:
+
+ - `markdown.gfm` to disable GitHub-Flavored Markdown
+ - `markdown.smartypants` to disable SmartyPants
+
+ ```diff
+ // astro.config.mjs
+ import { defineConfig } from 'astro/config';
+
+ export default defineConfig({
+ markdown: {
+ - extendDefaultPlugins: false,
+ + smartypants: false,
+ + gfm: false,
+ }
+ });
+ ```
+
+ Additionally, applying remark and rehype plugins **no longer disables** `gfm` and `smartypants`. You will need to opt-out manually by setting `gfm` and `smartypants` to `false`.
+
+ - **MDX**
+
+ - Support _all_ Markdown configuration options (except `drafts`) from your MDX integration config. This includes `syntaxHighlighting` and `shikiConfig` options to further customize the MDX renderer.
+
+ - Simplify `extendPlugins` to an `extendMarkdownConfig` option. MDX options will default to their equivalent in your Markdown config. By setting `extendMarkdownConfig` to false, you can "eject" to set your own syntax highlighting, plugins, and more.
+
+ - **Migrate MDX's `extendPlugins` to `extendMarkdownConfig`**
+
+ You may have used the `extendPlugins` option to manage plugin defaults in MDX. This has been replaced by 3 flags:
+
+ - `extendMarkdownConfig` (`true` by default) to toggle Markdown config inheritance. This replaces the `extendPlugins: 'markdown'` option.
+ - `gfm` (`true` by default) and `smartypants` (`true` by default) to toggle GitHub-Flavored Markdown and SmartyPants in MDX. This replaces the `extendPlugins: 'defaults'` option.
+
+- [#5687](https://github.com/withastro/astro/pull/5687) [`e2019be6f`](https://github.com/withastro/astro/commit/e2019be6ffa46fa33d92cfd346f9ecbe51bb7144) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Give remark and rehype plugins access to user frontmatter via frontmatter injection. This means `data.astro.frontmatter` is now the _complete_ Markdown or MDX document's frontmatter, rather than an empty object.
+
+ This allows plugin authors to modify existing frontmatter, or compute new properties based on other properties. For example, say you want to compute a full image URL based on an `imageSrc` slug in your document frontmatter:
+
+ ```ts
+ export function remarkInjectSocialImagePlugin() {
+ return function (tree, file) {
+ const { frontmatter } = file.data.astro;
+ frontmatter.socialImageSrc = new URL(frontmatter.imageSrc, 'https://my-blog.com/').pathname;
+ };
+ }
+ ```
+
+ When using Content Collections, you can access this modified frontmatter using the `remarkPluginFrontmatter` property returned when rendering an entry.
+
+ **Migration instructions**
+
+ Plugin authors should now **check for user frontmatter when applying defaults.**
+
+ For example, say a remark plugin wants to apply a default `title` if none is present. Add a conditional to check if the property is present, and update if none exists:
+
+ ```diff
+ export function remarkInjectTitlePlugin() {
+ return function (tree, file) {
+ const { frontmatter } = file.data.astro;
+ + if (!frontmatter.title) {
+ frontmatter.title = 'Default title';
+ + }
+ }
+ }
+ ```
+
+ This differs from previous behavior, where a Markdown file's frontmatter would _always_ override frontmatter injected via remark or reype.
+
+- [#5891](https://github.com/withastro/astro/pull/5891) [`05caf445d`](https://github.com/withastro/astro/commit/05caf445d4d2728f1010aeb2179a9e756c2fd17d) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Remove deprecated Markdown APIs from Astro v0.X. This includes `getHeaders()`, the `.astro` property for layouts, and the `rawContent()` and `compiledContent()` error messages for MDX.
+
+- [#5782](https://github.com/withastro/astro/pull/5782) [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 14. Minimum supported Node version is now >=16.12.0
+
+- [#5825](https://github.com/withastro/astro/pull/5825) [`52209ca2a`](https://github.com/withastro/astro/commit/52209ca2ad72a30854947dcb3a90ab4db0ac0a6f) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Baseline the experimental `contentCollections` flag. You're free to remove this from your astro config!
+
+ ```diff
+ import { defineConfig } from 'astro/config';
+
+ export default defineConfig({
+ - experimental: { contentCollections: true }
+ })
+ ```
+
+### Patch Changes
+
+- [#5837](https://github.com/withastro/astro/pull/5837) [`12f65a4d5`](https://github.com/withastro/astro/commit/12f65a4d55e3fd2993c2f67b18794dd536280c69) Thanks [@giuseppelt](https://github.com/giuseppelt)! - fix shiki css class replace logic
+
+- [#5741](https://github.com/withastro/astro/pull/5741) [`000d3e694`](https://github.com/withastro/astro/commit/000d3e6940839c2aebba1984e6fb3b133cec6749) Thanks [@delucis](https://github.com/delucis)! - Fix broken links in README
+
+- Updated dependencies [[`93e633922`](https://github.com/withastro/astro/commit/93e633922c2e449df3bb2357b3683af1d3c0e07b), [`e2019be6f`](https://github.com/withastro/astro/commit/e2019be6ffa46fa33d92cfd346f9ecbe51bb7144), [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a), [`12f65a4d5`](https://github.com/withastro/astro/commit/12f65a4d55e3fd2993c2f67b18794dd536280c69), [`16107b6a1`](https://github.com/withastro/astro/commit/16107b6a10514ef1b563e585ec9add4b14f42b94), [`a9c292026`](https://github.com/withastro/astro/commit/a9c2920264e36cc5dc05f4adc1912187979edb0d), [`52209ca2a`](https://github.com/withastro/astro/commit/52209ca2ad72a30854947dcb3a90ab4db0ac0a6f), [`7572f7402`](https://github.com/withastro/astro/commit/7572f7402238da37de748be58d678fedaf863b53)]:
+ - @astrojs/markdown-remark@2.0.0
+ - @astrojs/prism@2.0.0
+
+## 1.0.0-beta.2
+
+<details>
+<summary>See changes in 1.0.0-beta.2</summary>
+
+### Major Changes
+
+- [#5825](https://github.com/withastro/astro/pull/5825) [`52209ca2a`](https://github.com/withastro/astro/commit/52209ca2ad72a30854947dcb3a90ab4db0ac0a6f) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Baseline the experimental `contentCollections` flag. You're free to remove this from your astro config!
+
+ ```diff
+ import { defineConfig } from 'astro/config';
+
+ export default defineConfig({
+ - experimental: { contentCollections: true }
+ })
+ ```
+
+### Minor Changes
+
+- [#5782](https://github.com/withastro/astro/pull/5782) [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 14. Minimum supported Node version is now >=16.12.0
+
+### Patch Changes
+
+- [#5837](https://github.com/withastro/astro/pull/5837) [`12f65a4d5`](https://github.com/withastro/astro/commit/12f65a4d55e3fd2993c2f67b18794dd536280c69) Thanks [@giuseppelt](https://github.com/giuseppelt)! - fix shiki css class replace logic
+
+- Updated dependencies [[`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a), [`12f65a4d5`](https://github.com/withastro/astro/commit/12f65a4d55e3fd2993c2f67b18794dd536280c69), [`16107b6a1`](https://github.com/withastro/astro/commit/16107b6a10514ef1b563e585ec9add4b14f42b94), [`52209ca2a`](https://github.com/withastro/astro/commit/52209ca2ad72a30854947dcb3a90ab4db0ac0a6f), [`7572f7402`](https://github.com/withastro/astro/commit/7572f7402238da37de748be58d678fedaf863b53)]:
+ - @astrojs/prism@2.0.0-beta.0
+ - @astrojs/markdown-remark@2.0.0-beta.2
+
+</details>
+
+## 0.15.0-beta.1
+
+<details>
+<summary>See changes in 0.15.0-beta.1</summary>
+
+### Minor Changes
+
+- [#5769](https://github.com/withastro/astro/pull/5769) [`93e633922`](https://github.com/withastro/astro/commit/93e633922c2e449df3bb2357b3683af1d3c0e07b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Introduce a `smartypants` flag to opt-out of Astro's default SmartyPants plugin.
+
+ ```js
+ {
+ markdown: {
+ smartypants: false,
+ }
+ }
+ ```
+
+ #### Migration
+
+ You may have disabled Astro's built-in plugins (GitHub-Flavored Markdown and Smartypants) with the `extendDefaultPlugins` option. This has now been split into 2 flags to disable each plugin individually:
+
+ - `markdown.gfm` to disable GitHub-Flavored Markdown
+ - `markdown.smartypants` to disable SmartyPants
+
+ ```diff
+ // astro.config.mjs
+ import { defineConfig } from 'astro/config';
+
+ export default defineConfig({
+ markdown: {
+ - extendDefaultPlugins: false,
+ + smartypants: false,
+ + gfm: false,
+ }
+ });
+ ```
+
+### Patch Changes
+
+- [#5741](https://github.com/withastro/astro/pull/5741) [`000d3e694`](https://github.com/withastro/astro/commit/000d3e6940839c2aebba1984e6fb3b133cec6749) Thanks [@delucis](https://github.com/delucis)! - Fix broken links in README
+
+- Updated dependencies [[`93e633922`](https://github.com/withastro/astro/commit/93e633922c2e449df3bb2357b3683af1d3c0e07b)]:
+ - @astrojs/markdown-remark@2.0.0-beta.1
+
+</details>
+
+## 0.15.0-beta.0
+
+<details>
+<summary>See changes in 0.15.0-beta.0</summary>
+
+### Minor Changes
+
+- [#5687](https://github.com/withastro/astro/pull/5687) [`e2019be6f`](https://github.com/withastro/astro/commit/e2019be6ffa46fa33d92cfd346f9ecbe51bb7144) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Give remark and rehype plugins access to user frontmatter via frontmatter injection. This means `data.astro.frontmatter` is now the _complete_ Markdown or MDX document's frontmatter, rather than an empty object.
+
+ This allows plugin authors to modify existing frontmatter, or compute new properties based on other properties. For example, say you want to compute a full image URL based on an `imageSrc` slug in your document frontmatter:
+
+ ```ts
+ export function remarkInjectSocialImagePlugin() {
+ return function (tree, file) {
+ const { frontmatter } = file.data.astro;
+ frontmatter.socialImageSrc = new URL(frontmatter.imageSrc, 'https://my-blog.com/').pathname;
+ };
+ }
+ ```
+
+ #### Content Collections - new `remarkPluginFrontmatter` property
+
+ We have changed _inject_ frontmatter to _modify_ frontmatter in our docs to improve discoverability. This is based on support forum feedback, where "injection" is rarely the term used.
+
+ To reflect this, the `injectedFrontmatter` property has been renamed to `remarkPluginFrontmatter`. This should clarify this plugin is still separate from the `data` export Content Collections expose today.
+
+ #### Migration instructions
+
+ Plugin authors should now **check for user frontmatter when applying defaults.**
+
+ For example, say a remark plugin wants to apply a default `title` if none is present. Add a conditional to check if the property is present, and update if none exists:
+
+ ```diff
+ export function remarkInjectTitlePlugin() {
+ return function (tree, file) {
+ const { frontmatter } = file.data.astro;
+ + if (!frontmatter.title) {
+ frontmatter.title = 'Default title';
+ + }
+ }
+ }
+ ```
+
+ This differs from previous behavior, where a Markdown file's frontmatter would _always_ override frontmatter injected via remark or reype.
+
+- [#5684](https://github.com/withastro/astro/pull/5684) [`a9c292026`](https://github.com/withastro/astro/commit/a9c2920264e36cc5dc05f4adc1912187979edb0d) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Refine Markdown and MDX configuration options for ease-of-use.
+
+ #### Markdown
+
+ - **Remove `remark-smartypants`** from Astro's default Markdown plugins.
+ - **Replace the `extendDefaultPlugins` option** with a simplified `gfm` boolean. This is enabled by default, and can be disabled to remove GitHub-Flavored Markdown.
+ - Ensure GitHub-Flavored Markdown is applied whether or not custom `remarkPlugins` or `rehypePlugins` are configured. If you want to apply custom plugins _and_ remove GFM, manually set `gfm: false` in your config.
+
+ #### MDX
+
+ - Support _all_ Markdown configuration options (except `drafts`) from your MDX integration config. This includes `syntaxHighlighting` and `shikiConfig` options to further customize the MDX renderer.
+ - Simplify `extendDefaults` to an `extendMarkdownConfig` option. MDX options will default to their equivalent in your Markdown config. By setting `extendMarkdownConfig` to false, you can "eject" to set your own syntax highlighting, plugins, and more.
+
+ #### Migration
+
+ To preserve your existing Markdown and MDX setup, you may need some configuration changes:
+
+ ##### Smartypants manual installation
+
+ [Smartypants](https://github.com/silvenon/remark-smartypants) has been removed from Astro's default setup. If you rely on this plugin, [install `remark-smartypants`](https://github.com/silvenon/remark-smartypants#installing) and apply to your `astro.config.*`:
+
+ ```diff
+ // astro.config.mjs
+ import { defineConfig } from 'astro/config';
+ + import smartypants from 'remark-smartypants';
+
+ export default defineConfig({
+ markdown: {
+ + remarkPlugins: [smartypants],
+ }
+ });
+ ```
+
+ ##### Migrate `extendDefaultPlugins` to `gfm`
+
+ You may have disabled Astro's built-in plugins (GitHub-Flavored Markdown and Smartypants) with the `extendDefaultPlugins` option. Since Smartypants has been removed, this has been renamed to `gfm`.
+
+ ```diff
+ // astro.config.mjs
+ import { defineConfig } from 'astro/config';
+
+ export default defineConfig({
+ markdown: {
+ - extendDefaultPlugins: false,
+ + gfm: false,
+ }
+ });
+ ```
+
+ Additionally, applying remark and rehype plugins **no longer disables** `gfm`. You will need to opt-out manually by setting `gfm` to `false`.
+
+ ##### Migrate MDX's `extendPlugins` to `extendMarkdownConfig`
+
+ You may have used the `extendPlugins` option to manage plugin defaults in MDX. This has been replaced by 2 flags:
+
+ - `extendMarkdownConfig` (`true` by default) to toggle Markdown config inheritance. This replaces the `extendPlugins: 'markdown'` option.
+ - `gfm` (`true` by default) to toggle GitHub-Flavored Markdown in MDX. This replaces the `extendPlugins: 'defaults'` option.
+
+### Patch Changes
+
+- Updated dependencies [[`e2019be6f`](https://github.com/withastro/astro/commit/e2019be6ffa46fa33d92cfd346f9ecbe51bb7144), [`a9c292026`](https://github.com/withastro/astro/commit/a9c2920264e36cc5dc05f4adc1912187979edb0d)]:
+ - @astrojs/markdown-remark@2.0.0-beta.0
+
+</details>
+
+## 0.14.0
+
+### Minor Changes
+
+- [#5654](https://github.com/withastro/astro/pull/5654) [`2c65b433b`](https://github.com/withastro/astro/commit/2c65b433bf840a1bb93b0a1947df5949e33512ff) Thanks [@delucis](https://github.com/delucis)! - Run heading ID injection after user plugins
+
+ ⚠️ BREAKING CHANGE ⚠️
+
+ If you are using a rehype plugin that depends on heading IDs injected by Astro, the IDs will no longer be available when your plugin runs by default.
+
+ To inject IDs before your plugins run, import and add the `rehypeHeadingIds` plugin to your `rehypePlugins` config:
+
+ ```diff
+ // astro.config.mjs
+ + import { rehypeHeadingIds } from '@astrojs/markdown-remark';
+ import mdx from '@astrojs/mdx';
+
+ export default {
+ integrations: [mdx()],
+ markdown: {
+ rehypePlugins: [
+ + rehypeHeadingIds,
+ otherPluginThatReliesOnHeadingIDs,
+ ],
+ },
+ }
+ ```
+
+### Patch Changes
+
+- [#5667](https://github.com/withastro/astro/pull/5667) [`a5ba4af79`](https://github.com/withastro/astro/commit/a5ba4af79930145f4edf66d45cd40ddad045cc86) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Chore: remove verbose "Now inheriting Markdown plugins..." logs
+
+- [#5648](https://github.com/withastro/astro/pull/5648) [`853081d1c`](https://github.com/withastro/astro/commit/853081d1c857d8ad8a9634c37ed8fd123d32d241) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Prevent relative image paths in `src/content/`
+
+- Updated dependencies [[`853081d1c`](https://github.com/withastro/astro/commit/853081d1c857d8ad8a9634c37ed8fd123d32d241), [`2c65b433b`](https://github.com/withastro/astro/commit/2c65b433bf840a1bb93b0a1947df5949e33512ff)]:
+ - @astrojs/markdown-remark@1.2.0
+
+## 0.13.0
+
+### Minor Changes
+
+- [#5291](https://github.com/withastro/astro/pull/5291) [`5ec0f6ed5`](https://github.com/withastro/astro/commit/5ec0f6ed55b0a14a9663a90a03428345baf126bd) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Introduce Content Collections experimental API
+ - Organize your Markdown and MDX content into easy-to-manage collections.
+ - Add type safety to your frontmatter with schemas.
+ - Generate landing pages, static routes, and SSR endpoints from your content using the collection query APIs.
+
+## 0.12.2
+
+### Patch Changes
+
+- [#5586](https://github.com/withastro/astro/pull/5586) [`f4ff69a3c`](https://github.com/withastro/astro/commit/f4ff69a3cd874c8804c6d01c7cbbaed8a8e90be7) Thanks [@delucis](https://github.com/delucis)! - Fix link in MDX integration README
+
+- [#5570](https://github.com/withastro/astro/pull/5570) [`3f811eb68`](https://github.com/withastro/astro/commit/3f811eb682d55bd1f908f9b4bc3b795d2859d713) Thanks [@sarah11918](https://github.com/sarah11918)! - Revise README
+
+## 0.12.1
+
+### Patch Changes
+
+- [#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
+
+## 0.12.0
+
+### Minor Changes
+
+- [#5427](https://github.com/withastro/astro/pull/5427) [`2a1c085b1`](https://github.com/withastro/astro/commit/2a1c085b199f24e34424ec8c19041c03602c53c5) Thanks [@backflip](https://github.com/backflip)! - Uses remark-rehype options from astro.config.mjs
+
+### Patch Changes
+
+- [#5448](https://github.com/withastro/astro/pull/5448) [`ef2ffc7ae`](https://github.com/withastro/astro/commit/ef2ffc7ae9ff554860238ecd2fb3bf6d82b5801b) Thanks [@delucis](https://github.com/delucis)! - Fix broken link in README
+
+## 0.11.6
+
+### Patch Changes
+
+- [#5335](https://github.com/withastro/astro/pull/5335) [`dca762cf7`](https://github.com/withastro/astro/commit/dca762cf734a657d8f126fd6958892b6163a4f67) Thanks [@bluwy](https://github.com/bluwy)! - Preserve code element node `data.meta` in `properties.metastring` for rehype syntax highlighters, like `rehype-pretty-code``
+
+## 0.11.5
+
+### Patch Changes
+
+- [#5146](https://github.com/withastro/astro/pull/5146) [`308e565ad`](https://github.com/withastro/astro/commit/308e565ad39957e3353d72ca5d3bbce1a1b45008) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support recmaPlugins config option
+
+## 0.11.4
+
+### Patch Changes
+
+- [#4953](https://github.com/withastro/astro/pull/4953) [`a59731995`](https://github.com/withastro/astro/commit/a59731995b93ae69c21dc3adc5c8b482b466d12e) Thanks [@bluwy](https://github.com/bluwy)! - Log markdown hints with console.info
+
+## 0.11.3
+
+### Patch Changes
+
+- [#4842](https://github.com/withastro/astro/pull/4842) [`812658ad2`](https://github.com/withastro/astro/commit/812658ad2ab3732a99e35c4fd903e302e723db46) Thanks [@bluwy](https://github.com/bluwy)! - Add missing dependencies, support strict dependency installation (e.g. pnpm)
+
+## 0.11.2
+
+### Patch Changes
+
+- [#4700](https://github.com/withastro/astro/pull/4700) [`e5f71142e`](https://github.com/withastro/astro/commit/e5f71142eb62bd72456e889dad5774347c3753f2) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Document MDXLayoutProps utility type
+
+- [#4858](https://github.com/withastro/astro/pull/4858) [`58a2dca22`](https://github.com/withastro/astro/commit/58a2dca2286cb14f6211cf51267c02447e78433a) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Correctly parse import.meta.env in MDX files
+
+## 0.11.1
+
+### Patch Changes
+
+- [#4588](https://github.com/withastro/astro/pull/4588) [`db38f61b2`](https://github.com/withastro/astro/commit/db38f61b2b2dc55f03b28797d19b163b1940f1c8) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: Add GFM and Smartypants to MDX by default
+
+## 0.11.0
+
+### Minor Changes
+
+- [#4504](https://github.com/withastro/astro/pull/4504) [`8f8dff4d3`](https://github.com/withastro/astro/commit/8f8dff4d339a3a12ee155d81a97132032ef3b622) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Introduce new `extendPlugins` configuration option. This defaults to inheriting all remark and rehype plugins from your `markdown` config, with options to use either Astro's defaults or no inheritance at all.
+
+## 0.10.3
+
+### Patch Changes
+
+- [#4519](https://github.com/withastro/astro/pull/4519) [`a2e8e76c3`](https://github.com/withastro/astro/commit/a2e8e76c303e8d6f39c24c122905a10f06907997) Thanks [@JuanM04](https://github.com/JuanM04)! - Upgraded Shiki to v0.11.1
+
+- [#4530](https://github.com/withastro/astro/pull/4530) [`8504cd79b`](https://github.com/withastro/astro/commit/8504cd79b708e0d3bf1a2bb4ff9b86936bdd692b) Thanks [@kylebutts](https://github.com/kylebutts)! - Add custom components to README
+
+## 0.10.2
+
+### Patch Changes
+
+- [#4423](https://github.com/withastro/astro/pull/4423) [`d4cd7a59f`](https://github.com/withastro/astro/commit/d4cd7a59fd38d411c442a818cfaab40f74106628) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Update Markdown type signature to match new markdown plugin,and update top-level layout props for better alignment
+
+## 0.10.2-next.0
+
+### Patch Changes
+
+- [#4423](https://github.com/withastro/astro/pull/4423) [`d4cd7a59f`](https://github.com/withastro/astro/commit/d4cd7a59fd38d411c442a818cfaab40f74106628) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Update Markdown type signature to match new markdown plugin,and update top-level layout props for better alignment
+
+## 0.10.1
+
+### Patch Changes
+
+- [#4443](https://github.com/withastro/astro/pull/4443) [`adb207979`](https://github.com/withastro/astro/commit/adb20797962c280d4d38f335f577fd52a1b48d4b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix MDX style imports when layout is not applied
+
+* [#4428](https://github.com/withastro/astro/pull/4428) [`a2414bf59`](https://github.com/withastro/astro/commit/a2414bf59e2e2cd633aece68e724401c4ad281b9) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix dev server reload performance when globbing from an MDX layout
+
+## 0.10.0
+
+### Minor Changes
+
+- [#4292](https://github.com/withastro/astro/pull/4292) [`f1a52c18a`](https://github.com/withastro/astro/commit/f1a52c18afe66e6d310743ae6884be76f69be265) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Switch from Shiki Twoslash to Astro's Shiki Markdown highlighter
+
+## 0.9.0
+
+### Minor Changes
+
+- [#4268](https://github.com/withastro/astro/pull/4268) [`f7afdb889`](https://github.com/withastro/astro/commit/f7afdb889fe4e97177958c8ec92f80c5f6e5cb51) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Align MD with MDX on layout props and "glob" import results:
+ - Add `Content` to MDX
+ - Add `file` and `url` to MDX frontmatter (layout import only)
+ - Update glob types to reflect differences (lack of `rawContent` and `compiledContent`)
+
+### Patch Changes
+
+- [#4272](https://github.com/withastro/astro/pull/4272) [`24d2f7a6e`](https://github.com/withastro/astro/commit/24d2f7a6e6700c10c863f826f37bb653d70e3a83) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Properly handle hydration for namespaced components
+
+## 0.8.3
+
+### Patch Changes
+
+- [#4248](https://github.com/withastro/astro/pull/4248) [`869d00935`](https://github.com/withastro/astro/commit/869d0093596b709cfcc1a1a95ee631b48d6d1c26) Thanks [@svemat01](https://github.com/svemat01)! - Load builtin rehype plugins before user plugins instead of after
+
+* [#4255](https://github.com/withastro/astro/pull/4255) [`411612808`](https://github.com/withastro/astro/commit/4116128082121ee276d51cb245bf8095be4728a1) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Pass injected frontmatter from remark and rehype plugins to layouts
+
+* Updated dependencies [[`1f0dd31d9`](https://github.com/withastro/astro/commit/1f0dd31d9239b5e3dca99c88d021e7a9a3e2054d)]:
+ - @astrojs/prism@1.0.1
+
+## 0.8.2
+
+### Patch Changes
+
+- [#4237](https://github.com/withastro/astro/pull/4237) [`9d5ab5508`](https://github.com/withastro/astro/commit/9d5ab55086964fbede17da3d78c209c6d8d13711) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Update "Astro.props.content" -> "Astro.props.frontmatter" in README
+
+## 0.8.1
+
+### Patch Changes
+
+- Updated dependencies [[`04ad44563`](https://github.com/withastro/astro/commit/04ad445632c67bdd60c1704e1e0dcbcaa27b9308)]:
+ - @astrojs/prism@1.0.0
+
+## 0.8.0
+
+### Minor Changes
+
+- [#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
+
+### Patch Changes
+
+- [#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
+
+## 0.7.0
+
+### Minor Changes
+
+- [#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
+
+### Patch Changes
+
+- [#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
+
+* [#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
+
+- [#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.
+
+## 0.6.0
+
+### Minor Changes
+
+- [#4134](https://github.com/withastro/astro/pull/4134) [`2968ba2b6`](https://github.com/withastro/astro/commit/2968ba2b6f00775b6e9872681b390cb466fdbfa2) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add `headings` and `frontmatter` properties to layout props
+
+## 0.5.0
+
+### Minor Changes
+
+- [#4095](https://github.com/withastro/astro/pull/4095) [`40ef43a59`](https://github.com/withastro/astro/commit/40ef43a59b08a1a8fbcd9f4a53745a9636a4fbb9) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add IDs to MDX headings and expose via getHeadings() export
+
+* [#4114](https://github.com/withastro/astro/pull/4114) [`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Refactor `@astrojs/mdx` and `@astrojs/markdown-remark` to use `@astrojs/prism` instead of duplicating the code
+
+### Patch Changes
+
+- [#4112](https://github.com/withastro/astro/pull/4112) [`e33fc9bc4`](https://github.com/withastro/astro/commit/e33fc9bc46ff0a30013deb6dc76e545e70cc3a3e) Thanks [@matthewp](https://github.com/matthewp)! - Fix MDX working with a ts config file
+
+* [#4049](https://github.com/withastro/astro/pull/4049) [`b60cc0538`](https://github.com/withastro/astro/commit/b60cc0538bc5c68dd411117780d20d892530789d) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve `injectScript` handling for non-Astro pages
+
+* Updated dependencies [[`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7)]:
+ - @astrojs/prism@0.7.0
+
+## 0.4.0
+
+### Minor Changes
+
+- [#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
+
+## 0.3.1
+
+### Patch Changes
+
+- [#4076](https://github.com/withastro/astro/pull/4076) [`6120a71e5`](https://github.com/withastro/astro/commit/6120a71e5425ad55a17ddac800d64a3f50273bce) Thanks [@matthewp](https://github.com/matthewp)! - Ensure file and url are always present in MDX for Astro.glob
+
+## 0.3.0
+
+### Minor Changes
+
+- [#3977](https://github.com/withastro/astro/pull/3977) [`19433eb4a`](https://github.com/withastro/astro/commit/19433eb4a4441522f68492ca914ad2ab4f061343) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add remarkPlugins and rehypePlugins to config, with the same default plugins as our standard Markdown parser
+
+* [#4002](https://github.com/withastro/astro/pull/4002) [`3b8a74452`](https://github.com/withastro/astro/commit/3b8a7445247221100462ba035f6778b43ea180e7) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support Prism and Shiki syntax highlighting based on project config
+
+- [#3995](https://github.com/withastro/astro/pull/3995) [`b2b367c96`](https://github.com/withastro/astro/commit/b2b367c969493aaf21c974064beb241d05228066) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support YAML frontmatter in MDX files
+
+### Patch Changes
+
+- [#4050](https://github.com/withastro/astro/pull/4050) [`9ab66c4ba`](https://github.com/withastro/astro/commit/9ab66c4ba9bf2250990114c76b792f26d0694365) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Add support for injected "page-ssr" scripts
+
+* [#3981](https://github.com/withastro/astro/pull/3981) [`61fec6304`](https://github.com/withastro/astro/commit/61fec63044e1585348e8405bee6fdf4dec635efa) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Include page url in MDX glob result
+
+## 0.2.1
+
+### Patch Changes
+
+- [#3937](https://github.com/withastro/astro/pull/3937) [`31f9c0bf0`](https://github.com/withastro/astro/commit/31f9c0bf029ffa4b470e620f2c32e1370643e81e) Thanks [@delucis](https://github.com/delucis)! - Roll back supported Node engines
+
+## 0.2.0
+
+### Minor Changes
+
+- [#3914](https://github.com/withastro/astro/pull/3914) [`b48767985`](https://github.com/withastro/astro/commit/b48767985359bd359df8071324952ea5f2bc0d86) Thanks [@ran-dall](https://github.com/ran-dall)! - Rollback supported `node@16` version. Minimum versions are now `node@14.20.0` or `node@16.14.0`.
+
+## 0.1.1
+
+### Patch Changes
+
+- [#3885](https://github.com/withastro/astro/pull/3885) [`bf5d1cc1e`](https://github.com/withastro/astro/commit/bf5d1cc1e71da38a14658c615e9481f2145cc6e7) Thanks [@delucis](https://github.com/delucis)! - Integration README fixes
+
+## 0.1.0
+
+### Minor Changes
+
+- [#3871](https://github.com/withastro/astro/pull/3871) [`1cc5b7890`](https://github.com/withastro/astro/commit/1cc5b78905633608e5b07ad291f916f54e67feb1) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Update supported `node` versions. Minimum versions are now `node@14.20.0` or `node@16.16.0`.
+
+## 0.0.3
+
+### Patch Changes
+
+- [#3854](https://github.com/withastro/astro/pull/3854) [`b012ee55`](https://github.com/withastro/astro/commit/b012ee55b107dea0730286263b27d83e530fad5d) Thanks [@bholmesdev](https://github.com/bholmesdev)! - [astro add] Support adapters and third party packages
+
+## 0.0.2
+
+### Patch Changes
+
+- [#3706](https://github.com/withastro/astro/pull/3706) [`032ad1c0`](https://github.com/withastro/astro/commit/032ad1c047a62dd663067cc562537d16f2872aa7) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Initial release! 🎉
diff --git a/packages/integrations/mdx/README.md b/packages/integrations/mdx/README.md
new file mode 100644
index 000000000..738d55265
--- /dev/null
+++ b/packages/integrations/mdx/README.md
@@ -0,0 +1,38 @@
+# @astrojs/mdx 📝
+
+This **[Astro integration][astro-integration]** enables the usage of [MDX](https://mdxjs.com/) components and allows you to create pages as `.mdx` files.
+
+## Documentation
+
+Read the [`@astrojs/mdx` docs][docs]
+
+## Support
+
+- Get help in the [Astro Discord][discord]. Post questions in our `#support` forum, or visit our dedicated `#dev` channel to discuss current development and more!
+
+- Check our [Astro Integration Documentation][astro-integration] for more on integrations.
+
+- Submit bug reports and feature requests as [GitHub issues][issues].
+
+## Contributing
+
+This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! These links will help you get started:
+
+- [Contributor Manual][contributing]
+- [Code of Conduct][coc]
+- [Community Guide][community]
+
+## License
+
+MIT
+
+Copyright (c) 2023–present [Astro][astro]
+
+[astro]: https://astro.build/
+[docs]: https://docs.astro.build/en/guides/integrations-guide/mdx/
+[contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md
+[coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md
+[community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md
+[discord]: https://astro.build/chat/
+[issues]: https://github.com/withastro/astro/issues
+[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
new file mode 100644
index 000000000..6957bf8d1
--- /dev/null
+++ b/packages/integrations/mdx/package.json
@@ -0,0 +1,80 @@
+{
+ "name": "@astrojs/mdx",
+ "description": "Add support for MDX pages in your Astro site",
+ "version": "4.3.0",
+ "type": "module",
+ "types": "./dist/index.d.ts",
+ "author": "withastro",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/withastro/astro.git",
+ "directory": "packages/integrations/mdx"
+ },
+ "keywords": [
+ "astro-integration",
+ "astro-component",
+ "mdx"
+ ],
+ "bugs": "https://github.com/withastro/astro/issues",
+ "homepage": "https://docs.astro.build/en/guides/integrations-guide/mdx/",
+ "exports": {
+ ".": "./dist/index.js",
+ "./server.js": "./dist/server.js",
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "dist",
+ "template"
+ ],
+ "scripts": {
+ "build": "astro-scripts build \"src/**/*.ts\" && tsc",
+ "build:ci": "astro-scripts build \"src/**/*.ts\"",
+ "dev": "astro-scripts dev \"src/**/*.ts\"",
+ "test": "astro-scripts test --timeout 70000 \"test/**/*.test.js\""
+ },
+ "dependencies": {
+ "@astrojs/markdown-remark": "workspace:*",
+ "@mdx-js/mdx": "^3.1.0",
+ "acorn": "^8.14.1",
+ "es-module-lexer": "^1.6.0",
+ "estree-util-visit": "^2.0.0",
+ "hast-util-to-html": "^9.0.5",
+ "kleur": "^4.1.5",
+ "rehype-raw": "^7.0.0",
+ "remark-gfm": "^4.0.1",
+ "remark-smartypants": "^3.0.2",
+ "source-map": "^0.7.4",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.3"
+ },
+ "peerDependencies": {
+ "astro": "^5.0.0"
+ },
+ "devDependencies": {
+ "@types/estree": "^1.0.7",
+ "@types/hast": "^3.0.4",
+ "@types/mdast": "^4.0.4",
+ "astro": "workspace:*",
+ "astro-scripts": "workspace:*",
+ "cheerio": "1.0.0",
+ "linkedom": "^0.18.9",
+ "mdast-util-mdx": "^3.0.0",
+ "mdast-util-mdx-jsx": "^3.2.0",
+ "rehype-mathjax": "^7.1.0",
+ "rehype-pretty-code": "^0.14.1",
+ "remark-math": "^6.0.0",
+ "remark-rehype": "^11.1.1",
+ "remark-shiki-twoslash": "^3.1.3",
+ "remark-toc": "^9.0.0",
+ "shiki": "^3.2.1",
+ "unified": "^11.0.5",
+ "vite": "^6.3.4"
+ },
+ "engines": {
+ "node": "18.20.8 || ^20.3.0 || >=22.0.0"
+ },
+ "publishConfig": {
+ "provenance": true
+ }
+}
diff --git a/packages/integrations/mdx/src/README.md b/packages/integrations/mdx/src/README.md
new file mode 100644
index 000000000..5c01ce755
--- /dev/null
+++ b/packages/integrations/mdx/src/README.md
@@ -0,0 +1,124 @@
+# Internal documentation
+
+## rehype-optimize-static
+
+The `rehype-optimize-static` plugin helps optimize the intermediate [`hast`](https://github.com/syntax-tree/hast) when processing MDX, collapsing static subtrees of the `hast` as a `"static string"` in the final JSX output. Here's a "before" and "after" result:
+
+Before:
+
+```jsx
+function _createMdxContent() {
+ return (
+ <>
+ <h1>My MDX Content</h1>
+ <pre>
+ <code class="language-js">
+ <span class="token function">console</span>
+ <span class="token punctuation">.</span>
+ <span class="token function">log</span>
+ <span class="token punctuation">(</span>
+ <span class="token string">'hello world'</span>
+ <span class="token punctuation">)</span>
+ </code>
+ </pre>
+ </>
+ );
+}
+```
+
+After:
+
+```jsx
+function _createMdxContent() {
+ return <Fragment set:html="<h1>My MDX Content</h1>\n<code class=...</code>" />;
+}
+```
+
+> NOTE: If one of the nodes in `pre` is MDX, the optimization will not be applied to `pre`, but could be applied to the inner MDX node if its children are static.
+
+This results in fewer JSX nodes, less compiled JS output, and less parsed AST, which results in faster Rollup builds and runtime rendering.
+
+To achieve this, we use an algorithm to detect `hast` subtrees that are entirely static (containing no JSX) to be inlined as `set:html` to the root of the subtree.
+
+The next section explains the algorithm, which you can follow along by pairing with the [source code](./rehype-optimize-static.ts). To analyze the `hast`, you can paste the MDX code into https://mdxjs.com/playground.
+
+### How it works
+
+The flow can be divided into a "scan phase" and a "mutation phase". The scan phase searches for nodes that can be optimized, and the mutation phase applies the optimization on the `hast` nodes.
+
+#### Scan phase
+
+Variables:
+
+- `allPossibleElements`: A set of subtree roots where we can add a new `set:html` property with its children as value.
+- `elementStack`: The stack of elements (that could be subtree roots) while traversing the `hast` (node ancestors).
+- `elementMetadatas`: A weak map to store the metadata used only by the mutation phase later.
+
+Flow:
+
+1. Walk the `hast` tree.
+2. For each `node` we enter, if the `node` is static (`type` is `element` or starts with `mdx`), record in `allPossibleElements` and push to `elementStack`. We also record additional metadata in `elementMetadatas` for the mutation phase later.
+ - Q: Why do we record `mdxJsxFlowElement`, it's MDX? <br>
+ A: Because we're looking for nodes whose children are static. The node itself doesn't need to be static.
+ - Q: Are we sure this is the subtree root node in `allPossibleElements`? <br>
+ A: No, but we'll clear that up later in step 3.
+3. For each `node` we leave, pop from `elementStack`. If the `node`'s parent is in `allPossibleElements`, we also remove the `node` from `allPossibleElements`.
+ - Q: Why do we check for the node's parent? <br>
+ A: Checking for the node's parent allows us to identify a subtree root. When we enter a subtree like `C -> D -> E`, we leave in reverse: `E -> D -> C`. When we leave `E`, we see that it's parent `D` exists, so we remove `E`. When we leave `D`, we see `C` exists, so we remove `D`. When we leave `C`, we see that its parent doesn't exist, so we keep `C`, a subtree root.
+4. _(Returning to the code written for step 2's `node` enter handling)_ We also need to handle the case where we find non-static elements. If found, we remove all the elements in `elementStack` from `allPossibleElements`. This happens before the code in step 2.
+ - Q: Why? <br>
+ A: Because if the `node` isn't static, that means all its ancestors (`elementStack`) have non-static children. So, the ancestors couldn't be a subtree root to be optimized anymore.
+ - Q: Why before step 2's `node` enter handling? <br>
+ A: If we find a non-static `node`, the `node` should still be considered in `allPossibleElements` as its children could be static.
+5. Walk done. This leaves us with `allPossibleElements` containing only subtree roots that can be optimized.
+6. Proceed to the mutation phase.
+
+#### Mutation phase
+
+Inputs:
+
+- `allPossibleElements` from the scan phase.
+- `elementMetadatas` from the scan phase.
+
+Flow:
+
+1. Before we mutate the `hast` tree, each element in `allPossibleElements` may have siblings that can be optimized together. Sibling elements are grouped with the `findElementGroups()` function, which returns an array of element groups (new variable `elementGroups`) and mutates `allPossibleElements` to remove elements that are already part of a group.
+
+ - Q: How does `findElementGroups()` work? <br>
+ A: For each elements in `allPossibleElements` that are non-static, we're able to take the element metadata from `elementMetadatas` and guess the next sibling node. If the next sibling node is static and is an element in `allPossibleElements`, we group them together for optimization. It continues to guess until it hits a non-static node or an element not in `allPossibleElements`, which it'll finalize the group as part of the returned result.
+
+2. For each elements in `allPossibleElements`, we serialize them as HTML and add it to the `set:html` property of the `hast` node, and remove its children.
+3. For each element group in `elementGroups`, we serialize the group children as HTML and add it to a new `<Fragment set:html="..." />` node, and replace the group children with the new `<Fragment />` node.
+4. 🎉 The rest of the MDX pipeline will do its thing and generate the desired JSX like above.
+
+### Extra
+
+#### MDX custom components
+
+Astro's MDX implementation supports specifying `export const components` in the MDX file to render some HTML elements as Astro components or framework components. `rehype-optimize-static` also needs to parse this JS to recognize some elements as non-static.
+
+#### Further optimizations
+
+In [Scan phase](#scan-phase) step 4,
+
+> we remove all the elements in `elementStack` from `allPossibleElements`
+
+We can further optimize this by then also emptying the `elementStack`. This ensures that if we run this same flow for a deeper node in the tree, we don't remove the already-removed nodes from `allPossibleElements`.
+
+While this breaks the concept of `elementStack`, it doesn't matter as the `elementStack` array pop in the "leave" handler (in step 3) would become a no-op.
+
+Example `elementStack` value during walking phase:
+
+```
+Enter: A
+Enter: A, B
+Enter: A, B, C
+(Non-static node found): <empty>
+Enter: D
+Enter: D, E
+Leave: D
+Leave: <empty>
+Leave: <empty>
+Leave: <empty>
+Leave: <empty>
+```
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts
new file mode 100644
index 000000000..fe2cbde0b
--- /dev/null
+++ b/packages/integrations/mdx/src/index.ts
@@ -0,0 +1,152 @@
+import fs from 'node:fs/promises';
+import { fileURLToPath } from 'node:url';
+import { markdownConfigDefaults } from '@astrojs/markdown-remark';
+import type {
+ AstroIntegration,
+ AstroIntegrationLogger,
+ ContainerRenderer,
+ ContentEntryType,
+ HookParameters,
+} from 'astro';
+import type { Options as RemarkRehypeOptions } from 'remark-rehype';
+import type { PluggableList } from 'unified';
+import type { OptimizeOptions } from './rehype-optimize-static.js';
+import { ignoreStringPlugins, safeParseFrontmatter } from './utils.js';
+import { vitePluginMdxPostprocess } from './vite-plugin-mdx-postprocess.js';
+import { type VitePluginMdxOptions, vitePluginMdx } from './vite-plugin-mdx.js';
+
+export type MdxOptions = Omit<typeof markdownConfigDefaults, 'remarkPlugins' | 'rehypePlugins'> & {
+ extendMarkdownConfig: boolean;
+ recmaPlugins: PluggableList;
+ // Markdown allows strings as remark and rehype plugins.
+ // This is not supported by the MDX compiler, so override types here.
+ remarkPlugins: PluggableList;
+ rehypePlugins: PluggableList;
+ remarkRehype: RemarkRehypeOptions;
+ optimize: boolean | OptimizeOptions;
+};
+
+type SetupHookParams = HookParameters<'astro:config:setup'> & {
+ // `addPageExtension` and `contentEntryType` are not a public APIs
+ // Add type defs here
+ addPageExtension: (extension: string) => void;
+ addContentEntryType: (contentEntryType: ContentEntryType) => void;
+};
+
+export function getContainerRenderer(): ContainerRenderer {
+ return {
+ name: 'astro:jsx',
+ serverEntrypoint: '@astrojs/mdx/server.js',
+ };
+}
+
+export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroIntegration {
+ // @ts-expect-error Temporarily assign an empty object here, which will be re-assigned by the
+ // `astro:config:done` hook later. This is so that `vitePluginMdx` can get hold of a reference earlier.
+ let vitePluginMdxOptions: VitePluginMdxOptions = {};
+
+ return {
+ name: '@astrojs/mdx',
+ hooks: {
+ 'astro:config:setup': async (params) => {
+ const { updateConfig, config, addPageExtension, addContentEntryType, addRenderer } =
+ params as SetupHookParams;
+
+ addRenderer({
+ name: 'astro:jsx',
+ serverEntrypoint: new URL('../dist/server.js', import.meta.url),
+ });
+ addPageExtension('.mdx');
+ addContentEntryType({
+ extensions: ['.mdx'],
+ async getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
+ const parsed = safeParseFrontmatter(contents, fileURLToPath(fileUrl));
+ return {
+ data: parsed.frontmatter,
+ body: parsed.content.trim(),
+ slug: parsed.frontmatter.slug,
+ rawData: parsed.rawFrontmatter,
+ };
+ },
+ contentModuleTypes: await fs.readFile(
+ new URL('../template/content-module-types.d.ts', import.meta.url),
+ 'utf-8',
+ ),
+ // MDX can import scripts and styles,
+ // so wrap all MDX files with script / style propagation checks
+ handlePropagation: true,
+ });
+
+ updateConfig({
+ vite: {
+ plugins: [vitePluginMdx(vitePluginMdxOptions), vitePluginMdxPostprocess(config)],
+ },
+ });
+ },
+ 'astro:config:done': ({ config, logger }) => {
+ // We resolve the final MDX options here so that other integrations have a chance to modify
+ // `config.markdown` before we access it
+ const extendMarkdownConfig =
+ partialMdxOptions.extendMarkdownConfig ?? defaultMdxOptions.extendMarkdownConfig;
+
+ const resolvedMdxOptions = applyDefaultOptions({
+ options: partialMdxOptions,
+ defaults: markdownConfigToMdxOptions(
+ extendMarkdownConfig ? config.markdown : markdownConfigDefaults,
+ logger,
+ ),
+ });
+
+ // Mutate `mdxOptions` so that `vitePluginMdx` can reference the actual options
+ Object.assign(vitePluginMdxOptions, {
+ mdxOptions: resolvedMdxOptions,
+ srcDir: config.srcDir,
+ experimentalHeadingIdCompat: config.experimental.headingIdCompat,
+ });
+ // @ts-expect-error After we assign, we don't need to reference `mdxOptions` in this context anymore.
+ // Re-assign it so that the garbage can be collected later.
+ vitePluginMdxOptions = {};
+ },
+ },
+ };
+}
+
+const defaultMdxOptions = {
+ extendMarkdownConfig: true,
+ recmaPlugins: [],
+ optimize: false,
+} satisfies Partial<MdxOptions>;
+
+function markdownConfigToMdxOptions(
+ markdownConfig: typeof markdownConfigDefaults,
+ logger: AstroIntegrationLogger,
+): MdxOptions {
+ return {
+ ...defaultMdxOptions,
+ ...markdownConfig,
+ remarkPlugins: ignoreStringPlugins(markdownConfig.remarkPlugins, logger),
+ rehypePlugins: ignoreStringPlugins(markdownConfig.rehypePlugins, logger),
+ remarkRehype: (markdownConfig.remarkRehype as any) ?? {},
+ };
+}
+
+function applyDefaultOptions({
+ options,
+ defaults,
+}: {
+ options: Partial<MdxOptions>;
+ defaults: MdxOptions;
+}): MdxOptions {
+ return {
+ syntaxHighlight: options.syntaxHighlight ?? defaults.syntaxHighlight,
+ extendMarkdownConfig: options.extendMarkdownConfig ?? defaults.extendMarkdownConfig,
+ recmaPlugins: options.recmaPlugins ?? defaults.recmaPlugins,
+ remarkRehype: options.remarkRehype ?? defaults.remarkRehype,
+ gfm: options.gfm ?? defaults.gfm,
+ smartypants: options.smartypants ?? defaults.smartypants,
+ remarkPlugins: options.remarkPlugins ?? defaults.remarkPlugins,
+ rehypePlugins: options.rehypePlugins ?? defaults.rehypePlugins,
+ shikiConfig: options.shikiConfig ?? defaults.shikiConfig,
+ optimize: options.optimize ?? defaults.optimize,
+ };
+}
diff --git a/packages/integrations/mdx/src/plugins.ts b/packages/integrations/mdx/src/plugins.ts
new file mode 100644
index 000000000..c44c8fffd
--- /dev/null
+++ b/packages/integrations/mdx/src/plugins.ts
@@ -0,0 +1,111 @@
+import {
+ rehypeHeadingIds,
+ rehypePrism,
+ rehypeShiki,
+ remarkCollectImages,
+} from '@astrojs/markdown-remark';
+import { createProcessor, nodeTypes } from '@mdx-js/mdx';
+import { rehypeAnalyzeAstroMetadata } from 'astro/jsx/rehype.js';
+import rehypeRaw from 'rehype-raw';
+import remarkGfm from 'remark-gfm';
+import remarkSmartypants from 'remark-smartypants';
+import { SourceMapGenerator } from 'source-map';
+import type { PluggableList } from 'unified';
+import type { MdxOptions } from './index.js';
+import { rehypeApplyFrontmatterExport } from './rehype-apply-frontmatter-export.js';
+import { rehypeInjectHeadingsExport } from './rehype-collect-headings.js';
+import { rehypeImageToComponent } from './rehype-images-to-component.js';
+import rehypeMetaString from './rehype-meta-string.js';
+import { rehypeOptimizeStatic } from './rehype-optimize-static.js';
+
+// Skip nonessential plugins during performance benchmark runs
+const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
+
+interface MdxProcessorExtraOptions {
+ sourcemap: boolean;
+ experimentalHeadingIdCompat: boolean;
+}
+
+export function createMdxProcessor(mdxOptions: MdxOptions, extraOptions: MdxProcessorExtraOptions) {
+ return createProcessor({
+ remarkPlugins: getRemarkPlugins(mdxOptions),
+ rehypePlugins: getRehypePlugins(mdxOptions, extraOptions),
+ recmaPlugins: mdxOptions.recmaPlugins,
+ remarkRehypeOptions: mdxOptions.remarkRehype,
+ jsxImportSource: 'astro',
+ // Note: disable `.md` (and other alternative extensions for markdown files like `.markdown`) support
+ format: 'mdx',
+ mdExtensions: [],
+ elementAttributeNameCase: 'html',
+ SourceMapGenerator: extraOptions.sourcemap ? SourceMapGenerator : undefined,
+ });
+}
+
+function getRemarkPlugins(mdxOptions: MdxOptions): PluggableList {
+ let remarkPlugins: PluggableList = [];
+
+ if (!isPerformanceBenchmark) {
+ if (mdxOptions.gfm) {
+ remarkPlugins.push(remarkGfm);
+ }
+ if (mdxOptions.smartypants) {
+ remarkPlugins.push(remarkSmartypants);
+ }
+ }
+
+ remarkPlugins.push(...mdxOptions.remarkPlugins, remarkCollectImages);
+
+ return remarkPlugins;
+}
+
+function getRehypePlugins(
+ mdxOptions: MdxOptions,
+ { experimentalHeadingIdCompat }: MdxProcessorExtraOptions,
+): PluggableList {
+ let rehypePlugins: PluggableList = [
+ // ensure `data.meta` is preserved in `properties.metastring` for rehype syntax highlighters
+ rehypeMetaString,
+ // rehypeRaw allows custom syntax highlighters to work without added config
+ [rehypeRaw, { passThrough: nodeTypes }],
+ ];
+
+ const syntaxHighlight = mdxOptions.syntaxHighlight;
+ if (syntaxHighlight && !isPerformanceBenchmark) {
+ const syntaxHighlightType =
+ typeof syntaxHighlight === 'string' ? syntaxHighlight : syntaxHighlight?.type;
+ const excludeLangs =
+ typeof syntaxHighlight === 'object' ? syntaxHighlight?.excludeLangs : undefined;
+ // Apply syntax highlighters after user plugins to match `markdown/remark` behavior
+ if (syntaxHighlightType === 'shiki') {
+ rehypePlugins.push([rehypeShiki, mdxOptions.shikiConfig, excludeLangs]);
+ } else if (syntaxHighlightType === 'prism') {
+ rehypePlugins.push([rehypePrism, excludeLangs]);
+ }
+ }
+
+ rehypePlugins.push(...mdxOptions.rehypePlugins, rehypeImageToComponent);
+
+ if (!isPerformanceBenchmark) {
+ // getHeadings() is guaranteed by TS, so this must be included.
+ // We run `rehypeHeadingIds` _last_ to respect any custom IDs set by user plugins.
+ rehypePlugins.push(
+ [rehypeHeadingIds, { experimentalHeadingIdCompat }],
+ rehypeInjectHeadingsExport,
+ );
+ }
+
+ rehypePlugins.push(
+ // Render info from `vfile.data.astro.frontmatter` as JS
+ rehypeApplyFrontmatterExport,
+ // Analyze MDX nodes and attach to `vfile.data.__astroMetadata`
+ rehypeAnalyzeAstroMetadata,
+ );
+
+ if (mdxOptions.optimize) {
+ // Convert user `optimize` option to compatible `rehypeOptimizeStatic` option
+ const options = mdxOptions.optimize === true ? undefined : mdxOptions.optimize;
+ rehypePlugins.push([rehypeOptimizeStatic, options]);
+ }
+
+ return rehypePlugins;
+}
diff --git a/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts b/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts
new file mode 100644
index 000000000..5880c30b3
--- /dev/null
+++ b/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts
@@ -0,0 +1,113 @@
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { isFrontmatterValid } from '@astrojs/markdown-remark';
+import type { Root, RootContent } from 'hast';
+import type { VFile } from 'vfile';
+import { jsToTreeNode } from './utils.js';
+
+// Passed metadata to help determine adding charset utf8 by default
+declare module 'vfile' {
+ interface DataMap {
+ applyFrontmatterExport?: {
+ srcDir?: URL;
+ };
+ }
+}
+
+const exportConstPartialTrueRe = /export\s+const\s+partial\s*=\s*true/;
+
+export function rehypeApplyFrontmatterExport() {
+ return function (tree: Root, vfile: VFile) {
+ const frontmatter = vfile.data.astro?.frontmatter;
+ if (!frontmatter || !isFrontmatterValid(frontmatter))
+ throw new Error(
+ // Copied from Astro core `errors-data`
+ // TODO: find way to import error data from core
+ '[MDX] A remark or rehype plugin attempted to inject invalid frontmatter. Ensure "astro.frontmatter" is set to a valid JSON object that is not `null` or `undefined`.',
+ );
+ const extraChildren: RootContent[] = [
+ jsToTreeNode(`export const frontmatter = ${JSON.stringify(frontmatter)};`),
+ ];
+ if (frontmatter.layout) {
+ extraChildren.unshift(
+ jsToTreeNode(
+ // NOTE: Use `__astro_*` import names to prevent conflicts with user code
+ /** @see 'vite-plugin-markdown' for layout props reference */
+ `\
+import { jsx as __astro_layout_jsx__ } from 'astro/jsx-runtime';
+import __astro_layout_component__ from ${JSON.stringify(frontmatter.layout)};
+
+export default function ({ children }) {
+ const { layout, ...content } = frontmatter;
+ content.file = file;
+ content.url = url;
+ return __astro_layout_jsx__(__astro_layout_component__, {
+ file,
+ url,
+ content,
+ frontmatter: content,
+ headings: getHeadings(),
+ 'server:root': true,
+ children,
+ });
+};`,
+ ),
+ );
+ } else if (shouldAddCharset(tree, vfile)) {
+ extraChildren.unshift({
+ type: 'mdxJsxFlowElement',
+ name: 'meta',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'charset',
+ value: 'utf-8',
+ },
+ ],
+ children: [],
+ });
+ }
+ tree.children = extraChildren.concat(tree.children);
+ };
+}
+
+/**
+ * If this is a page (e.g. in src/pages), has no layout frontmatter (handled before calling this function),
+ * has no leading component that looks like a wrapping layout, and `partial` isn't set to true, we default to
+ * adding charset=utf-8 like markdown so that users don't have to worry about it for MDX pages without layouts.
+ */
+function shouldAddCharset(tree: Root, vfile: VFile) {
+ const srcDirUrl = vfile.data.applyFrontmatterExport?.srcDir;
+ if (!srcDirUrl) return false;
+
+ const hasConstPartialTrue = tree.children.some(
+ (node) => node.type === 'mdxjsEsm' && exportConstPartialTrueRe.test(node.value),
+ );
+ if (hasConstPartialTrue) return false;
+
+ // NOTE: the pages directory is a non-configurable Astro behaviour
+ const pagesDir = path.join(fileURLToPath(srcDirUrl), 'pages').replace(/\\/g, '/');
+ // `vfile.path` comes from Vite, which is a normalized path (no backslashes)
+ const filePath = vfile.path;
+ if (!filePath.startsWith(pagesDir)) return false;
+
+ const hasLeadingUnderscoreInPath = filePath
+ .slice(pagesDir.length)
+ .replace(/\\/g, '/')
+ .split('/')
+ .some((part) => part.startsWith('_'));
+ if (hasLeadingUnderscoreInPath) return false;
+
+ // Bail if the first content found is a wrapping layout component
+ for (const child of tree.children) {
+ if (child.type === 'element') break;
+ if (child.type === 'mdxJsxFlowElement') {
+ // If is fragment or lowercase tag name (html tags), skip and assume there's no layout
+ if (child.name == null) break;
+ if (child.name[0] === child.name[0].toLowerCase()) break;
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/packages/integrations/mdx/src/rehype-collect-headings.ts b/packages/integrations/mdx/src/rehype-collect-headings.ts
new file mode 100644
index 000000000..a51e8e9f0
--- /dev/null
+++ b/packages/integrations/mdx/src/rehype-collect-headings.ts
@@ -0,0 +1,11 @@
+import type { VFile } from 'vfile';
+import { jsToTreeNode } from './utils.js';
+
+export function rehypeInjectHeadingsExport() {
+ return function (tree: any, file: VFile) {
+ const headings = file.data.astro?.headings ?? [];
+ tree.children.unshift(
+ jsToTreeNode(`export function getHeadings() { return ${JSON.stringify(headings)} }`),
+ );
+ };
+}
diff --git a/packages/integrations/mdx/src/rehype-images-to-component.ts b/packages/integrations/mdx/src/rehype-images-to-component.ts
new file mode 100644
index 000000000..6676ee323
--- /dev/null
+++ b/packages/integrations/mdx/src/rehype-images-to-component.ts
@@ -0,0 +1,188 @@
+import type { Properties, Root } from 'hast';
+import type { MdxJsxAttribute, MdxjsEsm } from 'mdast-util-mdx';
+import type { MdxJsxFlowElementHast } from 'mdast-util-mdx-jsx';
+import { visit } from 'unist-util-visit';
+import type { VFile } from 'vfile';
+import { jsToTreeNode } from './utils.js';
+
+export const ASTRO_IMAGE_ELEMENT = 'astro-image';
+export const ASTRO_IMAGE_IMPORT = '__AstroImage__';
+export const USES_ASTRO_IMAGE_FLAG = '__usesAstroImage';
+
+function createArrayAttribute(name: string, values: (string | number)[]): MdxJsxAttribute {
+ return {
+ type: 'mdxJsxAttribute',
+ name: name,
+ value: {
+ type: 'mdxJsxAttributeValueExpression',
+ value: name,
+ data: {
+ estree: {
+ type: 'Program',
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'ArrayExpression',
+ elements: values.map((value) => ({
+ type: 'Literal',
+ value: value,
+ raw: String(value),
+ })),
+ },
+ },
+ ],
+ sourceType: 'module',
+ comments: [],
+ },
+ },
+ },
+ };
+}
+
+/**
+ * Convert the <img /> element properties (except `src`) to MDX JSX attributes.
+ *
+ * @param {Properties} props - The element properties
+ * @returns {MdxJsxAttribute[]} The MDX attributes
+ */
+function getImageComponentAttributes(props: Properties): MdxJsxAttribute[] {
+ const attrs: MdxJsxAttribute[] = [];
+
+ for (const [prop, value] of Object.entries(props)) {
+ if (prop === 'src') continue;
+
+ /*
+ * <Image /> component expects an array for those attributes but the
+ * received properties are sanitized as strings. So we need to convert them
+ * back to an array.
+ */
+ if (prop === 'widths' || prop === 'densities') {
+ attrs.push(createArrayAttribute(prop, String(value).split(' ')));
+ } else {
+ attrs.push({
+ name: prop,
+ type: 'mdxJsxAttribute',
+ value: String(value),
+ });
+ }
+ }
+
+ return attrs;
+}
+
+export function rehypeImageToComponent() {
+ return function (tree: Root, file: VFile) {
+ if (!file.data.astro?.localImagePaths?.length && !file.data.astro?.remoteImagePaths?.length)
+ return;
+ const importsStatements: MdxjsEsm[] = [];
+ const importedImages = new Map<string, string>();
+
+ visit(tree, 'element', (node, index, parent) => {
+ if (node.tagName !== 'img' || !node.properties.src) return;
+
+ const src = decodeURI(String(node.properties.src));
+
+ const isLocalImage = file.data.astro?.localImagePaths?.includes(src);
+ const isRemoteImage = file.data.astro?.remoteImagePaths?.includes(src);
+
+ let element: MdxJsxFlowElementHast;
+ if (isLocalImage) {
+ let importName = importedImages.get(src);
+
+ if (!importName) {
+ importName = `__${importedImages.size}_${src.replace(/\W/g, '_')}__`;
+
+ importsStatements.push({
+ type: 'mdxjsEsm',
+ value: '',
+ data: {
+ estree: {
+ type: 'Program',
+ sourceType: 'module',
+ body: [
+ {
+ attributes: [],
+ type: 'ImportDeclaration',
+ source: {
+ type: 'Literal',
+ value: src,
+ raw: JSON.stringify(src),
+ },
+ specifiers: [
+ {
+ type: 'ImportDefaultSpecifier',
+ local: { type: 'Identifier', name: importName },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ });
+ importedImages.set(src, importName);
+ }
+
+ // Build a component that's equivalent to <Image src={importName} {...attributes} />
+ element = {
+ name: ASTRO_IMAGE_ELEMENT,
+ type: 'mdxJsxFlowElement',
+ attributes: [
+ ...getImageComponentAttributes(node.properties),
+ {
+ name: 'src',
+ type: 'mdxJsxAttribute',
+ value: {
+ type: 'mdxJsxAttributeValueExpression',
+ value: importName,
+ data: {
+ estree: {
+ type: 'Program',
+ sourceType: 'module',
+ comments: [],
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: { type: 'Identifier', name: importName },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ children: [],
+ };
+ } else if (isRemoteImage) {
+ // Build a component that's equivalent to <Image src={url} {...attributes} />
+ element = {
+ name: ASTRO_IMAGE_ELEMENT,
+ type: 'mdxJsxFlowElement',
+ attributes: [
+ ...getImageComponentAttributes(node.properties),
+ {
+ name: 'src',
+ type: 'mdxJsxAttribute',
+ value: src,
+ },
+ ],
+ children: [],
+ };
+ } else {
+ return;
+ }
+
+ parent!.children.splice(index!, 1, element);
+ });
+
+ // Add all the import statements to the top of the file for the images
+ tree.children.unshift(...importsStatements);
+
+ tree.children.unshift(
+ jsToTreeNode(`import { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`),
+ );
+ // Export `__usesAstroImage` to pick up `astro:assets` usage in the module graph.
+ // @see the '@astrojs/mdx-postprocess' plugin
+ tree.children.push(jsToTreeNode(`export const ${USES_ASTRO_IMAGE_FLAG} = true`));
+ };
+}
diff --git a/packages/integrations/mdx/src/rehype-meta-string.ts b/packages/integrations/mdx/src/rehype-meta-string.ts
new file mode 100644
index 000000000..c3f2dbd2f
--- /dev/null
+++ b/packages/integrations/mdx/src/rehype-meta-string.ts
@@ -0,0 +1,17 @@
+import { visit } from 'unist-util-visit';
+
+/**
+ * Moves `data.meta` to `properties.metastring` for the `code` element node
+ * as `rehype-raw` strips `data` from all nodes, which may contain useful information.
+ * e.g. ```js {1:3} => metastring: "{1:3}"
+ */
+export default function rehypeMetaString() {
+ return function (tree: any) {
+ visit(tree, (node) => {
+ if (node.type === 'element' && node.tagName === 'code' && node.data?.meta) {
+ node.properties ??= {};
+ node.properties.metastring = node.data.meta;
+ }
+ });
+ };
+}
diff --git a/packages/integrations/mdx/src/rehype-optimize-static.ts b/packages/integrations/mdx/src/rehype-optimize-static.ts
new file mode 100644
index 000000000..eba31cae0
--- /dev/null
+++ b/packages/integrations/mdx/src/rehype-optimize-static.ts
@@ -0,0 +1,302 @@
+import type { RehypePlugin } from '@astrojs/markdown-remark';
+import { SKIP, visit } from 'estree-util-visit';
+import type { Element, RootContent, RootContentMap } from 'hast';
+import { toHtml } from 'hast-util-to-html';
+import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from 'mdast-util-mdx-jsx';
+
+// This import includes ambient types for hast to include mdx nodes
+import type {} from 'mdast-util-mdx';
+
+// Alias as the main hast node
+type Node = RootContent;
+// Nodes that have the `children` property
+type ParentNode = Element | MdxJsxFlowElementHast | MdxJsxTextElementHast;
+// Nodes that can have its children optimized as a single HTML string
+type OptimizableNode = Element | MdxJsxFlowElementHast | MdxJsxTextElementHast;
+
+export interface OptimizeOptions {
+ ignoreElementNames?: string[];
+}
+
+interface ElementMetadata {
+ parent: ParentNode;
+ index: number;
+}
+
+const exportConstComponentsRe = /export\s+const\s+components\s*=/;
+
+/**
+ * For MDX only, collapse static subtrees of the hast into `set:html`. Subtrees
+ * do not include any MDX elements.
+ *
+ * This optimization reduces the JS output as more content are represented as a
+ * string instead, which also reduces the AST size that Rollup holds in memory.
+ */
+export const rehypeOptimizeStatic: RehypePlugin<[OptimizeOptions?]> = (options) => {
+ return (tree) => {
+ // A set of non-static components to avoid collapsing when walking the tree
+ // as they need to be preserved as JSX to be rendered dynamically.
+ const ignoreElementNames = new Set<string>(options?.ignoreElementNames);
+
+ // Find `export const components = { ... }` and get it's object's keys to be
+ // populated into `ignoreElementNames`. This configuration is used to render
+ // some HTML elements as custom components, and we also want to avoid collapsing them.
+ for (const child of tree.children) {
+ if (child.type === 'mdxjsEsm' && exportConstComponentsRe.test(child.value)) {
+ const keys = getExportConstComponentObjectKeys(child);
+ if (keys) {
+ for (const key of keys) {
+ ignoreElementNames.add(key);
+ }
+ }
+ break;
+ }
+ }
+
+ // All possible elements that could be the root of a subtree
+ const allPossibleElements = new Set<OptimizableNode>();
+ // The current collapsible element stack while traversing the tree
+ const elementStack: Node[] = [];
+ // Metadata used by `findElementGroups` later
+ const elementMetadatas = new WeakMap<OptimizableNode, ElementMetadata>();
+
+ /**
+ * A non-static node causes all its parents to be non-optimizable
+ */
+ const isNodeNonStatic = (node: Node) => {
+ return (
+ node.type.startsWith('mdx') ||
+ // @ts-expect-error `node` should never have `type: 'root'`, but in some cases plugins may inject it as children,
+ // which MDX will render as a fragment instead (an MDX fragment is a `mdxJsxFlowElement` type).
+ node.type === 'root' ||
+ // @ts-expect-error Access `.tagName` naively for perf
+ ignoreElementNames.has(node.tagName)
+ );
+ };
+
+ visit(tree as any, {
+ // @ts-expect-error Force coerce node as hast node
+ enter(node: Node, key, index, parents: ParentNode[]) {
+ // `estree-util-visit` may traverse in MDX `attributes`, we don't want that. Only continue
+ // if it's traversing the root, or the `children` key.
+ if (key != null && key !== 'children') return SKIP;
+
+ // Mutate `node` as a normal hast element node if it's a plain MDX node, e.g. `<kbd>something</kbd>`
+ simplifyPlainMdxComponentNode(node, ignoreElementNames);
+
+ // For nodes that are not static, eliminate all elements in the `elementStack` from the
+ // `allPossibleElements` set.
+ if (isNodeNonStatic(node)) {
+ for (const el of elementStack) {
+ allPossibleElements.delete(el as OptimizableNode);
+ }
+ // Micro-optimization: While this destroys the meaning of an element
+ // stack for this node, things will still work but we won't repeatedly
+ // run the above for other nodes anymore. If this is confusing, you can
+ // comment out the code below when reading.
+ elementStack.length = 0;
+ }
+ // For possible subtree root nodes, record them in `elementStack` and
+ // `allPossibleElements` to be used in the "leave" hook below.
+ if (node.type === 'element' || isMdxComponentNode(node)) {
+ elementStack.push(node);
+ allPossibleElements.add(node);
+
+ if (index != null && node.type === 'element') {
+ // Record metadata for element node to be used for grouping analysis later
+ elementMetadatas.set(node, { parent: parents[parents.length - 1], index });
+ }
+ }
+ },
+ // @ts-expect-error Force coerce node as hast node
+ leave(node: Node, key, _, parents: ParentNode[]) {
+ // `estree-util-visit` may traverse in MDX `attributes`, we don't want that. Only continue
+ // if it's traversing the root, or the `children` key.
+ if (key != null && key !== 'children') return SKIP;
+
+ // Do the reverse of the if condition above, popping the `elementStack`,
+ // and consolidating `allPossibleElements` as a subtree root.
+ if (node.type === 'element' || isMdxComponentNode(node)) {
+ elementStack.pop();
+ // Many possible elements could be part of a subtree, in order to find
+ // the root, we check the parent of the element we're popping. If the
+ // parent exists in `allPossibleElements`, then we're definitely not
+ // the root, so remove ourselves. This will work retroactively as we
+ // climb back up the tree.
+ const parent = parents[parents.length - 1];
+ if (allPossibleElements.has(parent)) {
+ allPossibleElements.delete(node);
+ }
+ }
+ },
+ });
+
+ // Within `allPossibleElements`, element nodes are often siblings and instead of setting `set:html`
+ // on each of the element node, we can create a `<Fragment set:html="...">` element that includes
+ // all element nodes instead, simplifying the output.
+ const elementGroups = findElementGroups(allPossibleElements, elementMetadatas, isNodeNonStatic);
+
+ // For all possible subtree roots, collapse them into `set:html` and
+ // strip of their children
+ for (const el of allPossibleElements) {
+ // Avoid adding empty `set:html` attributes if there's no children
+ if (el.children.length === 0) continue;
+
+ if (isMdxComponentNode(el)) {
+ el.attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'set:html',
+ value: toHtml(el.children),
+ });
+ } else {
+ el.properties['set:html'] = toHtml(el.children);
+ }
+ el.children = [];
+ }
+
+ // For each element group, we create a new `<Fragment />` MDX node with `set:html` of the children
+ // serialized as HTML. We insert this new fragment, replacing all the group children nodes.
+ // We iterate in reverse to avoid changing the index of groups of the same parent.
+ for (let i = elementGroups.length - 1; i >= 0; i--) {
+ const group = elementGroups[i];
+ const fragmentNode: MdxJsxFlowElementHast = {
+ type: 'mdxJsxFlowElement',
+ name: 'Fragment',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'set:html',
+ value: toHtml(group.children),
+ },
+ ],
+ children: [],
+ };
+ group.parent.children.splice(group.startIndex, group.children.length, fragmentNode);
+ }
+ };
+};
+
+interface ElementGroup {
+ parent: ParentNode;
+ startIndex: number;
+ children: Node[];
+}
+
+/**
+ * Iterate through `allPossibleElements` and find elements that are siblings, and return them. `allPossibleElements`
+ * will be mutated to exclude these grouped elements.
+ */
+function findElementGroups(
+ allPossibleElements: Set<OptimizableNode>,
+ elementMetadatas: WeakMap<OptimizableNode, ElementMetadata>,
+ isNodeNonStatic: (node: Node) => boolean,
+): ElementGroup[] {
+ const elementGroups: ElementGroup[] = [];
+
+ for (const el of allPossibleElements) {
+ // Non-static nodes can't be grouped. It can only optimize its static children.
+ if (isNodeNonStatic(el)) continue;
+
+ // Get the metadata for the element node, this should always exist
+ const metadata = elementMetadatas.get(el);
+ if (!metadata) {
+ throw new Error(
+ 'Internal MDX error: rehype-optimize-static should have metadata for element node',
+ );
+ }
+
+ // For this element, iterate through the next siblings and add them to this array
+ // if they are text nodes or elements that are in `allPossibleElements` (optimizable).
+ // If one of the next siblings don't match the criteria, break the loop as others are no longer siblings.
+ const groupableElements: Node[] = [el];
+ for (let i = metadata.index + 1; i < metadata.parent.children.length; i++) {
+ const node = metadata.parent.children[i];
+
+ // If the node is non-static, we can't group it with the current element
+ if (isNodeNonStatic(node)) break;
+
+ if (node.type === 'element') {
+ // This node is now (presumably) part of a group, remove it from `allPossibleElements`
+ const existed = allPossibleElements.delete(node);
+ // If this node didn't exist in `allPossibleElements`, it's likely that one of its children
+ // are non-static, hence this node can also not be grouped. So we break out here.
+ if (!existed) break;
+ }
+
+ groupableElements.push(node);
+ }
+
+ // If group elements are more than one, add them to the `elementGroups`.
+ // Grouping is most effective if there's multiple elements in it.
+ if (groupableElements.length > 1) {
+ elementGroups.push({
+ parent: metadata.parent,
+ startIndex: metadata.index,
+ children: groupableElements,
+ });
+ // The `el` is also now part of a group, remove it from `allPossibleElements`
+ allPossibleElements.delete(el);
+ }
+ }
+
+ return elementGroups;
+}
+
+function isMdxComponentNode(node: Node): node is MdxJsxFlowElementHast | MdxJsxTextElementHast {
+ return node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement';
+}
+
+/**
+ * Get the object keys from `export const components`
+ *
+ * @example
+ * `export const components = { foo, bar: Baz }`, returns `['foo', 'bar']`
+ */
+function getExportConstComponentObjectKeys(node: RootContentMap['mdxjsEsm']) {
+ const exportNamedDeclaration = node.data?.estree?.body[0];
+ if (exportNamedDeclaration?.type !== 'ExportNamedDeclaration') return;
+
+ const variableDeclaration = exportNamedDeclaration.declaration;
+ if (variableDeclaration?.type !== 'VariableDeclaration') return;
+
+ const variableInit = variableDeclaration.declarations[0]?.init;
+ if (variableInit?.type !== 'ObjectExpression') return;
+
+ const keys: string[] = [];
+ for (const propertyNode of variableInit.properties) {
+ if (propertyNode.type === 'Property' && propertyNode.key.type === 'Identifier') {
+ keys.push(propertyNode.key.name);
+ }
+ }
+ return keys;
+}
+
+/**
+ * Some MDX nodes are simply `<kbd>something</kbd>` which isn't needed to be completely treated
+ * as an MDX node. This function tries to mutate this node as a simple hast element node if so.
+ */
+function simplifyPlainMdxComponentNode(node: Node, ignoreElementNames: Set<string>) {
+ if (
+ !isMdxComponentNode(node) ||
+ // Attributes could be dynamic, so bail if so.
+ node.attributes.length > 0 ||
+ // Fragments are also dynamic
+ !node.name ||
+ // Ignore if the node name is in the ignore list
+ ignoreElementNames.has(node.name) ||
+ // If the node name has uppercase characters, it's likely an actual MDX component
+ node.name.toLowerCase() !== node.name
+ ) {
+ return;
+ }
+
+ // Mutate as hast element node
+ const newNode = node as unknown as Element;
+ newNode.type = 'element';
+ newNode.tagName = node.name;
+ newNode.properties = {};
+
+ // @ts-expect-error Delete mdx-specific properties
+ node.attributes = undefined;
+ node.data = undefined;
+}
diff --git a/packages/integrations/mdx/src/server.ts b/packages/integrations/mdx/src/server.ts
new file mode 100644
index 000000000..79934eb32
--- /dev/null
+++ b/packages/integrations/mdx/src/server.ts
@@ -0,0 +1,73 @@
+import type { NamedSSRLoadedRendererValue } from 'astro';
+import { AstroError } from 'astro/errors';
+import { AstroJSX, jsx } from 'astro/jsx-runtime';
+import { renderJSX } from 'astro/runtime/server/index.js';
+
+const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
+
+// NOTE: In practice, MDX components are always tagged with `__astro_tag_component__`, so the right renderer
+// is used directly, and this check is not often used to return true.
+export async function check(
+ Component: any,
+ props: any,
+ { default: children = null, ...slotted } = {},
+) {
+ if (typeof Component !== 'function') return false;
+ const slots: Record<string, any> = {};
+ for (const [key, value] of Object.entries(slotted)) {
+ const name = slotName(key);
+ slots[name] = value;
+ }
+ try {
+ const result = await Component({ ...props, ...slots, children });
+ return result[AstroJSX];
+ } catch (e) {
+ throwEnhancedErrorIfMdxComponent(e as Error, Component);
+ }
+ return false;
+}
+
+export async function renderToStaticMarkup(
+ this: any,
+ Component: any,
+ props = {},
+ { default: children = null, ...slotted } = {},
+) {
+ const slots: Record<string, any> = {};
+ for (const [key, value] of Object.entries(slotted)) {
+ const name = slotName(key);
+ slots[name] = value;
+ }
+
+ const { result } = this;
+ try {
+ const html = await renderJSX(result, jsx(Component, { ...props, ...slots, children }));
+ return { html };
+ } catch (e) {
+ throwEnhancedErrorIfMdxComponent(e as Error, Component);
+ throw e;
+ }
+}
+
+function throwEnhancedErrorIfMdxComponent(error: Error, Component: any) {
+ // if the exception is from an mdx component
+ // throw an error
+ if (Component[Symbol.for('mdx-component')]) {
+ // if it's an existing AstroError, we don't need to re-throw, keep the original hint
+ if (AstroError.is(error)) return;
+ // Mimic the fields of the internal `AstroError` class (not from `astro/errors`) to
+ // provide better title and hint for the error overlay
+ (error as any).title = error.name;
+ (error as any).hint =
+ `This issue often occurs when your MDX component encounters runtime errors.`;
+ throw error;
+ }
+}
+
+const renderer: NamedSSRLoadedRendererValue = {
+ name: 'astro:jsx',
+ check,
+ renderToStaticMarkup,
+};
+
+export default renderer;
diff --git a/packages/integrations/mdx/src/utils.ts b/packages/integrations/mdx/src/utils.ts
new file mode 100644
index 000000000..7dcd4a14c
--- /dev/null
+++ b/packages/integrations/mdx/src/utils.ts
@@ -0,0 +1,108 @@
+import { parseFrontmatter } from '@astrojs/markdown-remark';
+import type { Options as AcornOpts } from 'acorn';
+import { parse } from 'acorn';
+import type { AstroConfig, AstroIntegrationLogger, SSRError } from 'astro';
+import { bold } from 'kleur/colors';
+import type { MdxjsEsm } from 'mdast-util-mdx';
+import type { PluggableList } from 'unified';
+
+function appendForwardSlash(path: string) {
+ return path.endsWith('/') ? path : path + '/';
+}
+
+export interface FileInfo {
+ fileId: string;
+ fileUrl: string;
+}
+
+/** @see 'vite-plugin-utils' for source */
+export function getFileInfo(id: string, config: AstroConfig): FileInfo {
+ const sitePathname = appendForwardSlash(
+ config.site ? new URL(config.base, config.site).pathname : config.base,
+ );
+
+ // Try to grab the file's actual URL
+ let url: URL | undefined = undefined;
+ try {
+ url = new URL(`file://${id}`);
+ } catch {}
+
+ const fileId = id.split('?')[0];
+ let fileUrl: string;
+ const isPage = fileId.includes('/pages/');
+ if (isPage) {
+ fileUrl = fileId.replace(/^.*?\/pages\//, sitePathname).replace(/(?:\/index)?\.mdx$/, '');
+ } else if (url?.pathname.startsWith(config.root.pathname)) {
+ fileUrl = url.pathname.slice(config.root.pathname.length);
+ } else {
+ fileUrl = fileId;
+ }
+
+ if (fileUrl && config.trailingSlash === 'always') {
+ fileUrl = appendForwardSlash(fileUrl);
+ }
+ return { fileId, fileUrl };
+}
+
+/**
+ * Match YAML exception handling from Astro core errors
+ * @see 'astro/src/core/errors.ts'
+ */
+export function safeParseFrontmatter(code: string, id: string) {
+ try {
+ return parseFrontmatter(code, { frontmatter: 'empty-with-spaces' });
+ } catch (e: any) {
+ if (e.name === 'YAMLException') {
+ const err: SSRError = e;
+ err.id = id;
+ err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
+ err.message = e.reason;
+ throw err;
+ } else {
+ throw e;
+ }
+ }
+}
+
+export function jsToTreeNode(
+ jsString: string,
+ acornOpts: AcornOpts = {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ },
+): MdxjsEsm {
+ return {
+ type: 'mdxjsEsm',
+ value: '',
+ data: {
+ // @ts-expect-error `parse` return types is incompatible but it should work in runtime
+ estree: {
+ ...parse(jsString, acornOpts),
+ type: 'Program',
+ sourceType: 'module',
+ },
+ },
+ };
+}
+
+export function ignoreStringPlugins(plugins: any[], logger: AstroIntegrationLogger): PluggableList {
+ let validPlugins: PluggableList = [];
+ let hasInvalidPlugin = false;
+ for (const plugin of plugins) {
+ if (typeof plugin === 'string') {
+ logger.warn(`${bold(plugin)} not applied.`);
+ hasInvalidPlugin = true;
+ } else if (Array.isArray(plugin) && typeof plugin[0] === 'string') {
+ logger.warn(`${bold(plugin[0])} not applied.`);
+ hasInvalidPlugin = true;
+ } else {
+ validPlugins.push(plugin);
+ }
+ }
+ if (hasInvalidPlugin) {
+ logger.warn(
+ `To inherit Markdown plugins in MDX, please use explicit imports in your config instead of "strings." See Markdown docs: https://docs.astro.build/en/guides/markdown-content/#markdown-plugins`,
+ );
+ }
+ return validPlugins;
+}
diff --git a/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts b/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts
new file mode 100644
index 000000000..e00173fbe
--- /dev/null
+++ b/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts
@@ -0,0 +1,148 @@
+import type { AstroConfig } from 'astro';
+import { type ExportSpecifier, type ImportSpecifier, parse } from 'es-module-lexer';
+import type { Plugin } from 'vite';
+import {
+ ASTRO_IMAGE_ELEMENT,
+ ASTRO_IMAGE_IMPORT,
+ USES_ASTRO_IMAGE_FLAG,
+} from './rehype-images-to-component.js';
+import { type FileInfo, getFileInfo } from './utils.js';
+
+const underscoreFragmentImportRegex = /[\s,{]_Fragment[\s,}]/;
+const astroTagComponentImportRegex = /[\s,{]__astro_tag_component__[\s,}]/;
+
+// These transforms must happen *after* JSX runtime transformations
+export function vitePluginMdxPostprocess(astroConfig: AstroConfig): Plugin {
+ return {
+ name: '@astrojs/mdx-postprocess',
+ transform(code, id, opts) {
+ if (!id.endsWith('.mdx')) return;
+
+ const fileInfo = getFileInfo(id, astroConfig);
+ const [imports, exports] = parse(code);
+
+ // Call a series of functions that transform the code
+ code = injectUnderscoreFragmentImport(code, imports);
+ code = injectMetadataExports(code, exports, fileInfo);
+ code = transformContentExport(code, exports);
+ code = annotateContentExport(code, id, !!opts?.ssr, imports);
+
+ // The code transformations above are append-only, so the line/column mappings are the same
+ // and we can omit the sourcemap for performance.
+ return { code, map: null };
+ },
+ };
+}
+
+/**
+ * Inject `Fragment` identifier import if not already present.
+ */
+function injectUnderscoreFragmentImport(code: string, imports: readonly ImportSpecifier[]) {
+ if (!isSpecifierImported(code, imports, underscoreFragmentImportRegex, 'astro/jsx-runtime')) {
+ code += `\nimport { Fragment as _Fragment } from 'astro/jsx-runtime';`;
+ }
+ return code;
+}
+
+/**
+ * Inject MDX metadata as exports of the module.
+ */
+function injectMetadataExports(
+ code: string,
+ exports: readonly ExportSpecifier[],
+ fileInfo: FileInfo,
+) {
+ if (!exports.some(({ n }) => n === 'url')) {
+ code += `\nexport const url = ${JSON.stringify(fileInfo.fileUrl)};`;
+ }
+ if (!exports.some(({ n }) => n === 'file')) {
+ code += `\nexport const file = ${JSON.stringify(fileInfo.fileId)};`;
+ }
+ return code;
+}
+
+/**
+ * Transforms the `MDXContent` default export as `Content`, which wraps `MDXContent` and
+ * passes additional `components` props.
+ */
+function transformContentExport(code: string, exports: readonly ExportSpecifier[]) {
+ if (exports.find(({ n }) => n === 'Content')) return code;
+
+ // If have `export const components`, pass that as props to `Content` as fallback
+ const hasComponents = exports.find(({ n }) => n === 'components');
+ const usesAstroImage = exports.find(({ n }) => n === USES_ASTRO_IMAGE_FLAG);
+
+ // Generate code for the `components` prop passed to `MDXContent`
+ let componentsCode = `{ Fragment: _Fragment${
+ hasComponents ? ', ...components' : ''
+ }, ...props.components,`;
+ if (usesAstroImage) {
+ componentsCode += ` ${JSON.stringify(ASTRO_IMAGE_ELEMENT)}: ${
+ hasComponents ? 'components.img ?? ' : ''
+ } props.components?.img ?? ${ASTRO_IMAGE_IMPORT}`;
+ }
+ componentsCode += ' }';
+
+ // Make `Content` the default export so we can wrap `MDXContent` and pass in `Fragment`
+ code = code.replace('export default function MDXContent', 'function MDXContent');
+ code += `
+export const Content = (props = {}) => MDXContent({
+ ...props,
+ components: ${componentsCode},
+});
+export default Content;`;
+ return code;
+}
+
+/**
+ * Add properties to the `Content` export.
+ */
+function annotateContentExport(
+ code: string,
+ id: string,
+ ssr: boolean,
+ imports: readonly ImportSpecifier[],
+) {
+ // Mark `Content` as MDX component
+ code += `\nContent[Symbol.for('mdx-component')] = true`;
+ // Ensure styles and scripts are injected into a `<head>` when a layout is not applied
+ code += `\nContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
+ // Assign the `moduleId` metadata to `Content`
+ code += `\nContent.moduleId = ${JSON.stringify(id)};`;
+
+ // Tag the `Content` export as "astro:jsx" so it's quicker to identify how to render this component
+ if (ssr) {
+ if (
+ !isSpecifierImported(
+ code,
+ imports,
+ astroTagComponentImportRegex,
+ 'astro/runtime/server/index.js',
+ )
+ ) {
+ code += `\nimport { __astro_tag_component__ } from 'astro/runtime/server/index.js';`;
+ }
+ code += `\n__astro_tag_component__(Content, 'astro:jsx');`;
+ }
+
+ return code;
+}
+
+/**
+ * Check whether the `specifierRegex` matches for an import of `source` in the `code`.
+ */
+function isSpecifierImported(
+ code: string,
+ imports: readonly ImportSpecifier[],
+ specifierRegex: RegExp,
+ source: string,
+) {
+ for (const imp of imports) {
+ if (imp.n !== source) continue;
+
+ const importStatement = code.slice(imp.ss, imp.se);
+ if (specifierRegex.test(importStatement)) return true;
+ }
+
+ return false;
+}
diff --git a/packages/integrations/mdx/src/vite-plugin-mdx.ts b/packages/integrations/mdx/src/vite-plugin-mdx.ts
new file mode 100644
index 000000000..7dda9d714
--- /dev/null
+++ b/packages/integrations/mdx/src/vite-plugin-mdx.ts
@@ -0,0 +1,110 @@
+import type { SSRError } from 'astro';
+import { getAstroMetadata } from 'astro/jsx/rehype.js';
+import { VFile } from 'vfile';
+import type { Plugin } from 'vite';
+import type { MdxOptions } from './index.js';
+import { createMdxProcessor } from './plugins.js';
+import { safeParseFrontmatter } from './utils.js';
+
+export interface VitePluginMdxOptions {
+ mdxOptions: MdxOptions;
+ srcDir: URL;
+ experimentalHeadingIdCompat: boolean;
+}
+
+// NOTE: Do not destructure `opts` as we're assigning a reference that will be mutated later
+export function vitePluginMdx(opts: VitePluginMdxOptions): Plugin {
+ let processor: ReturnType<typeof createMdxProcessor> | undefined;
+ let sourcemapEnabled: boolean;
+
+ return {
+ name: '@mdx-js/rollup',
+ enforce: 'pre',
+ buildEnd() {
+ processor = undefined;
+ },
+ configResolved(resolved) {
+ sourcemapEnabled = !!resolved.build.sourcemap;
+
+ // HACK: Remove the `astro:jsx` plugin if defined as we handle the JSX transformation ourselves
+ const jsxPluginIndex = resolved.plugins.findIndex((p) => p.name === 'astro:jsx');
+ if (jsxPluginIndex !== -1) {
+ // @ts-ignore-error ignore readonly annotation
+ resolved.plugins.splice(jsxPluginIndex, 1);
+ }
+ },
+ async resolveId(source, importer, options) {
+ if (importer?.endsWith('.mdx') && source[0] !== '/') {
+ let resolved = await this.resolve(source, importer, options);
+ if (!resolved) resolved = await this.resolve('./' + source, importer, options);
+ return resolved;
+ }
+ },
+ // Override transform to alter code before MDX compilation
+ // ex. inject layouts
+ async transform(code, id) {
+ if (!id.endsWith('.mdx')) return;
+
+ const { frontmatter, content } = safeParseFrontmatter(code, id);
+
+ const vfile = new VFile({
+ value: content,
+ path: id,
+ data: {
+ astro: {
+ frontmatter,
+ },
+ applyFrontmatterExport: {
+ srcDir: opts.srcDir,
+ },
+ },
+ });
+
+ // Lazily initialize the MDX processor
+ if (!processor) {
+ processor = createMdxProcessor(opts.mdxOptions, {
+ sourcemap: sourcemapEnabled,
+ experimentalHeadingIdCompat: opts.experimentalHeadingIdCompat,
+ });
+ }
+
+ try {
+ const compiled = await processor.process(vfile);
+
+ return {
+ code: String(compiled.value),
+ map: compiled.map,
+ meta: getMdxMeta(vfile),
+ };
+ } catch (e: any) {
+ const err: SSRError = e;
+
+ // For some reason MDX puts the error location in the error's name, not very useful for us.
+ err.name = 'MDXError';
+ err.loc = { file: id, line: e.line, column: e.column };
+
+ // For another some reason, MDX doesn't include a stack trace. Weird
+ Error.captureStackTrace(err);
+
+ throw err;
+ }
+ },
+ };
+}
+
+function getMdxMeta(vfile: VFile): Record<string, any> {
+ const astroMetadata = getAstroMetadata(vfile);
+ if (!astroMetadata) {
+ throw new Error(
+ 'Internal MDX error: Astro metadata is not set by rehype-analyze-astro-metadata',
+ );
+ }
+ return {
+ astro: astroMetadata,
+ vite: {
+ // Setting this vite metadata to `ts` causes Vite to resolve .js
+ // extensions to .ts files.
+ lang: 'ts',
+ },
+ };
+}
diff --git a/packages/integrations/mdx/template/content-module-types.d.ts b/packages/integrations/mdx/template/content-module-types.d.ts
new file mode 100644
index 000000000..848289f01
--- /dev/null
+++ b/packages/integrations/mdx/template/content-module-types.d.ts
@@ -0,0 +1,10 @@
+declare module 'astro:content' {
+ interface Render {
+ '.mdx': Promise<{
+ Content: import('astro').MarkdownInstance<{}>['Content'];
+ headings: import('astro').MarkdownHeading[];
+ remarkPluginFrontmatter: Record<string, any>;
+ components: import('astro').MDXInstance<{}>['components'];
+ }>;
+ }
+}
diff --git a/packages/integrations/mdx/test/css-head-mdx.test.js b/packages/integrations/mdx/test/css-head-mdx.test.js
new file mode 100644
index 000000000..96ee7c900
--- /dev/null
+++ b/packages/integrations/mdx/test/css-head-mdx.test.js
@@ -0,0 +1,101 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import * as cheerio from 'cheerio';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('Head injection w/ MDX', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/css-head-mdx/', import.meta.url),
+ integrations: [mdx()],
+ // test suite was authored when inlineStylesheets defaulted to never
+ build: { inlineStylesheets: 'never' },
+ });
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+
+ it('injects content styles into head', async () => {
+ const html = await fixture.readFile('/indexThree/index.html');
+ const { document } = parseHTML(html);
+
+ const links = document.querySelectorAll('head link[rel=stylesheet]');
+ assert.equal(links.length, 1);
+
+ const scripts = document.querySelectorAll('script[type=module]');
+ assert.equal(scripts.length, 1);
+ });
+
+ it('injects into the head for content collections', async () => {
+ const html = await fixture.readFile('/posts/test/index.html');
+ const { document } = parseHTML(html);
+
+ const links = document.querySelectorAll('head link[rel=stylesheet]');
+ assert.equal(links.length, 1);
+ });
+
+ it('injects content from a component using Content#render()', async () => {
+ const html = await fixture.readFile('/DirectContentUsage/index.html');
+ const { document } = parseHTML(html);
+
+ const links = document.querySelectorAll('head link[rel=stylesheet]');
+ assert.equal(links.length, 1);
+
+ const scripts = document.querySelectorAll('script[type=module]');
+ assert.equal(scripts.length, 1);
+ });
+
+ it('Using component using slots.render() API', async () => {
+ const html = await fixture.readFile('/remote/index.html');
+ const { document } = parseHTML(html);
+
+ const links = document.querySelectorAll('head link[rel=stylesheet]');
+ assert.equal(links.length, 1);
+ });
+
+ it('Using component but no layout', async () => {
+ const html = await fixture.readFile('/noLayoutWithComponent/index.html');
+ // Using cheerio here because linkedom doesn't support head tag injection
+ const $ = cheerio.load(html);
+
+ const headLinks = $('head link[rel=stylesheet]');
+ assert.equal(headLinks.length, 1);
+
+ const bodyLinks = $('body link[rel=stylesheet]');
+ assert.equal(bodyLinks.length, 0);
+ });
+
+ it('JSX component rendering Astro children within head buffering phase', async () => {
+ const html = await fixture.readFile('/posts/using-component/index.html');
+ // Using cheerio here because linkedom doesn't support head tag injection
+ const $ = cheerio.load(html);
+
+ const headLinks = $('head link[rel=stylesheet]');
+ assert.equal(headLinks.length, 1);
+
+ const bodyLinks = $('body link[rel=stylesheet]');
+ assert.equal(bodyLinks.length, 0);
+ });
+
+ it('Injection caused by delayed slots', async () => {
+ const html = await fixture.readFile('/componentwithtext/index.html');
+
+ // Using cheerio here because linkedom doesn't support head tag injection
+ const $ = cheerio.load(html);
+
+ const headLinks = $('head link[rel=stylesheet]');
+ assert.equal(headLinks.length, 1);
+
+ const bodyLinks = $('body link[rel=stylesheet]');
+ assert.equal(bodyLinks.length, 0);
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/package.json b/packages/integrations/mdx/test/fixtures/css-head-mdx/package.json
new file mode 100644
index 000000000..0abf82349
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@test/mdx-css-head-mdx",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*",
+ "astro-remote": "0.3.4"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BaseHead.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BaseHead.astro
new file mode 100644
index 000000000..19f517789
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BaseHead.astro
@@ -0,0 +1,11 @@
+---
+const { title } = Astro.props;
+---
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width" />
+<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+<meta name="generator" content={Astro.generator} />
+<title>{title}</title>
+<style is:global>
+ @import "../styles/global.css";
+</style>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BasicBlock.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BasicBlock.astro
new file mode 100644
index 000000000..95660b6b9
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/BasicBlock.astro
@@ -0,0 +1,14 @@
+---
+const { inlineStyle, title, display = 'horizontal' } = Astro.props;
+const lineEnding = display === 'horizontal' ? ', ' : '<br>';
+---
+
+{title && <h2 set:html={title} />}
+<address style={inlineStyle}>
+ <span class="name">some name</span><Fragment set:html={lineEnding} />
+ line 1<Fragment set:html={lineEnding} />
+ line 2<Fragment set:html={lineEnding} />
+ line 3<Fragment set:html={lineEnding} />
+ line 4<Fragment set:html={lineEnding} />
+ line 5
+</address>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/GenericComponent.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/GenericComponent.astro
new file mode 100644
index 000000000..ebcd3ff35
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/GenericComponent.astro
@@ -0,0 +1 @@
+<span>just a generic component</span>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/HelloWorld.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/HelloWorld.astro
new file mode 100644
index 000000000..ee8084b46
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/HelloWorld.astro
@@ -0,0 +1,11 @@
+---
+---
+
+<h3>Hello world!!</h3>
+<slot />
+
+<style>h3 { color: red }</style>
+
+<script>
+console.log('hellooooo')
+</script> \ No newline at end of file
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/MDXWrapper.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/MDXWrapper.astro
new file mode 100644
index 000000000..fbd530e14
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/MDXWrapper.astro
@@ -0,0 +1,9 @@
+---
+import Component from "./GenericComponent.astro";
+---
+
+<div>
+ <slot name="title" />
+ <slot name="intro" class="inline" />
+ <Component />
+</div>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/P.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/P.astro
new file mode 100644
index 000000000..071e08a12
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/P.astro
@@ -0,0 +1,3 @@
+<p>
+ <slot />
+</p>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/SmallCaps.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/SmallCaps.astro
new file mode 100644
index 000000000..a0bd6e1f1
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/SmallCaps.astro
@@ -0,0 +1,3 @@
+---
+---
+<span style={{fontVariant: "small-caps"}}><slot /></span>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/UsingMdx.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/UsingMdx.astro
new file mode 100644
index 000000000..3ef9d7639
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/UsingMdx.astro
@@ -0,0 +1,8 @@
+---
+import { getEntry, render } from 'astro:content';
+
+const launchWeek = await getEntry('blog', 'using-mdx');
+const { Content } = await render(launchWeek);
+---
+
+<Content />
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/WithHoistedScripts.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/WithHoistedScripts.astro
new file mode 100644
index 000000000..0b8c4445f
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/components/WithHoistedScripts.astro
@@ -0,0 +1,6 @@
+---
+---
+
+<script>
+ console.log('hoisted')
+ </script>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/content/config.ts b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/content/config.ts
new file mode 100644
index 000000000..2c8944d51
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/content/config.ts
@@ -0,0 +1,18 @@
+import { defineCollection } from "astro:content";
+import { glob } from "astro/loaders"
+
+const posts = defineCollection({
+ loader: glob({
+ pattern: "*.mdx",
+ base: "src/data/posts",
+ })
+});
+
+const blog = defineCollection({
+ loader: glob({
+ pattern: "*.mdx",
+ base: "src/data/blog",
+ })
+});
+
+export const collections = { posts, blog };
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/_styles.css b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/_styles.css
new file mode 100644
index 000000000..1379b29c0
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/_styles.css
@@ -0,0 +1,3 @@
+body {
+ color: red !important;
+}
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/using-mdx.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/using-mdx.mdx
new file mode 100644
index 000000000..917fc3331
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/blog/using-mdx.mdx
@@ -0,0 +1,6 @@
+import './_styles.css';
+import WithHoistedScripts from '../../components/WithHoistedScripts.astro';
+
+# Using mdx
+
+<WithHoistedScripts />
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/test.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/test.mdx
new file mode 100644
index 000000000..0bb1153ca
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/test.mdx
@@ -0,0 +1,5 @@
+---
+title: Testing
+---
+
+<SmallCaps>A test file</SmallCaps>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/using-component.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/using-component.mdx
new file mode 100644
index 000000000..fa550fb04
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/data/posts/using-component.mdx
@@ -0,0 +1,13 @@
+---
+title: testing
+---
+import MDXWrapper from "../../components/MDXWrapper.astro";
+
+<MDXWrapper>
+ <h1 slot="title">
+ testing
+ </h1>
+ <div slot="intro">
+ Intro
+ </div>
+</MDXWrapper>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/ContentLayout.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/ContentLayout.astro
new file mode 100644
index 000000000..1e4b6a6f8
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/ContentLayout.astro
@@ -0,0 +1,18 @@
+---
+import BaseHead from "../components/BaseHead.astro";
+interface Props {
+ title: string;
+}
+
+const { title } = Astro.props;
+---
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <BaseHead title={title} />
+ </head>
+ <body>
+ <slot />
+ </body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/DocumentLayout.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/DocumentLayout.astro
new file mode 100644
index 000000000..a09a1c218
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/DocumentLayout.astro
@@ -0,0 +1,15 @@
+---
+// Extend the BaseLayout, adding space for a banner at the top of the page
+// after the main heading, then the detail for the actual page
+import ContentLayout from './ContentLayout.astro';
+const { frontmatter } = Astro.props;
+---
+
+<ContentLayout>
+ <div class="content-container">
+ <article id="main-content" class="pad-z5 flow">
+ <h1 set:html={frontmatter.pageHeading ? frontmatter.pageHeading : frontmatter.title} />
+ <slot />
+ </article>
+ </div>
+</ContentLayout>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/One.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/One.astro
new file mode 100644
index 000000000..b9916e106
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/One.astro
@@ -0,0 +1,15 @@
+---
+---
+
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+ <meta name="viewport" content="width=device-width" />
+ <meta name="generator" content={Astro.generator} />
+ <title>Astro</title>
+ </head>
+ <body>
+ <slot />
+ </body>
+</html> \ No newline at end of file
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Three.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Three.astro
new file mode 100644
index 000000000..3f0fdfa72
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Three.astro
@@ -0,0 +1,6 @@
+---
+import Two from './Two.astro'
+---
+<Two>
+<slot />
+</Two> \ No newline at end of file
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Two.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Two.astro
new file mode 100644
index 000000000..51f0ca18c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/layouts/Two.astro
@@ -0,0 +1,6 @@
+---
+import One from './One.astro'
+---
+<One>
+<slot />
+</One> \ No newline at end of file
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/DirectContentUsage.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/DirectContentUsage.astro
new file mode 100644
index 000000000..cbf4295a7
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/DirectContentUsage.astro
@@ -0,0 +1,17 @@
+---
+import UsingMdx from '../components/UsingMdx.astro'
+---
+
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+ <meta name="viewport" content="width=device-width" />
+ <meta name="generator" content={Astro.generator} />
+ <title>Astro</title>
+ </head>
+ <body>
+ <h1>Astro</h1>
+ <UsingMdx />
+ </body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/componentwithtext.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/componentwithtext.mdx
new file mode 100644
index 000000000..2c6f3032f
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/componentwithtext.mdx
@@ -0,0 +1,12 @@
+---
+layout: ../layouts/DocumentLayout.astro
+title: blah blah
+---
+
+import BasicBlock from '../components/BasicBlock.astro';
+
+Some text for a paragraph.
+
+<BasicBlock title="This causes css in wrong place." />
+
+Some other text.
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexOne.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexOne.astro
new file mode 100644
index 000000000..f24bf4f3c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexOne.astro
@@ -0,0 +1,10 @@
+---
+import One from '../layouts/One.astro'
+
+import { Content } from '../test.mdx'
+---
+
+<One>
+ <h1>Astro</h1>
+ <Content />
+</One>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexThree.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexThree.astro
new file mode 100644
index 000000000..99be9677c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexThree.astro
@@ -0,0 +1,10 @@
+---
+import Three from '../layouts/Three.astro'
+
+import { Content } from '../test.mdx'
+---
+
+<Three>
+ <h1>Astro</h1>
+ <Content />
+</Three>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexTwo.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexTwo.astro
new file mode 100644
index 000000000..af07af926
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/indexTwo.astro
@@ -0,0 +1,10 @@
+---
+import Two from '../layouts/Two.astro'
+
+import { Content } from '../test.mdx'
+---
+
+<Two>
+ <h1>Astro</h1>
+ <Content />
+</Two>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx
new file mode 100644
index 000000000..9d799d4db
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx
@@ -0,0 +1,22 @@
+---
+title: 'Lorem'
+description: 'Lorem ipsum dolor sit amet'
+pubDate: 'Jul 02 2022'
+---
+
+import MyComponent from '../components/HelloWorld.astro';
+
+
+## Lorem
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+## Lorem 2
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+<MyComponent />
+
+## Lorem 3
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/posts/[post].astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/posts/[post].astro
new file mode 100644
index 000000000..13a9e56db
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/posts/[post].astro
@@ -0,0 +1,18 @@
+---
+import { getCollection, render } from 'astro:content';
+import SmallCaps from '../../components/SmallCaps.astro';
+import Layout from '../../layouts/ContentLayout.astro';
+
+export async function getStaticPaths() {
+ const entries = await getCollection('posts');
+ return entries.map(entry => {
+ return { params: { post: entry.id }, props: { entry }};
+ });
+}
+
+const { entry } = Astro.props;
+const { Content } = await render(entry);
+---
+<Layout title="">
+ <Content components={{ SmallCaps }} />
+</Layout>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/remote.astro b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/remote.astro
new file mode 100644
index 000000000..7c8000642
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/remote.astro
@@ -0,0 +1,17 @@
+---
+import { Markdown } from 'astro-remote'
+import Paragraph from '../components/P.astro';
+import Layout from '../layouts/One.astro';
+import '../styles/global.css'
+---
+
+<Layout title="Welcome to Astro.">
+ <main>
+ <Markdown
+ components={{
+ p: Paragraph,
+ }}>
+ **Removing p component fixes the problem**
+ </Markdown>
+ </main>
+</Layout>
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testOne.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testOne.mdx
new file mode 100644
index 000000000..6874b499f
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testOne.mdx
@@ -0,0 +1,15 @@
+---
+layout: '../layouts/One.astro'
+title: "hello world"
+publishDate: "2023-01-01"
+---
+
+import HelloWorld from '../components/HelloWorld.astro';
+
+# Test
+
+123
+
+<HelloWorld />
+
+456
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testThree.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testThree.mdx
new file mode 100644
index 000000000..b0e55eed2
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testThree.mdx
@@ -0,0 +1,15 @@
+---
+layout: '../layouts/Three.astro'
+title: "hello world"
+publishDate: "2023-01-01"
+---
+
+import HelloWorld from '../components/HelloWorld.astro';
+
+# Test
+
+123
+
+<HelloWorld />
+
+456
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testTwo.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testTwo.mdx
new file mode 100644
index 000000000..9a80ed5f0
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/testTwo.mdx
@@ -0,0 +1,15 @@
+---
+layout: '../layouts/Two.astro'
+title: "hello world"
+publishDate: "2023-01-01"
+---
+
+import HelloWorld from '../components/HelloWorld.astro';
+
+# Test
+
+123
+
+<HelloWorld />
+
+456
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/styles/global.css b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/styles/global.css
new file mode 100644
index 000000000..e1450526f
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/styles/global.css
@@ -0,0 +1,3 @@
+html {
+ font-weight: bolder;
+}
diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/test.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/test.mdx
new file mode 100644
index 000000000..c8ecc4daa
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/test.mdx
@@ -0,0 +1,14 @@
+---
+title: "hello world"
+publishDate: "2023-01-01"
+---
+
+import HelloWorld from './components/HelloWorld.astro';
+
+# Test
+
+123
+
+<HelloWorld />
+
+456
diff --git a/packages/integrations/mdx/test/fixtures/image-remark-imgattr/astro.config.mjs b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/astro.config.mjs
new file mode 100644
index 000000000..7cd97f8b1
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/astro.config.mjs
@@ -0,0 +1,8 @@
+import mdx from '@astrojs/mdx';
+import { defineConfig } from 'astro/config';
+import plugin from "./remarkPlugin"
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [mdx({remarkPlugins:[plugin]})],
+});
diff --git a/packages/integrations/mdx/test/fixtures/image-remark-imgattr/package.json b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/package.json
new file mode 100644
index 000000000..89868f9f4
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@test/image-remark-imgattr",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*"
+ },
+ "scripts": {
+ "dev": "astro dev"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/image-remark-imgattr/remarkPlugin.js b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/remarkPlugin.js
new file mode 100644
index 000000000..f42d9ec52
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/remarkPlugin.js
@@ -0,0 +1,22 @@
+export default function plugin() {
+ return transformer;
+
+ function transformer(tree) {
+ function traverse(node) {
+ if (node.type === "image") {
+ node.data = node.data || {};
+ node.data.hProperties = node.data.hProperties || {};
+ node.data.hProperties.id = "test";
+ node.data.hProperties.width = "300";
+ node.data.hProperties.widths = [300,600];
+ node.data.hProperties.sizes = "(min-width: 600px) 600w, 300w";
+ }
+
+ if (node.children) {
+ node.children.forEach(traverse);
+ }
+ }
+
+ traverse(tree);
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/assets/penguin2.jpg b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/assets/penguin2.jpg
new file mode 100644
index 000000000..e859ac3c9
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/assets/penguin2.jpg
Binary files differ
diff --git a/packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/pages/index.mdx
new file mode 100644
index 000000000..e415e505d
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/image-remark-imgattr/src/pages/index.mdx
@@ -0,0 +1 @@
+![alt](../assets/penguin2.jpg)
diff --git a/packages/integrations/mdx/test/fixtures/invalid-mdx-component/src/pages/invalid-content.mdx b/packages/integrations/mdx/test/fixtures/invalid-mdx-component/src/pages/invalid-content.mdx
new file mode 100644
index 000000000..9ebaa695b
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/invalid-mdx-component/src/pages/invalid-content.mdx
@@ -0,0 +1,7 @@
+---
+title: Hello, World
+---
+
+# {A.VALID.JAVASCRIPT.EXPRESSION.THAT.RESULTS.IN.A.RUNTIME.ERROR}
+
+Invalid content in the frontmatter
diff --git a/packages/integrations/mdx/test/fixtures/mdx-astro-markdown-remarkRehype/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-astro-markdown-remarkRehype/src/pages/index.mdx
new file mode 100644
index 000000000..8b8f1d189
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-astro-markdown-remarkRehype/src/pages/index.mdx
@@ -0,0 +1,5 @@
+# Hello world
+
+This[^1] should be visible.
+
+[^1]: And there would be a footnote. \ No newline at end of file
diff --git a/packages/integrations/mdx/test/fixtures/mdx-component/src/components/Test.mdx b/packages/integrations/mdx/test/fixtures/mdx-component/src/components/Test.mdx
new file mode 100644
index 000000000..1c6b33184
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-component/src/components/Test.mdx
@@ -0,0 +1,3 @@
+# Hello component!
+
+<div id="foo">bar</div>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-component/src/components/WithFragment.mdx b/packages/integrations/mdx/test/fixtures/mdx-component/src/components/WithFragment.mdx
new file mode 100644
index 000000000..c6058697e
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-component/src/components/WithFragment.mdx
@@ -0,0 +1,3 @@
+# MDX containing `<Fragment />`
+
+<p><Fragment>bar</Fragment></p>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro
new file mode 100644
index 000000000..ab5f417b1
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro
@@ -0,0 +1,20 @@
+---
+import { parse } from 'node:path';
+const components = await Astro.glob('../components/*.mdx');
+---
+
+<div data-default-export>
+ {components.map(Component => (
+ <div data-file={parse(Component.file).base}>
+ <Component.default />
+ </div>
+ ))}
+</div>
+
+<div data-content-export>
+ {components.map(({ Content, file }) => (
+ <div data-file={parse(file).base}>
+ <Content />
+ </div>
+ ))}
+</div>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/index.astro b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/index.astro
new file mode 100644
index 000000000..ed5ae98a3
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/index.astro
@@ -0,0 +1,5 @@
+---
+import Test from '../components/Test.mdx';
+---
+
+<Test />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/w-fragment.astro b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/w-fragment.astro
new file mode 100644
index 000000000..d394413f0
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/w-fragment.astro
@@ -0,0 +1,5 @@
+---
+import WithFragment from '../components/WithFragment.mdx';
+---
+
+<WithFragment />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Em.astro b/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Em.astro
new file mode 100644
index 000000000..8166c0586
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Em.astro
@@ -0,0 +1,7 @@
+<em><slot/></em>
+
+<style>
+ em {
+ color: red;
+ }
+</style>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/P.astro b/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/P.astro
new file mode 100644
index 000000000..e29ac6d8f
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/P.astro
@@ -0,0 +1 @@
+<p><slot /></p>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Title.astro b/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Title.astro
new file mode 100644
index 000000000..333ec04a2
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-escape/src/components/Title.astro
@@ -0,0 +1 @@
+<h1><slot/></h1>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/html-tag.mdx b/packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/html-tag.mdx
new file mode 100644
index 000000000..e668c0dc7
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/html-tag.mdx
@@ -0,0 +1,5 @@
+import P from '../components/P.astro';
+import Em from '../components/Em.astro';
+
+<P>Render <Em>Me</Em></P>
+<P><Em>Me</Em></P>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/index.mdx
new file mode 100644
index 000000000..d1c6cec9d
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-escape/src/pages/index.mdx
@@ -0,0 +1,13 @@
+import P from '../components/P.astro';
+import Em from '../components/Em.astro';
+import Title from '../components/Title.astro';
+
+export const components = { p: P, em: Em, h1: Title };
+
+# Hello _there_
+
+# _there_
+
+Hello _there_
+
+_there_
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/astro.config.mjs
new file mode 100644
index 000000000..00d8e38f5
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/astro.config.mjs
@@ -0,0 +1,12 @@
+import mdx from '@astrojs/mdx';
+import { defineConfig } from 'astro/config';
+import { rehypeReadingTime, remarkDescription, remarkTitle } from './src/markdown-plugins.mjs';
+
+// https://astro.build/config
+export default defineConfig({
+ site: 'https://astro.build/',
+ integrations: [mdx({
+ remarkPlugins: [remarkTitle, remarkDescription],
+ rehypePlugins: [rehypeReadingTime],
+ })],
+});
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/package.json b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/package.json
new file mode 100644
index 000000000..684063ee5
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@test/mdx-frontmatter-injection",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*",
+ "mdast-util-to-string": "^4.0.0",
+ "reading-time": "^1.5.0",
+ "unist-util-visit": "^5.0.0"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/layouts/Base.astro b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/layouts/Base.astro
new file mode 100644
index 000000000..b3d55f0a8
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/layouts/Base.astro
@@ -0,0 +1,17 @@
+---
+const defaults = { title: 'Frontmatter not passed to layout!' }
+const { frontmatter = defaults, content = defaults } = Astro.props;
+---
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>{frontmatter.title}</title>
+</head>
+<body>
+ <slot />
+</body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs
new file mode 100644
index 000000000..0366bbfe9
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs
@@ -0,0 +1,27 @@
+import { toString } from 'mdast-util-to-string';
+import getReadingTime from 'reading-time';
+import { visit } from 'unist-util-visit';
+
+export function rehypeReadingTime() {
+ return function (tree, { data }) {
+ const readingTime = getReadingTime(toString(tree));
+ data.astro.frontmatter.injectedReadingTime = readingTime;
+ };
+}
+
+export function remarkTitle() {
+ return function (tree, { data }) {
+ visit(tree, ['heading'], (node) => {
+ if (node.depth === 1) {
+ data.astro.frontmatter.title = toString(node.children);
+ }
+ });
+ };
+}
+
+export function remarkDescription() {
+ return function (tree, vfile) {
+ const { frontmatter } = vfile.data.astro;
+ frontmatter.description = `Processed by remarkDescription plugin: ${frontmatter.description}`
+ };
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js
new file mode 100644
index 000000000..63248dc56
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js
@@ -0,0 +1,6 @@
+export async function GET() {
+ const docs = await import.meta.glob('./*.mdx', { eager: true });
+ return new Response(
+ JSON.stringify(Object.values(docs).map(doc => doc.frontmatter))
+ );
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx
new file mode 100644
index 000000000..0d96d95b9
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx
@@ -0,0 +1,8 @@
+---
+layout: '../layouts/Base.astro'
+description: Page 1 description
+---
+
+# Page 1
+
+Look at that!
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx
new file mode 100644
index 000000000..fe6a8286b
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx
@@ -0,0 +1,24 @@
+---
+layout: '../layouts/Base.astro'
+description: Page 2 description
+---
+
+# Page 2
+
+## Table of contents
+
+## Section 1
+
+Some text!
+
+### Subsection 1
+
+Some subsection test!
+
+### Subsection 2
+
+Oh cool, more text!
+
+## Section 2
+
+More content
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro
new file mode 100644
index 000000000..9dde6e980
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro
@@ -0,0 +1,38 @@
+---
+const {
+ content = { title: "content didn't work" },
+ file = "file didn't work",
+ url = "url didn't work",
+ frontmatter = {
+ title: "frontmatter didn't work",
+ file: "file didn't work",
+ url: "url didn't work",
+ },
+ headings = [],
+} = Astro.props;
+---
+
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body>
+ <p data-content-title>{content.title}</p>
+ <p data-frontmatter-title>{frontmatter.title}</p>
+ <p data-frontmatter-file>{frontmatter.file}</p>
+ <p data-frontmatter-url>{frontmatter.url}</p>
+ <p data-file>{frontmatter.file}</p>
+ <p data-url>{frontmatter.url}</p>
+ <p data-layout-rendered>Layout rendered!</p>
+ <ul data-headings>
+ {headings.map(heading => <li>{heading.slug}</li>)}
+ </ul>
+ <slot />
+</body>
+
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js
new file mode 100644
index 000000000..d82d9f770
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js
@@ -0,0 +1,6 @@
+export async function GET() {
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
+ return Response.json({
+ titles: Object.values(mdxPages ?? {}).map((v) => v?.frontmatter?.title),
+ });
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx
new file mode 100644
index 000000000..e6f9c8f4a
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx
@@ -0,0 +1,10 @@
+---
+title: 'Using YAML frontmatter'
+layout: '../layouts/Base.astro'
+illThrowIfIDontExist: "Oh no, that's scary!"
+---
+
+{frontmatter.illThrowIfIDontExist}
+
+> Note: newline intentionally missing from the end of this file.
+> Useful since that can be the source of bugs in our compile step. \ No newline at end of file
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/with-headings.mdx b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/with-headings.mdx
new file mode 100644
index 000000000..cc4db9582
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/with-headings.mdx
@@ -0,0 +1,7 @@
+---
+layout: '../layouts/Base.astro'
+---
+
+## Section 1
+
+## Section 2
diff --git a/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/pages.json.js b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/pages.json.js
new file mode 100644
index 000000000..e57192c61
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/pages.json.js
@@ -0,0 +1,8 @@
+export async function GET() {
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
+ return Response.json({
+ headingsByPage: Object.fromEntries(
+ Object.entries(mdxPages ?? {}).map(([k, v]) => [k, v?.getHeadings()])
+ ),
+ });
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-frontmatter.mdx b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-frontmatter.mdx
new file mode 100644
index 000000000..d40537eb8
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-frontmatter.mdx
@@ -0,0 +1,45 @@
+---
+title: The Frontmatter Title
+keywords: [Keyword 1, Keyword 2, Keyword 3]
+tags:
+ - Tag 1
+ - Tag 2
+ - Tag 3
+items:
+ - value: Item 1
+ - value: Item 2
+ - value: Item 3
+nested_items:
+ nested:
+ - value: Nested Item 1
+ - value: Nested Item 2
+ - value: Nested Item 3
+---
+
+# {frontmatter.title}
+
+This ID should be the frontmatter title.
+
+## frontmatter.title
+
+The ID should not be the frontmatter title.
+
+### {frontmatter.keywords[1]}
+
+The ID should be the frontmatter keyword #2.
+
+### {frontmatter.tags[0]}
+
+The ID should be the frontmatter tag #1.
+
+#### {frontmatter.items[1].value}
+
+The ID should be the frontmatter item #2.
+
+##### {frontmatter.nested_items.nested[2].value}
+
+The ID should be the frontmatter nested item #3.
+
+###### {frontmatter.unknown}
+
+This ID should not reference the frontmatter.
diff --git a/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-jsx-expressions.mdx b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-jsx-expressions.mdx
new file mode 100644
index 000000000..2ec7b1686
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test-with-jsx-expressions.mdx
@@ -0,0 +1,8 @@
+export const h2Title = "Section 1"
+export const h3Title = "Subsection 1"
+
+# Heading test with JSX expressions
+
+## {h2Title}
+
+### {h3Title}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test.mdx b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test.mdx
new file mode 100644
index 000000000..97f1dd37e
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-get-headings/src/pages/test.mdx
@@ -0,0 +1,13 @@
+# Heading test
+
+## Section 1
+
+### Subsection 1
+
+### Subsection 2
+
+## Section 2
+
+## `<Picture />`
+
+### « Sacrebleu ! »
diff --git a/packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/content/1.mdx b/packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/content/1.mdx
new file mode 100644
index 000000000..7ad64edfe
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/content/1.mdx
@@ -0,0 +1,5 @@
+---
+one: hello
+slug: one
+---
+First mdx file
diff --git a/packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/pages/[slug].astro b/packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/pages/[slug].astro
new file mode 100644
index 000000000..fe453a8bf
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-get-static-paths/src/pages/[slug].astro
@@ -0,0 +1,34 @@
+---
+export const getStaticPaths = async () => {
+ const content = await Astro.glob('../content/*.mdx');
+
+ return content
+ .filter((page) => !page.frontmatter.draft) // skip drafts
+ .map(({ default: MdxContent, frontmatter, url, file }) => {
+ return {
+ params: { slug: frontmatter.slug || "index" },
+ props: {
+ MdxContent,
+ file,
+ frontmatter,
+ url
+ }
+ }
+ })
+}
+
+const { MdxContent, frontmatter, url, file } = Astro.props;
+---
+
+<html>
+ <head>
+ <title>Page</title>
+ </head>
+ <body>
+ <MdxContent />
+
+ <div id="one">{frontmatter.one}</div>
+ <div id="url">{url}</div>
+ <div id="file">{file}</div>
+ </body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/astro.config.ts b/packages/integrations/mdx/test/fixtures/mdx-images/astro.config.ts
new file mode 100644
index 000000000..a6326190e
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/astro.config.ts
@@ -0,0 +1,13 @@
+import mdx from '@astrojs/mdx';
+import { testImageService } from '../../../../../astro/test/test-image-service.js';
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ integrations: [mdx()],
+ image: {
+ service: testImageService(),
+ },
+ experimental: {
+ responsiveImages: true,
+ }
+})
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/package.json b/packages/integrations/mdx/test/fixtures/mdx-images/package.json
new file mode 100644
index 000000000..44b4a9fcf
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@test/mdx-images",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg b/packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg
new file mode 100644
index 000000000..f157bd1c5
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
+ <path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
+ <style>
+ path { fill: #000; }
+ @media (prefers-color-scheme: dark) {
+ path { fill: #FFF; }
+ }
+ </style>
+</svg>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston in space.webp b/packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston in space.webp
new file mode 100644
index 000000000..3727bc508
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston in space.webp
Binary files differ
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston.webp b/packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston.webp
new file mode 100644
index 000000000..3727bc508
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/assets/houston.webp
Binary files differ
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx
new file mode 100644
index 000000000..7463939ba
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx
@@ -0,0 +1,5 @@
+Optimized image:
+![Houston](../assets/houston.webp)
+
+Public image:
+![Astro logo](/favicon.svg)
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro
new file mode 100644
index 000000000..3cb699e3a
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro
@@ -0,0 +1,25 @@
+---
+import { Image } from 'astro:assets';
+import type { ImageMetadata } from 'astro';
+
+type Props = {
+ src: string | ImageMetadata;
+ alt: string;
+};
+
+const { src, alt } = Astro.props;
+---
+
+{
+ typeof src === 'string' ? (
+ <img data-my-image src={src} alt={alt} />
+ ) : (
+ <Image data-my-image {src} {alt} />
+ )
+}
+
+<style>
+ [data-my-image] {
+ border: 1px solid red;
+ }
+</style>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx
new file mode 100644
index 000000000..58aebcf54
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx
@@ -0,0 +1,5 @@
+Optimized image:
+![Houston](../../assets/houston.webp)
+
+Public image:
+![Astro logo](/favicon.svg)
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts
new file mode 100644
index 000000000..14443e78d
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts
@@ -0,0 +1,5 @@
+import { defineCollection, z } from 'astro:content';
+
+const blog = defineCollection({});
+
+export const collections = { blog };
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro
new file mode 100644
index 000000000..68a3fe3ba
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro
@@ -0,0 +1,19 @@
+---
+import { getEntry, render } from 'astro:content';
+import MyImage from 'src/components/MyImage.astro';
+
+const entry = await getEntry('blog', 'entry');
+const { Content } = await render(entry)
+---
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Renderer</title>
+</head>
+<body>
+ <Content components={{ img: MyImage }} />
+</body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro
new file mode 100644
index 000000000..4e4db66a4
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro
@@ -0,0 +1,16 @@
+---
+import MyImage from 'src/components/MyImage.astro';
+import MDX from '../components/Component.mdx';
+---
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Renderer</title>
+</head>
+<body>
+ <MDX components={{ img: MyImage }} />
+</body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/houston.png b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/houston.png
new file mode 100644
index 000000000..345ed0e9d
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/houston.png
Binary files differ
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/index.mdx
new file mode 100644
index 000000000..de126a6a0
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/index.mdx
@@ -0,0 +1,17 @@
+Image using a relative path:
+![Houston](../assets/houston.webp)
+
+Image using an aliased path:
+![Houston](~/assets/houston.webp)
+
+Image with a title:
+![Houston](~/assets/houston.webp "Houston title")
+
+Image with spaces in the path:
+![Houston](<~/assets/houston in space.webp>)
+
+Image using a relative path with no slashes:
+![Houston](houston.png)
+
+Image using a relative path with nested directory:
+![Houston](relative/houston.png)
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/no-image.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/no-image.mdx
new file mode 100644
index 000000000..944c593a9
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/no-image.mdx
@@ -0,0 +1 @@
+Nothing to see here.
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/relative/houston.png b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/relative/houston.png
new file mode 100644
index 000000000..345ed0e9d
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/relative/houston.png
Binary files differ
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx
new file mode 100644
index 000000000..763256b1c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx
@@ -0,0 +1,9 @@
+import MyImage from '../components/MyImage.astro';
+
+export const components = { img: MyImage };
+
+Optimized image:
+![Houston](../assets/houston.webp)
+
+Public image:
+![Astro logo](/favicon.svg)
diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json b/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json
new file mode 100644
index 000000000..c193287fc
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "astro/tsconfigs/base",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "~/assets/*": ["src/assets/*"]
+ },
+ },
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist"]
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/astro.config.ts b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/astro.config.ts
new file mode 100644
index 000000000..75a2b5f3a
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/astro.config.ts
@@ -0,0 +1,6 @@
+import mdx from '@astrojs/mdx';
+import preact from '@astrojs/preact';
+
+export default {
+ integrations: [mdx(), preact()]
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/package.json b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/package.json
new file mode 100644
index 000000000..9e21461e1
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@test/mdx-infinite-loop",
+ "type": "module",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "@astrojs/preact": "workspace:*",
+ "astro": "workspace:*",
+ "preact": "^10.26.4"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/components/Test.js b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/components/Test.js
new file mode 100644
index 000000000..831fb327c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/components/Test.js
@@ -0,0 +1,3 @@
+export default function () {
+ return 'Hello world'
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/doc.mdx b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/doc.mdx
new file mode 100644
index 000000000..8b8dc5e07
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/doc.mdx
@@ -0,0 +1,6 @@
+import Test, { Missing } from '../components/Test';
+
+# Hello page!
+
+<Test />
+<Missing />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/index.astro b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/index.astro
new file mode 100644
index 000000000..11f7af385
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-infinite-loop/src/pages/index.astro
@@ -0,0 +1,5 @@
+---
+const files = await Astro.glob('./**/*.mdx')
+---
+
+{files.map((file: any) => <file.Content />)}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-math/src/pages/mathjax.mdx b/packages/integrations/mdx/test/fixtures/mdx-math/src/pages/mathjax.mdx
new file mode 100644
index 000000000..d90b79f2f
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-math/src/pages/mathjax.mdx
@@ -0,0 +1,5 @@
+# Mathjax
+
+$$
+\left(\frac{\sqrt{x+2}}{y^{2}}\right)
+$$
diff --git a/packages/integrations/mdx/test/fixtures/mdx-namespace/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-namespace/astro.config.mjs
new file mode 100644
index 000000000..4671227d3
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-namespace/astro.config.mjs
@@ -0,0 +1,6 @@
+import mdx from '@astrojs/mdx';
+import react from '@astrojs/react';
+
+export default {
+ integrations: [react(), mdx()]
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json b/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json
new file mode 100644
index 000000000..435bb7f54
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@test/mdx-namespace",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "@astrojs/react": "workspace:*",
+ "astro": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-namespace/src/components/Component.jsx b/packages/integrations/mdx/test/fixtures/mdx-namespace/src/components/Component.jsx
new file mode 100644
index 000000000..19a3d9c19
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-namespace/src/components/Component.jsx
@@ -0,0 +1,6 @@
+const Component = () => {
+ return <p id="component">Hello world</p>;
+};
+export const ns = {
+ Component
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/object.mdx b/packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/object.mdx
new file mode 100644
index 000000000..6f3990137
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/object.mdx
@@ -0,0 +1,3 @@
+import * as mod from '../components/Component.jsx';
+
+<mod.ns.Component client:load />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/star.mdx b/packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/star.mdx
new file mode 100644
index 000000000..b3af5422c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-namespace/src/pages/star.mdx
@@ -0,0 +1,3 @@
+import { ns } from '../components/Component.jsx';
+
+<ns.Component client:load />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-optimize/astro.config.mjs
new file mode 100644
index 000000000..a3626c3a3
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/astro.config.mjs
@@ -0,0 +1,37 @@
+import mdx from '@astrojs/mdx';
+
+export default {
+ integrations: [
+ mdx({
+ optimize: {
+ ignoreElementNames: ['strong'],
+ },
+ }),
+ ],
+ markdown: {
+ rehypePlugins: [
+ () => {
+ return (tree) => {
+ tree.children.push({
+ type: 'root',
+ children: [
+ {
+ type: 'element',
+ tagName: 'p',
+ properties: {
+ id: 'injected-root-hast',
+ },
+ children: [
+ {
+ type: 'text',
+ value: 'Injected root hast from rehype plugin',
+ },
+ ],
+ },
+ ],
+ });
+ };
+ },
+ ],
+ },
+};
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/package.json b/packages/integrations/mdx/test/fixtures/mdx-optimize/package.json
new file mode 100644
index 000000000..69120477e
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/mdx-optimize",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Blockquote.astro b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Blockquote.astro
new file mode 100644
index 000000000..aa55e82b1
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Blockquote.astro
@@ -0,0 +1,3 @@
+<blockquote {...Astro.props} class="custom-blockquote">
+ <slot />
+</blockquote>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Strong.astro b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Strong.astro
new file mode 100644
index 000000000..3c0b39ffc
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Strong.astro
@@ -0,0 +1,3 @@
+<strong {...Astro.props} class="custom-strong">
+ <slot />
+</strong>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/_imported.mdx b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/_imported.mdx
new file mode 100644
index 000000000..efe520341
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/_imported.mdx
@@ -0,0 +1,3 @@
+I once heard a very **inspirational** quote:
+
+> I like pancakes
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/import.astro b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/import.astro
new file mode 100644
index 000000000..9bfa7cebf
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/import.astro
@@ -0,0 +1,15 @@
+---
+import Strong from '../components/Strong.astro'
+import { Content, components } from './index.mdx'
+---
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Import MDX component</title>
+ </head>
+ <body>
+ <h1>Astro page</h1>
+ <Content components={{ ...components, strong: Strong }} />
+ </body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/index.mdx
new file mode 100644
index 000000000..a3545bd8c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/index.mdx
@@ -0,0 +1,15 @@
+import Blockquote from '../components/Blockquote.astro'
+
+export const components = {
+ blockquote: Blockquote
+}
+
+# MDX page
+
+I once heard a very inspirational quote:
+
+> I like pancakes
+
+```js
+const pancakes = 'yummy'
+```
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/astro.config.ts b/packages/integrations/mdx/test/fixtures/mdx-page/astro.config.ts
new file mode 100644
index 000000000..f1d5e8bd7
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/astro.config.ts
@@ -0,0 +1,5 @@
+import mdx from '@astrojs/mdx';
+
+export default {
+ integrations: [mdx()]
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/package.json b/packages/integrations/mdx/test/fixtures/mdx-page/package.json
new file mode 100644
index 000000000..0429b4ee4
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@test/mdx-page",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro b/packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro
new file mode 100644
index 000000000..13e0e91ed
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/layouts/EncodingLayout.astro
@@ -0,0 +1 @@
+<slot></slot>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx
new file mode 100644
index 000000000..471827de0
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-frontmatter.mdx
@@ -0,0 +1,7 @@
+---
+layout: ../layouts/EncodingLayout.astro
+---
+
+# 我的第一篇博客文章
+
+发表于:2022-07-01
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx
new file mode 100644
index 000000000..1c8c78630
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding-layout-manual.mdx
@@ -0,0 +1,12 @@
+import EncodingLayout from '../layouts/EncodingLayout.astro'
+
+{/* Ensure random stuff preceding the wrapper layout is ignored when detecting a wrapper layout */}
+export const foo = {}
+
+<EncodingLayout>
+
+# 我的第一篇博客文章
+
+发表于:2022-07-01
+
+</EncodingLayout>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx
new file mode 100644
index 000000000..572b3c370
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/chinese-encoding.mdx
@@ -0,0 +1,3 @@
+# 我的第一篇博客文章
+
+发表于:2022-07-01
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/index.mdx
new file mode 100644
index 000000000..2c9af4d03
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/index.mdx
@@ -0,0 +1,3 @@
+import '../styles.css'
+
+# Hello page!
diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/src/styles.css b/packages/integrations/mdx/test/fixtures/mdx-page/src/styles.css
new file mode 100644
index 000000000..7d5c79e1e
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/styles.css
@@ -0,0 +1,3 @@
+p {
+ color: red;
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plugins/src/pages/with-plugins.mdx b/packages/integrations/mdx/test/fixtures/mdx-plugins/src/pages/with-plugins.mdx
new file mode 100644
index 000000000..8699f4a22
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plugins/src/pages/with-plugins.mdx
@@ -0,0 +1,25 @@
+export let recmaPluginWorking = false
+
+# TOC test
+
+## Table of contents
+
+## Section 1
+
+Some text!
+
+### Subsection 1
+
+Some subsection test!
+
+### Subsection 2
+
+Oh cool, more text!
+
+## Section 2
+
+And section 2, with a hyperlink to check GFM is preserved: https://handle-me-gfm.com
+
+<div data-recma-plugin-works={recmaPluginWorking}></div>
+
+> "Smartypants" is -- awesome
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/astro.config.mjs
new file mode 100644
index 000000000..2905fe476
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/astro.config.mjs
@@ -0,0 +1,6 @@
+import mdx from '@astrojs/mdx';
+import react from '@astrojs/react';
+
+export default {
+ integrations: [mdx(), react()],
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json
new file mode 100644
index 000000000..b76dc19a6
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@test/mdx-plus-react-errors",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "@astrojs/react": "workspace:*",
+ "astro": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/components/BrokenComponent.jsx b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/components/BrokenComponent.jsx
new file mode 100644
index 000000000..f9091c825
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/components/BrokenComponent.jsx
@@ -0,0 +1,8 @@
+import { useState } from "react";
+
+export default function BrokenComponent() {
+ useState(0);
+ a;
+
+ return <p>Whoops!</p>;
+};
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js
new file mode 100644
index 000000000..6250d13c8
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js
@@ -0,0 +1,12 @@
+import { z, defineCollection } from "astro:content";
+
+const filesSchema = () => {
+ return z.object({});
+};
+
+const filesCollection = defineCollection({
+ type: "content",
+ schema: filesSchema(),
+});
+
+export const collections = { files: filesCollection, };
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/files/file.mdx b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/files/file.mdx
new file mode 100644
index 000000000..9c536e9ff
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/files/file.mdx
@@ -0,0 +1,4 @@
+
+import BrokenComponent from '../../components/BrokenComponent'
+
+<BrokenComponent {...props} />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro
new file mode 100644
index 000000000..bee4c85b5
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro
@@ -0,0 +1,9 @@
+---
+import { getCollection } from "astro:content";
+const files = await getCollection("files");
+
+const { Content } = await files[0].render();
+---
+
+<Content />
+
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs
new file mode 100644
index 000000000..fd017ed8e
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs
@@ -0,0 +1,6 @@
+import mdx from '@astrojs/mdx';
+import react from '@astrojs/react';
+
+export default {
+ integrations: [mdx(), react()]
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json b/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json
new file mode 100644
index 000000000..a177efaff
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@test/mdx-plus-react",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "@astrojs/react": "workspace:*",
+ "astro": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx
new file mode 100644
index 000000000..70c336314
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx
@@ -0,0 +1,8 @@
+import { useState } from "react";
+
+const Component = () => {
+ const [name] = useState('world');
+ return <p>Hello {name}</p>;
+};
+
+export default Component;
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro
new file mode 100644
index 000000000..2486e7834
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro
@@ -0,0 +1,11 @@
+---
+import Component from "../components/Component.jsx";
+---
+<html>
+ <head>
+ <title>Testing</title>
+ </head>
+ <body>
+ <Component />
+ </body>
+</html>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/inline-component.mdx b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/inline-component.mdx
new file mode 100644
index 000000000..4071a2c70
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/inline-component.mdx
@@ -0,0 +1,5 @@
+export const Comp = () => <span>Comp</span>
+
+# Inline component
+
+This is an inline component: <Comp />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/post.mdx b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/post.mdx
new file mode 100644
index 000000000..734f33e82
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/post.mdx
@@ -0,0 +1,3 @@
+# Testing
+
+This works!
diff --git a/packages/integrations/mdx/test/fixtures/mdx-script-style-raw/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-script-style-raw/src/pages/index.mdx
new file mode 100644
index 000000000..a603c6626
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-script-style-raw/src/pages/index.mdx
@@ -0,0 +1,13 @@
+# Script style raw
+
+<script id="test-script">
+{`console.log('raw script')`}
+</script>
+
+<style id="test-style">
+{`
+h1[id="script-style-raw"] {
+ color: red;
+}
+`}
+</style>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Slotted.astro b/packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Slotted.astro
new file mode 100644
index 000000000..99453b685
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Slotted.astro
@@ -0,0 +1,4 @@
+<div class="slotted">
+ <div data-default-slot><slot /></div>
+ <div data-named-slot><slot name="named" /></div>
+</div>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Test.mdx b/packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Test.mdx
new file mode 100644
index 000000000..8e901aa1a
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-slots/src/components/Test.mdx
@@ -0,0 +1,15 @@
+import Slotted from './Slotted.astro'
+
+# Hello slotted component!
+
+<Slotted>
+
+Default content.
+
+<Fragment slot="named">
+
+Content for named slot.
+
+</Fragment>
+
+</Slotted>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/glob.astro b/packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/glob.astro
new file mode 100644
index 000000000..ae857fe27
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/glob.astro
@@ -0,0 +1,11 @@
+---
+const components = await Astro.glob('../components/*.mdx');
+---
+
+<div data-default-export>
+ {components.map(Component => <Component.default />)}
+</div>
+
+<div data-content-export>
+ {components.map(({ Content }) => <Content />)}
+</div>
diff --git a/packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/index.astro b/packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/index.astro
new file mode 100644
index 000000000..ed5ae98a3
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-slots/src/pages/index.astro
@@ -0,0 +1,5 @@
+---
+import Test from '../components/Test.mdx';
+---
+
+<Test />
diff --git a/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx
new file mode 100644
index 000000000..866387e57
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx
@@ -0,0 +1,9 @@
+# Syntax highlighting
+
+```astro {2}
+---
+const handlesAstroSyntax = true
+---
+
+<h1>{handlesAstroSyntax}</h1>
+```
diff --git a/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/pages.json.js b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/pages.json.js
new file mode 100644
index 000000000..02cff64a8
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/pages.json.js
@@ -0,0 +1,6 @@
+export async function GET() {
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
+ return Response.json({
+ urls: Object.values(mdxPages ?? {}).map((v) => v?.url),
+ });
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-1.mdx b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-1.mdx
new file mode 100644
index 000000000..c9b984787
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-1.mdx
@@ -0,0 +1 @@
+# I'm a page with a url of "/test-1!"
diff --git a/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-2.mdx b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-2.mdx
new file mode 100644
index 000000000..360f72fc3
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/test-2.mdx
@@ -0,0 +1 @@
+# I'm a page with a url of "/test-2!"
diff --git a/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/with-url-override.mdx b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/with-url-override.mdx
new file mode 100644
index 000000000..e9961928b
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-url-export/src/pages/with-url-override.mdx
@@ -0,0 +1,3 @@
+export const url = '/AH!'
+
+# I'm a test with a url override!
diff --git a/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/astro.config.mjs
new file mode 100644
index 000000000..717d379c0
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/astro.config.mjs
@@ -0,0 +1,16 @@
+import mdx from '@astrojs/mdx';
+
+export default {
+ site: 'https://mdx-is-neat.com/',
+ markdown: {
+ syntaxHighlight: false,
+ },
+ integrations: [mdx()],
+ vite: {
+ build: {
+ // Enabling sourcemap may crash the build when using `import.meta.env.UNKNOWN_VAR`
+ // https://github.com/withastro/astro/issues/9012
+ sourcemap: true,
+ },
+ },
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/package.json b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/package.json
new file mode 100644
index 000000000..b7c45b3e3
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/mdx-env-variables",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*",
+ "@astrojs/mdx": "workspace:*"
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/frontmatter.json.js b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/frontmatter.json.js
new file mode 100644
index 000000000..152285b9c
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/frontmatter.json.js
@@ -0,0 +1,5 @@
+import { frontmatter } from './vite-env-vars.mdx';
+
+export function GET() {
+ return Response.json(frontmatter);
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/vite-env-vars.mdx b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/vite-env-vars.mdx
new file mode 100644
index 000000000..7fb9960ce
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-vite-env-vars/src/pages/vite-env-vars.mdx
@@ -0,0 +1,46 @@
+---
+title: Let's talk about my import.meta.env.SITE
+---
+
+export const modeWorks =
+ import.meta.env.MODE === 'production' ? 'MODE works' : 'MODE does not work!';
+
+export const unknownVar = import.meta.env.UNKNOWN_VAR;
+
+# About my import.meta.env.SITE
+
+My `import.meta.env.SITE` is so cool, I can put env variables in code!
+
+```js
+const site = import.meta.env.SITE;
+```
+
+## But I can use import.meta.env properly too
+
+<div data-env-site>
+
+I can compute my site, for example: {new URL('/blog/cool-post', import.meta.env.SITE)}
+
+</div>
+
+<div data-env-variable-exports>
+
+I can also use `import.meta.env` in variable exports: {modeWorks}
+
+</div>
+
+<div data-env-variable-exports-unknown>
+
+I can also use `import.meta.env.UNKNOWN_VAR` through exports: "{unknownVar}"
+
+</div>
+
+I can also use vars as HTML attributes:
+
+<div
+ data-env-dump
+ data-env-prod={import.meta.env.PROD}
+ data-env-dev={import.meta.env.DEV}
+ data-env-base-url={import.meta.env.BASE_URL}
+ data-env-mode={import.meta.env.MODE}
+></div>
diff --git a/packages/integrations/mdx/test/invalid-mdx-component.test.js b/packages/integrations/mdx/test/invalid-mdx-component.test.js
new file mode 100644
index 000000000..b8152e89c
--- /dev/null
+++ b/packages/integrations/mdx/test/invalid-mdx-component.test.js
@@ -0,0 +1,39 @@
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+import mdx from '../dist/index.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/invalid-mdx-component/', import.meta.url);
+
+describe('MDX component with runtime error', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ });
+
+ describe('build', () => {
+ /** @type {Error | null} */
+ let error;
+
+ before(async () => {
+ error = null;
+ try {
+ await fixture.build();
+ } catch (e) {
+ error = e;
+ }
+ });
+
+ it('Throws the right error', async () => {
+ assert.ok(error);
+ assert.match(
+ error?.hint,
+ /This issue often occurs when your MDX component encounters runtime errors/,
+ );
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js b/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js
new file mode 100644
index 000000000..07197e0e5
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-astro-markdown-remarkRehype.test.js
@@ -0,0 +1,89 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX with Astro Markdown remark-rehype config', () => {
+ it('Renders footnotes with values from the default configuration', async () => {
+ const fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-astro-markdown-remarkRehype/', import.meta.url),
+ integrations: [mdx()],
+ markdown: {
+ remarkRehype: {
+ footnoteLabel: 'Catatan kaki',
+ footnoteBackLabel: 'Kembali ke konten',
+ },
+ },
+ });
+
+ await fixture.build();
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.querySelector('#footnote-label').textContent, 'Catatan kaki');
+ assert.equal(
+ document.querySelector('.data-footnote-backref').getAttribute('aria-label'),
+ 'Kembali ke konten',
+ );
+ });
+
+ it('Renders footnotes with values from custom configuration extending the default', async () => {
+ const fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-astro-markdown-remarkRehype/', import.meta.url),
+ integrations: [
+ mdx({
+ remarkRehype: {
+ footnoteLabel: 'Catatan kaki',
+ footnoteBackLabel: 'Kembali ke konten',
+ },
+ }),
+ ],
+ markdown: {
+ remarkRehype: {
+ footnoteBackLabel: 'Replace me',
+ },
+ },
+ });
+
+ await fixture.build();
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.querySelector('#footnote-label').textContent, 'Catatan kaki');
+ assert.equal(
+ document.querySelector('.data-footnote-backref').getAttribute('aria-label'),
+ 'Kembali ke konten',
+ );
+ });
+
+ it('Renders footnotes with values from custom configuration without extending the default', async () => {
+ const fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-astro-markdown-remarkRehype/', import.meta.url),
+ integrations: [
+ mdx({
+ extendPlugins: 'astroDefaults',
+ remarkRehype: {
+ footnoteLabel: 'Catatan kaki',
+ },
+ }),
+ ],
+ markdown: {
+ remarkRehype: {
+ footnoteBackLabel: 'Kembali ke konten',
+ },
+ },
+ });
+
+ await fixture.build();
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.querySelector('#footnote-label').textContent, 'Catatan kaki');
+ assert.equal(
+ document.querySelector('.data-footnote-backref').getAttribute('aria-label'),
+ 'Back to reference 1',
+ );
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-component.test.js b/packages/integrations/mdx/test/mdx-component.test.js
new file mode 100644
index 000000000..1983730e7
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-component.test.js
@@ -0,0 +1,195 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX Component', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-component/', import.meta.url),
+ integrations: [mdx()],
+ });
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+
+ it('supports top-level imports', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ const foo = document.querySelector('#foo');
+
+ assert.equal(h1.textContent, 'Hello component!');
+ assert.equal(foo.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Component.default />', async () => {
+ const html = await fixture.readFile('/glob/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-default-export] h1');
+ const foo = document.querySelector('[data-default-export] #foo');
+
+ assert.equal(h1.textContent, 'Hello component!');
+ assert.equal(foo.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Content />', async () => {
+ const html = await fixture.readFile('/glob/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-content-export] h1');
+ const foo = document.querySelector('[data-content-export] #foo');
+
+ assert.equal(h1.textContent, 'Hello component!');
+ assert.equal(foo.textContent, 'bar');
+ });
+
+ describe('with <Fragment>', () => {
+ it('supports top-level imports', async () => {
+ const html = await fixture.readFile('/w-fragment/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ const p = document.querySelector('p');
+
+ assert.equal(h1.textContent, 'MDX containing <Fragment />');
+ assert.equal(p.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Component.default />', async () => {
+ const html = await fixture.readFile('/glob/index.html');
+ const { document } = parseHTML(html);
+
+ const h = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] h1');
+ const p = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] p');
+
+ assert.equal(h.textContent, 'MDX containing <Fragment />');
+ assert.equal(p.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Content />', async () => {
+ const html = await fixture.readFile('/glob/index.html');
+ const { document } = parseHTML(html);
+
+ const h = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] h1');
+ const p = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] p');
+
+ assert.equal(h.textContent, 'MDX containing <Fragment />');
+ assert.equal(p.textContent, 'bar');
+ });
+ });
+ });
+
+ describe('dev', () => {
+ let devServer;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('supports top-level imports', async () => {
+ const res = await fixture.fetch('/');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ const foo = document.querySelector('#foo');
+
+ assert.equal(h1.textContent, 'Hello component!');
+ assert.equal(foo.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Component.default />', async () => {
+ const res = await fixture.fetch('/glob');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-default-export] h1');
+ const foo = document.querySelector('[data-default-export] #foo');
+
+ assert.equal(h1.textContent, 'Hello component!');
+ assert.equal(foo.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Content />', async () => {
+ const res = await fixture.fetch('/glob');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-content-export] h1');
+ const foo = document.querySelector('[data-content-export] #foo');
+
+ assert.equal(h1.textContent, 'Hello component!');
+ assert.equal(foo.textContent, 'bar');
+ });
+
+ describe('with <Fragment>', () => {
+ it('supports top-level imports', async () => {
+ const res = await fixture.fetch('/w-fragment');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ const p = document.querySelector('p');
+
+ assert.equal(h1.textContent, 'MDX containing <Fragment />');
+ assert.equal(p.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Component.default />', async () => {
+ const res = await fixture.fetch('/glob');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] h1');
+ const p = document.querySelector('[data-default-export] [data-file="WithFragment.mdx"] p');
+
+ assert.equal(h.textContent, 'MDX containing <Fragment />');
+ assert.equal(p.textContent, 'bar');
+ });
+
+ it('supports glob imports - <Content />', async () => {
+ const res = await fixture.fetch('/glob');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] h1');
+ const p = document.querySelector('[data-content-export] [data-file="WithFragment.mdx"] p');
+
+ assert.equal(h.textContent, 'MDX containing <Fragment />');
+ assert.equal(p.textContent, 'bar');
+ });
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-escape.test.js b/packages/integrations/mdx/test/mdx-escape.test.js
new file mode 100644
index 000000000..0d1200fe7
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-escape.test.js
@@ -0,0 +1,33 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-escape/', import.meta.url);
+
+describe('MDX frontmatter', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ await fixture.build();
+ });
+
+ it('does not have unescaped HTML at top-level', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.body.textContent.includes('<em'), false);
+ });
+
+ it('does not have unescaped HTML inside html tags', async () => {
+ const html = await fixture.readFile('/html-tag/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.body.textContent.includes('<em'), false);
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-frontmatter-injection.test.js b/packages/integrations/mdx/test/mdx-frontmatter-injection.test.js
new file mode 100644
index 000000000..5c8b69d07
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-frontmatter-injection.test.js
@@ -0,0 +1,60 @@
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter-injection/', import.meta.url);
+
+describe('MDX frontmatter injection', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ });
+ await fixture.build();
+ });
+
+ it('remark supports custom vfile data - get title', async () => {
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
+ const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
+ assert.equal(titles.includes('Page 1'), true);
+ assert.equal(titles.includes('Page 2'), true);
+ });
+
+ it('rehype supports custom vfile data - reading time', async () => {
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
+ const readingTimes = frontmatterByPage.map(
+ (frontmatter = {}) => frontmatter.injectedReadingTime,
+ );
+ assert.equal(readingTimes.length > 0, true);
+ for (let readingTime of readingTimes) {
+ assert.notEqual(readingTime, null);
+ assert.match(readingTime.text, /^\d+ min read/);
+ }
+ });
+
+ it('allow user frontmatter mutation', async () => {
+ const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
+ const descriptions = frontmatterByPage.map((frontmatter = {}) => frontmatter.description);
+ assert.equal(
+ descriptions.includes('Processed by remarkDescription plugin: Page 1 description'),
+ true,
+ );
+ assert.equal(
+ descriptions.includes('Processed by remarkDescription plugin: Page 2 description'),
+ true,
+ );
+ });
+
+ it('passes injected frontmatter to layouts', async () => {
+ const html1 = await fixture.readFile('/page-1/index.html');
+ const html2 = await fixture.readFile('/page-2/index.html');
+
+ const title1 = parseHTML(html1).document.querySelector('title');
+ const title2 = parseHTML(html2).document.querySelector('title');
+
+ assert.equal(title1.innerHTML, 'Page 1');
+ assert.equal(title2.innerHTML, 'Page 2');
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-frontmatter.test.js b/packages/integrations/mdx/test/mdx-frontmatter.test.js
new file mode 100644
index 000000000..df36a2439
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-frontmatter.test.js
@@ -0,0 +1,79 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter/', import.meta.url);
+
+describe('MDX frontmatter', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ await fixture.build();
+ });
+ it('builds when "frontmatter.property" is in JSX expression', async () => {
+ assert.equal(true, true);
+ });
+
+ it('extracts frontmatter to "frontmatter" export', async () => {
+ const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
+ assert.equal(titles.includes('Using YAML frontmatter'), true);
+ });
+
+ it('renders layout from "layout" frontmatter property', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const layoutParagraph = document.querySelector('[data-layout-rendered]');
+
+ assert.notEqual(layoutParagraph, null);
+ });
+
+ it('passes frontmatter to layout via "content" and "frontmatter" props', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const contentTitle = document.querySelector('[data-content-title]');
+ const frontmatterTitle = document.querySelector('[data-frontmatter-title]');
+
+ assert.equal(contentTitle.textContent, 'Using YAML frontmatter');
+ assert.equal(frontmatterTitle.textContent, 'Using YAML frontmatter');
+ });
+
+ it('passes headings to layout via "headings" prop', async () => {
+ const html = await fixture.readFile('/with-headings/index.html');
+ const { document } = parseHTML(html);
+
+ const headingSlugs = [...document.querySelectorAll('[data-headings] > li')].map(
+ (el) => el.textContent,
+ );
+
+ assert.equal(headingSlugs.length > 0, true);
+ assert.equal(headingSlugs.includes('section-1'), true);
+ assert.equal(headingSlugs.includes('section-2'), true);
+ });
+
+ it('passes "file" and "url" to layout', async () => {
+ const html = await fixture.readFile('/with-headings/index.html');
+ const { document } = parseHTML(html);
+
+ const frontmatterFile = document.querySelector('[data-frontmatter-file]')?.textContent;
+ const frontmatterUrl = document.querySelector('[data-frontmatter-url]')?.textContent;
+ const file = document.querySelector('[data-file]')?.textContent;
+ const url = document.querySelector('[data-url]')?.textContent;
+
+ assert.equal(
+ frontmatterFile?.endsWith('with-headings.mdx'),
+ true,
+ '"file" prop does not end with correct path or is undefined',
+ );
+ assert.equal(frontmatterUrl, '/with-headings');
+ assert.equal(file, frontmatterFile);
+ assert.equal(url, frontmatterUrl);
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-get-headings.test.js b/packages/integrations/mdx/test/mdx-get-headings.test.js
new file mode 100644
index 000000000..10ce965e1
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-get-headings.test.js
@@ -0,0 +1,251 @@
+import { rehypeHeadingIds } from '@astrojs/markdown-remark';
+import mdx from '@astrojs/mdx';
+import { visit } from 'unist-util-visit';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX getHeadings', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
+ integrations: [mdx()],
+ });
+
+ await fixture.build();
+ });
+
+ it('adds anchor IDs to headings', async () => {
+ const html = await fixture.readFile('/test/index.html');
+ const { document } = parseHTML(html);
+
+ const h2Ids = document.querySelectorAll('h2').map((el) => el?.id);
+ const h3Ids = document.querySelectorAll('h3').map((el) => el?.id);
+ assert.equal(document.querySelector('h1').id, 'heading-test');
+ assert.equal(h2Ids.includes('section-1'), true);
+ assert.equal(h2Ids.includes('section-2'), true);
+ assert.equal(h3Ids.includes('subsection-1'), true);
+ assert.equal(h3Ids.includes('subsection-2'), true);
+ });
+
+ it('generates correct getHeadings() export', async () => {
+ const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
+ // TODO: make this a snapshot test :)
+ assert.equal(
+ JSON.stringify(headingsByPage['./test.mdx']),
+ JSON.stringify([
+ { depth: 1, slug: 'heading-test', text: 'Heading test' },
+ { depth: 2, slug: 'section-1', text: 'Section 1' },
+ { depth: 3, slug: 'subsection-1', text: 'Subsection 1' },
+ { depth: 3, slug: 'subsection-2', text: 'Subsection 2' },
+ { depth: 2, slug: 'section-2', text: 'Section 2' },
+ { depth: 2, slug: 'picture', text: '<Picture />' },
+ { depth: 3, slug: '-sacrebleu-', text: '« Sacrebleu ! »' },
+ ]),
+ );
+ });
+
+ it('generates correct getHeadings() export for JSX expressions', async () => {
+ const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
+ assert.equal(
+ JSON.stringify(headingsByPage['./test-with-jsx-expressions.mdx']),
+ JSON.stringify([
+ {
+ depth: 1,
+ slug: 'heading-test-with-jsx-expressions',
+ text: 'Heading test with JSX expressions',
+ },
+ { depth: 2, slug: 'h2title', text: 'h2Title' },
+ { depth: 3, slug: 'h3title', text: 'h3Title' },
+ ]),
+ );
+ });
+});
+
+describe('MDX heading IDs can be customized by user plugins', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
+ integrations: [mdx()],
+ markdown: {
+ rehypePlugins: [
+ () => (tree) => {
+ let count = 0;
+ visit(tree, 'element', (node) => {
+ if (!/^h\d$/.test(node.tagName)) return;
+ if (!node.properties?.id) {
+ node.properties = { ...node.properties, id: String(count++) };
+ }
+ });
+ },
+ ],
+ },
+ });
+
+ await fixture.build();
+ });
+
+ it('adds user-specified IDs to HTML output', async () => {
+ const html = await fixture.readFile('/test/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ assert.equal(h1?.textContent, 'Heading test');
+ assert.equal(h1?.getAttribute('id'), '0');
+
+ const headingIDs = document.querySelectorAll('h1,h2,h3').map((el) => el.id);
+ assert.equal(
+ JSON.stringify(headingIDs),
+ JSON.stringify(Array.from({ length: headingIDs.length }, (_, idx) => String(idx))),
+ );
+ });
+
+ it('generates correct getHeadings() export', async () => {
+ const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
+ assert.equal(
+ JSON.stringify(headingsByPage['./test.mdx']),
+ JSON.stringify([
+ { depth: 1, slug: '0', text: 'Heading test' },
+ { depth: 2, slug: '1', text: 'Section 1' },
+ { depth: 3, slug: '2', text: 'Subsection 1' },
+ { depth: 3, slug: '3', text: 'Subsection 2' },
+ { depth: 2, slug: '4', text: 'Section 2' },
+ { depth: 2, slug: '5', text: '<Picture />' },
+ { depth: 3, slug: '6', text: '« Sacrebleu ! »' },
+ ]),
+ );
+ });
+});
+
+describe('MDX heading IDs can be injected before user plugins', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
+ integrations: [
+ mdx({
+ rehypePlugins: [
+ rehypeHeadingIds,
+ () => (tree) => {
+ visit(tree, 'element', (node) => {
+ if (!/^h\d$/.test(node.tagName)) return;
+ if (node.properties?.id) {
+ node.children.push({ type: 'text', value: ' ' + node.properties.id });
+ }
+ });
+ },
+ ],
+ }),
+ ],
+ });
+
+ await fixture.build();
+ });
+
+ it('adds user-specified IDs to HTML output', async () => {
+ const html = await fixture.readFile('/test/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ assert.equal(h1?.textContent, 'Heading test heading-test');
+ assert.equal(h1?.id, 'heading-test');
+ });
+});
+
+describe('MDX headings with frontmatter', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
+ integrations: [mdx()],
+ });
+
+ await fixture.build();
+ });
+
+ it('adds anchor IDs to headings', async () => {
+ const html = await fixture.readFile('/test-with-frontmatter/index.html');
+ const { document } = parseHTML(html);
+
+ const h3Ids = document.querySelectorAll('h3').map((el) => el?.id);
+
+ assert.equal(document.querySelector('h1').id, 'the-frontmatter-title');
+ assert.equal(document.querySelector('h2').id, 'frontmattertitle');
+ assert.equal(h3Ids.includes('keyword-2'), true);
+ assert.equal(h3Ids.includes('tag-1'), true);
+ assert.equal(document.querySelector('h4').id, 'item-2');
+ assert.equal(document.querySelector('h5').id, 'nested-item-3');
+ assert.equal(document.querySelector('h6').id, 'frontmatterunknown');
+ });
+
+ it('generates correct getHeadings() export', async () => {
+ const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
+ assert.equal(
+ JSON.stringify(headingsByPage['./test-with-frontmatter.mdx']),
+ JSON.stringify([
+ { depth: 1, slug: 'the-frontmatter-title', text: 'The Frontmatter Title' },
+ { depth: 2, slug: 'frontmattertitle', text: 'frontmatter.title' },
+ { depth: 3, slug: 'keyword-2', text: 'Keyword 2' },
+ { depth: 3, slug: 'tag-1', text: 'Tag 1' },
+ { depth: 4, slug: 'item-2', text: 'Item 2' },
+ { depth: 5, slug: 'nested-item-3', text: 'Nested Item 3' },
+ { depth: 6, slug: 'frontmatterunknown', text: 'frontmatter.unknown' },
+ ]),
+ );
+ });
+});
+
+describe('experimental.headingIdCompat', () => {
+ describe('MDX getHeadings', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
+ integrations: [mdx()],
+ experimental: { headingIdCompat: true },
+ });
+
+ await fixture.build();
+ });
+
+ it('adds anchor IDs to headings', async () => {
+ const html = await fixture.readFile('/test/index.html');
+ const { document } = parseHTML(html);
+
+ const h2Ids = document.querySelectorAll('h2').map((el) => el?.id);
+ const h3Ids = document.querySelectorAll('h3').map((el) => el?.id);
+ assert.equal(document.querySelector('h1').id, 'heading-test');
+ assert.equal(h2Ids.includes('section-1'), true);
+ assert.equal(h2Ids.includes('section-2'), true);
+ assert.equal(h2Ids.includes('picture-'), true);
+ assert.equal(h3Ids.includes('subsection-1'), true);
+ assert.equal(h3Ids.includes('subsection-2'), true);
+ assert.equal(h3Ids.includes('-sacrebleu--'), true);
+ });
+
+ it('generates correct getHeadings() export', async () => {
+ const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
+ assert.equal(
+ JSON.stringify(headingsByPage['./test.mdx']),
+ JSON.stringify([
+ { depth: 1, slug: 'heading-test', text: 'Heading test' },
+ { depth: 2, slug: 'section-1', text: 'Section 1' },
+ { depth: 3, slug: 'subsection-1', text: 'Subsection 1' },
+ { depth: 3, slug: 'subsection-2', text: 'Subsection 2' },
+ { depth: 2, slug: 'section-2', text: 'Section 2' },
+ { depth: 2, slug: 'picture-', text: '<Picture />' },
+ { depth: 3, slug: '-sacrebleu--', text: '« Sacrebleu ! »' },
+ ]),
+ );
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-get-static-paths.test.js b/packages/integrations/mdx/test/mdx-get-static-paths.test.js
new file mode 100644
index 000000000..96f0dc9a4
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-get-static-paths.test.js
@@ -0,0 +1,34 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import * as cheerio from 'cheerio';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-get-static-paths', import.meta.url);
+
+describe('getStaticPaths', () => {
+ /** @type {import('astro/test/test-utils').Fixture} */
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ await fixture.build();
+ });
+
+ it('Provides file and url', async () => {
+ const html = await fixture.readFile('/one/index.html');
+
+ const $ = cheerio.load(html);
+ assert.equal($('p').text(), 'First mdx file');
+ assert.equal($('#one').text(), 'hello', 'Frontmatter included');
+ assert.equal($('#url').text(), 'src/content/1.mdx', 'url is included');
+ assert.equal(
+ $('#file').text().includes('fixtures/mdx-get-static-paths/src/content/1.mdx'),
+ true,
+ 'file is included',
+ );
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-images.test.js b/packages/integrations/mdx/test/mdx-images.test.js
new file mode 100644
index 000000000..543b9021e
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-images.test.js
@@ -0,0 +1,82 @@
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const imageTestRoutes = ['with-components', 'esm-import', 'content-collection'];
+
+describe('MDX Page', () => {
+ let devServer;
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-images/', import.meta.url),
+ });
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ describe('Optimized images in MDX', () => {
+ it('works', async () => {
+ const res = await fixture.fetch('/');
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const imgs = document.getElementsByTagName('img');
+ assert.equal(imgs.length, 6);
+ // Image using a relative path
+ assert.equal(imgs.item(0).src.startsWith('/_image'), true);
+ // Image using an aliased path
+ assert.equal(imgs.item(1).src.startsWith('/_image'), true);
+ // Image with title
+ assert.equal(imgs.item(2).title, 'Houston title');
+ // Image with spaces in the path
+ assert.equal(imgs.item(3).src.startsWith('/_image'), true);
+ // Image using a relative path with no slashes
+ assert.equal(imgs.item(4).src.startsWith('/_image'), true);
+ // Image using a relative path with nested directory
+ assert.equal(imgs.item(5).src.startsWith('/_image'), true);
+ });
+
+ for (const route of imageTestRoutes) {
+ it(`supports img component - ${route}`, async () => {
+ const res = await fixture.fetch(`/${route}`);
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const imgs = document.getElementsByTagName('img');
+ assert.equal(imgs.length, 2);
+
+ const assetsImg = imgs.item(0);
+ assert.equal(assetsImg.src.startsWith('/_image'), true);
+ assert.equal(assetsImg.hasAttribute('data-my-image'), true);
+
+ const publicImg = imgs.item(1);
+ assert.equal(publicImg.src, '/favicon.svg');
+ assert.equal(publicImg.hasAttribute('data-my-image'), true);
+ });
+ }
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+ it('includes responsive styles', async () => {
+ const code = await fixture.readFile('/index.html');
+ assert.ok(code.includes('[data-astro-image]'));
+ });
+ it("doesn't include styles on pages without images", async () => {
+ const code = await fixture.readFile('/no-image/index.html');
+ assert.ok(!code.includes('[data-astro-image]'));
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-infinite-loop.test.js b/packages/integrations/mdx/test/mdx-infinite-loop.test.js
new file mode 100644
index 000000000..ed98d5c6d
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-infinite-loop.test.js
@@ -0,0 +1,31 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX Infinite Loop', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-infinite-loop/', import.meta.url),
+ integrations: [mdx()],
+ });
+ });
+
+ describe('build', () => {
+ let err;
+ before(async () => {
+ try {
+ await fixture.build();
+ } catch (e) {
+ err = e;
+ }
+ });
+
+ it('does not hang forever if an error is thrown', async () => {
+ assert.equal(!!err, true);
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-math.test.js b/packages/integrations/mdx/test/mdx-math.test.js
new file mode 100644
index 000000000..ff54a1042
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-math.test.js
@@ -0,0 +1,73 @@
+import * as assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import mdx from '@astrojs/mdx';
+import { parseHTML } from 'linkedom';
+import rehypeMathjaxSvg from 'rehype-mathjax';
+import rehypeMathjaxChtml from 'rehype-mathjax/chtml';
+import remarkMath from 'remark-math';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-math/', import.meta.url);
+
+describe('MDX math', () => {
+ describe('mathjax', () => {
+ it('works with svg', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ remarkPlugins: [remarkMath],
+ rehypePlugins: [rehypeMathjaxSvg],
+ },
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/mathjax/index.html');
+ const { document } = parseHTML(html);
+
+ const mjxContainer = document.querySelector('mjx-container[jax="SVG"]');
+ assert.notEqual(mjxContainer, null);
+
+ const mjxStyle = document.querySelectorAll('style')[0].innerHTML;
+ assert.equal(
+ mjxStyle.includes('mjx-container[jax="SVG"]'),
+ true,
+ 'style should not be html-escaped',
+ );
+ });
+
+ it('works with chtml', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ remarkPlugins: [remarkMath],
+ rehypePlugins: [
+ [
+ rehypeMathjaxChtml,
+ {
+ chtml: {
+ fontURL: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2',
+ },
+ },
+ ],
+ ],
+ },
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/mathjax/index.html');
+ const { document } = parseHTML(html);
+
+ const mjxContainer = document.querySelector('mjx-container[jax="CHTML"]');
+ assert.notEqual(mjxContainer, null);
+
+ const mjxStyle = document.querySelectorAll('style')[0].innerHTML;
+ assert.equal(
+ mjxStyle.includes('mjx-container[jax="CHTML"]'),
+ true,
+ 'style should not be html-escaped',
+ );
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-namespace.test.js b/packages/integrations/mdx/test/mdx-namespace.test.js
new file mode 100644
index 000000000..13137e40e
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-namespace.test.js
@@ -0,0 +1,84 @@
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX Namespace', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-namespace/', import.meta.url),
+ });
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+
+ it('works for object', async () => {
+ const html = await fixture.readFile('/object/index.html');
+ const { document } = parseHTML(html);
+
+ const island = document.querySelector('astro-island');
+ const component = document.querySelector('#component');
+
+ assert.notEqual(island, undefined);
+ assert.equal(component.textContent, 'Hello world');
+ });
+
+ it('works for star', async () => {
+ const html = await fixture.readFile('/star/index.html');
+ const { document } = parseHTML(html);
+
+ const island = document.querySelector('astro-island');
+ const component = document.querySelector('#component');
+
+ assert.notEqual(island, undefined);
+ assert.equal(component.textContent, 'Hello world');
+ });
+ });
+
+ describe('dev', () => {
+ let devServer;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('works for object', async () => {
+ const res = await fixture.fetch('/object');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const island = document.querySelector('astro-island');
+ const component = document.querySelector('#component');
+
+ assert.notEqual(island, undefined);
+ assert.equal(component.textContent, 'Hello world');
+ });
+
+ it('works for star', async () => {
+ const res = await fixture.fetch('/star');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const island = document.querySelector('astro-island');
+ const component = document.querySelector('#component');
+
+ assert.notEqual(island, undefined);
+ assert.equal(component.textContent, 'Hello world');
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-optimize.test.js b/packages/integrations/mdx/test/mdx-optimize.test.js
new file mode 100644
index 000000000..c45ecda15
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-optimize.test.js
@@ -0,0 +1,61 @@
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-optimize/', import.meta.url);
+
+describe('MDX optimize', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ });
+ await fixture.build();
+ });
+
+ it('renders an MDX page fine', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.querySelector('h1').textContent.includes('MDX page'), true);
+ assert.equal(
+ document.querySelector('p').textContent.includes('I once heard a very inspirational quote:'),
+ true,
+ );
+
+ const blockquote = document.querySelector('blockquote.custom-blockquote');
+ assert.notEqual(blockquote, null);
+ assert.equal(blockquote.textContent.includes('I like pancakes'), true);
+
+ const code = document.querySelector('pre.astro-code');
+ assert.notEqual(code, null);
+ assert.equal(code.textContent.includes(`const pancakes = 'yummy'`), true);
+ });
+
+ it('renders an Astro page that imports MDX fine', async () => {
+ const html = await fixture.readFile('/import/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.querySelector('h1').textContent.includes('Astro page'), true);
+ assert.equal(
+ document.querySelector('p').textContent.includes('I once heard a very inspirational quote:'),
+ true,
+ );
+
+ const blockquote = document.querySelector('blockquote.custom-blockquote');
+ assert.notEqual(blockquote, null);
+ assert.equal(blockquote.textContent.includes('I like pancakes'), true);
+ });
+
+ it('renders MDX with rehype plugin that incorrectly injects root hast node', async () => {
+ const html = await fixture.readFile('/import/index.html');
+ const { document } = parseHTML(html);
+
+ assert.doesNotMatch(html, /set:html=/);
+ assert.equal(
+ document.getElementById('injected-root-hast').textContent,
+ 'Injected root hast from rehype plugin',
+ );
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-page.test.js b/packages/integrations/mdx/test/mdx-page.test.js
new file mode 100644
index 000000000..b58781efc
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-page.test.js
@@ -0,0 +1,109 @@
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import * as cheerio from 'cheerio';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX Page', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-page/', import.meta.url),
+ // test suite was authored when inlineStylesheets defaulted to never
+ build: { inlineStylesheets: 'never' },
+ });
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+
+ it('works', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+
+ assert.equal(h1.textContent, 'Hello page!');
+ });
+
+ it('injects style imports when layout is not applied', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const stylesheet = document.querySelector('link[rel="stylesheet"]');
+
+ assert.notEqual(stylesheet, null);
+ });
+
+ it('Renders MDX in utf-8 by default', async () => {
+ const html = await fixture.readFile('/chinese-encoding/index.html');
+ const $ = cheerio.load(html);
+ assert.equal($('h1').text(), '我的第一篇博客文章');
+ assert.match(html, /<meta charset="utf-8"/);
+ });
+
+ it('Renders MDX with layout frontmatter without utf-8 by default', async () => {
+ const html = await fixture.readFile('/chinese-encoding-layout-frontmatter/index.html');
+ assert.doesNotMatch(html, /<meta charset="utf-8"/);
+ });
+
+ it('Renders MDX with layout manual import without utf-8 by default', async () => {
+ const html = await fixture.readFile('/chinese-encoding-layout-manual/index.html');
+ assert.doesNotMatch(html, /<meta charset="utf-8"/);
+ });
+ });
+
+ describe('dev', () => {
+ let devServer;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('works', async () => {
+ const res = await fixture.fetch('/');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+
+ assert.equal(h1.textContent, 'Hello page!');
+ });
+
+ it('Renders MDX in utf-8 by default', async () => {
+ const res = await fixture.fetch('/chinese-encoding/');
+ assert.equal(res.status, 200);
+ const html = await res.text();
+ const $ = cheerio.load(html);
+ assert.equal($('h1').text(), '我的第一篇博客文章');
+ assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/);
+ assert.match(html, /<meta charset="utf-8"/);
+ });
+
+ it('Renders MDX with layout frontmatter without utf-8 by default', async () => {
+ const res = await fixture.fetch('/chinese-encoding-layout-frontmatter/');
+ assert.equal(res.status, 200);
+ const html = await res.text();
+ assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/);
+ assert.doesNotMatch(html, /<meta charset="utf-8"/);
+ });
+
+ it('Renders MDX with layout manual import without utf-8 by default', async () => {
+ const res = await fixture.fetch('/chinese-encoding-layout-manual/');
+ assert.equal(res.status, 200);
+ const html = await res.text();
+ assert.doesNotMatch(res.headers.get('content-type'), /charset=utf-8/);
+ assert.doesNotMatch(html, /<meta charset="utf-8"/);
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-plugins.test.js b/packages/integrations/mdx/test/mdx-plugins.test.js
new file mode 100644
index 000000000..68ce1e22e
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-plugins.test.js
@@ -0,0 +1,315 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { visit as estreeVisit } from 'estree-util-visit';
+import { parseHTML } from 'linkedom';
+import remarkToc from 'remark-toc';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-plugins/', import.meta.url);
+const FILE = '/with-plugins/index.html';
+
+describe('MDX plugins', () => {
+ it('supports custom remark plugins - TOC', async () => {
+ const fixture = await buildFixture({
+ integrations: [
+ mdx({
+ remarkPlugins: [remarkToc],
+ }),
+ ],
+ });
+
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectTocLink(document), null);
+ });
+
+ it('Applies GFM by default', async () => {
+ const fixture = await buildFixture({
+ integrations: [mdx()],
+ });
+
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectGfmLink(document), null);
+ });
+
+ it('Applies SmartyPants by default', async () => {
+ const fixture = await buildFixture({
+ integrations: [mdx()],
+ });
+
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ const quote = selectSmartypantsQuote(document);
+ assert.notEqual(quote, null);
+ assert.equal(quote.textContent.includes('“Smartypants” is — awesome'), true);
+ });
+
+ it('supports custom rehype plugins', async () => {
+ const fixture = await buildFixture({
+ integrations: [
+ mdx({
+ rehypePlugins: [rehypeExamplePlugin],
+ }),
+ ],
+ });
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectRehypeExample(document), null);
+ });
+
+ it('supports custom rehype plugins from integrations', async () => {
+ const fixture = await buildFixture({
+ integrations: [
+ mdx(),
+ {
+ name: 'test',
+ hooks: {
+ 'astro:config:setup': ({ updateConfig }) => {
+ updateConfig({
+ markdown: {
+ rehypePlugins: [rehypeExamplePlugin],
+ },
+ });
+ },
+ },
+ },
+ ],
+ });
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectRehypeExample(document), null);
+ });
+
+ it('supports custom rehype plugins with namespaced attributes', async () => {
+ const fixture = await buildFixture({
+ integrations: [
+ mdx({
+ rehypePlugins: [rehypeSvgPlugin],
+ }),
+ ],
+ });
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectRehypeSvg(document), null);
+ });
+
+ it('extends markdown config by default', async () => {
+ const fixture = await buildFixture({
+ markdown: {
+ remarkPlugins: [remarkExamplePlugin],
+ rehypePlugins: [rehypeExamplePlugin],
+ },
+ integrations: [mdx()],
+ });
+
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectRemarkExample(document), null);
+ assert.notEqual(selectRehypeExample(document), null);
+ });
+
+ it('ignores string-based plugins in markdown config', async () => {
+ const fixture = await buildFixture({
+ markdown: {
+ remarkPlugins: [['remark-toc', {}]],
+ },
+ integrations: [mdx()],
+ });
+
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.equal(selectTocLink(document), null);
+ });
+
+ for (const extendMarkdownConfig of [true, false]) {
+ describe(`extendMarkdownConfig = ${extendMarkdownConfig}`, () => {
+ let fixture;
+ before(async () => {
+ fixture = await buildFixture({
+ markdown: {
+ remarkPlugins: [remarkToc],
+ gfm: false,
+ smartypants: false,
+ },
+ integrations: [
+ mdx({
+ extendMarkdownConfig,
+ remarkPlugins: [remarkExamplePlugin],
+ rehypePlugins: [rehypeExamplePlugin],
+ }),
+ ],
+ });
+ });
+
+ it('Handles MDX plugins', async () => {
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectRemarkExample(document, 'MDX remark plugins not applied.'), null);
+ assert.notEqual(selectRehypeExample(document, 'MDX rehype plugins not applied.'), null);
+ });
+
+ it('Handles Markdown plugins', async () => {
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.equal(
+ selectTocLink(
+ document,
+ '`remarkToc` plugin applied unexpectedly. Should override Markdown config.',
+ ),
+ null,
+ );
+ });
+
+ it('Handles gfm', async () => {
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ if (extendMarkdownConfig === true) {
+ assert.equal(selectGfmLink(document), null, 'Does not respect `markdown.gfm` option.');
+ } else {
+ assert.notEqual(selectGfmLink(document), null, 'Respects `markdown.gfm` unexpectedly.');
+ }
+ });
+
+ it('Handles smartypants', async () => {
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ const quote = selectSmartypantsQuote(document);
+
+ if (extendMarkdownConfig === true) {
+ assert.equal(
+ quote.textContent.includes('"Smartypants" is -- awesome'),
+ true,
+ 'Does not respect `markdown.smartypants` option.',
+ );
+ } else {
+ assert.equal(
+ quote.textContent.includes('“Smartypants” is — awesome'),
+ true,
+ 'Respects `markdown.smartypants` unexpectedly.',
+ );
+ }
+ });
+ });
+ }
+
+ it('supports custom recma plugins', async () => {
+ const fixture = await buildFixture({
+ integrations: [
+ mdx({
+ recmaPlugins: [recmaExamplePlugin],
+ }),
+ ],
+ });
+
+ const html = await fixture.readFile(FILE);
+ const { document } = parseHTML(html);
+
+ assert.notEqual(selectRecmaExample(document), null);
+ });
+});
+
+async function buildFixture(config) {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ ...config,
+ });
+ await fixture.build();
+ return fixture;
+}
+
+function remarkExamplePlugin() {
+ return (tree) => {
+ tree.children.push({
+ type: 'html',
+ value: '<div data-remark-plugin-works="true"></div>',
+ });
+ };
+}
+
+function rehypeExamplePlugin() {
+ return (tree) => {
+ tree.children.push({
+ type: 'element',
+ tagName: 'div',
+ properties: { 'data-rehype-plugin-works': 'true' },
+ });
+ };
+}
+
+function rehypeSvgPlugin() {
+ return (tree) => {
+ tree.children.push({
+ type: 'element',
+ tagName: 'svg',
+ properties: { xmlns: 'http://www.w3.org/2000/svg' },
+ children: [
+ {
+ type: 'element',
+ tagName: 'use',
+ properties: { xLinkHref: '#icon' },
+ },
+ ],
+ });
+ };
+}
+
+function recmaExamplePlugin() {
+ return (tree) => {
+ estreeVisit(tree, (node) => {
+ if (
+ node.type === 'VariableDeclarator' &&
+ node.id.name === 'recmaPluginWorking' &&
+ node.init?.type === 'Literal'
+ ) {
+ node.init = {
+ ...(node.init ?? {}),
+ value: true,
+ raw: 'true',
+ };
+ }
+ });
+ };
+}
+
+function selectTocLink(document) {
+ return document.querySelector('ul a[href="#section-1"]');
+}
+
+function selectGfmLink(document) {
+ return document.querySelector('a[href="https://handle-me-gfm.com"]');
+}
+
+function selectSmartypantsQuote(document) {
+ return document.querySelector('blockquote');
+}
+
+function selectRemarkExample(document) {
+ return document.querySelector('div[data-remark-plugin-works]');
+}
+
+function selectRehypeExample(document) {
+ return document.querySelector('div[data-rehype-plugin-works]');
+}
+
+function selectRehypeSvg(document) {
+ return document.querySelector('svg > use[xlink\\:href]');
+}
+
+function selectRecmaExample(document) {
+ return document.querySelector('div[data-recma-plugin-works]');
+}
diff --git a/packages/integrations/mdx/test/mdx-plus-react-errors.test.js b/packages/integrations/mdx/test/mdx-plus-react-errors.test.js
new file mode 100644
index 000000000..9d87fa8a0
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-plus-react-errors.test.js
@@ -0,0 +1,33 @@
+import * as assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+function hookError() {
+ const error = console.error;
+ const errors = [];
+ console.error = function (...args) {
+ errors.push(args);
+ };
+ return () => {
+ console.error = error;
+ return errors;
+ };
+}
+
+describe('MDX and React with build errors', () => {
+ let fixture;
+ let unhook;
+
+ it('shows correct error messages on build error', async () => {
+ try {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-plus-react-errors/', import.meta.url),
+ });
+ unhook = hookError();
+ await fixture.build();
+ } catch (err) {
+ assert.equal(err.message, 'a is not defined');
+ }
+ unhook();
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-plus-react.test.js b/packages/integrations/mdx/test/mdx-plus-react.test.js
new file mode 100644
index 000000000..0aa3ed459
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-plus-react.test.js
@@ -0,0 +1,55 @@
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+function hookError() {
+ const error = console.error;
+ const errors = [];
+ console.error = function (...args) {
+ errors.push(args);
+ };
+ return () => {
+ console.error = error;
+ return errors;
+ };
+}
+
+describe('MDX and React', () => {
+ let fixture;
+ let unhook;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-plus-react/', import.meta.url),
+ });
+ unhook = hookError();
+ await fixture.build();
+ });
+
+ it('can be used in the same project', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const p = document.querySelector('p');
+
+ assert.equal(p.textContent, 'Hello world');
+ });
+
+ it('mdx renders fine', async () => {
+ const html = await fixture.readFile('/post/index.html');
+ const { document } = parseHTML(html);
+ const h = document.querySelector('#testing');
+ assert.equal(h.textContent, 'Testing');
+ });
+
+ it('does not get a invalid hook call warning', () => {
+ const errors = unhook();
+ assert.equal(errors.length === 0, true);
+ });
+
+ it('renders inline mdx component', async () => {
+ const html = await fixture.readFile('/inline-component/index.html');
+ assert.match(html, /This is an inline component: <span>Comp<\/span>/);
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-script-style-raw.test.js b/packages/integrations/mdx/test/mdx-script-style-raw.test.js
new file mode 100644
index 000000000..dc4f337bf
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-script-style-raw.test.js
@@ -0,0 +1,75 @@
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import mdx from '@astrojs/mdx';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-script-style-raw/', import.meta.url);
+
+describe('MDX script style raw', () => {
+ describe('dev', () => {
+ let fixture;
+ let devServer;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('works with with raw script and style strings', async () => {
+ const res = await fixture.fetch('/index.html');
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const scriptContent = document.getElementById('test-script').innerHTML;
+ assert.equal(
+ scriptContent.includes("console.log('raw script')"),
+ true,
+ 'script should not be html-escaped',
+ );
+
+ const styleContent = document.getElementById('test-style').innerHTML;
+ assert.equal(
+ styleContent.includes('h1[id="script-style-raw"]'),
+ true,
+ 'style should not be html-escaped',
+ );
+ });
+ });
+
+ describe('build', () => {
+ it('works with with raw script and style strings', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const scriptContent = document.getElementById('test-script').innerHTML;
+ assert.equal(
+ scriptContent.includes("console.log('raw script')"),
+ true,
+ 'script should not be html-escaped',
+ );
+
+ const styleContent = document.getElementById('test-style').innerHTML;
+ assert.equal(
+ styleContent.includes('h1[id="script-style-raw"]'),
+ true,
+ 'style should not be html-escaped',
+ );
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-slots.test.js b/packages/integrations/mdx/test/mdx-slots.test.js
new file mode 100644
index 000000000..e8dc68e67
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-slots.test.js
@@ -0,0 +1,125 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX slots', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-slots/', import.meta.url),
+ integrations: [mdx()],
+ });
+ });
+
+ describe('build', () => {
+ before(async () => {
+ await fixture.build();
+ });
+
+ it('supports top-level imports', async () => {
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ const defaultSlot = document.querySelector('[data-default-slot]');
+ const namedSlot = document.querySelector('[data-named-slot]');
+
+ assert.equal(h1.textContent, 'Hello slotted component!');
+ assert.equal(defaultSlot.textContent, 'Default content.');
+ assert.equal(namedSlot.textContent, 'Content for named slot.');
+ });
+
+ it('supports glob imports - <Component.default />', async () => {
+ const html = await fixture.readFile('/glob/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-default-export] h1');
+ const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]');
+ const namedSlot = document.querySelector('[data-default-export] [data-named-slot]');
+
+ assert.equal(h1.textContent, 'Hello slotted component!');
+ assert.equal(defaultSlot.textContent, 'Default content.');
+ assert.equal(namedSlot.textContent, 'Content for named slot.');
+ });
+
+ it('supports glob imports - <Content />', async () => {
+ const html = await fixture.readFile('/glob/index.html');
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-content-export] h1');
+ const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]');
+ const namedSlot = document.querySelector('[data-content-export] [data-named-slot]');
+
+ assert.equal(h1.textContent, 'Hello slotted component!');
+ assert.equal(defaultSlot.textContent, 'Default content.');
+ assert.equal(namedSlot.textContent, 'Content for named slot.');
+ });
+ });
+
+ describe('dev', () => {
+ let devServer;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('supports top-level imports', async () => {
+ const res = await fixture.fetch('/');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('h1');
+ const defaultSlot = document.querySelector('[data-default-slot]');
+ const namedSlot = document.querySelector('[data-named-slot]');
+
+ assert.equal(h1.textContent, 'Hello slotted component!');
+ assert.equal(defaultSlot.textContent, 'Default content.');
+ assert.equal(namedSlot.textContent, 'Content for named slot.');
+ });
+
+ it('supports glob imports - <Component.default />', async () => {
+ const res = await fixture.fetch('/glob');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-default-export] h1');
+ const defaultSlot = document.querySelector('[data-default-export] [data-default-slot]');
+ const namedSlot = document.querySelector('[data-default-export] [data-named-slot]');
+
+ assert.equal(h1.textContent, 'Hello slotted component!');
+ assert.equal(defaultSlot.textContent, 'Default content.');
+ assert.equal(namedSlot.textContent, 'Content for named slot.');
+ });
+
+ it('supports glob imports - <Content />', async () => {
+ const res = await fixture.fetch('/glob');
+
+ assert.equal(res.status, 200);
+
+ const html = await res.text();
+ const { document } = parseHTML(html);
+
+ const h1 = document.querySelector('[data-content-export] h1');
+ const defaultSlot = document.querySelector('[data-content-export] [data-default-slot]');
+ const namedSlot = document.querySelector('[data-content-export] [data-named-slot]');
+
+ assert.equal(h1.textContent, 'Hello slotted component!');
+ assert.equal(defaultSlot.textContent, 'Default content.');
+ assert.equal(namedSlot.textContent, 'Content for named slot.');
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js b/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js
new file mode 100644
index 000000000..662c1a0cd
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js
@@ -0,0 +1,146 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import rehypePrettyCode from 'rehype-pretty-code';
+import shikiTwoslash from 'remark-shiki-twoslash';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-syntax-hightlighting/', import.meta.url);
+
+describe('MDX syntax highlighting', () => {
+ describe('shiki', () => {
+ it('works', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: 'shiki',
+ },
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const shikiCodeBlock = document.querySelector('pre.astro-code');
+ assert.notEqual(shikiCodeBlock, null);
+ assert.equal(shikiCodeBlock.getAttribute('style').includes('background-color:#24292e'), true);
+ });
+
+ it('respects markdown.shikiConfig.theme', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: 'shiki',
+ shikiConfig: {
+ theme: 'dracula',
+ },
+ },
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const shikiCodeBlock = document.querySelector('pre.astro-code');
+ assert.notEqual(shikiCodeBlock, null);
+ assert.equal(shikiCodeBlock.getAttribute('style').includes('background-color:#282A36'), true);
+ });
+ });
+
+ describe('prism', () => {
+ it('works', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: 'prism',
+ },
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const prismCodeBlock = document.querySelector('pre.language-astro');
+ assert.notEqual(prismCodeBlock, null);
+ });
+
+ for (const extendMarkdownConfig of [true, false]) {
+ it(`respects syntaxHighlight when extendMarkdownConfig = ${extendMarkdownConfig}`, async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: 'shiki',
+ },
+ integrations: [
+ mdx({
+ extendMarkdownConfig,
+ syntaxHighlight: 'prism',
+ }),
+ ],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const shikiCodeBlock = document.querySelector('pre.astro-code');
+ assert.equal(shikiCodeBlock, null, 'Markdown config syntaxHighlight used unexpectedly');
+ const prismCodeBlock = document.querySelector('pre.language-astro');
+ assert.notEqual(prismCodeBlock, null);
+ });
+ }
+ });
+
+ it('supports custom highlighter - shiki-twoslash', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: false,
+ },
+ integrations: [
+ mdx({
+ remarkPlugins: [shikiTwoslash.default ?? shikiTwoslash],
+ }),
+ ],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ const { document } = parseHTML(html);
+
+ const twoslashCodeBlock = document.querySelector('pre.shiki');
+ assert.notEqual(twoslashCodeBlock, null);
+ });
+
+ it('supports custom highlighter - rehype-pretty-code', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ markdown: {
+ syntaxHighlight: false,
+ },
+ integrations: [
+ mdx({
+ rehypePlugins: [
+ [
+ rehypePrettyCode,
+ {
+ onVisitHighlightedLine(node) {
+ node.properties.style = 'background-color:#000000';
+ },
+ },
+ ],
+ ],
+ }),
+ ],
+ });
+ await fixture.build();
+
+ const html = await fixture.readFile('/index.html');
+ assert.equal(html.includes('style="background-color:#000000"'), true);
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-url-export.test.js b/packages/integrations/mdx/test/mdx-url-export.test.js
new file mode 100644
index 000000000..db7288bff
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-url-export.test.js
@@ -0,0 +1,29 @@
+import mdx from '@astrojs/mdx';
+
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX url export', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-url-export/', import.meta.url),
+ integrations: [mdx()],
+ });
+
+ await fixture.build();
+ });
+
+ it('generates correct urls in glob result', async () => {
+ const { urls } = JSON.parse(await fixture.readFile('/pages.json'));
+ assert.equal(urls.includes('/test-1'), true);
+ assert.equal(urls.includes('/test-2'), true);
+ });
+
+ it('respects "export url" overrides in glob result', async () => {
+ const { urls } = JSON.parse(await fixture.readFile('/pages.json'));
+ assert.equal(urls.includes('/AH!'), true);
+ });
+});
diff --git a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js b/packages/integrations/mdx/test/mdx-vite-env-vars.test.js
new file mode 100644
index 000000000..213386ceb
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-vite-env-vars.test.js
@@ -0,0 +1,65 @@
+import * as assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+describe('MDX - Vite env vars', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-vite-env-vars/', import.meta.url),
+ });
+ await fixture.build();
+ });
+
+ it('Avoids transforming `import.meta.env` outside JSX expressions', async () => {
+ const html = await fixture.readFile('/vite-env-vars/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(document.querySelector('h1')?.innerHTML.includes('import.meta.env.SITE'), true);
+ assert.equal(document.querySelector('code')?.innerHTML.includes('import.meta.env.SITE'), true);
+ assert.equal(document.querySelector('pre')?.innerHTML.includes('import.meta.env.SITE'), true);
+ });
+ it('Allows referencing `import.meta.env` in frontmatter', async () => {
+ const { title = '' } = JSON.parse(await fixture.readFile('/frontmatter.json'));
+ assert.equal(title.includes('import.meta.env.SITE'), true);
+ });
+ it('Transforms `import.meta.env` in {JSX expressions}', async () => {
+ const html = await fixture.readFile('/vite-env-vars/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(
+ document
+ .querySelector('[data-env-site]')
+ ?.innerHTML.includes('https://mdx-is-neat.com/blog/cool-post'),
+ true,
+ );
+ });
+ it('Transforms `import.meta.env` in variable exports', async () => {
+ const html = await fixture.readFile('/vite-env-vars/index.html');
+ const { document } = parseHTML(html);
+
+ assert.equal(
+ document.querySelector('[data-env-variable-exports]')?.innerHTML.includes('MODE works'),
+ true,
+ );
+ assert.equal(
+ document
+ .querySelector('[data-env-variable-exports-unknown]')
+ ?.innerHTML.includes('exports: ""'),
+ true,
+ );
+ });
+ it('Transforms `import.meta.env` in HTML attributes', async () => {
+ const html = await fixture.readFile('/vite-env-vars/index.html');
+ const { document } = parseHTML(html);
+
+ const dataAttrDump = document.querySelector('[data-env-dump]');
+ assert.notEqual(dataAttrDump, null);
+
+ assert.equal(dataAttrDump.getAttribute('data-env-prod'), 'true');
+ assert.equal(dataAttrDump.getAttribute('data-env-dev'), 'false');
+ assert.equal(dataAttrDump.getAttribute('data-env-base-url'), '/');
+ assert.equal(dataAttrDump.getAttribute('data-env-mode'), 'production');
+ });
+});
diff --git a/packages/integrations/mdx/test/remark-imgattr.test.js b/packages/integrations/mdx/test/remark-imgattr.test.js
new file mode 100644
index 000000000..067d18e23
--- /dev/null
+++ b/packages/integrations/mdx/test/remark-imgattr.test.js
@@ -0,0 +1,49 @@
+import * as assert from 'node:assert/strict';
+import { after, before, describe, it } from 'node:test';
+import * as cheerio from 'cheerio';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/image-remark-imgattr/', import.meta.url);
+
+describe('Testing remark plugins for image processing', () => {
+ /** @type {import('../../../astro/test/test-utils.js').Fixture} */
+ let fixture;
+
+ describe('start dev server', () => {
+ /** @type {import('../../../astro/test/test-utils.js').DevServer} */
+ let devServer;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ });
+
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ describe('Test image attributes can be added by remark plugins', () => {
+ let $;
+ before(async () => {
+ let res = await fixture.fetch('/');
+ let html = await res.text();
+ $ = cheerio.load(html);
+ });
+
+ it('<img> has correct attributes', async () => {
+ let $img = $('img');
+ assert.equal($img.attr('id'), 'test');
+ assert.equal($img.attr('sizes'), '(min-width: 600px) 600w, 300w');
+ assert.ok($img.attr('srcset'));
+ });
+
+ it('<img> was processed properly', async () => {
+ let $img = $('img');
+ assert.equal(new URL($img.attr('src'), 'http://example.com').searchParams.get('w'), '300');
+ });
+ });
+ });
+});
diff --git a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js b/packages/integrations/mdx/test/units/rehype-optimize-static.test.js
new file mode 100644
index 000000000..6121975a4
--- /dev/null
+++ b/packages/integrations/mdx/test/units/rehype-optimize-static.test.js
@@ -0,0 +1,89 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { compile as _compile } from '@mdx-js/mdx';
+import { rehypeOptimizeStatic } from '../../dist/rehype-optimize-static.js';
+
+/**
+ * @param {string} mdxCode
+ * @param {Readonly<import('@mdx-js/mdx').CompileOptions>} options
+ */
+async function compile(mdxCode, options) {
+ const result = await _compile(mdxCode, {
+ jsx: true,
+ rehypePlugins: [rehypeOptimizeStatic],
+ ...options,
+ });
+ const code = result.toString();
+ // Capture the returned JSX code for testing
+ const jsx = /return (.+);\n\}\nexport default function MDXContent/s.exec(code)?.[1];
+ if (jsx == null) throw new Error('Could not find JSX code in compiled MDX');
+ return dedent(jsx);
+}
+
+function dedent(str) {
+ const lines = str.split('\n');
+ if (lines.length <= 1) return str;
+ // Get last line indent, and dedent this amount for the other lines
+ const lastLineIndent = lines[lines.length - 1].match(/^\s*/)[0].length;
+ return lines.map((line, i) => (i === 0 ? line : line.slice(lastLineIndent))).join('\n');
+}
+
+describe('rehype-optimize-static', () => {
+ it('works', async () => {
+ const jsx = await compile(`# hello`);
+ assert.equal(
+ jsx,
+ `\
+<_components.h1 {...{
+ "set:html": "hello"
+}} />`,
+ );
+ });
+
+ it('groups sibling nodes as a single Fragment', async () => {
+ const jsx = await compile(`\
+# hello
+
+foo bar
+`);
+ assert.equal(
+ jsx,
+ `\
+<Fragment set:html="<h1>hello</h1>
+<p>foo bar</p>" />`,
+ );
+ });
+
+ it('skips optimization of components', async () => {
+ const jsx = await compile(`\
+import Comp from './Comp.jsx';
+
+# hello
+
+This is a <Comp />
+`);
+ assert.equal(
+ jsx,
+ `\
+<><Fragment set:html="<h1>hello</h1>
+" /><_components.p>{"This is a "}<Comp /></_components.p></>`,
+ );
+ });
+
+ it('optimizes explicit html elements', async () => {
+ const jsx = await compile(`\
+# hello
+
+foo <strong>bar</strong> baz
+
+<strong>qux</strong>
+`);
+ assert.equal(
+ jsx,
+ `\
+<Fragment set:html="<h1>hello</h1>
+<p>foo <strong>bar</strong> baz</p>
+<strong>qux</strong>" />`,
+ );
+ });
+});
diff --git a/packages/integrations/mdx/tsconfig.json b/packages/integrations/mdx/tsconfig.json
new file mode 100644
index 000000000..1504b4b6d
--- /dev/null
+++ b/packages/integrations/mdx/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "outDir": "./dist"
+ }
+}