Skip to content

Commit

Permalink
Merge pull request #1 from evanw/master
Browse files Browse the repository at this point in the history
fix evanw#1614: proxy from "__require" to "require"
  • Loading branch information
sthagen authored Sep 21, 2021
2 parents 9c86523 + e03e743 commit 670bf22
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 12 deletions.
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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(<string>)` 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))
Expand Down
12 changes: 6 additions & 6 deletions internal/bundler/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/bundler/snapshots/snapshots_importstar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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());
});
Expand Down
2 changes: 1 addition & 1 deletion internal/bundler/snapshots/snapshots_ts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions internal/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 + ''
Expand Down

0 comments on commit 670bf22

Please sign in to comment.