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 <nate@astro.build>
This commit is contained in:
Nate Moore 2022-06-30 14:09:09 -04:00 committed by GitHub
parent 91635f05df
commit 032ad1c047
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1153 additions and 57 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Internal changes needed to support `@astrojs/mdx`

View file

@ -0,0 +1,5 @@
---
'@astrojs/mdx': patch
---
Initial release! 🎉

19
examples/with-mdx/.gitignore vendored Normal file
View file

@ -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

2
examples/with-mdx/.npmrc Normal file
View file

@ -0,0 +1,2 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true

View file

@ -0,0 +1,6 @@
{
"startCommand": "npm start",
"env": {
"ENABLE_CJS_IMPORTS": true
}
}

View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
examples/with-mdx/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View file

@ -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/).

View file

@ -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()
]
});

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,11 @@
{
"infiniteLoopProtection": true,
"hardReloadOnChange": false,
"view": "browser",
"template": "node",
"container": {
"port": 3000,
"startScript": "start",
"node": "14"
}
}

View file

@ -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 (
<>
<div class="counter">
<button onClick={subtract}>-</button>
<pre>{count}</pre>
<button onClick={add}>+</button>
</div>
<div class="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,7 @@
<h1><slot /></h1>
<style>
h1 {
color: red;
}
</style>

View file

@ -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 client:idle>
## Counter
</Counter>

View file

@ -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"]
}
}

View file

@ -50,6 +50,7 @@
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"rollup",
"@babel/core",
"@babel/plugin-transform-react-jsx",
"vite"

View file

@ -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()],
});

View file

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@astrojs/preact": "workspace:*",
"@astrojs/mdx": "workspace:*",
"astro": "workspace:*",
"preact": "^10.7.3"
}

View file

@ -0,0 +1,29 @@
import Counter from '../components/Counter.jsx';
import PreactComponent from '../components/JSXComponent.jsx';
export const someProps = {
count: 0,
};
<Counter id="server-only" {...someProps}>
# Hello, server!
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
# Hello, client:idle!
</Counter>
<Counter id="client-load" {...someProps} client:load>
# Hello, client:load!
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
# Hello, client:visible!
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
# Hello, client:media!
</Counter>
<PreactComponent id="client-only" client:only="preact" />

View file

@ -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()],
});

View file

@ -5,6 +5,7 @@
"dependencies": {
"@astrojs/react": "workspace:*",
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}

View file

@ -0,0 +1,28 @@
import Counter from '../components/Counter.jsx';
import ReactComponent from '../components/JSXComponent.jsx';
export const someProps = {
count: 0,
};
<Counter id="server-only" {...someProps}>
# Hello, server!
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
# Hello, client:idle!
</Counter>
<Counter id="client-load" {...someProps} client:load>
# Hello, client:load!
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
# Hello, client:visible!
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
# Hello, client:media!
</Counter>
<ReactComponent id="client-only" client:only="react" />

View file

@ -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()],
});

View file

@ -4,7 +4,8 @@
"private": true,
"dependencies": {
"@astrojs/solid-js": "workspace:*",
"astro": "workspace:*"
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*"
},
"devDependencies": {
"solid-js": "^1.4.3"

View file

@ -1,3 +1,5 @@
import 'solid-js';
export default function SolidComponent({ id }) {
return (
<div id={id}>Framework client:only component</div>

View file

@ -0,0 +1,28 @@
import Counter from '../components/Counter.jsx';
import SolidComponent from '../components/SolidComponent.jsx';
export const someProps = {
count: 0,
};
<Counter id="server-only" {...someProps}>
# Hello, server!
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
# Hello, client:idle!
</Counter>
<Counter id="client-load" {...someProps} client:load>
# Hello, client:load!
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
# Hello, client:visible!
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
# Hello, client:media!
</Counter>
<SolidComponent id="client-only" client:only="solid" />

View file

@ -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()],
});

View file

@ -5,6 +5,7 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*",
"svelte": "^3.48.0"
}
}

View file

@ -0,0 +1,28 @@
import Counter from '../components/Counter.svelte';
import SvelteComponent from '../components/SvelteComponent.svelte';
export const someProps = {
count: 0,
};
<Counter id="server-only" {...someProps}>
# Hello, server!
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
# Hello, client:idle!
</Counter>
<Counter id="client-load" {...someProps} client:load>
# Hello, client:load!
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
# Hello, client:visible!
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
# Hello, client:media!
</Counter>
<SvelteComponent id="client-only" client:only="svelte" />

