Skip to content

Add support for the automatic dev runtime #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 18, 2022
Merged
115 changes: 93 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const regex = /@(jsx|jsxFrag|jsxImportSource|jsxRuntime)\s+(\S+)/g
* @typedef {import('estree-jsx').Comment} Comment
* @typedef {import('estree-jsx').Expression} Expression
* @typedef {import('estree-jsx').Pattern} Pattern
* @typedef {import('estree-jsx').ObjectExpression} ObjectExpression
* @typedef {import('estree-jsx').Property} Property
* @typedef {import('estree-jsx').ImportSpecifier} ImportSpecifier
* @typedef {import('estree-jsx').SpreadElement} SpreadElement
Expand Down Expand Up @@ -35,6 +36,8 @@ const regex = /@(jsx|jsxFrag|jsxImportSource|jsxRuntime)\s+(\S+)/g
* @property {string} [importSource='react']
* @property {string} [pragma='React.createElement']
* @property {string} [pragmaFrag='React.Fragment']
* @property {boolean} [development=false]
* @property {string} [filePath]
*/

/**
Expand All @@ -55,7 +58,7 @@ export function buildJsx(tree, options = {}) {
let automatic = options.runtime === 'automatic'
/** @type {Annotations} */
const annotations = {}
/** @type {{fragment?: boolean, jsx?: boolean, jsxs?: boolean}} */
/** @type {{fragment?: boolean, jsx?: boolean, jsxs?: boolean, jsxDEV?: boolean}} */
const imports = {}

walk(tree, {
Expand Down Expand Up @@ -139,6 +142,14 @@ export function buildJsx(tree, options = {}) {
})
}

if (imports.jsxDEV) {
specifiers.push({
type: 'ImportSpecifier',
imported: {type: 'Identifier', name: 'jsxDEV'},
local: {type: 'Identifier', name: '_jsxDEV'}
})
}

if (specifiers.length > 0) {
node.body.unshift({
type: 'ImportDeclaration',
Expand All @@ -148,7 +159,8 @@ export function buildJsx(tree, options = {}) {
value:
(annotations.jsxImportSource ||
options.importSource ||
'react') + '/jsx-runtime'
'react') +
(options.development ? '/jsx-dev-runtime' : '/jsx-runtime')
}
})
}
Expand Down Expand Up @@ -267,19 +279,21 @@ export function buildJsx(tree, options = {}) {
)
}

if (automatic && children.length > 0) {
fields.push({
type: 'Property',
key: {type: 'Identifier', name: 'children'},
value:
children.length > 1
? {type: 'ArrayExpression', elements: children}
: children[0],
kind: 'init',
method: false,
shorthand: false,
computed: false
})
if (automatic) {
if (children.length > 0) {
fields.push({
type: 'Property',
key: {type: 'Identifier', name: 'children'},
value:
children.length > 1
? {type: 'ArrayExpression', elements: children}
: children[0],
kind: 'init',
method: false,
shorthand: false,
computed: false
})
}
} else {
parameters = children
}
Expand Down Expand Up @@ -310,19 +324,76 @@ export function buildJsx(tree, options = {}) {
}

if (automatic) {
if (children.length > 1) {
parameters.push(props || {type: 'ObjectExpression', properties: []})

if (key) {
parameters.push(key)
} else if (options.development) {
parameters.push({
type: 'UnaryExpression',
operator: 'void',
prefix: true,
argument: {type: 'Literal', value: 0}
})
}

const isStaticChildren = children.length > 1
if (options.development) {
imports.jsxDEV = true
callee = {
type: 'Identifier',
name: '_jsxDEV'
}
parameters.push({type: 'Literal', value: isStaticChildren})
if (options.filePath) {
/** @type {ObjectExpression} */
const source = {
type: 'ObjectExpression',
properties: [
{
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: {type: 'Identifier', name: 'fileName'},
value: {type: 'Literal', value: options.filePath}
}
]
}

if (node.loc) {
source.properties.push(
{
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: {type: 'Identifier', name: 'lineNumber'},
value: {type: 'Literal', value: node.loc.start.line}
},
{
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: {type: 'Identifier', name: 'columnNumber'},
value: {type: 'Literal', value: node.loc.start.column + 1}
}
)
}

parameters.push(source)
}
} else if (isStaticChildren) {
imports.jsxs = true
callee = {type: 'Identifier', name: '_jsxs'}
} else {
imports.jsx = true
callee = {type: 'Identifier', name: '_jsx'}
}

parameters.push(props || {type: 'ObjectExpression', properties: []})

if (key) {
parameters.push(key)
}
}
// Classic.
else {
Expand Down
10 changes: 10 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ runtime is automatic (`string`, default: `'react'`).
Comment: `@jsxImportSource theSource`.
Note that `/jsx-runtime` is appended to this provided source.

##### `options.development`

If the automatic runtime is used, this compiles JSX into automatic runtime
development mode (`boolean`, default: `false`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If the automatic runtime is used, this compiles JSX into automatic runtime
development mode (`boolean`, default: `false`).
Add location info on where a component originated from (`boolean`, default:
`false`).
This helps debugging but adds a lot of code that you don’t want in production.
Only used when `filePath` is and in the automatic runtime.

Btw: does React still show somewhat useful info without a filePath? Maybe we could still do something with line/column without and lift the restriction? Maybe more confusing but potentially we could default to '<source.js>' or so even?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what React Devtools shows if filePath is missing.

image

So let’s not add it in this case.

If positional information is missing, it’s rendered as undefined, which I think is ok.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it doesn’t show anything?
And what if we use '<source.js>'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My screenshot was a bit unclear. This error view replaces what would normally be the part that display component details.

Anyway, showing <source.js> looks fine. 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I’m interested whether you have a screenshot that shows this, because I haven’t seen it yet!
Glad a fake name is somewhat useful!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

I created the following Next.js page in recma-nextjs-static-props, then tweaked the source parameters:

import { jsxDEV } from 'react/jsx-dev-runtime'

export default function Foo() {
  return jsxDEV(
    'p',
    { children: 'Hello world!' },
    'key',
    false,
    {
      fileName: '/home/remco/Projects/recma-nextjs-static-props/pages/foo.jsx',
      lineNumber: 35,
      columnNumber: 42,
    },
    this,
  )
}

Using the React devtools chrome extension, it looks like this:

Complete information

The source copy button copies /home/remco/Projects/recma-nextjs-static-props/pages/foo.jsx:35 to the clipboard. This format can be copied directly into the VSCode file search for example

The Log this component data to the console button logs the following positional data to the console:

Component data logged to console

With removed positional information, it looks like this:

missing positional information

With a removed filename, it looks like this:

missing filename

With source set to undefined, it looks like this:

source undefined

And just for you, a screenshot with fileName set to <source.js>. 😄

image


###### `options.filePath`

If the automatic runtime development mode is used, this option is used to
provide a file path to map the JSX node to a source file (`string`).

###### `options.pragma`

Identifier or member expression to call when the effective runtime is classic
Expand Down
Loading