Skip to content
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

Add support for jsxImportSource #1172

Closed
rosenfeld opened this issue Apr 20, 2021 · 7 comments · Fixed by #2349
Closed

Add support for jsxImportSource #1172

rosenfeld opened this issue Apr 20, 2021 · 7 comments · Fixed by #2349

Comments

@rosenfeld
Copy link

I know this has been requested once in issue #718, however the described use case in that issue won't explain why adding support for jsxImportSource would be awesome.

Auto-importing those libraries (React, Preact and so on) on jsx files is awesome, however, in some projects, people are interesting in mixing different libraries relying on the JSX syntax.

The way auto-importing is currently implemented in esbuild is on a per-build option from my understanding after reading the documentation. All injected files are added to all sources. It's currently not possible to state that only .jsx files should get some file injected, for example, from my current understanding.

If you're using React or Preact in parts of the application and would like to implement a new feature using the Callbag JSX library, which uses another factory for JSX, of course, it wouldn't be possible to do so with esbuild unless you use Callbag JSX without using the JSX syntax. If we could conditionally apply files injections based on patterns then we could have some sources based on Callbag JSX using another extension such as .cbjsx or something else.

Another way to handle cases like this would be to enable Babel's jsxImportSource pragma, that would allow some sources to opt for the corresponding source for JSX when using Callbag JSX.

Webpack is very slow, specially when compared to esbuild, which is the main reason why I'd love to be able to convert my project from Webpack to esbuild, however Webpack is very flexible. And I think some of that flexbility could be added to esbuild without impacting on the build performance in a noticeable way.

Webpack supports the possibility of chaining loaders and apply all transformations using a set of rules and this is very useful indeed.

Currently, from my understanding, if we enable JSX parsing in .js files, this will have an impact on the build time because even sources from node_modules will be parsed with a more expensive parser. At least I've measured the build time following the example in the Getting Started section of the documentation and noticed that if I process all JS files as JSX, the build gets a bit slower. However, I guess it wouldn't be the case if esbuild supported configurations such as include and exclude rule options for deciding when to apply a given configuration. For example, if we specify that only .js files under ./src should support JSX, then I believe the build won't become noticeable slower.

But it would allow us to specify that a different file should be injected in .jsx files belonging to path src/callbag-jsx/.

These are all ideas that would enable esbuild to support projects mixing different libraries relying on the JSX syntax, but with different implementations. Supporting jsxImportSource is one of them, but allowing conditional rules based on the file path is another way to support it.

If any of this is confusing, please don't hesitate to ask for more information, as I'm very keen to get all features I need implemented in esbuild so that I could finally move my project from webpack to esbuild. My back-end loads instantly, but deploying a new version of the application to our servers currently could take several minutes, mostly because generating the final build with Webpack is really slow for our project. I've performed a few tests with esbuild and it's really orders of magnitude faster than webpack or any other bundler I'm aware of. Congratulations for such achievement. I really hope to be able to convert my project to esbuild as soon as possible. It would allow us to deploy new versions of the application much quicker.

@ggoodman
Copy link

@rosenfeld I wonder if the inline /* @jsx jsxFactoryFn */ pragma might help you achieve your objectives. It looks like this is enabled in the lexer. Strangely a search on GitHub didn't turn up JSXFactoryPragmaComment beyond the definition and assignment 🤔 .

@rosenfeld
Copy link
Author

Hi @ggoodman. The @jsx pragma only defines what factory to use when parsing JSX, it doesn't handle auto-importing the required libraries. That means we'd need to include the proper imports for each source using Callbag-JSX, for example, rather than having them being automatically imported.

I think most projects are not mixing React with other React-like libraries, such as Preact, Inferno.js and so on. However, Callbag-JSX is not supposed to be another React replacement, it takes a different and novel approach. It's also a new project, so there are probably just a few people trying it out and even less mixing it with other projects that also rely on JSX, like all React-like libraries. That's why I think the jsxImportSource is not much used yet. Also, it's a new supported pragma from that Babel module.

I used the auto-importing Webpack feature in our project to allow me to test the application against Preact, Inferno and React by simply changing one file rather than just because it's more convenient. I realize this is not the case with Callbag-JSX, but if this would be something simple to support, it would be a great addition to esbuild. Of course, finishing the work on code-splitting and proper CSS support is way more important, but if this would be simple to implement, then adding support for jsxImportSource could be quite useful. Or allowing loaders to be applied according to some path rules, which is supported by Webpack, for example.

@evanw
Copy link
Owner

evanw commented Apr 22, 2021

Another way to handle cases like this would be to enable Babel's jsxImportSource pragma, that would allow some sources to opt for the corresponding source for JSX when using Callbag JSX.

I thought it was the case that the @jsxImportSource pragma also implies the special JSX transform from React 17 instead of the general-purpose JSX transform, since it was introduced around the same time. If that's the case, then it's not as simple as adding the @jsxImportSource feature because switching to the React 17 JSX transform would break things.

The JSX transform that's specific to React 17 isn't supported by esbuild; only the standard JSX transform is supported. AFAIK the new React 17 transform is incompatible with the rest of the JSX ecosystem, although it's optional and unnecessary and the old JSX transform is sufficient and more compatible.

It would be good to confirm this though. Is there a specification for how the @jsxImportSource pragma is supposed to behave? Do you know which project invented it?

Currently, from my understanding, if we enable JSX parsing in .js files, this will have an impact on the build time because even sources from node_modules will be parsed with a more expensive parser. At least I've measured the build time following the example in the Getting Started section of the documentation and noticed that if I process all JS files as JSX, the build gets a bit slower.

That's not true. There is only one JS parser in esbuild and it can handle JS+JSX+TS+TSX. The only reason JSX syntax is disabled in JS files is because they are different syntaxes, not for performance reasons. The speed of --loader:.js=jsx should be indistinguishable from --loader:.js=js. If you are observing different bundling times then it's likely noise in your measurements.

@rosenfeld
Copy link
Author

Hi @evanw, thanks for the reply. I'm not really following React very closely these days, so I wasn't aware that this feature was created in order to support a new feature in React. I was simply reading the Babel's documentation on the feature:

https://babeljs.io/docs/en/babel-plugin-transform-react-jsx

This is the description: "Automatic runtime is a feature added in v7.9.0. With this runtime enabled, the functions that JSX compiles to will be imported automatically."

After looking at the generated output, I thought that this could be great in order to support any sort of JSX custom implementation.

If we combine that with new rules to esbuild to allow us to specify macros to specific sources, that would be awesome. By macro I mean something like the C language macros, in the sense that some code would be prepended to sources that match those rules. For example, we could configure esbuild to prepend /** @jsxImportSource callbag-jsx */ to all ./src/callbag/**/*.js files, for example while using another source for all other .js files. That should take care of automatically importing callbag-jsx or React (or Preact, Inferno...) for other \.js or .jsx extensions depending on the source location.

Regarding the performance of --loader:.js=jsx, you're right. I had tried time node build.js a few times and compared the performance when jsx was enabled for js files, but apparently not times enough to get to any conclusions. I've tried running way more times and noticed that it was instead just noise and I came to wrong conclusions.

@Andarist
Copy link

I thought it was the case that the @jsxImportSource pragma also implies the special JSX transform from React 17 instead of the general-purpose JSX transform, since it was introduced around the same time.

Yes, that's true.

FAIK the new React 17 transform is incompatible with the rest of the JSX ecosystem,

What do u mean by that? There are some differences between them but they should be transparent for the consumers.

although it's optional and unnecessary and the old JSX transform is sufficient and more compatible.

That's true to some extent - but from React's PoV the old JSX transform is legacy. Both Babel and TS have adopted it and AFAIK this will also become the default in the next major version of Babel.

It would be good to confirm this though. Is there a specification for how the @jsxImportSource pragma is supposed to behave?

I believe that this RFC goes into details: reactjs/rfcs#107, there is also this reference implementation here: babel/babel#11154 (note though that some minor bug fixes were implemented in other PRs).

Do you know which project invented it?

React.

@hiddenest
Copy link

Hi, @evanw, could you check this again?
I've been searching about emotion plugins for esbuild, and just saw this issue.

@alextompkins
Copy link

For those looking for an easy solution: I made a plugin for esbuild which adds support for @jsxImportSource. It works by substituting the @jsxImportSource pragma with the @jsx one which esbuild supports natively.

https://www.npmjs.com/package/esbuild-plugin-jsximportsource

It's my first public NPM package and still pretty raw, so please feel free to submit issues/PRs if there are problems 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants