Skip to content

Commit 641eb91

Browse files
authoredDec 14, 2022
Add support JSX development runtime
The JSX dev runtime exposes more debugging information to users. For example, React exposes positional information through React Devtools. Although GH-2035 started off as an issue to support the `__source` prop, I have refrained from actually supporting the `__source` prop, as it’s specific to React. The automatic dev runtime supports this information without patching props. Closes GH-2035. Closes GH-2045. Reviewed-by: Titus Wormer <tituswormer@gmail.com>
1 parent 4a1415d commit 641eb91

File tree

5 files changed

+104
-16
lines changed

5 files changed

+104
-16
lines changed
 

‎packages/mdx/lib/core.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export function createProcessor(options = {}) {
125125
.use(recmaJsxRewrite, {development, providerImportSource, outputFormat})
126126

127127
if (!jsx) {
128-
pipeline.use(recmaJsxBuild, {outputFormat})
128+
pipeline.use(recmaJsxBuild, {development, outputFormat})
129129
}
130130

131131
pipeline.use(recmaStringify, {SourceMapGenerator}).use(recmaPlugins || [])

‎packages/mdx/lib/plugin/recma-jsx-build.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/**
22
* @typedef {import('estree-jsx').Program} Program
3+
* @typedef {import('estree-util-build-jsx').BuildJsxOptions} BuildJsxOptions
34
*
45
* @typedef RecmaJsxBuildOptions
56
* @property {'program'|'function-body'} [outputFormat='program']
@@ -15,13 +16,13 @@ import {toIdOrMemberExpression} from '../util/estree-util-to-id-or-member-expres
1516
* A plugin to build JSX into function calls.
1617
* `estree-util-build-jsx` does all the work for us!
1718
*
18-
* @type {import('unified').Plugin<[RecmaJsxBuildOptions]|[], Program>}
19+
* @type {import('unified').Plugin<[BuildJsxOptions & RecmaJsxBuildOptions?], Program>}
1920
*/
2021
export function recmaJsxBuild(options = {}) {
21-
const {outputFormat} = options
22+
const {development, outputFormat} = options
2223

23-
return (tree) => {
24-
buildJsx(tree)
24+
return (tree, file) => {
25+
buildJsx(tree, {development, filePath: file.history[0]})
2526

2627
// When compiling to a function body, replace the import that was just
2728
// generated, and get `jsx`, `jsxs`, and `Fragment` from `arguments[0]`
@@ -31,7 +32,7 @@ export function recmaJsxBuild(options = {}) {
3132
tree.body[0] &&
3233
tree.body[0].type === 'ImportDeclaration' &&
3334
typeof tree.body[0].source.value === 'string' &&
34-
/\/jsx-runtime$/.test(tree.body[0].source.value)
35+
/\/jsx-(dev-)?runtime$/.test(tree.body[0].source.value)
3536
) {
3637
tree.body[0] = {
3738
type: 'VariableDeclaration',

‎packages/mdx/lib/util/resolve-evaluate-options.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
* @typedef RunnerOptions
55
* @property {*} Fragment
66
* Symbol to use for fragments.
7-
* @property {*} jsx
8-
* Function to generate an element with static children.
9-
* @property {*} jsxs
10-
* Function to generate an element with dynamic children.
7+
* @property {*} [jsx]
8+
* Function to generate an element with static children in production mode.
9+
* @property {*} [jsxs]
10+
* Function to generate an element with dynamic children in production mode.
11+
* @property {*} [jsxDEV]
12+
* Function to generate an element in development mode.
1113
* @property {*} [useMDXComponents]
1214
* Function to get `MDXComponents` from context.
1315
*
@@ -23,18 +25,24 @@
2325
* @returns {{compiletime: ProcessorOptions, runtime: RunnerOptions}}
2426
*/
2527
export function resolveEvaluateOptions(options) {
26-
const {Fragment, jsx, jsxs, useMDXComponents, ...rest} = options || {}
28+
const {development, Fragment, jsx, jsxs, jsxDEV, useMDXComponents, ...rest} =
29+
options || {}
2730

2831
if (!Fragment) throw new Error('Expected `Fragment` given to `evaluate`')
29-
if (!jsx) throw new Error('Expected `jsx` given to `evaluate`')
30-
if (!jsxs) throw new Error('Expected `jsxs` given to `evaluate`')
32+
if (development) {
33+
if (!jsxDEV) throw new Error('Expected `jsxDEV` given to `evaluate`')
34+
} else {
35+
if (!jsx) throw new Error('Expected `jsx` given to `evaluate`')
36+
if (!jsxs) throw new Error('Expected `jsxs` given to `evaluate`')
37+
}
3138

3239
return {
3340
compiletime: {
3441
...rest,
42+
development,
3543
outputFormat: 'function-body',
3644
providerImportSource: useMDXComponents ? '#' : undefined
3745
},
38-
runtime: {Fragment, jsx, jsxs, useMDXComponents}
46+
runtime: {Fragment, jsx, jsxs, jsxDEV, useMDXComponents}
3947
}
4048
}

‎packages/mdx/test/compile.js

+50
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,22 @@ test('compile', async () => {
543543
)
544544
console.log('\nnote: the preceding warning is expected!\n')
545545

546+
const developmentSourceNode = (
547+
await run(
548+
compileSync(
549+
{value: '<div />', path: 'path/to/file.js'},
550+
{development: true}
551+
).value
552+
)
553+
)({})
554+
assert.equal(
555+
// @ts-expect-error React attaches source information on this property,
556+
// but it’s private and untyped.
557+
developmentSourceNode._source,
558+
{fileName: 'path/to/file.js', lineNumber: 1, columnNumber: 1},
559+
'should expose source information in the automatic jsx dev runtime'
560+
)
561+
546562
try {
547563
renderToStaticMarkup(
548564
React.createElement(await run(compileSync('<X />', {development: true})))
@@ -1503,6 +1519,40 @@ test('MDX (ESM)', async () => {
15031519
'<div style="color:red"><p>a</p></div>',
15041520
'should support rexporting the default export, and other things, from a source'
15051521
)
1522+
1523+
assert.equal(
1524+
compileSync({value: '<X />', path: 'path/to/file.js'}, {development: true})
1525+
.value,
1526+
[
1527+
'/*@jsxRuntime automatic @jsxImportSource react*/',
1528+
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
1529+
'function _createMdxContent(props) {',
1530+
' const {X} = props.components || ({});',
1531+
' if (!X) _missingMdxReference("X", true, "1:1-1:6");',
1532+
' return _jsxDEV(X, {}, undefined, false, {',
1533+
' fileName: "path/to/file.js",',
1534+
' lineNumber: 1,',
1535+
' columnNumber: 1',
1536+
' }, this);',
1537+
'}',
1538+
'function MDXContent(props = {}) {',
1539+
' const {wrapper: MDXLayout} = props.components || ({});',
1540+
' return MDXLayout ? _jsxDEV(MDXLayout, Object.assign({}, props, {',
1541+
' children: _jsxDEV(_createMdxContent, props, undefined, false, {',
1542+
' fileName: "path/to/file.js"',
1543+
' }, this)',
1544+
' }), undefined, false, {',
1545+
' fileName: "path/to/file.js"',
1546+
' }, this) : _createMdxContent(props);',
1547+
'}',
1548+
'export default MDXContent;',
1549+
'function _missingMdxReference(id, component, place) {',
1550+
' throw new Error("Expected " + (component ? "component" : "object") + " `" + id + "` to be defined: you likely forgot to import, pass, or provide it." + (place ? "\\nIt’s referenced in your code at `" + place + "` in `path/to/file.js`" : ""));',
1551+
'}',
1552+
''
1553+
].join('\n'),
1554+
'should support the jsx dev runtime'
1555+
)
15061556
})
15071557

15081558
test('source maps', async () => {

‎packages/mdx/test/evaluate.js

+31-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {renderToStaticMarkup as renderToStaticMarkup_} from '../../react/node_mo
66
// @ts-expect-error: make sure a single react is used.
77
import * as runtime_ from '../../react/node_modules/react/jsx-runtime.js'
88
// @ts-expect-error: make sure a single react is used.
9+
import * as devRuntime from '../../react/node_modules/react/jsx-dev-runtime.js'
10+
// @ts-expect-error: make sure a single react is used.
911
import React_ from '../../react/node_modules/react/index.js'
1012
import * as provider from '../../react/index.js'
1113

@@ -30,7 +32,6 @@ test('evaluate', async () => {
3032

3133
assert.throws(
3234
() => {
33-
// @ts-expect-error: missing required arguments
3435
evaluateSync('a', {Fragment: runtime.Fragment})
3536
},
3637
/Expected `jsx` given to `evaluate`/,
@@ -39,13 +40,20 @@ test('evaluate', async () => {
3940

4041
assert.throws(
4142
() => {
42-
// @ts-expect-error: missing required arguments
4343
evaluateSync('a', {Fragment: runtime.Fragment, jsx: runtime.jsx})
4444
},
4545
/Expected `jsxs` given to `evaluate`/,
4646
'should throw on missing `jsxs`'
4747
)
4848

49+
assert.throws(
50+
() => {
51+
evaluateSync('a', {Fragment: runtime.Fragment, development: true})
52+
},
53+
/Expected `jsxDEV` given to `evaluate`/,
54+
'should throw on missing `jsxDEV` in dev mode'
55+
)
56+
4957
assert.equal(
5058
renderToStaticMarkup(
5159
React.createElement((await evaluate('# hi!', runtime)).default)
@@ -62,6 +70,27 @@ test('evaluate', async () => {
6270
'should evaluate (sync)'
6371
)
6472

73+
assert.equal(
74+
renderToStaticMarkup(
75+
React.createElement(
76+
(await evaluate('# hi dev!', {development: true, ...devRuntime}))
77+
.default
78+
)
79+
),
80+
'<h1>hi dev!</h1>',
81+
'should evaluate (sync)'
82+
)
83+
84+
assert.equal(
85+
renderToStaticMarkup(
86+
React.createElement(
87+
evaluateSync('# hi dev!', {development: true, ...devRuntime}).default
88+
)
89+
),
90+
'<h1>hi dev!</h1>',
91+
'should evaluate (sync)'
92+
)
93+
6594
assert.equal(
6695
renderToStaticMarkup(
6796
React.createElement(

1 commit comments

Comments
 (1)

vercel[bot] commented on Dec 14, 2022

@vercel[bot]

Successfully deployed to the following URLs:

mdx – ./

mdx-mdx.vercel.app
mdxjs.com
v2.mdxjs.com
mdx-git-main-mdx.vercel.app

Please sign in to comment.