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

fix #1614: proxy from "__require" to "require" #1

Merged
merged 1 commit into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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