From 5d88053ee3d70e10f0b5461f42c03332dd36efd1 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Wed, 18 May 2022 14:32:05 +0200 Subject: [PATCH 1/4] Support jsx dev runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JSX dev runtime exposes more debugging information to users. For example, React exposes positional information through React Devtools. Although #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 #2035 --- package-lock.json | 16 +++---- packages/mdx/lib/core.js | 2 +- packages/mdx/lib/plugin/recma-jsx-build.js | 9 ++-- packages/mdx/test/compile.js | 50 ++++++++++++++++++++++ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4f971109..72d6b4b87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5307,17 +5307,17 @@ "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==" }, "node_modules/estree-util-build-jsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.0.0.tgz", - "integrity": "sha512-d49hPGqBCJF/bF06g1Ywg7zjH1mrrUdPPrixBlKBxcX4WvMYlUUJ8BkrwlzWc8/fm6XqGgk5jilhgeZBDEGwOQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.1.0.tgz", + "integrity": "sha512-gsBGfsY6LOJUIDwmMkTOcgCX+3r/LUjRBccgHMSW55PHjhZsV13RmPl/iwpAvW8KcQqoN9P0FEFWTSS2Zc5bGA==", "dependencies": { "@types/estree-jsx": "^0.0.1", "estree-util-is-identifier-name": "^2.0.0", "estree-walker": "^3.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/estree-util-is-identifier-name": { @@ -22867,9 +22867,9 @@ } }, "estree-util-build-jsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.0.0.tgz", - "integrity": "sha512-d49hPGqBCJF/bF06g1Ywg7zjH1mrrUdPPrixBlKBxcX4WvMYlUUJ8BkrwlzWc8/fm6XqGgk5jilhgeZBDEGwOQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.1.0.tgz", + "integrity": "sha512-gsBGfsY6LOJUIDwmMkTOcgCX+3r/LUjRBccgHMSW55PHjhZsV13RmPl/iwpAvW8KcQqoN9P0FEFWTSS2Zc5bGA==", "requires": { "@types/estree-jsx": "^0.0.1", "estree-util-is-identifier-name": "^2.0.0", diff --git a/packages/mdx/lib/core.js b/packages/mdx/lib/core.js index 96e34d709..1d3aec731 100644 --- a/packages/mdx/lib/core.js +++ b/packages/mdx/lib/core.js @@ -125,7 +125,7 @@ export function createProcessor(options = {}) { .use(recmaJsxRewrite, {development, providerImportSource, outputFormat}) if (!jsx) { - pipeline.use(recmaJsxBuild, {outputFormat}) + pipeline.use(recmaJsxBuild, {development, outputFormat}) } pipeline.use(recmaStringify, {SourceMapGenerator}).use(recmaPlugins || []) diff --git a/packages/mdx/lib/plugin/recma-jsx-build.js b/packages/mdx/lib/plugin/recma-jsx-build.js index e15e733ed..a7980a97e 100644 --- a/packages/mdx/lib/plugin/recma-jsx-build.js +++ b/packages/mdx/lib/plugin/recma-jsx-build.js @@ -1,5 +1,6 @@ /** * @typedef {import('estree-jsx').Program} Program + * @typedef {import('estree-util-build-jsx').BuildJsxOptions} BuildJsxOptions * * @typedef RecmaJsxBuildOptions * @property {'program'|'function-body'} [outputFormat='program'] @@ -15,13 +16,13 @@ import {toIdOrMemberExpression} from '../util/estree-util-to-id-or-member-expres * A plugin to build JSX into function calls. * `estree-util-build-jsx` does all the work for us! * - * @type {import('unified').Plugin<[RecmaJsxBuildOptions]|[], Program>} + * @type {import('unified').Plugin<[BuildJsxOptions & RecmaJsxBuildOptions?], Program>} */ export function recmaJsxBuild(options = {}) { - const {outputFormat} = options + const {development, outputFormat} = options - return (tree) => { - buildJsx(tree) + return (tree, file) => { + buildJsx(tree, {development, filePath: file.history[0]}) // When compiling to a function body, replace the import that was just // generated, and get `jsx`, `jsxs`, and `Fragment` from `arguments[0]` diff --git a/packages/mdx/test/compile.js b/packages/mdx/test/compile.js index 4385ec205..f3b53fe70 100644 --- a/packages/mdx/test/compile.js +++ b/packages/mdx/test/compile.js @@ -548,6 +548,56 @@ test('compile', async () => { ) console.log('\nnote: the preceding warning is expected!\n') + const developmentSourceNode = ( + await run( + compileSync( + {value: '
', path: 'path/to/file.js'}, + {development: true} + ).value + ) + )({}) + assert.equal( + // @ts-expect-error React attaches source information on this property, + // but it’s private and untyped. + developmentSourceNode._source, + {fileName: 'path/to/file.js', lineNumber: 1, columnNumber: 1}, + 'should expose source information in the automatic jsx dev runtime' + ) + + assert.equal( + compileSync({value: '', path: 'path/to/file.js'}, {development: true}) + .value, + [ + '/*@jsxRuntime automatic @jsxImportSource react*/', + 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', + 'function MDXContent(props = {}) {', + ' const {wrapper: MDXLayout} = props.components || ({});', + ' return MDXLayout ? _jsxDEV(MDXLayout, Object.assign({}, props, {', + ' children: _jsxDEV(_createMdxContent, {}, undefined, false, {', + ' fileName: "path/to/file.js"', + ' }, this)', + ' }), undefined, false, {', + ' fileName: "path/to/file.js"', + ' }, this) : _createMdxContent();', + ' function _createMdxContent() {', + ' const {X} = props.components || ({});', + ' if (!X) _missingMdxReference("X", true, "1:1-1:6");', + ' return _jsxDEV(X, {}, undefined, false, {', + ' fileName: "path/to/file.js",', + ' lineNumber: 1,', + ' columnNumber: 1', + ' }, this);', + ' }', + '}', + 'export default MDXContent;', + 'function _missingMdxReference(id, component, place) {', + ' 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`" : ""));', + '}', + '' + ].join('\n'), + 'should support the jsx dev runtime' + ) + try { renderToStaticMarkup( React.createElement(await run(compileSync('', {development: true}))) From 3ca82dc4c6653e9b2a139e58d0c8952f484faac7 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Tue, 6 Dec 2022 14:35:28 +0100 Subject: [PATCH 2/4] Support jsx dev runtime in evaluate --- packages/mdx/lib/plugin/recma-jsx-build.js | 2 +- .../mdx/lib/util/resolve-evaluate-options.js | 24 +++++++++----- packages/mdx/test/evaluate.js | 33 +++++++++++++++++-- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/packages/mdx/lib/plugin/recma-jsx-build.js b/packages/mdx/lib/plugin/recma-jsx-build.js index a7980a97e..f7e14b021 100644 --- a/packages/mdx/lib/plugin/recma-jsx-build.js +++ b/packages/mdx/lib/plugin/recma-jsx-build.js @@ -32,7 +32,7 @@ export function recmaJsxBuild(options = {}) { tree.body[0] && tree.body[0].type === 'ImportDeclaration' && typeof tree.body[0].source.value === 'string' && - /\/jsx-runtime$/.test(tree.body[0].source.value) + /\/jsx-(dev-)?runtime$/.test(tree.body[0].source.value) ) { tree.body[0] = { type: 'VariableDeclaration', diff --git a/packages/mdx/lib/util/resolve-evaluate-options.js b/packages/mdx/lib/util/resolve-evaluate-options.js index 45cfefdf1..de73f412e 100644 --- a/packages/mdx/lib/util/resolve-evaluate-options.js +++ b/packages/mdx/lib/util/resolve-evaluate-options.js @@ -4,10 +4,12 @@ * @typedef RunnerOptions * @property {*} Fragment * Symbol to use for fragments. - * @property {*} jsx - * Function to generate an element with static children. - * @property {*} jsxs - * Function to generate an element with dynamic children. + * @property {*} [jsx] + * Function to generate an element with static children in production mode. + * @property {*} [jsxs] + * Function to generate an element with dynamic children in production mode. + * @property {*} [jsxDEV] + * Function to generate an element in development mode. * @property {*} [useMDXComponents] * Function to get `MDXComponents` from context. * @@ -23,18 +25,24 @@ * @returns {{compiletime: ProcessorOptions, runtime: RunnerOptions}} */ export function resolveEvaluateOptions(options) { - const {Fragment, jsx, jsxs, useMDXComponents, ...rest} = options || {} + const {development, Fragment, jsx, jsxs, jsxDEV, useMDXComponents, ...rest} = + options || {} if (!Fragment) throw new Error('Expected `Fragment` given to `evaluate`') - if (!jsx) throw new Error('Expected `jsx` given to `evaluate`') - if (!jsxs) throw new Error('Expected `jsxs` given to `evaluate`') + if (development) { + if (!jsxDEV) throw new Error('Expected `jsxDEV` given to `evaluate`') + } else { + if (!jsx) throw new Error('Expected `jsx` given to `evaluate`') + if (!jsxs) throw new Error('Expected `jsxs` given to `evaluate`') + } return { compiletime: { ...rest, + development, outputFormat: 'function-body', providerImportSource: useMDXComponents ? '#' : undefined }, - runtime: {Fragment, jsx, jsxs, useMDXComponents} + runtime: {Fragment, jsx, jsxs, jsxDEV, useMDXComponents} } } diff --git a/packages/mdx/test/evaluate.js b/packages/mdx/test/evaluate.js index fa7fbe792..2e04151ff 100644 --- a/packages/mdx/test/evaluate.js +++ b/packages/mdx/test/evaluate.js @@ -6,6 +6,8 @@ import {renderToStaticMarkup as renderToStaticMarkup_} from '../../react/node_mo // @ts-expect-error: make sure a single react is used. import * as runtime_ from '../../react/node_modules/react/jsx-runtime.js' // @ts-expect-error: make sure a single react is used. +import * as devRuntime from '../../react/node_modules/react/jsx-dev-runtime.js' +// @ts-expect-error: make sure a single react is used. import React_ from '../../react/node_modules/react/index.js' import * as provider from '../../react/index.js' @@ -30,7 +32,6 @@ test('evaluate', async () => { assert.throws( () => { - // @ts-expect-error: missing required arguments evaluateSync('a', {Fragment: runtime.Fragment}) }, /Expected `jsx` given to `evaluate`/, @@ -39,13 +40,20 @@ test('evaluate', async () => { assert.throws( () => { - // @ts-expect-error: missing required arguments evaluateSync('a', {Fragment: runtime.Fragment, jsx: runtime.jsx}) }, /Expected `jsxs` given to `evaluate`/, 'should throw on missing `jsxs`' ) + assert.throws( + () => { + evaluateSync('a', {Fragment: runtime.Fragment, development: true}) + }, + /Expected `jsxDEV` given to `evaluate`/, + 'should throw on missing `jsxDEV` in dev mode' + ) + assert.equal( renderToStaticMarkup( React.createElement((await evaluate('# hi!', runtime)).default) @@ -62,6 +70,27 @@ test('evaluate', async () => { 'should evaluate (sync)' ) + assert.equal( + renderToStaticMarkup( + React.createElement( + (await evaluate('# hi dev!', {development: true, ...devRuntime})) + .default + ) + ), + '

hi dev!

', + 'should evaluate (sync)' + ) + + assert.equal( + renderToStaticMarkup( + React.createElement( + evaluateSync('# hi dev!', {development: true, ...devRuntime}).default + ) + ), + '

hi dev!

', + 'should evaluate (sync)' + ) + assert.equal( renderToStaticMarkup( React.createElement( From 7891c37e3d7f34a7bd7f43f8f1438f9f7cf46e05 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Tue, 6 Dec 2022 14:43:25 +0100 Subject: [PATCH 3/4] Move dev runtime compile output test --- packages/mdx/test/compile.js | 68 ++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/mdx/test/compile.js b/packages/mdx/test/compile.js index 0c57282b8..a59f23d49 100644 --- a/packages/mdx/test/compile.js +++ b/packages/mdx/test/compile.js @@ -559,40 +559,6 @@ test('compile', async () => { 'should expose source information in the automatic jsx dev runtime' ) - assert.equal( - compileSync({value: '', path: 'path/to/file.js'}, {development: true}) - .value, - [ - '/*@jsxRuntime automatic @jsxImportSource react*/', - 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', - 'function _createMdxContent(props) {', - ' const {X} = props.components || ({});', - ' if (!X) _missingMdxReference("X", true, "1:1-1:6");', - ' return _jsxDEV(X, {}, undefined, false, {', - ' fileName: "path/to/file.js",', - ' lineNumber: 1,', - ' columnNumber: 1', - ' }, this);', - '}', - 'function MDXContent(props = {}) {', - ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? _jsxDEV(MDXLayout, Object.assign({}, props, {', - ' children: _jsxDEV(_createMdxContent, props, undefined, false, {', - ' fileName: "path/to/file.js"', - ' }, this)', - ' }), undefined, false, {', - ' fileName: "path/to/file.js"', - ' }, this) : _createMdxContent(props);', - '}', - 'export default MDXContent;', - 'function _missingMdxReference(id, component, place) {', - ' 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`" : ""));', - '}', - '' - ].join('\n'), - 'should support the jsx dev runtime' - ) - try { renderToStaticMarkup( React.createElement(await run(compileSync('', {development: true}))) @@ -1553,6 +1519,40 @@ test('MDX (ESM)', async () => { '

a

', 'should support rexporting the default export, and other things, from a source' ) + + assert.equal( + compileSync({value: '', path: 'path/to/file.js'}, {development: true}) + .value, + [ + '/*@jsxRuntime automatic @jsxImportSource react*/', + 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', + 'function _createMdxContent(props) {', + ' const {X} = props.components || ({});', + ' if (!X) _missingMdxReference("X", true, "1:1-1:6");', + ' return _jsxDEV(X, {}, undefined, false, {', + ' fileName: "path/to/file.js",', + ' lineNumber: 1,', + ' columnNumber: 1', + ' }, this);', + '}', + 'function MDXContent(props = {}) {', + ' const {wrapper: MDXLayout} = props.components || ({});', + ' return MDXLayout ? _jsxDEV(MDXLayout, Object.assign({}, props, {', + ' children: _jsxDEV(_createMdxContent, props, undefined, false, {', + ' fileName: "path/to/file.js"', + ' }, this)', + ' }), undefined, false, {', + ' fileName: "path/to/file.js"', + ' }, this) : _createMdxContent(props);', + '}', + 'export default MDXContent;', + 'function _missingMdxReference(id, component, place) {', + ' 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`" : ""));', + '}', + '' + ].join('\n'), + 'should support the jsx dev runtime' + ) }) test('source maps', async () => { From 6085e65a7dccd5ab521ce84d74f59b1e4711f046 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Thu, 8 Dec 2022 16:36:20 +0100 Subject: [PATCH 4/4] Revert package-lock.json changes --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f78ba567..c9af7c736 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5575,9 +5575,9 @@ } }, "node_modules/estree-util-build-jsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.1.0.tgz", - "integrity": "sha512-gsBGfsY6LOJUIDwmMkTOcgCX+3r/LUjRBccgHMSW55PHjhZsV13RmPl/iwpAvW8KcQqoN9P0FEFWTSS2Zc5bGA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.0.tgz", + "integrity": "sha512-apsfRxF9uLrqosApvHVtYZjISPvTJ+lBiIydpC+9wE6cF6ssbhnjyQLqaIjgzGxvC2Hbmec1M7g91PoBayYoQQ==", "dependencies": { "@types/estree-jsx": "^1.0.0", "estree-util-is-identifier-name": "^2.0.0", @@ -23729,9 +23729,9 @@ } }, "estree-util-build-jsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.1.0.tgz", - "integrity": "sha512-gsBGfsY6LOJUIDwmMkTOcgCX+3r/LUjRBccgHMSW55PHjhZsV13RmPl/iwpAvW8KcQqoN9P0FEFWTSS2Zc5bGA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.0.tgz", + "integrity": "sha512-apsfRxF9uLrqosApvHVtYZjISPvTJ+lBiIydpC+9wE6cF6ssbhnjyQLqaIjgzGxvC2Hbmec1M7g91PoBayYoQQ==", "requires": { "@types/estree-jsx": "^1.0.0", "estree-util-is-identifier-name": "^2.0.0",