diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cac76f9787..781d763ab64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## Unreleased + +* Proxy from the `__require` shim to `require` ([#1614](https://github.com/evanw/esbuild/issues/1614)) + + Some background: esbuild's bundler emulates a CommonJS environment. The bundling process replaces the literal syntax `require()` with the referenced module at compile-time. However, other uses of `require` such as `require(someFunction())` are not bundled since the value of `someFunction()` depends on code evaluation, and esbuild does not evaluate code at compile-time. So it's possible for some references to `require` to remain after bundling. + + This was causing problems for some CommonJS code that was run in the browser and that expected `typeof require === 'function'` to be true (see [#1202](https://github.com/evanw/esbuild/issues/1202)), since the browser does not provide a global called `require`. Thus esbuild introduced a shim `require` function called `__require` (shown below) and replaced all references to `require` in the bundled code with `__require`: + + ```js + var __require = x => { + if (typeof require !== 'undefined') return require(x); + throw new Error('Dynamic require of "' + x + '" is not supported'); + }; + ``` + + However, this broke code that referenced `require.resolve` inside the bundle, which could hypothetically actually work since you could assign your own implementation to `window.require.resolve` (see [#1579](https://github.com/evanw/esbuild/issues/1579)). So the implementation of `__require` was changed to this: + + ```js + var __require = typeof require !== 'undefined' ? require : x => { + throw new Error('Dynamic require of "' + x + '" is not supported'); + }; + ``` + + However, that broke code that assigned to `window.require` later on after the bundle was loaded ([#1614](https://github.com/evanw/esbuild/issues/1614)). So with this release, the code for `__require` now handles all of these edge cases: + + * `typeof require` is still `function` even if `window.require` is undefined + * `window.require` can be assigned to either before or after the bundle is loaded + * `require.resolve` and arbitrary other properties can still be accessed + * `require` will now forward any number of arguments, not just the first one + + Handling all of these edge cases is only possible with the [Proxy API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). So the implementation of `__require` now looks like this: + + ```js + var __require = typeof require !== 'undefined' ? require : + (x => typeof Proxy !== 'undefined' ? new Proxy(x, { + get: (a, b) => (typeof require !== 'undefined' ? require : a)[b], + }) : x)(function(x) { + if (typeof require !== 'undefined') + return require.apply(this, arguments); + throw new Error('Dynamic require of "' + x + '" is not supported'); + }); + ``` + ## 0.12.28 * Fix U+30FB and U+FF65 in identifier names in ES5 vs. ES6+ ([#1599](https://github.com/evanw/esbuild/issues/1599)) diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 5cd8c547985..aaa36b44d95 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1812,7 +1812,7 @@ console.log(shared_default); ================================================================================ TestMinifiedBundleCommonJS ---------- /out.js ---------- -var t=r(n=>{n.foo=function(){return 123}});var u=r((l,s)=>{s.exports={test:!0}});var{foo:c}=t();console.log(c(),u()); +var t=r(n=>{n.foo=function(){return 123}});var u=r((q,c)=>{c.exports={test:!0}});var{foo:f}=t();console.log(f(),u()); ================================================================================ TestMinifiedBundleES6 @@ -1833,21 +1833,21 @@ import("foo");import(foo()); TestMinifiedExportsAndModuleFormatCommonJS ---------- /out.js ---------- // foo/test.js -var t = {}; -f(t, { +var e = {}; +f(e, { foo: () => l }); var l = 123; // bar/test.js -var r = {}; -f(r, { +var s = {}; +f(s, { bar: () => m }); var m = 123; // entry.js -console.log(exports, module.exports, t, r); +console.log(exports, module.exports, e, s); ================================================================================ TestMinifyArguments diff --git a/internal/bundler/snapshots/snapshots_importstar.txt b/internal/bundler/snapshots/snapshots_importstar.txt index 2e18229d5d2..97e9bb34b76 100644 --- a/internal/bundler/snapshots/snapshots_importstar.txt +++ b/internal/bundler/snapshots/snapshots_importstar.txt @@ -111,7 +111,7 @@ var foo = 123; TestExportSelfCommonJSMinified ---------- /out.js ---------- // entry.js -var l = n((c, r) => { +var l = s((f, r) => { r.exports = { foo: 123 }; console.log(l()); }); diff --git a/internal/bundler/snapshots/snapshots_ts.txt b/internal/bundler/snapshots/snapshots_ts.txt index 5cc0171ecd4..b6447caefc4 100644 --- a/internal/bundler/snapshots/snapshots_ts.txt +++ b/internal/bundler/snapshots/snapshots_ts.txt @@ -316,7 +316,7 @@ console.log(a, b, c, d, e, real); ================================================================================ TestTSMinifiedBundleCommonJS ---------- /out.js ---------- -var t=r(n=>{n.foo=function(){return 123}});var u=r((l,s)=>{s.exports={test:!0}});var{foo:c}=t();console.log(c(),u()); +var t=r(n=>{n.foo=function(){return 123}});var u=r((q,c)=>{c.exports={test:!0}});var{foo:f}=t();console.log(f(),u()); ================================================================================ TestTSMinifiedBundleES6 diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index b41e794a758..c793a9bcefa 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -116,10 +116,19 @@ func code(isES6 bool) string { // Tells importing modules that this can be considered an ES6 module export var __name = (target, value) => __defProp(target, 'name', { value, configurable: true }) - // This fallback "require" function exists so that "typeof require" can naturally be "function" - export var __require = typeof require !== 'undefined' ? require : x => { - throw new Error('Dynamic require of "' + x + '" is not supported') - } + // This fallback "require" function exists so that "typeof require" can + // naturally be "function" even in non-CommonJS environments since esbuild + // emulates a CommonJS environment (issue #1202). However, people want this + // shim to fall back to "globalThis.require" even if it's defined later + // (including property accesses such as "require.resolve") so we need to + // use a proxy (issue #1614). + export var __require = typeof require !== 'undefined' ? require : + /* @__PURE__ */ (x => typeof Proxy !== 'undefined' ? new Proxy(x, { + get: (a, b) => (typeof require !== 'undefined' ? require : a)[b], + }) : x)(function(x) { + if (typeof require !== 'undefined') return require.apply(this, arguments) + throw new Error('Dynamic require of "' + x + '" is not supported') + }) // For object rest patterns export var __restKey = key => typeof key === 'symbol' ? key : key + ''