View file

@ -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({
integrations: [
mdx(),
vue({
template: {
compilerOptions: {
isCustomElement: tag => tag.includes('my-button')
}
}
})],
}
)],
});

View file

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*"
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*"
}
}

View file

@ -0,0 +1,28 @@
import Counter from '../components/Counter.vue';
import VueComponent from '../components/VueComponent.vue';
export const someProps = {
count: 0,
};
<Counter id="server-only" {...someProps}>
# Hello, server!
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
# Hello, client:idle!
</Counter>
<Counter id="client-load" {...someProps} client:load>
# Hello, client:load!
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
# Hello, client:visible!
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
# Hello, client:media!
</Counter>
<VueComponent id="client-only" client:only="vue" />

View file

@ -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',
});
});

View file

@ -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',
});
});

View file

@ -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',
});
});

View file

@ -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',
});
});

View file

@ -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',
});
});

View file

@ -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",

View file

@ -939,6 +939,10 @@ export interface SSRLoadedRenderer extends AstroRenderer {
};
}
export type HookParameters<Hook extends keyof AstroIntegration['hooks'], Fn = AstroIntegration['hooks'][Hook]> = Fn extends (...args: any) => any
? Parameters<Fn>[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<void>;
'astro:config:done'?: (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;

View file

@ -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)

View file

@ -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<string[]> {

View file

@ -140,11 +140,10 @@ export async function render(opts: RenderOptions): Promise<Response> {
ssr,
});
if (!Component.isAstroComponentFactory) {
const props: Record<string, any> = { ...(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);
}

View file

@ -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: '',

View file

@ -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;

View file

@ -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<Fn>[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
Object.defineProperty(hooks, 'addPageExtension', {
value: (...input: (string | string[])[]) => {
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: addPageExtension,
writable: false,
enumerable: false,
});

View file

@ -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<string, any> = {};
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;

View file

@ -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();
}
}

View file

@ -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<Response> {
let iterable: AsyncIterable<any>;
if (!componentFactory.isAstroComponentFactory) {
const pageProps: Record<string, any> = { ...(props ?? {}), 'server:root': true };
const output = await renderComponent(result, componentFactory.name, componentFactory, pageProps, null);
let html = output.toString()
if (!/<!doctype html/i.test(html)) {
html = `<!DOCTYPE html>\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() {

View file

@ -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<any> {
return markHTMLString(
(await Promise.all(vnode.map((v: any) => renderJSX(result, v)))).join('')
);
case vnode.type.isAstroComponentFactory: {
let props: Record<string, any> = {};
let slots: Record<string, any> = {};
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 '';

View file

@ -12,11 +12,11 @@ import { error } from '../core/logger/core.js';
import { parseNpmName } from '../core/util.js';
const JSX_RENDERER_CACHE = new WeakMap<AstroConfig, Map<string, AstroRenderer>>();
const JSX_EXTENSIONS = new Set(['.jsx', '.tsx']);
const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']);
const IMPORT_STATEMENTS: Record<string, string> = {
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<string, string> = {
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);

View file

@ -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 = {

View file

@ -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.
- <strong>[Why MDX?](#why-mdx)</strong>
- <strong>[Installation](#installation)</strong>
- <strong>[Usage](#usage)</strong>
- <strong>[Configuration](#configuration)</strong>
- <strong>[Examples](#examples)</strong>
- <strong>[Troubleshooting](#troubleshooting)</strong>
- <strong>[Contributing](#contributing)</strong>
- <strong>[Changelog](#changelog)</strong>
## 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
<details>
<summary>Quick Install</summary>
<br/>
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.
</details>
<details>
<summary>Manual Install</summary>
<br/>
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.
</details>
## 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

View file

@ -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"
}
}

View file

@ -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();
}`
}
}
]
}
})
}
}
}
}

View file

@ -0,0 +1,3 @@
# Hello component!
<div id="foo">bar</div>

View file

@ -0,0 +1,5 @@
---
import Test from '../components/Test.mdx';
---
<Test />

View file

@ -0,0 +1 @@
# Hello page!

View file

@ -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');
});
})
})

View file

@ -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!');
});
})
})

View file

@ -0,0 +1,10 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"allowJs": true,
"module": "ES2020",
"outDir": "./dist",
"target": "ES2020"
}
}

View file

@ -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: