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

[question]Use react from CDN #806

Closed
bobzhang opened this issue Feb 15, 2021 · 14 comments
Closed

[question]Use react from CDN #806

bobzhang opened this issue Feb 15, 2021 · 14 comments

Comments

@bobzhang
Copy link

So suppose I have a file like below:

import {createElement} from "react"

Is there any way to configure it that esbuild knows "react" comes from globalThis.React (by using CDN: https://cdnjs.com/libraries/react)

external comes close but it still emits import code which does not work properly

@hardfist
Copy link
Contributor

I think you can make it works like following

build.onResolve({filter: /^react$/, (args)=> {
  return {
     path: args.path,
     namespace: 'globalExternal'
  }
}
build.onLoad({filter:/.*/,namespace: 'globalExternal'},args => {
    return { 
     contents: `module.exports = globalThis.React`,
     loader: 'js'
   }
}}

@evanw
Copy link
Owner

evanw commented Feb 15, 2021

^ that's correct. This is plugin code that would go inside the setup function of the plugin (see https://esbuild.github.io/plugins/#using-plugins) but that's the general idea.

@bobzhang
Copy link
Author

I understand this can be achieved using a plugin.
This seems to be a common use case, would this be configurable without building a binary?
The plugin can be done

  • in JS, which is too slow
  • in Go, in that case I need build at least 3 binaries for 3 major platforms

@evanw
Copy link
Owner

evanw commented Feb 15, 2021

in JS, which is too slow

How slow? Have you done performance measurements? This shouldn't really result in any slowdown since it uses a filter so it only matches one path, not all paths. The plugin will run once and and it will be in parallel with the rest of the build. Because esbuild is parallelized, the plugin will only block the resolution and loading of the react path. All other paths will remain unblocked and esbuild will stay busy parsing and scanning the rest of the bundle while the plugin is running.

@evanw
Copy link
Owner

evanw commented Feb 15, 2021

Although to answer your original question, you could potentially have some luck adding "browser": { "react": "./your-globalThis-override.js" } to your package.json file since esbuild respects browser substitutions with --platform=browser. The override file could then do module.exports = globalThis.React. But that's more of a special case since it doesn't work in all cases (e.g. with --platform=node). The general solution to this kind of custom path resolution is to use plugins. Edit: Another way to do this without a plugin could potentially be to use the NODE_PATH environment variable, since that comes before everything else in the path resolution order: https://esbuild.github.io/api/#node-paths.

@hardfist
Copy link
Contributor

I think it's not a problem for simple case like this, but when it comes to complex external scenario, it's kind of too tricky to write a right regular expression to cover all externals.

@evanw
Copy link
Owner

evanw commented Feb 15, 2021

Here is some code for constructing a regular expression in case it's helpful:

let escapeForRegExp = text => `^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
let filter = new RegExp(externals.map(escapeForRegExp).join('|'))

@evanw
Copy link
Owner

evanw commented Feb 17, 2021

I'm going to close this issue because I believe this question has been answered.

@evanw evanw closed this as completed Feb 17, 2021
@bobzhang
Copy link
Author

bobzhang commented Feb 17, 2021

Note the esbuild can bundle my working demo in 16ms within a cold start. Our compiler in general can finish compiling a file within 10ms.

While Node takes 40ms to start so clearly any short lived program written in Node will be subpar. For common use cases, i wish esbuild can handle it out of box without resolving to a plugin system which is may drag things slow down bit by bit

@bobzhang
Copy link
Author

@evanw This kind of works, but it introduced commonjs modules which is not desired. For example, it generates code like this

  // ... lots of code for `__toModule`
  // globalExternal:react
  var require_react = __commonJS((exports, module) => {
    module.exports = globalThis.React;
  });

  // globalExternal:react-dom
  var require_react_dom = __commonJS((exports, module) => {
    module.exports = globalThis.ReactDOM;
  });
 var React = __toModule(require_react()), ReactDom = __toModule(require_react_dom());

The ideal output would be

var React = globalThis.React
var ReactDOM = globalThis.ReactDOM

Is there any way to achieve that? thanks

@evanw
Copy link
Owner

evanw commented Feb 21, 2021

I don't think there is a way to achieve that, no. For one thing the calls to __toModule are deliberate and enforce ESM semantics since they are imported using ESM syntax.

@bobzhang
Copy link
Author

Indeed, after digging a little bit, this is some intentional difference between ESM and commonjs https://stackoverflow.com/questions/29844074/es6-export-all-values-from-object
I wonder if we can specialize this pattern a little bit, this is the bundled code using Rescript/React with esbuild, if we can get rid of __toModule, the output would be perfect.

@evanw
Copy link
Owner

evanw commented Feb 22, 2021

Another thought: React is in CommonJS module format, not ESM format, so arguably it makes sense to do import React from "react" in your source code instead of doing import * as React from "react". In that case you should be able to replace the react module with export default globalThis.React to get your ideal output.

@bobzhang
Copy link
Author

unfortunately react does not settle on ES modules (facebook/react#11503)
I wonder if we can provide an API for this, this is generally useful for module mocking/debugging

build.onLoad(..., args => return {replacement: 'globalThis.React'})

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

No branches or pull requests

3 participants