-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Support jsx automatic runtime #334
Comments
The React 17 JSX transform seems more complicated and React-specific then the standard JSX transform, so I'm not sure it's something I'm going to include in esbuild's core. It should be very straightforward to support this using a plugin though. I'm still working toward plugin support but I'm getting close. You can subscribe to #111 for updates about plugin support. |
React has an official blog post about this feature now. There is also a detailed RFC describing some of the motivations for this change. I like this one:
I'd love to see e.g. Vue be able to "just work" with JSX without any weird CLI tools running additional transforms, which is currently the case. None of this is high-urgency. These changes are still in-progress. But I think it is something to be considered down the road. |
FYI the recently-released |
I noticed the new This solves some (but not all) of the "JSX automatic runtime", which is really part of an overall revision to how JSX itself is transpiled. For example:
So the proposal requires changes to how JSX is transformed into function calls, which I assume requires changes to the |
@chowey It's too bad that React is trying to change how they use JSX considering there are hundreds of frameworks/libraries that use "standard" JSX of It's great that React has a Babel plugin though, and especially considering React is always in development and the RFC you quoted isn't settled, the best option is likely telling esbuild to preserve (i.e not touch) JSX and then tell Babel to convert all JSX found in all output chunks afterwards. |
Agreed. This upgrade is entirely optional. From the React blog post:
To reiterate, support for the JSX automatic runtime is completely optional and will continue to be completely optional into the future. Regarding changes to JSX, I would love for JSX to be simple and standardized, so that other non-React frameworks can use it too. So I don't mind a change to JSX provided it moves in that direction. I once tried to create a Vue app in JSX, without using Babel (which means no @vue/babel-preset-jsx). It was horrible. Vue's |
Any progress on this? It's been a few months. It looks like a plugin system now exists (albeit experimentally); but it sounds like from above that a core change is required (so plugins may be irrelevant). |
If you just want to use JSX without importing React, you can do this with esbuild today using the built-in inject feature. No changes to esbuild are required. If you still want to use the slightly different JSX implementation that comes with React 17, it's trivial to do this with an esbuild plugin. It would look something like this: const jsxPluginReact17 = {
name: 'jsx-react-17',
setup(build) {
const fs = require('fs')
const babel = require('@babel/core')
const plugin = require('@babel/plugin-transform-react-jsx')
.default({}, { runtime: 'automatic' })
build.onLoad({ filter: /\.jsx$/ }, async (args) => {
const jsx = await fs.promises.readFile(args.path, 'utf8')
const result = babel.transformSync(jsx, { plugins: [plugin] })
return { contents: result.code }
})
},
} |
Cool, thanks! |
Doesn't that means it's using Babel instead of esbuild to transform jsx? Does it has any performance advantages than using Babel directly? |
I presume this would take a performance hit for sure. I think it'll end up being a tradeoff in performance for a simpler migration path for babel-powered codebases.
Based on the plugin api docs |
I've been trying to replace
Now I think I have the following paths forward:
IMHO all the available options aren't great at all, especially if growing esbuild's userbase is a goal here. I don't know if that's a strong enough argument for implementing the new transform into core, but I'd imagine there are a good number of people in a situation similar to mine that just can't use more of esbuild without unreasonable efforts, plus in theory the new transform should unlock some perf in the future, which react+esbuild users would want to tap into as well, plus even though there are maybe hundreds of tools using JSX maybe React users account for more than 75% of all the JSX users, like if it weren't for them I'm not sure we would even have JSX. Also TypeScript shipped with support for this, so supporting it in esbuild too would make it support more of TypeScript too. Hopefully there will be a way for me to switch to esbuild with less compromises at some point, I'm so tired of these super slow tools. |
@fabiospampinato Have you considered:
In the same way you were calling babel slow, I'd argue using webpack at all is slow. Attaching esbuild as a loader seems like it is adding complexity. Many people are able to use esbuild plugins to support many webpack transforms (like LESS), and move away from webpack entirely. Once you're using esbuild directly you can use the inject feature to keep those imports removed but still use the common/standard JSX format. I have a feeling it'll be the fastest option too |
@heyheyhello I have considered it, but there are too many issues with that presently:
|
@evanw apparently Vite works around this issue via supporting an extra "jsxInject" property, by the sound of it I guess it just prepends modules with that string. Would that be something that could be implemented into esbuild itself? It should work decently enough and be easy to maintain, and it may have other uses, perhaps if named differently. |
@fabiospampinato I believe Vite's inject API is the same as the inject API already built in to esbuild, which Evan and I are describing. In the first paragraph of this comment here: #334 (comment) |
And that may break something, see: vitejs/vite#2369 The dev-server of |
@fabiospampinato Babel isn't necessarily going to be too slow given that you'll need fewer plugins. Essentially you're configuring it to do as little work as possible and leave the rest of the work to esbuild. At my work we came up with the following config to transform only the JSX auto runtime, and configured Babel to only run on .tsx files. presets: [
[
'@babel/preset-react',
{
development: !isProduction,
runtime: 'automatic',
},
],
],
plugins: [
// Allow Babel to parse TypeScript without transforming it
['@babel/plugin-syntax-typescript', { isTSX: true }],
], We're using the We're using a similar approach to #334 (comment) but with Note that this is without type checking - for that we just use So you might want to try this and see how it works for you. I guess for webpack you could have Unfortunately I can't share the repo as it's private but I might be able to work up a sample repo if I get time with some examples that combine Babel & esbuild for webpack, rollup and esbuild (with Babel via plugin API) along with some more detailed benchmarks. |
I don't use React in many projects but I still want to use JSX. The new changes to JSX actually solve basically all my gripes I had with using the original JSX in non-react projects. I don't believe the new changes are more react-centric. If anything they're less react-centric. React could just go on their way as they always have but these new changes make it much more performant and easier to wrap compiled jsx functions to whatever vdom you're using. One of the most annoying things about the old JSX was trying to render JSX statically. There was no way to tell the difference between text and static html. React team mentioned this in their post also. My solution was to wrap every static html string in an array then flatten all the arrays on the next iteration. It was very yucky. The new JSX has better ways of doing this. |
esbuild does not support automatic jsx/runtime, so the `import React from 'react'` is still needed. refs: evanw/esbuild#334
esbuild does not support automatic jsx/runtime, so the `import React from 'react'` is still needed. refs: evanw/esbuild#334
@ctjlewis FYI, the JSX function and the React.creatElement do not have the same function signature so that will likely lead to some weird bugs. The functions are different on how they handle “children” and “key” props. |
Things are speedy(TM) now, we had to write a tonne of plugins (copy, html, license, postcss) to do this. The resulting file is a chunk larger (460k vs 440k) and there is some ugliness due to evanw/esbuild#334, but it's not the end of the world - worth it just for the near-instant builds.
Feel bad to pile on here, but it is quite annoying to have to do a shim, especially when Typescript is perfectly happy with my code as is without the react import. I know we don't want esbuild to be tied to any one framework, but it seems like it should be possible add an option for automatic jsx runtime injection with different presets such as for react. |
I was ok with the proposed shim idea, but I really don't like how the react-shim is included in every file. Sometimes you don't want that. |
For anyone coming into this thread, this is achievable by adding a await esbuild({
...shared,
entryPoints: tsxFiles.filter((file) => !file.endsWith(".d.ts")),
jsxFactory: "createElement",
banner: {
js: "import { createElement } from 'react';\n",
},
}); Footnotes
|
@ctjlewis This doesn't seem to work for me. I just get
|
@alextompkins Does the emitted bundle contain the import statement we need? Why would it say it's undefined? I use this pattern in @tsmodule/tsmodule so I know it's stable. Post a link to demo and I'll review it. What are your Make sure not to use |
@ctjlewis - your solution worked for me for libraries that consider react to be external, but not for applications - where I want react bundled in. This slight modification of your solution helped me - // build.js
build({
jsxFactory: "createElement",
pure: ["createElement"],
inject: ['./react-shim.js'],
// ... other options
})
// react-shim.js
window.createElement = require("react").createElement; Not a great solution but it works ¯\(ツ)/¯ cc: @alextompkins |
@evanw What's your stance on this today a few years later? The newer JSX transform has been adopted in TypeScript and similar to babel they expose an |
For anyone trying to get this to work for TypeScript the following plugin works, however it does have significant performance penalty. A project with 1000 tsx files takes 12s vs 2s. This is really a suboptimal solution when combined with const jsxSource = {
name: 'jsx-source',
setup(build) {
const fs = require('fs');
const babel = require('@babel/core');
build.onLoad({ filter: /\.tsx$/ }, async args => {
const jsx = await fs.promises.readFile(args.path, 'utf8');
const result = babel.transformSync(jsx, {
filename: args.path,
presets: [
'@babel/preset-typescript',
[
'@babel/preset-react',
{
development: true,
runtime: 'automatic',
},
],
],
plugins: ['@babel/plugin-transform-react-jsx-source'],
});
return { contents: result.code };
});
},
}; |
Looks like using a shim is still the only concise way of doing this, especially when you use esbuild with es6. For anyone using the command line, use esbuild app/client.jsx --bundle --inject:app/react-shim.js --outfile=dist/client.js My export * as React from 'react' |
@kjoedion This works automatically now using the |
React 17 will be adding jsx automatic runtime. It's now available in the latest release candidate.
More info here.
i.e. react/jsx-runtime and react/jsx-dev-runtime
Thanks!
The text was updated successfully, but these errors were encountered: