From 032ad1c047a62dd663067cc562537d16f2872aa7 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 30 Jun 2022 14:09:09 -0400 Subject: [PATCH] MDX support (#3706) * feat: first pass at MDX support * fix: move built-in JSX renderer to come first * chore: remove jsx example * chore: update lockfile * chore: cleanup example * fix: missing deps * refactor: move component render logic to `renderPage` * chore: update HMR script * chore: update MDX example * refactor: prefer unshit * refactor: remove TODO comment * fix: remove duplicate identifier * refactor: cleanup mdx entrypoint * fix: better html handling * fix: add tsconfig to mdx package * chore: update lockfile * fix: do not sort plugins unless mdx is enabled * chore: update compiler * fix(hmr): maybe render head for non-Astro pages * fix: set initial pageExtensions * refactor: cleanup addPageExtension * refactor: remove addPageExtensions from types * refactor: expose HookParameters type * fix: only default to astro for MDX * test: pick up jsx support in test fixtures * refactor: simplify mdx entrypoint * test: add basic MDX tests * test(e2e): add mdx + framework tests * chore: update lockfile * test(e2e): fix preact mdx e2e test * fix(mdx): disable .md support * test(e2e): fix vue-component test missing mdx * test(e2e): fix solid component needing import * fix: allow `client:only="solid"` as an alias to `solid-js` * chore: move to with-mdx example * chore: update MDX readme * chore: update example readme * chore: bump astro version * chore: update lockfile * Update mod.d.ts * feat: support `export const components` in MDX pages * chore: update mdx example * fix: update jsx-runtime with better slot support * refactor: remove object style support * chore: cleanup package exports * chore: add todo comment * refactor: improve isPage function, move to utils * refactor: dry up manual HMR updates * chore: add dev tests for MDX * chore: prefer set to array * chore: add changesets * fix(hmr): flip public/private route Co-authored-by: Nate Moore --- .changeset/selfish-beers-dance.md | 5 + .changeset/shaggy-gifts-admire.md | 5 + examples/with-mdx/.gitignore | 19 ++ examples/with-mdx/.npmrc | 2 + examples/with-mdx/.stackblitzrc | 6 + examples/with-mdx/.vscode/extensions.json | 4 + examples/with-mdx/.vscode/launch.json | 11 + examples/with-mdx/README.md | 9 + examples/with-mdx/astro.config.mjs | 11 + examples/with-mdx/package.json | 17 + examples/with-mdx/public/favicon.ico | Bin 0 -> 4286 bytes examples/with-mdx/sandbox.config.json | 11 + examples/with-mdx/src/components/Counter.jsx | 18 + examples/with-mdx/src/components/Title.astro | 7 + examples/with-mdx/src/pages/index.mdx | 19 ++ examples/with-mdx/tsconfig.json | 15 + package.json | 1 + .../preact-component/astro.config.mjs | 3 +- .../fixtures/preact-component/package.json | 1 + .../preact-component/src/pages/mdx.mdx | 29 ++ .../fixtures/react-component/astro.config.mjs | 3 +- .../e2e/fixtures/react-component/package.json | 1 + .../react-component/src/pages/mdx.mdx | 28 ++ .../fixtures/solid-component/astro.config.mjs | 3 +- .../e2e/fixtures/solid-component/package.json | 3 +- .../src/components/SolidComponent.jsx | 2 + .../solid-component/src/pages/mdx.mdx | 28 ++ .../svelte-component/astro.config.mjs | 3 +- .../fixtures/svelte-component/package.json | 1 + .../svelte-component/src/pages/mdx.mdx | 28 ++ .../fixtures/vue-component/astro.config.mjs | 14 +- .../e2e/fixtures/vue-component/package.json | 3 +- .../fixtures/vue-component/src/pages/mdx.mdx | 28 ++ packages/astro/e2e/preact-component.test.js | 8 + packages/astro/e2e/react-component.test.js | 8 + packages/astro/e2e/solid-component.test.js | 8 + packages/astro/e2e/svelte-component.test.js | 9 + packages/astro/e2e/vue-component.test.js | 9 + packages/astro/package.json | 2 +- packages/astro/src/@types/astro.ts | 6 +- packages/astro/src/core/config.ts | 7 +- packages/astro/src/core/create-vite.ts | 17 + packages/astro/src/core/render/core.ts | 11 +- packages/astro/src/core/render/dev/index.ts | 4 +- packages/astro/src/core/util.ts | 27 ++ packages/astro/src/integrations/index.ts | 22 +- packages/astro/src/jsx-runtime/index.ts | 15 +- packages/astro/src/runtime/client/hmr.ts | 18 +- packages/astro/src/runtime/server/index.ts | 24 +- packages/astro/src/runtime/server/jsx.ts | 13 + packages/astro/src/vite-plugin-jsx/index.ts | 12 +- packages/astro/test/test-utils.js | 10 +- packages/integrations/mdx/README.md | 106 ++++++ packages/integrations/mdx/package.json | 47 +++ packages/integrations/mdx/src/index.ts | 39 +++ .../mdx-component/src/components/Test.mdx | 3 + .../mdx-component/src/pages/index.astro | 5 + .../fixtures/mdx-page/src/pages/index.mdx | 1 + .../mdx/test/mdx-component.test.js | 63 ++++ .../integrations/mdx/test/mdx-page.test.js | 59 ++++ packages/integrations/mdx/tsconfig.json | 10 + packages/webapi/mod.d.ts | 2 +- pnpm-lock.yaml | 307 +++++++++++++++++- 63 files changed, 1153 insertions(+), 57 deletions(-) create mode 100644 .changeset/selfish-beers-dance.md create mode 100644 .changeset/shaggy-gifts-admire.md create mode 100644 examples/with-mdx/.gitignore create mode 100644 examples/with-mdx/.npmrc create mode 100644 examples/with-mdx/.stackblitzrc create mode 100644 examples/with-mdx/.vscode/extensions.json create mode 100644 examples/with-mdx/.vscode/launch.json create mode 100644 examples/with-mdx/README.md create mode 100644 examples/with-mdx/astro.config.mjs create mode 100644 examples/with-mdx/package.json create mode 100644 examples/with-mdx/public/favicon.ico create mode 100644 examples/with-mdx/sandbox.config.json create mode 100644 examples/with-mdx/src/components/Counter.jsx create mode 100644 examples/with-mdx/src/components/Title.astro create mode 100644 examples/with-mdx/src/pages/index.mdx create mode 100644 examples/with-mdx/tsconfig.json create mode 100644 packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx create mode 100644 packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx create mode 100644 packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx create mode 100644 packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx create mode 100644 packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx create mode 100644 packages/integrations/mdx/README.md create mode 100644 packages/integrations/mdx/package.json create mode 100644 packages/integrations/mdx/src/index.ts create mode 100644 packages/integrations/mdx/test/fixtures/mdx-component/src/components/Test.mdx create mode 100644 packages/integrations/mdx/test/fixtures/mdx-component/src/pages/index.astro create mode 100644 packages/integrations/mdx/test/fixtures/mdx-page/src/pages/index.mdx create mode 100644 packages/integrations/mdx/test/mdx-component.test.js create mode 100644 packages/integrations/mdx/test/mdx-page.test.js create mode 100644 packages/integrations/mdx/tsconfig.json diff --git a/.changeset/selfish-beers-dance.md b/.changeset/selfish-beers-dance.md new file mode 100644 index 000000000..4386cdda2 --- /dev/null +++ b/.changeset/selfish-beers-dance.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Internal changes needed to support `@astrojs/mdx` diff --git a/.changeset/shaggy-gifts-admire.md b/.changeset/shaggy-gifts-admire.md new file mode 100644 index 000000000..e41f6ddab --- /dev/null +++ b/.changeset/shaggy-gifts-admire.md @@ -0,0 +1,5 @@ +--- +'@astrojs/mdx': patch +--- + +Initial release! πŸŽ‰ diff --git a/examples/with-mdx/.gitignore b/examples/with-mdx/.gitignore new file mode 100644 index 000000000..02f6e50b4 --- /dev/null +++ b/examples/with-mdx/.gitignore @@ -0,0 +1,19 @@ +# build output +dist/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/examples/with-mdx/.npmrc b/examples/with-mdx/.npmrc new file mode 100644 index 000000000..ef83021af --- /dev/null +++ b/examples/with-mdx/.npmrc @@ -0,0 +1,2 @@ +# Expose Astro dependencies for `pnpm` users +shamefully-hoist=true diff --git a/examples/with-mdx/.stackblitzrc b/examples/with-mdx/.stackblitzrc new file mode 100644 index 000000000..43798ecff --- /dev/null +++ b/examples/with-mdx/.stackblitzrc @@ -0,0 +1,6 @@ +{ + "startCommand": "npm start", + "env": { + "ENABLE_CJS_IMPORTS": true + } +} \ No newline at end of file diff --git a/examples/with-mdx/.vscode/extensions.json b/examples/with-mdx/.vscode/extensions.json new file mode 100644 index 000000000..22a15055d --- /dev/null +++ b/examples/with-mdx/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/examples/with-mdx/.vscode/launch.json b/examples/with-mdx/.vscode/launch.json new file mode 100644 index 000000000..d64220976 --- /dev/null +++ b/examples/with-mdx/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/examples/with-mdx/README.md b/examples/with-mdx/README.md new file mode 100644 index 000000000..1a13dcdcc --- /dev/null +++ b/examples/with-mdx/README.md @@ -0,0 +1,9 @@ +# Astro Example: MDX + +``` +npm init astro -- --template with-mdx +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-mdx) + +This example showcases using [`@astrojs/mdx`](https://www.npmjs.com/package/@astrojs/mdx) to author content using [MDX](https://mdxjs.com/). diff --git a/examples/with-mdx/astro.config.mjs b/examples/with-mdx/astro.config.mjs new file mode 100644 index 000000000..57011d4ee --- /dev/null +++ b/examples/with-mdx/astro.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig } from 'astro/config'; +import mdx from '@astrojs/mdx'; +import preact from '@astrojs/preact'; + +// https://astro.build/config +export default defineConfig({ + integrations: [ + mdx(), + preact() + ] +}); diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json new file mode 100644 index 000000000..fdff7d48d --- /dev/null +++ b/examples/with-mdx/package.json @@ -0,0 +1,17 @@ +{ + "name": "@example/with-mdx", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "devDependencies": { + "@astrojs/mdx": "^0.0.1", + "@astrojs/preact": "^0.2.0", + "astro": "^1.0.0-beta.58", + "preact": "^10.6.5" + } +} diff --git a/examples/with-mdx/public/favicon.ico b/examples/with-mdx/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..578ad458b8906c08fbed84f42b045fea04db89d1 GIT binary patch literal 4286 zcmchZF=!M)6ox0}Fc8GdTHG!cdIY>nA!3n2f|wxIl0rn}Hl#=uf>?-!2r&jMEF^_k zh**lGut*gwBmoNv7AaB&2~nbzULg{WBhPQ{ZVzvF_HL8Cb&hv$_s#qN|IO^o>?+mA zuTW6tU%k~z<&{z+7$G%*nRsTcEO|90xy<-G5&JTt%CgZZCDT4%R?+{Vd^wh>P8_)} z`+dF$HQb9!>1o`Ivn;GInlCw{9T@Rt%q+d^T3Ke%cxkk;$v`{s^zCB9nHAv6w$Vbn z8fb<+eQTNM`;rf9#obfGnV#3+OQEUv4gU;{oA@zol%keY9-e>4W>p7AHmH~&!P7f7!Uj` zwgFeQ=<3G4O;mwWO`L!=R-=y3_~-DPjH3W^3f&jjCfC$o#|oGaahSL`_=f?$&Aa+W z2h8oZ+@?NUcjGW|aWJfbM*ZzxzmCPY`b~RobNrrj=rd`=)8-j`iSW64@0_b6?;GYk zNB+-fzOxlqZ?`y{OA$WigtZXa8)#p#=DPYxH=VeC_Q5q9Cv`mvW6*zU&Gnp1;oPM6 zaK_B3j(l^FyJgYeE9RrmDyhE7W2}}nW%ic#0v@i1E!yTey$W)U>fyd+!@2hWQ!Wa==NAtKoj`f3tp4y$Al`e;?)76?AjdaRR>|?&r)~3Git> zb1)a?uiv|R0_{m#A9c;7)eZ1y6l@yQ#oE*>(Z2fG-&&smPa2QTW>m*^K65^~`coP$ z8y5Y?iS<4Gz{Zg##$1mk)u-0;X|!xu^FCr;ce~X<&UWE&pBgqfYmEJTzpK9I%vr%b z3Ksd6qlPJLI%HFfeXK_^|BXiKZC>Ocu(Kk6hD3G-8usLzVG^q00Qh gz)s7ge@$ApxGu7=(6IGIk+uG&HTev01^#CH3$(Wk5&!@I literal 0 HcmV?d00001 diff --git a/examples/with-mdx/sandbox.config.json b/examples/with-mdx/sandbox.config.json new file mode 100644 index 000000000..9178af77d --- /dev/null +++ b/examples/with-mdx/sandbox.config.json @@ -0,0 +1,11 @@ +{ + "infiniteLoopProtection": true, + "hardReloadOnChange": false, + "view": "browser", + "template": "node", + "container": { + "port": 3000, + "startScript": "start", + "node": "14" + } +} diff --git a/examples/with-mdx/src/components/Counter.jsx b/examples/with-mdx/src/components/Counter.jsx new file mode 100644 index 000000000..801eefe73 --- /dev/null +++ b/examples/with-mdx/src/components/Counter.jsx @@ -0,0 +1,18 @@ +import { useState } from 'preact/hooks'; + +export default function Counter({ children }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/examples/with-mdx/src/components/Title.astro b/examples/with-mdx/src/components/Title.astro new file mode 100644 index 000000000..6d0dcb86c --- /dev/null +++ b/examples/with-mdx/src/components/Title.astro @@ -0,0 +1,7 @@ +

+ + diff --git a/examples/with-mdx/src/pages/index.mdx b/examples/with-mdx/src/pages/index.mdx new file mode 100644 index 000000000..e541df27c --- /dev/null +++ b/examples/with-mdx/src/pages/index.mdx @@ -0,0 +1,19 @@ +import Counter from '../components/Counter.jsx' +import Title from '../components/Title.astro' +export const components = { h1: Title } + +# Hello world! + +export const authors = [ + {name: 'Jane', email: 'hi@jane.com'}, + {name: 'John', twitter: '@john2002'} +] +export const published = new Date('2022-02-01') + +Written by: {new Intl.ListFormat('en').format(authors.map(d => d.name))}. + +Published on: {new Intl.DateTimeFormat('en', {dateStyle: 'long'}).format(published)}. + + +## Counter + diff --git a/examples/with-mdx/tsconfig.json b/examples/with-mdx/tsconfig.json new file mode 100644 index 000000000..7ac81809a --- /dev/null +++ b/examples/with-mdx/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + // Enable top-level await, and other modern ESM features. + "target": "ESNext", + "module": "ESNext", + // Enable node-style module resolution, for things like npm package imports. + "moduleResolution": "node", + // Enable JSON imports. + "resolveJsonModule": true, + // Enable stricter transpilation for better output. + "isolatedModules": true, + // Add type definitions for our Vite runtime. + "types": ["vite/client"] + } +} diff --git a/package.json b/package.json index 769afbcd5..d9649eb72 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "pnpm": { "peerDependencyRules": { "ignoreMissing": [ + "rollup", "@babel/core", "@babel/plugin-transform-react-jsx", "vite" diff --git a/packages/astro/e2e/fixtures/preact-component/astro.config.mjs b/packages/astro/e2e/fixtures/preact-component/astro.config.mjs index 08916b1fe..7a8aef521 100644 --- a/packages/astro/e2e/fixtures/preact-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/preact-component/astro.config.mjs @@ -1,7 +1,8 @@ import { defineConfig } from 'astro/config'; import preact from '@astrojs/preact'; +import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ - integrations: [preact()], + integrations: [preact(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/preact-component/package.json b/packages/astro/e2e/fixtures/preact-component/package.json index 489d24338..0d9b84026 100644 --- a/packages/astro/e2e/fixtures/preact-component/package.json +++ b/packages/astro/e2e/fixtures/preact-component/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@astrojs/preact": "workspace:*", + "@astrojs/mdx": "workspace:*", "astro": "workspace:*", "preact": "^10.7.3" } diff --git a/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx new file mode 100644 index 000000000..76952d942 --- /dev/null +++ b/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx @@ -0,0 +1,29 @@ + +import Counter from '../components/Counter.jsx'; +import PreactComponent from '../components/JSXComponent.jsx'; + +export const someProps = { + count: 0, +}; + + + # Hello, server! + + + + # Hello, client:idle! + + + + # Hello, client:load! + + + + # Hello, client:visible! + + + + # Hello, client:media! + + + diff --git a/packages/astro/e2e/fixtures/react-component/astro.config.mjs b/packages/astro/e2e/fixtures/react-component/astro.config.mjs index 8a6f1951c..5c044b69d 100644 --- a/packages/astro/e2e/fixtures/react-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/react-component/astro.config.mjs @@ -1,7 +1,8 @@ import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; +import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ - integrations: [react()], + integrations: [react(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/react-component/package.json b/packages/astro/e2e/fixtures/react-component/package.json index 9236558f0..4e56631bd 100644 --- a/packages/astro/e2e/fixtures/react-component/package.json +++ b/packages/astro/e2e/fixtures/react-component/package.json @@ -5,6 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", + "@astrojs/mdx": "workspace:*", "react": "^18.1.0", "react-dom": "^18.1.0" } diff --git a/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx new file mode 100644 index 000000000..be13d365e --- /dev/null +++ b/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx @@ -0,0 +1,28 @@ +import Counter from '../components/Counter.jsx'; +import ReactComponent from '../components/JSXComponent.jsx'; + +export const someProps = { + count: 0, +}; + + + # Hello, server! + + + + # Hello, client:idle! + + + + # Hello, client:load! + + + + # Hello, client:visible! + + + + # Hello, client:media! + + + diff --git a/packages/astro/e2e/fixtures/solid-component/astro.config.mjs b/packages/astro/e2e/fixtures/solid-component/astro.config.mjs index a6c39b853..f527c69b4 100644 --- a/packages/astro/e2e/fixtures/solid-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/solid-component/astro.config.mjs @@ -1,7 +1,8 @@ import { defineConfig } from 'astro/config'; +import mdx from '@astrojs/mdx'; import solid from '@astrojs/solid-js'; // https://astro.build/config export default defineConfig({ - integrations: [solid()], + integrations: [solid(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/solid-component/package.json b/packages/astro/e2e/fixtures/solid-component/package.json index 5eef50d71..1ed5a5742 100644 --- a/packages/astro/e2e/fixtures/solid-component/package.json +++ b/packages/astro/e2e/fixtures/solid-component/package.json @@ -4,7 +4,8 @@ "private": true, "dependencies": { "@astrojs/solid-js": "workspace:*", - "astro": "workspace:*" + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*" }, "devDependencies": { "solid-js": "^1.4.3" diff --git a/packages/astro/e2e/fixtures/solid-component/src/components/SolidComponent.jsx b/packages/astro/e2e/fixtures/solid-component/src/components/SolidComponent.jsx index 248fd10d6..4010cdb95 100644 --- a/packages/astro/e2e/fixtures/solid-component/src/components/SolidComponent.jsx +++ b/packages/astro/e2e/fixtures/solid-component/src/components/SolidComponent.jsx @@ -1,3 +1,5 @@ +import 'solid-js'; + export default function SolidComponent({ id }) { return (
Framework client:only component
diff --git a/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx new file mode 100644 index 000000000..fcd3e3391 --- /dev/null +++ b/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx @@ -0,0 +1,28 @@ +import Counter from '../components/Counter.jsx'; +import SolidComponent from '../components/SolidComponent.jsx'; + +export const someProps = { + count: 0, +}; + + + # Hello, server! + + + + # Hello, client:idle! + + + + # Hello, client:load! + + + + # Hello, client:visible! + + + + # Hello, client:media! + + + diff --git a/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs b/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs index 77fdcd1b9..bc5c6c9bb 100644 --- a/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/svelte-component/astro.config.mjs @@ -1,7 +1,8 @@ import { defineConfig } from 'astro/config'; import svelte from '@astrojs/svelte'; +import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ - integrations: [svelte()], + integrations: [svelte(), mdx()], }); diff --git a/packages/astro/e2e/fixtures/svelte-component/package.json b/packages/astro/e2e/fixtures/svelte-component/package.json index 2777c8ecd..999a248c7 100644 --- a/packages/astro/e2e/fixtures/svelte-component/package.json +++ b/packages/astro/e2e/fixtures/svelte-component/package.json @@ -5,6 +5,7 @@ "dependencies": { "@astrojs/svelte": "workspace:*", "astro": "workspace:*", + "@astrojs/mdx": "workspace:*", "svelte": "^3.48.0" } } diff --git a/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx new file mode 100644 index 000000000..0550f3b9f --- /dev/null +++ b/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx @@ -0,0 +1,28 @@ +import Counter from '../components/Counter.svelte'; +import SvelteComponent from '../components/SvelteComponent.svelte'; + +export const someProps = { + count: 0, +}; + + + # Hello, server! + + + + # Hello, client:idle! + + + + # Hello, client:load! + + + + # Hello, client:visible! + + + + # Hello, client:media! + + + diff --git a/packages/astro/e2e/fixtures/vue-component/astro.config.mjs b/packages/astro/e2e/fixtures/vue-component/astro.config.mjs index 94bdad87f..9a3f12727 100644 --- a/packages/astro/e2e/fixtures/vue-component/astro.config.mjs +++ b/packages/astro/e2e/fixtures/vue-component/astro.config.mjs @@ -1,13 +1,17 @@ import { defineConfig } from 'astro/config'; import vue from '@astrojs/vue'; +import mdx from '@astrojs/mdx'; // https://astro.build/config export default defineConfig({ - integrations: [vue({ - template: { - compilerOptions: { - isCustomElement: tag => tag.includes('my-button') + integrations: [ + mdx(), + vue({ + template: { + compilerOptions: { + isCustomElement: tag => tag.includes('my-button') + } } } - })], + )], }); diff --git a/packages/astro/e2e/fixtures/vue-component/package.json b/packages/astro/e2e/fixtures/vue-component/package.json index 2322b5d2d..206bb68ca 100644 --- a/packages/astro/e2e/fixtures/vue-component/package.json +++ b/packages/astro/e2e/fixtures/vue-component/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@astrojs/vue": "workspace:*", - "astro": "workspace:*" + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*" } } diff --git a/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx new file mode 100644 index 000000000..b5d2285e5 --- /dev/null +++ b/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx @@ -0,0 +1,28 @@ +import Counter from '../components/Counter.vue'; +import VueComponent from '../components/VueComponent.vue'; + +export const someProps = { + count: 0, +}; + + + # Hello, server! + + + + # Hello, client:idle! + + + + # Hello, client:load! + + + + # Hello, client:visible! + + + + # Hello, client:media! + + + diff --git a/packages/astro/e2e/preact-component.test.js b/packages/astro/e2e/preact-component.test.js index f7b79e9da..98f723cf8 100644 --- a/packages/astro/e2e/preact-component.test.js +++ b/packages/astro/e2e/preact-component.test.js @@ -17,3 +17,11 @@ test.describe('Preact components in Markdown files', () => { componentFilePath: './src/components/JSXComponent.jsx', }); }); + +test.describe('Preact components in MDX files', () => { + createTests({ + pageUrl: '/mdx/', + pageSourceFilePath: './src/pages/mdx.mdx', + componentFilePath: './src/components/JSXComponent.jsx', + }); +}); diff --git a/packages/astro/e2e/react-component.test.js b/packages/astro/e2e/react-component.test.js index 05f8c1a87..7aba75a0c 100644 --- a/packages/astro/e2e/react-component.test.js +++ b/packages/astro/e2e/react-component.test.js @@ -17,3 +17,11 @@ test.describe('React components in Markdown files', () => { componentFilePath: './src/components/JSXComponent.jsx', }); }); + +test.describe('React components in MDX files', () => { + createTests({ + pageUrl: '/mdx/', + pageSourceFilePath: './src/pages/mdx.mdx', + componentFilePath: './src/components/JSXComponent.jsx', + }); +}); diff --git a/packages/astro/e2e/solid-component.test.js b/packages/astro/e2e/solid-component.test.js index 3a200c67e..033829b04 100644 --- a/packages/astro/e2e/solid-component.test.js +++ b/packages/astro/e2e/solid-component.test.js @@ -17,3 +17,11 @@ test.describe('Solid components in Markdown files', () => { componentFilePath: './src/components/SolidComponent.jsx', }); }); + +test.describe('Solid components in MDX files', () => { + createTests({ + pageUrl: '/mdx/', + pageSourceFilePath: './src/pages/mdx.mdx', + componentFilePath: './src/components/SolidComponent.jsx', + }); +}); diff --git a/packages/astro/e2e/svelte-component.test.js b/packages/astro/e2e/svelte-component.test.js index 6ee9f1100..7533d7cb7 100644 --- a/packages/astro/e2e/svelte-component.test.js +++ b/packages/astro/e2e/svelte-component.test.js @@ -19,3 +19,12 @@ test.describe('Svelte components in Markdown files', () => { counterCssFilePath: './src/components/Counter.svelte', }); }); + +test.describe('Svelte components in MDX files', () => { + createTests({ + pageUrl: '/mdx/', + pageSourceFilePath: './src/pages/mdx.mdx', + componentFilePath: './src/components/SvelteComponent.svelte', + counterCssFilePath: './src/components/Counter.svelte', + }); +}); diff --git a/packages/astro/e2e/vue-component.test.js b/packages/astro/e2e/vue-component.test.js index 67c22d627..d975600dc 100644 --- a/packages/astro/e2e/vue-component.test.js +++ b/packages/astro/e2e/vue-component.test.js @@ -19,3 +19,12 @@ test.describe('Vue components in Markdown files', () => { counterCssFilePath: './src/components/Counter.vue', }); }); + +test.describe('Vue components in MDX files', () => { + createTests({ + pageUrl: '/mdx/', + pageSourceFilePath: './src/pages/mdx.mdx', + componentFilePath: './src/components/VueComponent.vue', + counterCssFilePath: './src/components/Counter.vue', + }); +}); diff --git a/packages/astro/package.json b/packages/astro/package.json index 8b7b38369..9f5a92c5b 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -80,7 +80,7 @@ "test:e2e:match": "playwright test -g" }, "dependencies": { - "@astrojs/compiler": "^0.17.0", + "@astrojs/compiler": "^0.17.1", "@astrojs/language-server": "^0.13.4", "@astrojs/markdown-remark": "^0.11.3", "@astrojs/prism": "0.4.1", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index d63e1dbf4..72382e60a 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -939,6 +939,10 @@ export interface SSRLoadedRenderer extends AstroRenderer { }; } +export type HookParameters = Fn extends (...args: any) => any + ? Parameters[0] + : never; + export interface AstroIntegration { /** The name of the integration. */ name: string; @@ -955,7 +959,7 @@ export interface AstroIntegration { // This may require some refactoring of `scripts`, `styles`, and `links` into something // more generalized. Consider the SSR use-case as well. // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; - }) => void; + }) => void | Promise; 'astro:config:done'?: (options: { config: AstroConfig; setAdapter: (adapter: AstroAdapter) => void; diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 4ad099e1f..05f1b1ae9 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -339,7 +339,7 @@ export async function validateConfig( const result = { ...(await AstroConfigRelativeSchema.parseAsync(userConfig)), _ctx: { - pageExtensions: [], + pageExtensions: ['.astro', '.md'], scripts: [], renderers: [], injectedRoutes: [], @@ -347,12 +347,11 @@ export async function validateConfig( }, }; if ( - // TODO: expose @astrojs/mdx package result.integrations.find((integration) => integration.name === '@astrojs/mdx') ) { - // Enable default JSX integration + // Enable default JSX integration. It needs to come first, so unshift rather than push! const { default: jsxRenderer } = await import('../jsx/renderer.js'); - (result._ctx.renderers as any[]).push(jsxRenderer); + (result._ctx.renderers as any[]).unshift(jsxRenderer); } // Final-Pass Validation (perform checks that require the full config object) diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index d0fd6f3a6..f55dbf0cb 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -128,9 +128,26 @@ export async function createVite( let result = commonConfig; result = vite.mergeConfig(result, astroConfig.vite || {}); result = vite.mergeConfig(result, commandConfig); + sortPlugins(result); return result; } +function getPluginName(plugin: vite.PluginOption) { + if (plugin && typeof plugin === 'object' && !Array.isArray(plugin)) { + return plugin.name; + } +} + +function sortPlugins(result: ViteConfigWithSSR) { + // HACK: move mdxPlugin to top because it needs to run before internal JSX plugin + const mdxPluginIndex = result.plugins?.findIndex(plugin => getPluginName(plugin) === '@mdx-js/rollup') ?? -1; + if (mdxPluginIndex === -1) return; + const jsxPluginIndex = result.plugins?.findIndex(plugin => getPluginName(plugin) === 'astro:jsx') ?? -1; + const mdxPlugin = result.plugins?.[mdxPluginIndex]; + result.plugins?.splice(mdxPluginIndex, 1); + result.plugins?.splice(jsxPluginIndex, 0, mdxPlugin); +} + // Scans `projectRoot` for third-party Astro packages that could export an `.astro` file // `.astro` files need to be built by Vite, so these should use `noExternal` async function getAstroPackages({ root }: AstroConfig): Promise { diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index 2487a79e4..6e492ec8c 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -140,11 +140,10 @@ export async function render(opts: RenderOptions): Promise { ssr, }); - if (!Component.isAstroComponentFactory) { - const props: Record = { ...(pageProps ?? {}), 'server:root': true }; - const html = await renderComponent(result, Component.name, Component, props, null); - return new Response(html.toString(), result.response); - } else { - return await renderPage(result, Component, pageProps, null); + // Support `export const components` for `MDX` pages + if (typeof (mod as any).components === 'object') { + Object.assign(pageProps, { components: (mod as any).components }); } + + return await renderPage(result, Component, pageProps, null); } diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index be1a7b20b..317202a7b 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -11,7 +11,7 @@ import type { } from '../../../@types/astro'; import { prependForwardSlash } from '../../../core/path.js'; import { LogOptions } from '../../logger/core.js'; -import { isBuildingToSSR } from '../../util.js'; +import { isBuildingToSSR, isPage } from '../../util.js'; import { render as coreRender } from '../core.js'; import { RouteCache } from '../route-cache.js'; import { createModuleScriptElementWithSrcSet } from '../ssr-element.js'; @@ -113,7 +113,7 @@ export async function render( ); // Inject HMR scripts - if (mod.hasOwnProperty('$$metadata') && mode === 'development') { + if (isPage(filePath, astroConfig) && mode === 'development') { scripts.add({ props: { type: 'module', src: '/@vite/client' }, children: '', diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 8759a2fc8..2dae8ae12 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -152,6 +152,33 @@ export function resolvePages(config: AstroConfig) { return new URL('./pages', config.srcDir); } +function isInPagesDir(file: URL, config: AstroConfig): boolean { + const pagesDir = resolvePages(config); + return file.toString().startsWith(pagesDir.toString()); +} + +function isPublicRoute(file: URL, config: AstroConfig): boolean { + const pagesDir = resolvePages(config); + const parts = file.toString().replace(pagesDir.toString(), '').split('/').slice(1); + for (const part of parts) { + if (part.startsWith('_')) return false; + } + return true; +} + +function endsWithPageExt(file: URL, config: AstroConfig): boolean { + for (const ext of config._ctx.pageExtensions) { + if (file.toString().endsWith(ext)) return true; + } + return false; +} + +export function isPage(file: URL, config: AstroConfig): boolean { + if (!isInPagesDir(file, config)) return false; + if (!isPublicRoute(file, config)) return false; + return endsWithPageExt(file, config); +} + export function isBuildingToSSR(config: AstroConfig): boolean { const adapter = config._ctx.adapter; if (!adapter) return false; diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index 88c6a6f5e..c3064eae9 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -2,7 +2,7 @@ import type { AddressInfo } from 'net'; import type { ViteDevServer } from 'vite'; import { AstroConfig, - AstroIntegration, + HookParameters, AstroRenderer, BuildConfig, RouteData, @@ -14,11 +14,6 @@ import { mergeConfig } from '../core/config.js'; import type { ViteConfigWithSSR } from '../core/create-vite.js'; import { isBuildingToSSR } from '../core/util.js'; -type Hooks< - Hook extends keyof AstroIntegration['hooks'], - Fn = AstroIntegration['hooks'][Hook] -> = Fn extends (...args: any) => any ? Parameters[0] : never; - export async function runHookConfigSetup({ config: _config, command, @@ -45,7 +40,7 @@ export async function runHookConfigSetup({ * ``` */ if (integration?.hooks?.['astro:config:setup']) { - const hooks: Hooks<'astro:config:setup'> = { + const hooks: HookParameters<'astro:config:setup'> = { config: updatedConfig, command, addRenderer(renderer: AstroRenderer) { @@ -62,13 +57,14 @@ export async function runHookConfigSetup({ }, }; // Semi-private `addPageExtension` hook + function addPageExtension(...input: (string | string[])[]) { + const exts = (input.flat(Infinity) as string[]).map( + (ext) => `.${ext.replace(/^\./, '')}` + ); + updatedConfig._ctx.pageExtensions.push(...exts); + } Object.defineProperty(hooks, 'addPageExtension', { - value: (...input: (string | string[])[]) => { - const exts = (input.flat(Infinity) as string[]).map( - (ext) => `.${ext.replace(/^\./, '')}` - ); - updatedConfig._ctx.pageExtensions.push(...exts); - }, + value: addPageExtension, writable: false, enumerable: false, }); diff --git a/packages/astro/src/jsx-runtime/index.ts b/packages/astro/src/jsx-runtime/index.ts index 37d79a673..b257d2ab5 100644 --- a/packages/astro/src/jsx-runtime/index.ts +++ b/packages/astro/src/jsx-runtime/index.ts @@ -17,8 +17,20 @@ export function isVNode(vnode: any): vnode is AstroVNode { export function transformSlots(vnode: AstroVNode) { if (typeof vnode.type === 'string') return vnode; - if (!Array.isArray(vnode.props.children)) return; + // Handle single child with slot attribute const slots: Record = {}; + if (isVNode(vnode.props.children)) { + const child = vnode.props.children; + if (!isVNode(child)) return; + if (!('slot' in child.props)) return; + const name = toSlotName(child.props.slot); + slots[name] = [child]; + slots[name]['$$slot'] = true; + delete child.props.slot; + delete vnode.props.children; + } + if (!Array.isArray(vnode.props.children)) return; + // Handle many children with slot attributes vnode.props.children = vnode.props.children .map((child) => { if (!isVNode(child)) return child; @@ -28,6 +40,7 @@ export function transformSlots(vnode: AstroVNode) { slots[name].push(child); } else { slots[name] = [child]; + slots[name]['$$slot'] = true; } delete child.props.slot; return Empty; diff --git a/packages/astro/src/runtime/client/hmr.ts b/packages/astro/src/runtime/client/hmr.ts index 4e1713b2e..bc4780b01 100644 --- a/packages/astro/src/runtime/client/hmr.ts +++ b/packages/astro/src/runtime/client/hmr.ts @@ -1,6 +1,16 @@ if (import.meta.hot) { import.meta.hot.accept((mod) => mod); + const parser = new DOMParser(); + + const KNOWN_MANUAL_HMR_EXTENSIONS = new Set(['.astro', '.md', '.mdx']); + function needsManualHMR(path: string) { + for (const ext of KNOWN_MANUAL_HMR_EXTENSIONS.values()) { + if (path.endsWith(ext)) return true; + } + return false; + } + async function updatePage() { const { default: diff } = await import('micromorph'); const html = await fetch(`${window.location}`).then((res) => res.text()); @@ -35,11 +45,11 @@ if (import.meta.hot) { }); } async function updateAll(files: any[]) { - let hasAstroUpdate = false; + let hasManualUpdate = false; let styles = []; for (const file of files) { - if (file.acceptedPath.endsWith('.astro')) { - hasAstroUpdate = true; + if (needsManualHMR(file.acceptedPath)) { + hasManualUpdate = true; continue; } if (file.acceptedPath.includes('svelte&type=style')) { @@ -72,7 +82,7 @@ if (import.meta.hot) { updateStyle(id, content); } } - if (hasAstroUpdate) { + if (hasManualUpdate) { return await updatePage(); } } diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 068bc0c5c..4a6daf71c 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -166,6 +166,10 @@ function formatList(values: string[]): string { return `${values.slice(0, -1).join(', ')} or ${values[values.length - 1]}`; } +const rendererAliases = new Map([ + ['solid', 'solid-js'] +]) + export async function renderComponent( result: SSRResult, displayName: string, @@ -278,7 +282,8 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`') } else { // Attempt: use explicitly passed renderer name if (metadata.hydrateArgs) { - const rendererName = metadata.hydrateArgs; + const passedName = metadata.hydrateArgs; + const rendererName = rendererAliases.has(passedName) ? rendererAliases.get(passedName) : passedName; renderer = renderers.filter( ({ name }) => name === `@astrojs/${rendererName}` || name === rendererName )[0]; @@ -701,10 +706,25 @@ export async function renderPage( props: any, children: any ): Promise { + let iterable: AsyncIterable; + if (!componentFactory.isAstroComponentFactory) { + const pageProps: Record = { ...(props ?? {}), 'server:root': true }; + const output = await renderComponent(result, componentFactory.name, componentFactory, pageProps, null); + let html = output.toString() + if (!/\n${await maybeRenderHead(result)}${html}`; + } + return new Response(html, { + headers: new Headers([ + ['Content-Type', 'text/html; charset=utf-8'], + ['Content-Length', `${Buffer.byteLength(html, 'utf-8')}`] + ]) + }); + } const factoryReturnValue = await componentFactory(result, props, children); if (isAstroComponent(factoryReturnValue)) { - let iterable = renderAstroComponent(factoryReturnValue); + iterable = renderAstroComponent(factoryReturnValue); let stream = new ReadableStream({ start(controller) { async function read() { diff --git a/packages/astro/src/runtime/server/jsx.ts b/packages/astro/src/runtime/server/jsx.ts index 015378797..a62587259 100644 --- a/packages/astro/src/runtime/server/jsx.ts +++ b/packages/astro/src/runtime/server/jsx.ts @@ -5,6 +5,7 @@ import { HTMLString, markHTMLString, renderComponent, + renderToString, spreadAttributes, voidElementNames, } from './index.js'; @@ -23,6 +24,18 @@ export async function renderJSX(result: any, vnode: any): Promise { return markHTMLString( (await Promise.all(vnode.map((v: any) => renderJSX(result, v)))).join('') ); + case vnode.type.isAstroComponentFactory: { + let props: Record = {}; + let slots: Record = {}; + for (const [key, value] of Object.entries(vnode.props ?? {})) { + if (key === 'children' || value && typeof value === 'object' && (value as any)['$$slot']) { + slots[key === 'children' ? 'default' : key] = () => renderJSX(result, value); + } else { + props[key] = value; + } + } + return await renderToString(result, vnode.type, props, slots) + } } if (vnode[AstroJSX]) { if (!vnode.type && vnode.type !== 0) return ''; diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index 421c3622c..cc28a69b2 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -12,11 +12,11 @@ import { error } from '../core/logger/core.js'; import { parseNpmName } from '../core/util.js'; const JSX_RENDERER_CACHE = new WeakMap>(); -const JSX_EXTENSIONS = new Set(['.jsx', '.tsx']); +const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']); const IMPORT_STATEMENTS: Record = { react: "import React from 'react'", preact: "import { h } from 'preact'", - 'solid-js': "import 'solid-js/web'", + 'solid-js': "import 'solid-js'", astro: "import 'astro/jsx-runtime'", }; @@ -26,6 +26,7 @@ const IMPORT_STATEMENTS: Record = { const PREVENT_UNUSED_IMPORTS = ';;(React,Fragment,h);'; function getEsbuildLoader(fileExt: string): string { + if (fileExt === '.mdx') return 'jsx'; return fileExt.slice(1); } @@ -164,9 +165,10 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin } } + // if no imports were found, look for @jsxImportSource comment if (!importSource) { - const multiline = code.match(/\/\*\*[\S\s]*\*\//gm) || []; + const multiline = code.match(/\/\*\*?[\S\s]*\*\//gm) || []; for (const comment of multiline) { const [_, lib] = comment.slice(0, -2).match(/@jsxImportSource\s*(\S+)/) || []; if (lib) { @@ -176,6 +178,10 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin } } + if (!importSource && jsxRenderers.has('astro') && id.includes('.mdx')) { + importSource = 'astro'; + } + // if JSX renderer found, then use that if (importSource) { const jsxRenderer = jsxRenderers.get(importSource); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 523344c53..90511d812 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -2,7 +2,7 @@ import { execa } from 'execa'; import { polyfill } from '@astrojs/webapi'; import fs from 'fs'; import { fileURLToPath } from 'url'; -import { resolveConfig, loadConfig } from '../dist/core/config.js'; +import { loadConfig } from '../dist/core/config.js'; import dev from '../dist/core/dev/index.js'; import build from '../dist/core/build/index.js'; import preview from '../dist/core/preview/index.js'; @@ -71,17 +71,23 @@ export async function loadFixture(inlineConfig) { cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); } } + // Load the config. let config = await loadConfig({ cwd: fileURLToPath(cwd) }); config = merge(config, { ...inlineConfig, root: cwd }); - // Note: the inline config doesn't run through config validation where these normalizations usually occur + // HACK: the inline config doesn't run through config validation where these normalizations usually occur if (typeof inlineConfig.site === 'string') { config.site = new URL(inlineConfig.site); } if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { config.base = inlineConfig.base + '/'; } + if (config.integrations.find(integration => integration.name === '@astrojs/mdx')) { + // Enable default JSX integration. It needs to come first, so unshift rather than push! + const { default: jsxRenderer } = await import('astro/jsx/renderer.js'); + config._ctx.renderers.unshift(jsxRenderer); + } /** @type {import('../src/core/logger/core').LogOptions} */ const logging = { diff --git a/packages/integrations/mdx/README.md b/packages/integrations/mdx/README.md new file mode 100644 index 000000000..0a909cc2f --- /dev/null +++ b/packages/integrations/mdx/README.md @@ -0,0 +1,106 @@ +# @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. + +- [Why MDX?](#why-mdx) +- [Installation](#installation) +- [Usage](#usage) +- [Configuration](#configuration) +- [Examples](#examples) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [Changelog](#changelog) + +## Why MDX? + +MDX is the defacto solution for embedding components, such as interactive charts or alerts, within Markdown content. If you have existing content authored in MDX, this integration makes migrating to Astro a breeze. + +**Want to learn more about MDX before using this integration?** +Check out [β€œWhat is MDX?”](https://mdxjs.com/docs/what-is-mdx/), a deep-dive on the MDX format. + +## Installation + +
+ Quick Install +
+ +The `astro add` command-line tool automates the installation for you. Run one of the following commands in a new terminal window. (If you aren't sure which package manager you're using, run the first command.) Then, follow the prompts, and type "y" in the terminal (meaning "yes") for each one. + + ```sh + # Using NPM + npx astro add mdx + # Using Yarn + yarn astro add mdx + # Using PNPM + pnpx astro add mdx + ``` + +Then, restart the dev server by typing `CTRL-C` and then `npm run astro dev` in the terminal window that was running Astro. + +Because this command is new, it might not properly set things up. If that happens, [feel free to log an issue on our GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below. +
+ +
+ Manual Install +
+ +First, install the `@astrojs/mdx` package using your package manager. If you're using npm or aren't sure, run this in the terminal: + + ``` + npm install @astrojs/mdx + ``` + +Then, apply this integration to your `astro.config.*` file using the `integrations` property: + +__astro.config.mjs__ + +```js +import { defineConfig } from 'astro/config'; +import mdx from '@astrojs/mdx'; + +export default defineConfig({ + // ... + integrations: [mdx()], +}); +``` + +Finally, restart the dev server. +
+ +## Usage + +To write your first MDX page in Astro, head to our [UI framework documentation][astro-ui-frameworks]. You'll explore: +- πŸ“¦ how framework components are loaded, +- πŸ’§ client-side hydration options, and +- πŸͺ† opportunities to mix and nest frameworks together + +[**Client Directives**](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required in `.mdx` files. + +> **Note**: `.mdx` files adhere to strict JSX syntax rather than Astro's HTML-like syntax. + +Also check our [Astro Integration Documentation][astro-integration] for more on integrations. + +## Configuration + +There are currently no configuration options for the `@astrojs/mdx` integration. Please [open an issue](https://github.com/withastro/astro/issues/new/choose) if you have a compelling use case to share. + +## Examples + +- The [Astro MDX example](https://github.com/withastro/astro/tree/latest/examples/with-mdx) shows how to use MDX files in your Astro project. + +## Troubleshooting + +For help, check out the `#support-threads` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help! + +You can also check our [Astro Integration Documentation][astro-integration] for more on integrations. + +## Contributing + +This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this integration. + +[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ +[astro-ui-frameworks]: https://docs.astro.build/en/core-concepts/framework-components/#using-framework-components diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json new file mode 100644 index 000000000..7171c61f9 --- /dev/null +++ b/packages/integrations/mdx/package.json @@ -0,0 +1,47 @@ +{ + "name": "@astrojs/mdx", + "description": "Use MDX within Astro", + "version": "0.0.1", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/mdx" + }, + "keywords": [ + "astro-component", + "renderer", + "mdx" + ], + "bugs": "https://github.com/withastro/astro/issues", + "homepage": "https://astro.build", + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "build:ci": "astro-scripts build \"src/**/*.ts\"", + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "mocha --exit --timeout 20000" + }, + "dependencies": { + "@mdx-js/rollup": "^2.1.1" + }, + "devDependencies": { + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.1", + "@types/yargs-parser": "^21.0.0", + "astro": "workspace:*", + "astro-scripts": "workspace:*", + "chai": "^4.3.6", + "mocha": "^9.2.2", + "linkedom": "^0.14.12" + }, + "engines": { + "node": "^14.15.0 || >=16.0.0" + } +} diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts new file mode 100644 index 000000000..d07913ede --- /dev/null +++ b/packages/integrations/mdx/src/index.ts @@ -0,0 +1,39 @@ +import type { AstroIntegration } from 'astro'; +import mdxPlugin from '@mdx-js/rollup'; + +export default function mdx(): AstroIntegration { + return { + name: '@astrojs/mdx', + hooks: { + 'astro:config:setup': ({ updateConfig, addPageExtension, command }: any) => { + addPageExtension('.mdx'); + updateConfig({ + vite: { + plugins: [ + { + enforce: 'pre', + ...mdxPlugin({ + jsx: true, + jsxImportSource: 'astro', + // Note: disable `.md` support + format: 'mdx', + mdExtensions: [] + }) + }, + command === 'dev' && { + name: '@astrojs/mdx', + transform(code: string, id: string) { + if (!id.endsWith('.mdx')) return; + // TODO: decline HMR updates until we have a stable approach + return `${code}\nif (import.meta.hot) { + import.meta.hot.decline(); + }` + } + } + ] + } + }) + } + } + } +} 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! + +
bar
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'; +--- + + 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..195881e02 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-page/src/pages/index.mdx @@ -0,0 +1 @@ +# Hello page! 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..462e86e7b --- /dev/null +++ b/packages/integrations/mdx/test/mdx-component.test.js @@ -0,0 +1,63 @@ +import mdx from '@astrojs/mdx'; + +import { expect } from 'chai'; +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('works', async () => { + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + const foo = document.querySelector('#foo'); + + expect(h1.textContent).to.equal('Hello component!'); + expect(foo.textContent).to.equal('bar'); + }); + }) + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + const foo = document.querySelector('#foo'); + + expect(h1.textContent).to.equal('Hello component!'); + expect(foo.textContent).to.equal('bar'); + }); + }) +}) 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..6e4ae79af --- /dev/null +++ b/packages/integrations/mdx/test/mdx-page.test.js @@ -0,0 +1,59 @@ +import mdx from '@astrojs/mdx'; + +import { expect } from 'chai'; +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), + integrations: [ + mdx() + ] + }); + }); + + 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'); + + expect(h1.textContent).to.equal('Hello page!'); + }); + }) + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + + expect(h1.textContent).to.equal('Hello page!'); + }); + }) +}) diff --git a/packages/integrations/mdx/tsconfig.json b/packages/integrations/mdx/tsconfig.json new file mode 100644 index 000000000..44baf375c --- /dev/null +++ b/packages/integrations/mdx/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "module": "ES2020", + "outDir": "./dist", + "target": "ES2020" + } +} diff --git a/packages/webapi/mod.d.ts b/packages/webapi/mod.d.ts index a3c49dc5c..7150edbe7 100644 --- a/packages/webapi/mod.d.ts +++ b/packages/webapi/mod.d.ts @@ -9,4 +9,4 @@ interface PolyfillOptions { override?: Record; -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acbc0298e..46af0b24b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -400,6 +400,18 @@ importers: '@astrojs/markdown-remark': link:../../packages/markdown/remark astro: link:../../packages/astro + examples/with-mdx: + specifiers: + '@astrojs/mdx': ^0.0.1 + '@astrojs/preact': ^0.2.0 + astro: ^1.0.0-beta.58 + preact: ^10.6.5 + devDependencies: + '@astrojs/mdx': link:../../packages/integrations/mdx + '@astrojs/preact': 0.2.0_preact@10.8.2 + astro: link:../../packages/astro + preact: 10.8.2 + examples/with-nanostores: specifiers: '@astrojs/preact': ^0.3.1 @@ -463,7 +475,7 @@ importers: packages/astro: specifiers: - '@astrojs/compiler': ^0.17.0 + '@astrojs/compiler': ^0.17.1 '@astrojs/language-server': ^0.13.4 '@astrojs/markdown-remark': ^0.11.3 '@astrojs/prism': 0.4.1 @@ -948,21 +960,25 @@ importers: packages/astro/e2e/fixtures/preact-component: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/preact': workspace:* astro: workspace:* preact: ^10.7.3 dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/preact': link:../../../../integrations/preact astro: link:../../.. preact: 10.8.2 packages/astro/e2e/fixtures/react-component: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/react': workspace:* astro: workspace:* react: ^18.1.0 react-dom: ^18.1.0 dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/react': link:../../../../integrations/react astro: link:../../.. react: 18.2.0 @@ -970,10 +986,12 @@ importers: packages/astro/e2e/fixtures/solid-component: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/solid-js': workspace:* astro: workspace:* solid-js: ^1.4.3 dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/solid-js': link:../../../../integrations/solid astro: link:../../.. devDependencies: @@ -981,10 +999,12 @@ importers: packages/astro/e2e/fixtures/svelte-component: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/svelte': workspace:* astro: workspace:* svelte: ^3.48.0 dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/svelte': link:../../../../integrations/svelte astro: link:../../.. svelte: 3.48.0 @@ -1011,9 +1031,11 @@ importers: packages/astro/e2e/fixtures/vue-component: specifiers: + '@astrojs/mdx': workspace:* '@astrojs/vue': workspace:* astro: workspace:* dependencies: + '@astrojs/mdx': link:../../../../integrations/mdx '@astrojs/vue': link:../../../../integrations/vue astro: link:../../.. @@ -1904,6 +1926,29 @@ importers: cheerio: 1.0.0-rc.12 sass: 1.53.0 + packages/integrations/mdx: + specifiers: + '@mdx-js/rollup': ^2.1.1 + '@types/chai': ^4.3.1 + '@types/mocha': ^9.1.1 + '@types/yargs-parser': ^21.0.0 + astro: workspace:* + astro-scripts: workspace:* + chai: ^4.3.6 + linkedom: ^0.14.12 + mocha: ^9.2.2 + dependencies: + '@mdx-js/rollup': 2.1.2 + devDependencies: + '@types/chai': 4.3.1 + '@types/mocha': 9.1.1 + '@types/yargs-parser': 21.0.0 + astro: link:../../astro + astro-scripts: link:../../../scripts + chai: 4.3.6 + linkedom: 0.14.12 + mocha: 9.2.2 + packages/integrations/netlify: specifiers: '@astrojs/webapi': ^0.12.0 @@ -2565,6 +2610,19 @@ packages: vfile-message: 3.1.2 dev: false + /@astrojs/preact/0.2.0_preact@10.8.2: + resolution: {integrity: sha512-AedeRz65mmW8qvS2TR20lS1TdRSXzKY9g3X8k1OlL585XE8ZI5WfucpzNMYMQxNuRvRDoh7imO8JoKt19O/mgg==} + engines: {node: ^14.15.0 || >=16.0.0} + peerDependencies: + preact: ^10.6.5 + dependencies: + '@babel/plugin-transform-react-jsx': 7.18.6 + preact: 10.8.2 + preact-render-to-string: 5.2.0_preact@10.8.2 + transitivePeerDependencies: + - '@babel/core' + dev: true + /@astrojs/svelte-language-integration/0.1.6_typescript@4.6.4: resolution: {integrity: sha512-nqczE674kz7GheKSWQwTOL6+NGHghc4INQox048UyHJRaIKHEbCPyFLDBDVY7QJH0jug1komCJ8OZXUn6Z3eLA==} dependencies: @@ -2861,6 +2919,8 @@ packages: resolution: {integrity: sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==} engines: {node: '>=6.0.0'} hasBin: true + dependencies: + '@babel/types': 7.18.7 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.18.6: resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -3215,7 +3275,6 @@ packages: optional: true dependencies: '@babel/helper-plugin-utils': 7.18.6 - dev: false /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.18.6: resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} @@ -3680,7 +3739,6 @@ packages: '@babel/helper-plugin-utils': 7.18.6 '@babel/plugin-syntax-jsx': 7.18.6 '@babel/types': 7.18.7 - dev: false /@babel/plugin-transform-react-jsx/7.18.6_@babel+core@7.18.6: resolution: {integrity: sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw==} @@ -4563,6 +4621,46 @@ packages: - supports-color dev: false + /@mdx-js/mdx/2.1.2: + resolution: {integrity: sha512-ASN1GUH0gXsgJ2UD/Td7FzJo1SwFkkQ5V1i9at5o/ROra7brkyMcBsotsOWJWRzmXZaLw2uXWn4aN8B3PMNFMA==} + dependencies: + '@types/estree-jsx': 0.0.1 + '@types/mdx': 2.0.2 + astring: 1.8.3 + estree-util-build-jsx: 2.1.0 + estree-util-is-identifier-name: 2.0.1 + estree-walker: 3.0.1 + hast-util-to-estree: 2.0.2 + markdown-extensions: 1.1.1 + periscopic: 3.0.4 + remark-mdx: 2.1.1 + remark-parse: 10.0.1 + remark-rehype: 10.1.0 + unified: 10.1.2 + unist-util-position-from-estree: 1.1.1 + unist-util-stringify-position: 3.0.2 + unist-util-visit: 4.1.0 + vfile: 5.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /@mdx-js/rollup/2.1.2: + resolution: {integrity: sha512-3ahqp3DCpIlGlCRuLX4z7dFEgN5kWBljrk8BpipiWkVrvB4FQpTQu1T7lmDffm8tOunjHAsZEHtb076HiW51NQ==} + peerDependencies: + rollup: '>=2' + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@mdx-js/mdx': 2.1.2 + '@rollup/pluginutils': 4.2.1 + source-map: 0.7.4 + vfile: 5.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /@nanostores/preact/0.1.3_gzamsul3sgwlq3zzcqv3ims3km: resolution: {integrity: sha512-uiX1ned0LrzASot+sPUjyJzr8Js3pX075omazgsSdLf0zPp4ss8xwTiuNh5FSKigTSQEVqZFiS+W8CnHIrX62A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -6964,6 +7062,9 @@ packages: engines: {node: '>=8.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: rollup: 2.75.7 slash: 3.0.0 @@ -6981,6 +7082,8 @@ packages: optional: true '@types/babel__core': optional: true + rollup: + optional: true dependencies: '@babel/core': 7.18.6 '@babel/helper-module-imports': 7.18.6 @@ -6992,6 +7095,9 @@ packages: resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==} peerDependencies: rollup: ^1.20.0 || ^2.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.75.7 estree-walker: 2.0.2 @@ -7004,6 +7110,9 @@ packages: engines: {node: '>= 10.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.75.7 '@types/resolve': 1.17.1 @@ -7019,6 +7128,9 @@ packages: engines: {node: '>= 10.0.0'} peerDependencies: rollup: ^2.42.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.75.7 '@types/resolve': 1.17.1 @@ -7033,6 +7145,9 @@ packages: resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} peerDependencies: rollup: ^1.20.0 || ^2.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.75.7 magic-string: 0.25.9 @@ -7047,6 +7162,8 @@ packages: tslib: '*' typescript: '>=3.7.0' peerDependenciesMeta: + rollup: + optional: true tslib: optional: true dependencies: @@ -7062,6 +7179,9 @@ packages: engines: {node: '>= 8.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 @@ -7286,6 +7406,10 @@ packages: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: false + /@types/mdx/2.0.2: + resolution: {integrity: sha512-mJGfgj4aWpiKb8C0nnJJchs1sHBHn0HugkVfqqyQi7Wn6mBRksLeQsPOFvih/Pu8L1vlDzfe/LidhVHBeUk3aQ==} + dev: false + /@types/mime/1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true @@ -8064,6 +8188,11 @@ packages: tslib: 2.4.0 dev: false + /astring/1.8.3: + resolution: {integrity: sha512-sRpyiNrx2dEYIMmUXprS8nlpRg2Drs8m9ElX9vVEXaCB4XEAJhKfs7IcX0IwShjuOAjLR6wzIrgoptz1n19i1A==} + hasBin: true + dev: false + /async/3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: true @@ -8734,6 +8863,10 @@ packages: engines: {node: '>=4'} hasBin: true + /cssom/0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + dev: true + /csstype/2.6.20: resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==} @@ -8783,6 +8916,11 @@ packages: /debug/3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 dev: false @@ -9527,6 +9665,20 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-util-attach-comments/2.0.1: + resolution: {integrity: sha512-1wTBNndwMIsnvnuxjFIaYQz0M7PsCvcgP0YD7/dU8xWh1FuHk+O6pYpT4sLa5THY/CywJvdIdgw4uhozujga/g==} + dependencies: + '@types/estree': 0.0.51 + dev: false + + /estree-util-build-jsx/2.1.0: + resolution: {integrity: sha512-gsBGfsY6LOJUIDwmMkTOcgCX+3r/LUjRBccgHMSW55PHjhZsV13RmPl/iwpAvW8KcQqoN9P0FEFWTSS2Zc5bGA==} + dependencies: + '@types/estree-jsx': 0.0.1 + estree-util-is-identifier-name: 2.0.1 + estree-walker: 3.0.1 + dev: false + /estree-util-is-identifier-name/2.0.1: resolution: {integrity: sha512-rxZj1GkQhY4x1j/CSnybK9cGuMFQYFPLq0iNyopqf14aOVLFtMv7Esika+ObJWPWiOHuMOAHz3YkWoLYYRnzWQ==} dev: false @@ -10204,6 +10356,27 @@ packages: zwitch: 2.0.2 dev: false + /hast-util-to-estree/2.0.2: + resolution: {integrity: sha512-UQrZVeBj6A9od0lpFvqHKNSH9zvDrNoyWKbveu1a2oSCXEDUI+3bnd6BoiQLPnLrcXXn/jzJ6y9hmJTTlvf8lQ==} + dependencies: + '@types/estree-jsx': 0.0.1 + '@types/hast': 2.3.4 + '@types/unist': 2.0.6 + comma-separated-tokens: 2.0.2 + estree-util-attach-comments: 2.0.1 + estree-util-is-identifier-name: 2.0.1 + hast-util-whitespace: 2.0.0 + mdast-util-mdx-expression: 1.2.1 + mdast-util-mdxjs-esm: 1.2.0 + property-information: 6.1.1 + space-separated-tokens: 2.0.1 + style-to-object: 0.3.0 + unist-util-position: 4.0.3 + zwitch: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /hast-util-to-html/8.0.3: resolution: {integrity: sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==} dependencies: @@ -10267,7 +10440,6 @@ packages: /html-escaper/3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - dev: false /html-void-elements/2.0.1: resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} @@ -10620,6 +10792,12 @@ packages: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} dev: true + /is-reference/3.0.0: + resolution: {integrity: sha512-Eo1W3wUoHWoCoVM4GVl/a+K0IgiqE5aIo4kJABFyMum1ZORlPkC+UC357sSQUL5w5QCE5kCC9upl75b7+7CY/Q==} + dependencies: + '@types/estree': 0.0.52 + dev: false + /is-regex/1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -10884,6 +11062,16 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true + /linkedom/0.14.12: + resolution: {integrity: sha512-8uw8LZifCwyWeVWr80T79sQTMmNXt4Da7oN5yH5gTXRqQM+TuZWJyBqRMcIp32zx/f8anHNHyil9Avw9y76ziQ==} + dependencies: + css-select: 5.1.0 + cssom: 0.5.0 + html-escaper: 3.0.3 + htmlparser2: 8.0.1 + uhyphen: 0.1.0 + dev: true + /lit-element/3.2.1: resolution: {integrity: sha512-2PxyE9Yq9Jyo/YBK2anycaHcqo93YvB5D+24JxloPVqryW/BOXekne+jGsm0Ke3E5E2v7CDgkmpEmCAzYfrHCQ==} dependencies: @@ -11043,6 +11231,11 @@ packages: engines: {node: '>=8'} dev: true + /markdown-extensions/1.1.1: + resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} + engines: {node: '>=0.10.0'} + dev: false + /markdown-table/3.0.2: resolution: {integrity: sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==} dev: false @@ -11162,6 +11355,43 @@ packages: vfile-message: 3.1.2 dev: false + /mdast-util-mdx-jsx/2.0.1: + resolution: {integrity: sha512-oPC7/smPBf7vxnvIYH5y3fPo2lw1rdrswFfSb4i0GTAXRUQv7JUU/t/hbp07dgGdUFTSDOHm5DNamhNg/s2Hrg==} + dependencies: + '@types/estree-jsx': 0.0.1 + '@types/hast': 2.3.4 + '@types/mdast': 3.0.10 + ccount: 2.0.1 + mdast-util-to-markdown: 1.3.0 + parse-entities: 4.0.0 + stringify-entities: 4.0.3 + unist-util-remove-position: 4.0.1 + unist-util-stringify-position: 3.0.2 + vfile-message: 3.1.2 + dev: false + + /mdast-util-mdx/2.0.0: + resolution: {integrity: sha512-M09lW0CcBT1VrJUaF/PYxemxxHa7SLDHdSn94Q9FhxjCQfuW7nMAWKWimTmA3OyDMSTH981NN1csW1X+HPSluw==} + dependencies: + mdast-util-mdx-expression: 1.2.1 + mdast-util-mdx-jsx: 2.0.1 + mdast-util-mdxjs-esm: 1.2.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdxjs-esm/1.2.0: + resolution: {integrity: sha512-IPpX9GBzAIbIRCjbyeLDpMhACFb0wxTIujuR3YElB8LWbducUdMgRJuqs/Vg8xQ1bIAMm7lw8L+YNtua0xKXRw==} + dependencies: + '@types/estree-jsx': 0.0.1 + '@types/hast': 2.3.4 + '@types/mdast': 3.0.10 + mdast-util-from-markdown: 1.2.0 + mdast-util-to-markdown: 1.3.0 + transitivePeerDependencies: + - supports-color + dev: false + /mdast-util-to-hast/12.1.1: resolution: {integrity: sha512-qE09zD6ylVP14jV4mjLIhDBOrpFdShHZcEsYvvKGABlr9mGbV7mTlRWdoFxL/EYSTNDiC9GZXy7y8Shgb9Dtzw==} dependencies: @@ -11327,12 +11557,52 @@ packages: uvu: 0.5.4 dev: false + /micromark-extension-mdx-jsx/1.0.3: + resolution: {integrity: sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA==} + dependencies: + '@types/acorn': 4.0.6 + estree-util-is-identifier-name: 2.0.1 + micromark-factory-mdx-expression: 1.0.6 + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.4 + vfile-message: 3.1.2 + dev: false + /micromark-extension-mdx-md/1.0.0: resolution: {integrity: sha512-xaRAMoSkKdqZXDAoSgp20Azm0aRQKGOl0RrS81yGu8Hr/JhMsBmfs4wR7m9kgVUIO36cMUQjNyiyDKPrsv8gOw==} dependencies: micromark-util-types: 1.0.2 dev: false + /micromark-extension-mdxjs-esm/1.0.3: + resolution: {integrity: sha512-2N13ol4KMoxb85rdDwTAC6uzs8lMX0zeqpcyx7FhS7PxXomOnLactu8WI8iBNXW8AVyea3KIJd/1CKnUmwrK9A==} + dependencies: + micromark-core-commonmark: 1.0.6 + micromark-util-character: 1.1.0 + micromark-util-events-to-acorn: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + unist-util-position-from-estree: 1.1.1 + uvu: 0.5.4 + vfile-message: 3.1.2 + dev: false + + /micromark-extension-mdxjs/1.0.0: + resolution: {integrity: sha512-TZZRZgeHvtgm+IhtgC2+uDMR7h8eTKF0QUX9YsgoL9+bADBpBY6SiLvWqnBlLbCEevITmTqmEuY3FoxMKVs1rQ==} + dependencies: + acorn: 8.7.1 + acorn-jsx: 5.3.2_acorn@8.7.1 + micromark-extension-mdx-expression: 1.0.3 + micromark-extension-mdx-jsx: 1.0.3 + micromark-extension-mdx-md: 1.0.0 + micromark-extension-mdxjs-esm: 1.0.3 + micromark-util-combine-extensions: 1.0.0 + micromark-util-types: 1.0.2 + dev: false + /micromark-factory-destination/1.0.0: resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==} dependencies: @@ -11703,6 +11973,8 @@ packages: debug: 3.2.7 iconv-lite: 0.4.24 sax: 1.2.4 + transitivePeerDependencies: + - supports-color dev: false /netmask/2.0.2: @@ -11786,6 +12058,8 @@ packages: rimraf: 2.7.1 semver: 5.7.1 tar: 4.4.19 + transitivePeerDependencies: + - supports-color dev: false /node-releases/2.0.5: @@ -12246,6 +12520,13 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true + /periscopic/3.0.4: + resolution: {integrity: sha512-SFx68DxCv0Iyo6APZuw/AKewkkThGwssmU0QWtTlvov3VAtPX+QJ4CadwSaz8nrT5jPIuxdvJWB4PnD2KNDxQg==} + dependencies: + estree-walker: 3.0.1 + is-reference: 3.0.0 + dev: false + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -12697,7 +12978,6 @@ packages: dependencies: preact: 10.8.2 pretty-format: 3.8.0 - dev: false /preact/10.6.6: resolution: {integrity: sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==} @@ -12775,7 +13055,6 @@ packages: /pretty-format/3.8.0: resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} - dev: false /prismjs/1.28.0: resolution: {integrity: sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==} @@ -13159,6 +13438,15 @@ packages: - supports-color dev: false + /remark-mdx/2.1.1: + resolution: {integrity: sha512-0wXdEITnFyjLquN3VvACNLzbGzWM5ujzTvfgOkONBZgSFJ7ezLLDaTWqf6H9eUgVITEP8asp6LJ0W/X090dXBg==} + dependencies: + mdast-util-mdx: 2.0.0 + micromark-extension-mdxjs: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /remark-parse/10.0.1: resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==} dependencies: @@ -13285,6 +13573,9 @@ packages: resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} peerDependencies: rollup: ^2.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: '@babel/code-frame': 7.18.6 jest-worker: 26.6.2 @@ -14377,6 +14668,10 @@ packages: hasBin: true dev: true + /uhyphen/0.1.0: + resolution: {integrity: sha512-o0QVGuFg24FK765Qdd5kk0zU/U4dEsCtN/GSiwNI9i8xsSVtjIAOdTaVhLwZ1nrbWxFVMxNDDl+9fednsOMsBw==} + dev: true + /unbox-primitive/1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: