esbuild 0.12.29
-
Fix compilation of abstract class fields in TypeScript (#1623)
This release fixes a bug where esbuild could incorrectly include a TypeScript abstract class field in the compiled JavaScript output. This is incorrect because the official TypeScript compiler never does this. Note that this only happened in scenarios where TypeScript's
useDefineForClassFieldssetting was set totrue(or equivalently where TypeScript'stargetsetting was set toESNext). Here is the difference:// Original code abstract class Foo { abstract foo: any; } // Old output class Foo { foo; } // New output class Foo { } -
Proxy from the
__requireshim torequire(#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 ofrequiresuch asrequire(someFunction())are not bundled since the value ofsomeFunction()depends on code evaluation, and esbuild does not evaluate code at compile-time. So it's possible for some references torequireto 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), since the browser does not provide a global calledrequire. Thus esbuild introduced a shimrequirefunction called__require(shown below) and replaced all references torequirein the bundled code with__require: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.resolveinside the bundle, which could hypothetically actually work since you could assign your own implementation towindow.require.resolve(see #1579). So the implementation of__requirewas changed to this: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.requirelater on after the bundle was loaded (#1614). So with this release, the code for__requirenow handles all of these edge cases:typeof requireis stillfunctioneven ifwindow.requireis undefinedwindow.requirecan be assigned to either before or after the bundle is loadedrequire.resolveand arbitrary other properties can still be accessedrequirewill now forward any number of arguments, not just the first one
Handling all of these edge cases is only possible with the Proxy API. So the implementation of
__requirenow looks like this:var __require = (x => typeof require !== 'undefined' ? require : 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'); }); -
Consider
typeof xto have no side effectsThe
typeofoperator does not itself trigger any code evaluation so it can safely be removed if evaluating the operand does not cause any side effects. However, there is a special case of thetypeofoperator when the operand is an identifier expression. In that case no reference error is thrown if the referenced symbol does not exist (e.g.typeof xdoes not throw an error if there is no symbol namedx). With this release, esbuild will now considertypeof xto have no side effects even if evaluatingxwould have side effects (i.e. would throw a reference error):// Original code var unused = typeof React !== 'undefined'; // Old output var unused = typeof React !== 'undefined'; // New outputNote that there is actually an edge case where
typeof xcan throw an error: whenxis being referenced inside of its TDZ, or temporal dead zone (i.e. before it's declared). This applies tolet,const, andclasssymbols. However, esbuild doesn't currently handle TDZ rules so the possibility of errors thrown due to TDZ rules is not currently considered. This typically doesn't matter in real-world code so this hasn't been a priority to fix (and is actually tricky to fix with esbuild's current bundling approach). So esbuild may incorrectly remove atypeofexpression that actually has side effects. However, esbuild already incorrectly did this in previous releases so its behavior regardingtypeofand TDZ rules hasn't changed in this release.