1
1
mirror of https://github.com/mdx-js/mdx.git synced 2024-10-05 20:07:37 +03:00

Add rehype-twoslash

* check most examples with typescript
* add special twoslash directives to code blocks so that twoslash
  understand partial examples
* add dependencies for these examples, so typescript can check them
* remove theme-ui docs here, their site has info, link to it
This commit is contained in:
Titus Wormer 2024-07-04 17:22:57 +02:00
parent b749d38fd3
commit 11ac939bc3
No known key found for this signature in database
GPG Key ID: E6E581152ED04E2E
29 changed files with 2370 additions and 628 deletions

View File

@ -297,7 +297,6 @@ pre code {
}
pre code,
.hljs,
.frame-body,
.frame-tab-item-selected {
background-color: #fafafa !important; /* Color from one-light */
@ -583,7 +582,6 @@ button.success {
.navigation .icon {
display: block;
vertical-align: middle;
width: auto;
height: calc(1em + 1ex);
}
@ -660,9 +658,6 @@ button.success {
padding-inline-start: calc(0.5 * (1em + 1ex));
}
.nav-description {
}
.skip-to-navigation {
inset-block-start: 0;
inset-inline-start: 0;
@ -837,11 +832,13 @@ button.success {
overflow-y: auto;
}
.frame-tab-bar + pre {
.frame-tab-bar + pre,
.frame-tab-bar + .highlight > pre {
margin-block-start: 0;
}
.frame-tab-bar + pre code {
.frame-tab-bar + pre code,
.frame-tab-bar + .highlight > pre code {
--squircle-radius: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
@ -946,6 +943,55 @@ details[open] {
padding: calc(1em + 1ex);
}
.rehype-twoslash-completion-deprecated {
opacity: 0.5;
}
.rehype-twoslash-popover-target {
cursor: default;
}
.highlight:is(:hover, :focus-within) .rehype-twoslash-popover-target {
background-color: var(--gray-2);
}
/* Wavy underline for errors. */
.rehype-twoslash-error-target {
background-repeat: repeat-x;
background-position: bottom left;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 3" enable-background="new 0 0 6 3" height="3" width="6"><g fill="%23c94824"><polygon points="5.5,0 2.5,3 1.1,3 4.1,0"/><polygon points="4,0 6,2 6,0.6 5.4,0"/><polygon points="0,2 1,3 2.4,3 0,0.6"/></g></svg>');
}
/* The content that will be shown in the tooltip. */
.rehype-twoslash-popover {
position: absolute;
max-width: calc(45 * (1em + 1ex));
padding: calc(0.5 * (1em + 1ex));
margin: 0;
background-color: var(--bg);
border: 1px solid var(--gray-2);
border-radius: 3px;
}
/* No padding if we have a padded code block (and perhaps more blocks) */
.rehype-twoslash-popover:has(.rehype-twoslash-popover-code) {
padding: 0;
}
.rehype-twoslash-popover-code {
margin: 0;
}
.rehype-twoslash-popover-code > code {
mask-image: none;
border-radius: 0;
}
.rehype-twoslash-popover-description {
background-color: var(--bg);
padding: 0 1em;
}
@media (prefers-color-scheme: dark) {
:root {
--white: #f0f6fc;
@ -1038,6 +1084,14 @@ details[open] {
background-color: var(--gray-8);
}
.highlight:is(:hover, :focus-within) .rehype-twoslash-popover-target {
background-color: var(--gray-5);
}
.rehype-twoslash-popover {
border-color: var(--gray-6);
}
h6 {
color: var(--gray-3);
}
@ -1062,7 +1116,6 @@ details[open] {
}
pre code,
.hljs,
.frame-body,
.frame-tab-item-selected,
.frame-tab-item-dark.frame-tab-item-selected {
@ -1231,23 +1284,6 @@ details[open] {
}
}
/* Fix a11y. */
.hljs-built_in,
.hljs-symbol {
color: hsl(24deg 92% 40%) !important;
}
@media (prefers-color-scheme: dark) {
.hljs-section {
color: #488bef !important;
}
.hljs-built_in,
.hljs-symbol {
color: #ffa657 !important;
}
}
.playground {
min-height: 40rem;
gap: calc(1em + 1ex);

View File

@ -1,5 +1,7 @@
/* eslint-disable unicorn/prefer-query-selector */
/// <reference lib="dom" />
import {computePosition, shift} from '@floating-ui/dom'
import copyToClipboard from 'copy-to-clipboard'
import {ok as assert} from 'devlop'
@ -57,7 +59,35 @@ for (const copy of copies) {
assert(copy instanceof HTMLButtonElement)
copy.type = 'button'
copy.replaceChildren(copyIcon.cloneNode(true))
copy.addEventListener('click', onclick)
copy.addEventListener('click', oncopyonclick)
}
const popoverTargets = /** @type {Array<HTMLElement>} */ (
Array.from(document.querySelectorAll('.rehype-twoslash-popover-target'))
)
for (const popoverTarget of popoverTargets) {
/** @type {NodeJS.Timeout | number} */
let timeout = 0
popoverTarget.addEventListener('click', function () {
popoverShow(popoverTarget)
})
popoverTarget.addEventListener('mouseenter', function () {
clearTimeout(timeout)
timeout = setTimeout(function () {
popoverShow(popoverTarget)
}, 300)
})
popoverTarget.addEventListener('mouseleave', function () {
clearTimeout(timeout)
})
if (popoverTarget.classList.contains('rehype-twoslash-autoshow')) {
popoverShow(popoverTarget)
}
}
/**
@ -66,7 +96,7 @@ for (const copy of copies) {
* @returns {undefined}
* Nothing.
*/
function onclick() {
function oncopyonclick() {
assert(copyIcon)
assert(copiedIcon)
assert(this instanceof HTMLButtonElement)
@ -84,3 +114,31 @@ function onclick() {
this.replaceChildren(copyIcon.cloneNode(true))
}, 2000)
}
/**
* @param {HTMLElement} popoverTarget
* Popover target.
* @returns {undefined}
* Nothing.
*/
function popoverShow(popoverTarget) {
const id = popoverTarget.dataset.popoverTarget
if (!id) return
const popover = document.getElementById(id)
if (!popover) return
popover.showPopover()
computePosition(popoverTarget, popover, {
placement: 'bottom',
middleware: [shift({padding: 5})]
}).then(
/**
* @param {{x: number, y: number}} value
*/
function (value) {
popover.style.left = value.x + 'px'
popover.style.top = value.y + 'px'
}
)
}

View File

@ -1,6 +1,6 @@
{
/**
* @import {Item} from '../_component/sort.js
* @import {Item} from '../_component/sort.js'
*/
/**
@ -14,7 +14,7 @@ import {BlogGroup} from '../_component/blog.jsx'
export const info = {
author: [{name: 'MDX Contributors'}],
modified: new Date('2021-11-01'),
modified: new Date('2024-07-04'),
published: new Date('2021-11-01')
}
export const navExcludeGroup = true

View File

@ -58,9 +58,7 @@ technically breaking.
We now accept block expressions right next to block JSX tags:
{/* Note: `language` because our theme doesnt support it yet. */}
```jsx chrome=no language="mdx"
```mdx chrome=no
<style>{`
h1 {

View File

@ -1,6 +1,6 @@
{
/**
* @import {Item} from '../_component/sort.js
* @import {Item} from '../_component/sort.js'
*/
/**
@ -14,7 +14,7 @@ import {NavigationGroup} from '../_component/nav.jsx'
export const info = {
author: [{name: 'MDX Contributors'}],
modified: new Date('2021-11-01'),
modified: new Date('2024-07-04'),
published: new Date('2021-11-01')
}
export const navSortSelf = 6

View File

@ -4,7 +4,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-01-19'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-06')
}
export const navSortSelf = 4
@ -117,7 +117,7 @@ Where to pass plugins is encoded in their name: remark plugins go in
[`ProcessorOptions`][processor-options].
Those fields expect lists of plugins and/or of `[plugin, options]`:
```tsx
```tsx twoslash
import {compile} from '@mdx-js/mdx'
import rehypeKatex from 'rehype-katex' // Render math with KaTeX.
import remarkFrontmatter from 'remark-frontmatter' // YAML and such.

View File

@ -4,7 +4,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-24'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-05')
}
export const navSortSelf = 2
@ -160,7 +160,11 @@ to highlight code blocks on GitHub is maintained at
…TypeScript should automatically pick it up:
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.js
// ---cut---
import Post from './post.mdx' // `Post` is now typed.
```
</details>
@ -177,7 +181,7 @@ This package also exports several useful types,
such as `MDXComponents` which represents the `components` prop.
You can import them like so:
```tsx path="example.ts"
```ts twoslash path="example.ts"
import type {MDXComponents} from 'mdx/types.js'
```
@ -209,7 +213,7 @@ and make sure processes can be killed when taking too long.
<details>
<summary>Expand example</summary>
```tsx path="example.js"
```js twoslash path="example.js"
import mdx from '@mdx-js/esbuild'
import esbuild from 'esbuild'
@ -235,16 +239,20 @@ To use more modern JavaScript features than what your users support,
<details>
<summary>Expand example</summary>
```tsx path="rollup.config.js"
```js twoslash path="rollup.config.js"
/**
* @import {RollupOptions} from 'rollup'
*/
import mdx from '@mdx-js/rollup'
import {babel} from '@rollup/plugin-babel'
/** @type {import('rollup').RollupOptions} */
/** @type {RollupOptions} */
const config = {
// …
plugins: [
// …
mdx({/* jsxImportSource: …, otherOptions… */})
mdx({/* jsxImportSource: …, otherOptions… */}),
// Babel is optional:
babel({
// Also run on what used to be `.mdx` (but is now JS):
@ -275,8 +283,13 @@ for more info.
<details>
<summary>Expand example</summary>
```tsx path="webpack.config.js"
/** @type {import('webpack').Configuration} */
```js twoslash path="webpack.config.js"
/**
* @import {Options} from '@mdx-js/loader'
* @import {Configuration} from 'webpack'
*/
/** @type {Configuration} */
const webpackConfig = {
module: {
// …
@ -289,7 +302,7 @@ for more info.
{loader: 'babel-loader', options: {}},
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
/** @type {Options} */
options: {/* jsxImportSource: …, otherOptions… */}
}
]
@ -321,7 +334,7 @@ for more info.
<details>
<summary>Expand example</summary>
```tsx path="vite.config.js"
```js twoslash path="vite.config.js"
import mdx from '@mdx-js/rollup'
import {defineConfig} from 'vite'
@ -344,14 +357,18 @@ To use more modern JavaScript features than what your users support,
[configure Vites `build.target`][vite-build-target].
<Note type="info">
**Note**: If you also use `vitejs/vite-plugin-react`,
**Note**: If you also use `@vitejs/plugin-react`,
you must force `@mdx-js/rollup` to run in the `pre` phase before it:
```tsx path="vite.config.js"
```js twoslash path="vite.config.js"
import mdx from '@mdx-js/rollup'
import react from '@vitejs/plugin-react'
import {defineConfig} from 'vite'
// ---cut---
// …
const viteConfig = defineConfig({
plugins: [
{enforce: 'pre', ...mdx(/* jsxImportSource: …, otherOptions… */)},
{enforce: 'pre', ...mdx({/* jsxImportSource: …, otherOptions… */})},
react({include: /\.(jsx|js|mdx|md|tsx|ts)$/})
]
})
@ -372,49 +389,157 @@ for more info.
This plugin:
```tsx path="plugin.js"
import path from 'node:path'
```js twoslash path="plugin.js"
/**
* @import {ParseResult, ParserOptions} from '@babel/parser'
* @import {File} from '@babel/types'
* @import {Program} from 'estree'
* @import {Plugin} from 'unified'
*/
import parser from '@babel/parser'
import {compileSync} from '@mdx-js/mdx'
import estreeToBabel from 'estree-to-babel'
/**
* Plugin that tells Babel to use a different parser.
*/
export function babelPluginSyntaxMdx() {
// Tell Babel to use a different parser.
return {parserOverride: babelParserWithMdx}
}
// A Babel parser that parses MDX files with `@mdx-js/mdx` and passes any
// other things through to the normal Babel parser.
/**
* Parser that handles MDX with `@mdx-js/mdx` and passes other things through
* to the normal Babel parser.
*
* @param {string} value
* @param {ParserOptions} options
* @returns {ParseResult<File>}
*/
function babelParserWithMdx(value, options) {
if (options.sourceFileName && /\.mdx?$/.test(options.sourceFileName)) {
/** @type {string | undefined} */
// @ts-expect-error: babel changed the casing at some point and the types are out of date.
const filename = options.sourceFilename || options.sourceFileName
if (filename && /\.mdx?$/.test(filename)) {
// Babel does not support async parsers, unfortunately.
return compileSync(
{value, path: options.sourceFileName},
// Tell `@mdx-js/mdx` to return a Babel tree instead of serialized JS.
{recmaPlugins: [recmaBabel], /* jsxImportSource: …, otherOptions… */}
).result
const file = compileSync(
{value, path: options.sourceFilename},
{recmaPlugins: [recmaBabel] /* jsxImportSource: …, otherOptions… */}
)
return /** @type {ParseResult<File>} */ (file.result)
}
return parser.parse(value, options)
}
// A “recma” plugin is a unified plugin that runs on the estree (used by
// `@mdx-js/mdx` and much of the JS ecosystem but not Babel).
// This plugin defines `'estree-to-babel'` as the compiler,
// which means that the resulting Babel tree is given back by `compileSync`.
/**
* A “recma” plugin is a unified plugin that runs on the estree (used by
* `@mdx-js/mdx` and much of the JS ecosystem but not Babel).
* This plugin defines `'estree-to-babel'` as the compiler,
* which means that the resulting Babel tree is given back by `compileSync`.
*
* @type {Plugin<[], Program, unknown>}
*/
function recmaBabel() {
this.compiler = estreeToBabel
// @ts-expect-error: `Program` is similar enough to a unist node.
this.compiler = compiler
/**
* @param {Program} tree
* @returns {unknown}
*/
function compiler(tree) {
// @ts-expect-error TS2349: This expression *is* callable, `estreeToBabel` types are wrong.
return estreeToBabel(tree)
}
}
```
…can be used like so with the Babel API:
```tsx path="example.js"
```js twoslash path="example.js"
/// <reference types="node" />
// ---cut---
// @filename: plugin.js
/**
* @import {ParseResult, ParserOptions} from '@babel/parser'
* @import {File} from '@babel/types'
* @import {Program} from 'estree'
* @import {Plugin} from 'unified'
*/
import parser from '@babel/parser'
import {compileSync} from '@mdx-js/mdx'
import estreeToBabel from 'estree-to-babel'
/**
* Plugin that tells Babel to use a different parser.
*/
export function babelPluginSyntaxMdx() {
return {parserOverride: babelParserWithMdx}
}
/**
* Parser that handles MDX with `@mdx-js/mdx` and passes other things through
* to the normal Babel parser.
*
* @param {string} value
* @param {ParserOptions} options
* @returns {ParseResult<File>}
*/
function babelParserWithMdx(value, options) {
/** @type {string | undefined} */
// @ts-expect-error: babel types are wrong.
const filename = options.sourceFilename || options.sourceFileName
if (filename && /\.mdx?$/.test(filename)) {
// Babel does not support async parsers, unfortunately.
const file = compileSync(
{value, path: options.sourceFilename},
{recmaPlugins: [recmaBabel] /* jsxImportSource: …, otherOptions… */}
)
return /** @type {ParseResult<File>} */ (file.result)
}
return parser.parse(value, options)
}
/**
* A “recma” plugin is a unified plugin that runs on the estree (used by
* `@mdx-js/mdx` and much of the JS ecosystem but not Babel).
* This plugin defines `'estree-to-babel'` as the compiler,
* which means that the resulting Babel tree is given back by `compileSync`.
*
* @type {Plugin<[], Program, unknown>}
*/
function recmaBabel() {
// @ts-expect-error: `Program` is similar enough to a unist node.
this.compiler = compiler
/**
* @param {Program} tree
* @returns {unknown}
*/
function compiler(tree) {
// @ts-expect-error TS2349: This expression *is* callable, `estreeToBabel` types are wrong.
return estreeToBabel(tree)
}
}
// @filename: example.js
// ---cut---
import babel from '@babel/core'
import {babelPluginSyntaxMdx} from './plugin.js'
const document = '# Hello, world!'
// Note that a filename must be set for our plugin to know its MDX instead of JS.
await babel.transformAsync(file, {filename: 'example.mdx', plugins: [babelPluginSyntaxMdx]})
const result = await babel.transformAsync(document, {
filename: 'example.mdx',
plugins: [babelPluginSyntaxMdx]
})
console.log(result)
```
</details>
@ -460,7 +585,7 @@ See [`gatsby-plugin-mdx`][gatsby-plugin-mdx] on how to use MDX with Gatsby.
<details>
<summary>Expand example</summary>
```tsx path="next.config.js"
```js twoslash path="next.config.js"
import nextMdx from '@next/mdx'
const withMdx = nextMdx({
@ -499,7 +624,7 @@ on how to use MDX with Parcel.
<details>
<summary>Expand example</summary>
```tsx path="example.js"
```js twoslash path="example.js"
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: '@emotion/react', /* otherOptions… */}))
@ -527,19 +652,27 @@ for more info.
# Hi!
```
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.js
// @errors: 2769 -- something with Ink/twoslash/react getting different versions of React?
// ---cut---
import React from 'react'
import {Text, render} from 'ink'
import Content from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
const components = {
h1(properties) {
return React.createElement(Text, {bold: true, ...properties})
},
p: Text
}
render(React.createElement(Content, {components}))
render(
React.createElement(Content, {
components: {
h1(properties) {
// @ts-expect-error: `Ink` types dont match w/ `exactOptionalPropertyTypes: true`
return React.createElement(Text, {bold: true, ...properties})
},
p: Text
}
})
)
```
Can be used with:
@ -563,7 +696,7 @@ info.
<details>
<summary>Expand example</summary>
```tsx path="example.js"
```js twoslash path="example.js"
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'preact', /* otherOptions… */}))
@ -593,53 +726,15 @@ for more info.
#### Theme UI
<details>
<summary>Expand example</summary>
Example w/o `@mdx-js/react`
```tsx path="example.js"
import {base} from '@theme-ui/preset-base'
import {ThemeProvider, components} from 'theme-ui'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
<ThemeProvider theme={base}>
<Post components={components} />
</ThemeProvider>
```
Example w/ `@mdx-js/react`
```tsx path="example.js"
import {base} from '@theme-ui/preset-base'
import {ThemeProvider} from 'theme-ui'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
<ThemeProvider theme={base}>
<Post />
</ThemeProvider>
```
</details>
[Theme UI][theme-ui] is a React-specific library that depends on context to
access its effective components.
You can install and configure [`@mdx-js/react`][mdx-react] to support context
based component passing.
See also [¶ Emotion][jsx-runtime-emotion],
[¶ React][jsx-runtime-react],
[¶ Rollup][bundler-rollup], and
[¶ esbuild][bundler-esbuild],
[¶ webpack][bundler-webpack],
which you might be using,
for more info.
[Theme UI][theme-ui] has its own plugin to support MDX.
See [`@theme-ui/mdx`][theme-ui-mdx] on how to use MDX with Theme UI.
#### Svelte
<details>
<summary>Expand example</summary>
```tsx path="example.js"
```js twoslash path="example.js"
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'svelte-jsx', /* otherOptions… */}))
@ -660,7 +755,7 @@ for more info.
<details>
<summary>Expand example</summary>
```tsx path="example.js"
```js twoslash path="example.js"
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'vue', /* otherOptions… */}))
@ -681,7 +776,7 @@ for more info.
<details>
<summary>Expand example</summary>
```tsx path="example.js"
```js twoslash path="example.js"
import {compile} from '@mdx-js/mdx'
const js = String(await compile('# hi', {jsxImportSource: 'solid-js/h', /* otherOptions… */}))
@ -764,6 +859,8 @@ See its readme on how to configure it.
[theme-ui]: https://theme-ui.com
[theme-ui-mdx]: https://theme-ui.com/mdx
[typescript]: https://www.typescriptlang.org
[vim-mdx-js]: https://github.com/jxnblk/vim-mdx-js

View File

@ -1,6 +1,6 @@
{
/**
* @import {Item} from '../_component/sort.js
* @import {Item} from '../_component/sort.js'
*/
/**
@ -14,7 +14,7 @@ import {NavigationGroup} from '../_component/nav.jsx'
export const info = {
author: [{name: 'MDX Contributors'}],
modified: new Date('2021-11-01'),
modified: new Date('2024-07-04'),
published: new Date('2021-11-01')
}
export const navSortSelf = 1

View File

@ -4,7 +4,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-24'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-18')
}
export const navSortSelf = 5
@ -127,13 +127,13 @@ for how to create plugins.
The full error message in MDX 2 is as follows:
```txt chrome=no
```mdx-invalid chrome=no
Incorrect `format: 'detect'`: `createProcessor` can support either `md` or `mdx`; it does not support detecting the format
```
The full error message in MDX 3 is:
```txt chrome=no
```mdx-invalid chrome=no
Unexpected `format: 'detect'`, which is not supported by `createProcessor`, expected `'mdx'` or `'md'`
```
@ -201,7 +201,7 @@ for examples on how to pass these values.
The full error message in MDX is as follows:
```txt chrome=no
```mdx-invalid chrome=no
Unexpected missing `options.baseUrl` needed to support `export … from`, `import`, or `import.meta.url` when generating `function-body`
```
@ -240,7 +240,7 @@ It occurs when the keywords `import` or `export` are found at the start of a
line but they are not followed by valid JavaScript.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
import 1/1
```
@ -259,7 +259,7 @@ It occurs when, after an `import` or `export` statement, more JavaScript is
found.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
export const a = 1
const b = 2
```
@ -275,7 +275,7 @@ It was introduced in version 2.
It occurs when there is an opening curly brace not followed by a closing brace.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
a { b
```
@ -294,7 +294,7 @@ It was introduced in version 3.
It occurs when containers with lazy lines are combined with expressions
An example is:
```txt chrome=no
```mdx-invalid chrome=no
* {1 +
2}
@ -305,7 +305,7 @@ An example is:
The reason for this error is that the parser it likely points to a bug.
Be explicit with your list items and block quotes:
```txt chrome=no
```mdx-invalid chrome=no
* {1 +
2}
@ -321,13 +321,13 @@ It occurs when there are matching curly braces that, when interpreting whats
inside them as JavaScript, results in a syntax error.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
a {const b = 'c'} d
```
Another example:
```txt chrome=no
```mdx-invalid chrome=no
a {!} d
```
@ -349,7 +349,7 @@ It occurs when there are matching curly braces that, and valid JavaScript is
inside them, but theres too much JavaScript.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
a {'b' 'c'} d
```
@ -365,7 +365,7 @@ It was introduced in version 2.
It occurs when there are multiple values spread into a JSX tag.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
<div {...a, ...b} />
```
@ -385,7 +385,7 @@ They were introduced in version 2.
They occur when something other than a spread is used in braces.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
<div {values} {/* comment */} {} />
```
@ -404,7 +404,7 @@ They were introduced in MDX version 2.
They occur when something unexpected was found in a JSX tag.
Some examples are:
```txt chrome=no
```mdx-invalid chrome=no
<
<.>
</
@ -441,7 +441,7 @@ It was introduced in version 2.
It occurs when a closing tag is found but there are no open tags.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
</div>
```
@ -455,7 +455,7 @@ It was introduced in version 3.
It occurs when containers with lazy lines are combined with JSX.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
* <x
y />
@ -466,7 +466,7 @@ y />
The reason for this error is that the parser it likely points to a bug.
Be explicit with your list items and block quotes:
```txt chrome=no
```mdx-invalid chrome=no
* <x
y />
@ -481,7 +481,7 @@ It was introduced in version 2.
It occurs when attributes are placed on closing tags.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
<h1>Text</h1 id="text">
```
@ -495,7 +495,7 @@ It was introduced in version 2.
It occurs when a closing tag is also marked as self-closing.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
<h1>Text</h1/>
```
@ -511,7 +511,7 @@ It occurs when a closing tag is seen that does not match the expected opening
tag.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
<a>Text</b>
```
@ -527,7 +527,7 @@ It was introduced in version 2.
It typically occurs when markdown and JSX are not interleaved correctly.
An example is:
```txt chrome=no
```mdx-invalid chrome=no
> <div>
```

View File

@ -2,7 +2,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-09-30')
}
export const navSortSelf = 3
@ -40,7 +40,7 @@ export function Thing() {
Thats *roughly* turned into the following JavaScript.
The below might help to form a mental model:
```tsx path="output-outline.jsx"
```jsx twoslash path="output-outline.jsx"
/* @jsxRuntime automatic */
/* @jsxImportSource react */
@ -62,7 +62,8 @@ Some observations:
The *actual* output is:
```tsx path="output-actual.js"
```js twoslash path="output-actual.js"
// @noErrors
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'
export function Thing() {
@ -110,12 +111,18 @@ Take this file:
It could be imported and used in a React app like so:
```tsx path="example.js"
import React from 'react'
import ReactDom from 'react-dom'
```jsx twoslash path="example.jsx"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.jsx
/// <reference lib="dom" />
// ---cut---
import {createRoot} from 'react-dom/client'
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
const root = ReactDom.createRoot(document.getElementById('root'))
const container = document.getElementById('root')
if (!container) throw new Error('Expected `root`')
const root = createRoot(container)
root.render(<Example />)
```
@ -133,7 +140,15 @@ export function Thing() {
It could be imported in the following ways:
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: types.d.ts
declare module '*.mdx' {
export {MDXContent as default} from 'mdx/types';
export function Thing(): JSX.Element;
}
// @filename: example.js
/// <reference types="node" />
// ---cut---
// A namespace import to get everything:
import * as everything from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
console.log(everything) // {Thing: [Function: Thing], default: [Function: MDXContent]}
@ -175,14 +190,20 @@ The current year is {props.year}
This file could be used as:
```tsx path="example.jsx"
```jsx twoslash path="example.jsx"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.jsx
/// <reference lib="dom" />
// ---cut---
import React from 'react'
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
// Use a `createElement` call:
React.createElement(Example, {name: 'Venus', year: 2021})
console.log(React.createElement(Example, {name: 'Venus', year: 2021}))
// Use JSX:
<Example name="Mars" year={2022} />
console.log(<Example name="Mars" year={2022} />)
```
### Components
@ -197,16 +218,24 @@ Take this example:
It can be imported from JavaScript and passed components like so:
```tsx path="example.jsx"
```jsx twoslash path="example.jsx"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.jsx
/// <reference lib="dom" />
/* @jsxImportSource react */
// ---cut---
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Example
components={{
Planet() {
return <span style={{color: 'tomato'}}>Pluto</span>
}
}}
/>
console.log(
<Example
components={{
Planet() {
return <span style={{color: 'tomato'}}>Pluto</span>
}
}}
/>
)
```
You dont have to pass components.
@ -239,31 +268,40 @@ import Contributing from './docs/contributing.mdx'
Here are some other examples of passing components:
```tsx path="example.jsx"
<Example
components={{
// Map `h1` (`# heading`) to use `h2`s.
h1: 'h2',
// Rewrite `em`s (`*like so*`) to `i` with a goldenrod foreground color.
em(props) {
return <i style={{color: 'goldenrod'}} {...props} />
},
// Pass a layout (using the special `'wrapper'` key).
wrapper({components, ...rest}) {
return <main {...rest} />
},
// Pass a component.
Planet() {
return 'Neptune'
},
// This nested component can be used as `<theme.text>hi</theme.text>`
theme: {
text(props) {
return <span style={{color: 'grey'}} {...props} />
```jsx twoslash path="example.jsx"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.jsx
/// <reference lib="dom" />
/* @jsxImportSource react */
import Example from './example.mdx'
// ---cut---
console.log(
<Example
components={{
// Map `h1` (`# heading`) to use `h2`s.
h1: 'h2',
// Rewrite `em`s (`*like so*`) to `i` with a goldenrod foreground color.
em(props) {
return <i style={{color: 'goldenrod'}} {...props} />
},
// Pass a layout (using the special `'wrapper'` key).
wrapper({components, ...rest}) {
return <main {...rest} />
},
// Pass a component.
Planet() {
return 'Neptune'
},
// This nested component can be used as `<theme.text>hi</theme.text>`
theme: {
text(props) {
return <span style={{color: 'grey'}} {...props} />
}
}
}
}}
/>
}}
/>
)
```
The following keys can be passed in `components`:
@ -276,9 +314,9 @@ The following keys can be passed in `components`:
`<So />` or `<like.so />`, note that locally defined components take
precedence)**‡**
If you ever wondered what the rules are for whether a name in JSX (so `x` in
`<x>`) is a literal tag name (like `h1`) or not (like `Component`), they are as
follows:
**‡** If you ever wondered what the rules are for whether a name in JSX (so `x`
in `<x>`) is a literal tag name (like `h1`) or not (like `Component`), they are
as follows:
* If theres a dot, its a member expression (`<a.b>` -> `h(a.b)`),
which means that the `b` component is taken from object `a`
@ -303,9 +341,7 @@ All the things.
The layout can also be imported and *then* exported with an `export … from`:
{/* Note: `language` because theme in VS Code is broken. */}
```tsx language="mdx"
```mdx
export {Layout as default} from './components.js'
```
@ -325,10 +361,18 @@ Take for example this file:
Used like so:
```tsx path="app.jsx"
```jsx twoslash path="app.jsx"
// @filename: components.d.ts
import React from 'react'
import ReactDom from 'react-dom'
import {Heading, /* … */ Table} from './components/index.js'
declare const Heading: {H1: React.ComponentType}
declare const Table: React.ComponentType
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.jsx
/// <reference lib="dom" />
// ---cut---
import {createRoot} from 'react-dom/client'
import {Heading, /* … */ Table} from './components.js'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
const components = {
@ -337,7 +381,9 @@ const components = {
table: Table
}
const root = ReactDom.createRoot(document.getElementById('root'))
const container = document.getElementById('root')
if (!container) throw new Error('Expected `root`')
const root = createRoot(container)
root.render(<Post components={components} />)
```
@ -378,18 +424,14 @@ Set it up like so:
```diff
+import {MDXProvider} from '@mdx-js/react'
import React from 'react'
import ReactDom from 'react-dom'
import {createRoot} from 'react-dom/client'
import {Heading, /* … */ Table} from './components/index.js'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
@@ -13,4 +14,8 @@ const components = {
const components = {
h1: Heading.H1,
// …
table: Table
}
const root = ReactDom.createRoot(document.getElementById('root'))
const container = document.getElementById('root')
if (!container) throw new Error('Expected `root`')
const root = createRoot(container)
-root.render(<Post components={components} />)
+root.render(
+ <MDXProvider components={components}>
@ -418,14 +460,28 @@ Now you can remove the explicit and verbose component passing:
When `MDXProvider`s are nested, their components are merged.
Take this example:
```tsx
<>
```jsx twoslash
// @filename: types.d.ts
import React from 'react'
import type {MDXContent} from 'mdx/types.js'
declare const Component1: React.ComponentType
declare const Component2: React.ComponentType
declare const Component3: React.ComponentType
declare const Component4: React.ComponentType
declare const Content: MDXContent
// @filename: example.jsx
/// <reference lib="dom" />
import {MDXProvider} from '@mdx-js/react'
import React from 'react'
import {Component1, Component2, Component3, Component4, Content} from './types.js'
// ---cut---
console.log(
<MDXProvider components={{h1: Component1, h2: Component2}}>
<MDXProvider components={{h2: Component3, h3: Component4}}>
<Content />
</MDXProvider>
</MDXProvider>
</>
)
```
…which results in `h1`s using `Component1`, `h2`s using `Component3`, and `h3`s
@ -436,8 +492,22 @@ Its given the current context `components` and what it returns will be used
instead.
In this example the current context components are discarded:
```tsx
<>
```jsx twoslash
// @filename: types.d.ts
import React from 'react'
import type {MDXContent} from 'mdx/types.js'
declare const Component1: React.ComponentType
declare const Component2: React.ComponentType
declare const Component3: React.ComponentType
declare const Component4: React.ComponentType
declare const Content: MDXContent
// @filename: example.jsx
/// <reference lib="dom" />
import {MDXProvider} from '@mdx-js/react'
import React from 'react'
import {Component1, Component2, Component3, Component4, Content} from './types.js'
// ---cut---
console.log(
<MDXProvider components={{h1: Component1, h2: Component2}}>
<MDXProvider
components={
@ -449,7 +519,7 @@ In this example the current context components are discarded:
<Content />
</MDXProvider>
</MDXProvider>
</>
)
```
…which results in `h2`s using `Component3` and `h3`s using `Component4`.

View File

@ -5,7 +5,7 @@ export const info = {
{github: 'johno', name: 'John Otander', twitter: '4lpine'},
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-01-06'),
modified: new Date('2024-07-04'),
published: new Date('2018-08-11')
}
export const navSortSelf = 1
@ -321,7 +321,7 @@ Its not possible to wrap “blocks” if text and tags are on the same line b
corresponding opening and closing tags are in different blocks
(so this is invalid!):
```txt chrome=no
```mdx-invalid chrome=no
Welcome! <a href="about.html">
This is home of...

View File

@ -2,7 +2,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-06')
}
export const navSortSelf = 5
@ -27,7 +27,7 @@ Vue, etc.)
You can use [`@remark-embedder/core`][remark-embedder] by doing something like
this:
```tsx path="example.js"
```js path="example.js"
import {compile} from '@mdx-js/mdx'
// Note: `@remark-embedder` is currently using faux-esm.
import fauxRemarkEmbedder from '@remark-embedder/core'
@ -60,7 +60,7 @@ console.log(
<details>
<summary>Expand equivalent JSX</summary>
```tsx path="output.jsx"
```jsx path="output.jsx"
<>
<p>Check out this video:</p>
<iframe
@ -92,7 +92,7 @@ Heres a codepen, and some other blog post text.
<details>
<summary>Expand equivalent JSX</summary>
```tsx path="output.jsx"
```jsx path="output.jsx"
<>
<p>Heres a codepen, and some other blog post text.</p>
<CodePen codePenId="PNaGbb" />
@ -110,23 +110,25 @@ Heres a codepen, and some other blog post text.
Then you can either pass all components:
```tsx path="example.jsx"
```jsx path="example.jsx"
import * as embeds from 'mdx-embed'
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Example components={...embeds} />
console.log(<Example components={...embeds} />)
```
Or, if youve installed and configured [`@mdx-js/react`][mdx-react], you can
also use `MDXEmbedProvider`:
```tsx path="example.jsx"
```jsx path="example.jsx"
import {MDXEmbedProvider} from 'mdx-embed'
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<MDXEmbedProvider>
<Example />
</MDXEmbedProvider>
console.log(
<MDXEmbedProvider>
<Example />
</MDXEmbedProvider>
)
```
[commonmark]: https://spec.commonmark.org/current/

View File

@ -2,7 +2,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-06')
}
export const navSortSelf = 2
@ -26,7 +26,16 @@ export const title = 'Hi, ' + name + '!'
Can be used like so:
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: types.d.ts
declare module '*.mdx' {
export {MDXContent as default} from 'mdx/types'
export const name: string
export const title: string
}
// @filename: example.js
/// <reference types="node" />
// ---cut---
import * as Post from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
console.log(Post.title) // Prints 'Hi, World!'
@ -46,7 +55,9 @@ title: Hi, World!
Then without compiling or evaluating the metadata can be accessed like so:
```tsx path="example.js"
```js twoslash path="example.js"
/// <reference types="node" />
// ---cut---
import {read} from 'to-vfile'
import {matter} from 'vfile-matter'
@ -60,7 +71,10 @@ Our compiler, `@mdx-js/mdx`, doesnt understand YAML frontmatter by default bu
it can be enabled by using a remark plugin,
[`remark-frontmatter`][remark-frontmatter]:
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: example.js
/// <reference types="node" />
// ---cut---
import fs from 'node:fs/promises'
import {compile} from '@mdx-js/mdx'
import remarkFrontmatter from 'remark-frontmatter'

View File

@ -2,7 +2,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-06')
}
export const navSortSelf = 1
@ -51,7 +51,10 @@ A note[^1]
The above MDX with GFM can be transformed with the following module:
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: example.js
/// <reference types="node" />
// ---cut---
import fs from 'node:fs/promises'
import {compile} from '@mdx-js/mdx'
import remarkGfm from 'remark-gfm'
@ -66,7 +69,7 @@ console.log(
<details>
<summary>Expand equivalent JSX</summary>
```tsx path="output.jsx"
```jsx path="output.jsx"
<>
<h1>GFM</h1>
<h2>Autolink literals</h2>

View File

@ -1,6 +1,6 @@
{
/**
* @import {Item} from '../_component/sort.js
* @import {Item} from '../_component/sort.js'
*/
/**
@ -14,7 +14,7 @@ import {NavigationGroup} from '../_component/nav.jsx'
export const info = {
author: [{name: 'MDX Contributors'}],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-11-01')
}
export const navSortSelf = 2

View File

@ -2,7 +2,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-24'),
modified: new Date('2024-07-04'),
published: new Date('2023-10-24')
}
export const navSortSelf = 7
@ -25,19 +25,27 @@ as you can pass components to MDX:
You can pass `Planet` and say a component used instead of the `h1`:
```tsx path="example.jsx"
```jsx twoslash path="example.jsx"
// @filename: types.d.ts
import type {} from 'mdx'
// @filename: example.jsx
/// <reference lib="dom" />
/* @jsxImportSource react */
// ---cut---
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Example
components={{
Planet() {
return 'Pluto'
},
h1(properties) {
return <h2 {...properties} />
}
}}
/>
console.log(
<Example
components={{
Planet() {
return 'Pluto'
},
h1(properties) {
return <h2 {...properties} />
}
}}
/>
)
```
When you find yourself passing that `components` prop around a lot,
@ -91,8 +99,15 @@ We can use this interface to inject components from a file.
In that file,
we need a `useMDXComponents` function that returns our components.
```tsx path="mdx-components.js"
/** @returns {import('mdx/types.js').MDXComponents} */
```jsx twoslash path="mdx-components.js"
// @filename: mdx-components.jsx
/* @jsxImportSource react */
// ---cut---
/**
* @import {MDXComponents} from 'mdx/types.js'
*/
/** @returns {MDXComponents} */
export function useMDXComponents() {
return {
Planet() {

View File

@ -4,7 +4,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-06')
}
export const navSortSelf = 3
@ -30,7 +30,10 @@ Say we have an MDX file like this:
The above MDX with math can be transformed with the following module:
```tsx path="example.js"
```js twoslash path="example.js"
// @filename: example.js
/// <reference types="node" />
// ---cut---
import fs from 'node:fs/promises'
import {compile} from '@mdx-js/mdx'
import rehypeKatex from 'rehype-katex'
@ -49,7 +52,7 @@ console.log(
<details>
<summary>Expand equivalent JSX</summary>
```tsx path="output.jsx"
```jsx path="output.jsx"
<>
<h1>
<span className="katex">

View File

@ -4,7 +4,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-11-13')
}
export const navSortSelf = 6
@ -26,7 +26,7 @@ This is similar to what people sometimes use [`mdx-bundler`][mdx-bundler] or
On the server:
```tsx path="server.js"
```js twoslash path="server.js"
import {compile} from '@mdx-js/mdx'
const code = String(await compile('# hi', {
@ -38,12 +38,13 @@ const code = String(await compile('# hi', {
On the client:
```tsx path="client.js"
```js twoslash path="client.js"
import {run} from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime'
const code = '' // To do: get `code` from server somehow.
// @ts-expect-error: `runtime` types are currently broken.
const {default: Content} = await run(code, {...runtime, baseUrl: import.meta.url})
```
@ -66,20 +67,34 @@ and runs in one.
Some frameworks let you write the server and client code in one file, such as
Next.
```tsx path="pages/hello.js"
```js twoslash path="pages/hello.js"
/**
* @import {MDXModule} from 'mdx/types.js'
* @import {Dispatch, SetStateAction} from 'react'
*/
import {compile, run} from '@mdx-js/mdx'
import {Fragment, useEffect, useState} from 'react'
import * as runtime from 'react/jsx-runtime'
/**
* @param {{code: string}} props
* @returns {JSX.Element}
*/
export default function Page({code}) {
/** @type {[MDXModule | undefined, Dispatch<SetStateAction<MDXModule | undefined>>]} */
const [mdxModule, setMdxModule] = useState()
const Content = mdxModule ? mdxModule.default : Fragment
useEffect(function () {
;(async function () {
setMdxModule(await run(code, {...runtime, baseUrl: import.meta.url}))
})()
}, [code])
useEffect(
function () {
;(async function () {
// @ts-expect-error: `runtime` types are currently broken.
setMdxModule(await run(code, {...runtime, baseUrl: import.meta.url}))
})()
},
[code]
)
return <Content />
}
@ -87,7 +102,7 @@ export default function Page({code}) {
export async function getStaticProps() {
const code = String(
await compile('# hi', {
outputFormat: 'function-body',
outputFormat: 'function-body'
/* …otherOptions */
})
)

View File

@ -4,7 +4,7 @@ export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-23'),
modified: new Date('2024-07-04'),
published: new Date('2021-10-06')
}
export const navSortSelf = 4
@ -28,33 +28,34 @@ Vue, etc.)
## Syntax highlighting at compile time
Use either [`rehype-highlight`][rehype-highlight]
(`highlight.js`) or [`@mapbox/rehype-prism`][rehype-prism]
(Prism) by doing something like this:
Use for example [`rehype-starry-night`][rehype-starry-night] (`starry-night`),
[`rehype-highlight`][rehype-highlight] (`lowlight`, `highlight.js`),
or [`@mapbox/rehype-prism`][rehype-prism] (`refractor`, `prism`)
by doing something like this:
```tsx path="example.js"
```js twoslash path="example.js"
/// <reference types="node" />
// ---cut---
import {compile} from '@mdx-js/mdx'
import rehypeHighlight from 'rehype-highlight'
import rehypeStarryNight from 'rehype-starry-night'
const code = `~~~js
console.log(1)
~~~`
console.log(
String(await compile(code, {rehypePlugins: [rehypeHighlight]}))
String(await compile(code, {rehypePlugins: [rehypeStarryNight]}))
)
```
<details>
<summary>Expand equivalent JSX</summary>
```tsx path="output.jsx"
```jsx path="output.jsx"
<>
<pre>
<code className="hljs language-js">
<span className="hljs-variable language_">console</span>.
<span className="hljs-title function_">log</span>(
<span className="hljs-number">1</span>)
<code className="language-js">
<span className="pl-en">console</span>.<span className="pl-c1">log</span>(<span className="pl-c1">1</span>)
</code>
</pre>
</>
@ -62,24 +63,8 @@ console.log(
</details>
<Note type="important">
**Important**: If you chose `rehype-highlight`, then you should also use a
highlight.js theme somewhere on the page.
For example, to get GitHub Dark from cdnjs:
```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
```
{/* to do: once in a while, get the latest: <https://github.com/wooorm/lowlight#css> */}
If you chose `@mapbox/rehype-prism`, include something like this instead to
get Prism Dark:
```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/themes/prism-dark.min.css">
```
{/* to do: once in a while, get the latest: <https://github.com/wooorm/refractor#css> */}
**Important**: you must likely also include CSS somewhere on the page.
See the documentation of the plugin youre using for more information.
</Note>
## Syntax highlighting at run time
@ -88,11 +73,13 @@ Use for example
[`react-syntax-highlighter`][react-syntax-highlighter],
by doing something like this:
```tsx path="example.jsx"
{/* Note: `react-syntax-highlighter` doesnt seem actively maintained so no twoslash to check this example. */}
```jsx path="example.jsx"
import SyntaxHighlighter from 'react-syntax-highlighter'
import Post from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Post components={{code}} />
console.log(<Post components={{code}} />)
function code({className, ...properties}) {
const match = /language-(\w+)/.exec(className || '')
@ -105,7 +92,7 @@ function code({className, ...properties}) {
<details>
<summary>Expand equivalent JSX</summary>
```tsx path="output.jsx"
```jsx path="output.jsx"
<>
<pre>
<div
@ -161,6 +148,8 @@ More info on plugins is available in [§ Extending MDX][extend]
[commonmark]: https://spec.commonmark.org/current/
[rehype-starry-night]: https://github.com/rehypejs/rehype-starry-night
[rehype-highlight]: https://github.com/rehypejs/rehype-highlight
[rehype-prism]: https://github.com/mapbox/rehype-prism

View File

@ -590,7 +590,7 @@ You can more easily embed components in MDX because blank lines are allowed:
{/* Note: `language` because theme in VS Code is broken. */}
```tsx language="mdx" chrome=no
```mdx chrome=no
export function Button(props) {
const style = {color: 'red'}

View File

@ -1,6 +1,6 @@
{
/**
* @import {Item} from '../_component/sort.js
* @import {Item} from '../_component/sort.js'
*/
/**
@ -14,7 +14,7 @@ import {NavigationGroup} from '../_component/nav.jsx'
export const info = {
author: [{name: 'MDX Contributors'}],
modified: new Date('2021-11-01'),
modified: new Date('2024-07-04'),
published: new Date('2021-11-01')
}
export const navSortSelf = 3

1992
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,16 +21,22 @@
"packages/node-loader/",
"packages/rollup/"
],
"#": "note: `lz-string` is included because `@typescript/vfs` (through `twoslash`) types use it w/o marking it as a dep",
"devDependencies": {
"@babel/types": "^7.0.0",
"@floating-ui/dom": "^1.0.0",
"@next/mdx": "^14.0.0",
"@node-loader/core": "^2.0.0",
"@rollup/plugin-babel": "^6.0.0",
"@sparticuz/chromium": "^123.0.0",
"@types/babel__core": "^7.0.0",
"@types/dlv": "^1.0.0",
"@types/mdx": "^2.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/ungap__structured-clone": "^1.0.0",
"@ungap/structured-clone": "^1.0.0",
"@vitejs/plugin-react": "^4.0.0",
"@vue/server-renderer": "^3.0.0",
"@wooorm/starry-night": "^3.0.0",
"acorn": "^8.0.0",
@ -47,6 +53,7 @@
"eslint-plugin-es": "^4.0.0",
"eslint-plugin-react": "^7.0.0",
"eslint-plugin-react-hooks": "^4.0.0",
"estree-to-babel": "^9.0.0",
"estree-util-value-to-estree": "^3.0.0",
"globby": "^14.0.0",
"hast-util-from-html": "^2.0.0",
@ -56,6 +63,8 @@
"hast-util-to-jsx-runtime": "^2.0.0",
"hast-util-to-text": "^4.0.0",
"hastscript": "^9.0.0",
"ink": "^5.0.0",
"lz-string": "^1.0.0",
"p-all": "^5.0.0",
"periscopic": "^3.0.0",
"postcss": "^8.0.0",
@ -83,6 +92,7 @@
"rehype-slug": "^6.0.0",
"rehype-starry-night": "^2.0.0",
"rehype-stringify": "^10.0.0",
"rehype-twoslash": "^1.0.0",
"remark-cli": "^12.0.0",
"remark-directive": "^3.0.0",
"remark-frontmatter": "^5.0.0",
@ -104,6 +114,7 @@
"unist-util-remove-position": "^5.0.0",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.0",
"vfile-matter": "^5.0.0",
"vfile-message": "^4.0.0",
"vite": "^5.0.0",
"vue": "^3.0.0",

View File

@ -57,7 +57,12 @@ npm install @mdx-js/loader
Add something along these lines to your `webpack.config.js`:
```tsx
/** @type {import('webpack').Configuration} */
/**
* @import {Options} from '@mdx-js/loader'
* @import {Configuration} from 'webpack'
*/
/** @type {Configuration} */
const webpackConfig = {
module: {
// …
@ -68,7 +73,7 @@ const webpackConfig = {
use: [
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
/** @type {Options} */
options: {/* jsxImportSource: …, otherOptions… */}
}
]
@ -111,7 +116,12 @@ If you use modern JavaScript features you might want to use Babel through
[`babel-loader`][babel-loader] to compile to code that works in older browsers:
```tsx
/** @type {import('webpack').Configuration} */
/**
* @import {Options} from '@mdx-js/loader'
* @import {Configuration} from 'webpack'
*/
/** @type {Configuration} */
const webpackConfig = {
module: {
// …
@ -125,7 +135,7 @@ const webpackConfig = {
{loader: 'babel-loader', options: {}},
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
/** @type {Options} */
options: {}
}
]

View File

@ -66,6 +66,10 @@ In browsers with [`esm.sh`][esmsh]:
## Use
```tsx
/**
* @import {MDXComponents} from 'mdx/types.js'
*/
import {MDXProvider} from '@mdx-js/preact'
import Post from './post.mdx'
// ^-- Assumes an integration is used to compile MDX to JS, such as
@ -73,7 +77,7 @@ import Post from './post.mdx'
// `@mdx-js/rollup`, and that it is configured with
// `options.providerImportSource: '@mdx-js/preact'`.
/** @type {import('mdx/types.js').MDXComponents} */
/** @type {MDXComponents} */
const components = {
em(properties) {
return <i {...properties} />

View File

@ -70,6 +70,10 @@ In browsers with [`esm.sh`][esmsh]:
## Use
```tsx
/**
* @import {MDXComponents} from 'mdx/types.js'
*/
import {MDXProvider} from '@mdx-js/react'
import Post from './post.mdx'
// ^-- Assumes an integration is used to compile MDX to JS, such as
@ -77,7 +81,7 @@ import Post from './post.mdx'
// `@mdx-js/rollup`, and that it is configured with
// `options.providerImportSource: '@mdx-js/react'`.
/** @type {import('mdx/types.js').MDXComponents} */
/** @type {MDXComponents} */
const components = {
em(properties) {
return <i {...properties} />

View File

@ -57,9 +57,13 @@ npm install @mdx-js/rollup
Add something along these lines to your `rollup.config.js`:
```tsx
/**
* @import {RollupOptions} from 'rollup'
*/
import mdx from '@mdx-js/rollup'
/** @type {import('rollup').RollupOptions} */
/** @type {RollupOptions} */
const config = {
// …
plugins: [
@ -119,10 +123,14 @@ If you use modern JavaScript features you might want to use Babel through
[`@rollup/plugin-babel`][rollup-plugin-babel] to compile to code that works:
```tsx
/**
* @import {RollupOptions} from 'rollup'
*/
import mdx from '@mdx-js/rollup'
import {babel} from '@rollup/plugin-babel'
/** @type {import('rollup').RollupOptions} */
/** @type {RollupOptions} */
const config = {
// …
plugins: [

View File

@ -9,6 +9,7 @@
"jsx": "preserve",
"lib": ["es2022"],
"module": "node16",
"moduleResolution": "node16",
"strict": true,
"target": "es2022"
},

View File

@ -14,7 +14,8 @@
* List of keys to exclude (optional).
*/
import {pathToFileURL} from 'node:url'
import assert from 'node:assert/strict'
import {fileURLToPath, pathToFileURL} from 'node:url'
import {nodeTypes} from '@mdx-js/mdx'
import {common} from '@wooorm/starry-night'
import sourceMdx from '@wooorm/starry-night/source.mdx'
@ -28,11 +29,12 @@ import rehypeInferDescriptionMeta from 'rehype-infer-description-meta'
import rehypeInferReadingTimeMeta from 'rehype-infer-reading-time-meta'
import rehypeInferTitleMeta from 'rehype-infer-title-meta'
import rehypeMinifyUrl from 'rehype-minify-url'
import rehypePresetMinify from 'rehype-preset-minify'
import rehypeRaw from 'rehype-raw'
import rehypeShiftHeading from 'rehype-shift-heading'
import rehypeSlug from 'rehype-slug'
import rehypeStarryNight from 'rehype-starry-night'
import rehypePresetMinify from 'rehype-preset-minify'
import rehypeRaw from 'rehype-raw'
import rehypeTwoslash from 'rehype-twoslash'
import remarkFrontmatter from 'remark-frontmatter'
import remarkGemoji from 'remark-gemoji'
import remarkGfm from 'remark-gfm'
@ -42,8 +44,31 @@ import remarkSqueezeParagraphs from 'remark-squeeze-paragraphs'
import remarkStripBadges from 'remark-strip-badges'
import remarkToc from 'remark-toc'
import {visit} from 'unist-util-visit'
import typescript from 'typescript'
import {config} from '../docs/_config.js'
const configPath = typescript.findConfigFile(
fileURLToPath(import.meta.url),
typescript.sys.fileExists,
'tsconfig.json'
)
assert(configPath)
const commandLine = typescript.getParsedCommandLineOfConfigFile(
configPath,
undefined,
{
fileExists: typescript.sys.fileExists,
getCurrentDirectory: typescript.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic(x) {
console.warn('Unrecoverable diagnostic', x)
},
readDirectory: typescript.sys.readDirectory,
readFile: typescript.sys.readFile,
useCaseSensitiveFileNames: typescript.sys.useCaseSensitiveFileNames
}
)
assert(commandLine)
/** @type {Readonly<CompileOptions>} */
const options = {
recmaPlugins: [recmaInjectMeta],
@ -67,7 +92,14 @@ const options = {
properties: {ariaLabel: 'Link to this section', className: ['anchor']}
}
],
[rehypeStarryNight, {grammars: [...common, sourceMdx, sourceTsx]}],
[
rehypeStarryNight,
{
grammars: [...common, sourceMdx, sourceTsx],
plainText: ['mdx-invalid', 'txt']
}
],
[rehypeTwoslash, {twoslash: {compilerOptions: commandLine.options}}],
rehypePresetMinify,
rehypeMinifyUrl
],
@ -258,6 +290,14 @@ function rehypePrettyCodeBlocks() {
}
}
const className = Array.isArray(code.properties.className)
? code.properties.className
: (code.properties.className = [])
if (metaProperties.twoslash === '') {
className.push('twoslash')
}
if (metaProperties.chrome === 'no') {
return
}
@ -265,9 +305,6 @@ function rehypePrettyCodeBlocks() {
const textContent = toText(node)
/** @type {Array<ElementContent>} */
const children = [node]
const className = Array.isArray(code.properties.className)
? code.properties.className
: []
const lang = className.find(function (value) {
return String(value).slice(0, 9) === 'language-'
})
@ -275,8 +312,7 @@ function rehypePrettyCodeBlocks() {
const footer = []
/** @type {Array<ElementContent>} */
const header = []
const language =
metaProperties.language || (lang ? String(lang).slice(9) : undefined)
const language = lang ? String(lang).slice(9) : undefined
// Not giant.
if (textContent.length < 8192 && metaProperties.copy !== 'no') {