Skip to content

Commit

Permalink
change output for top-level TypeScript enums
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Nov 26, 2021
1 parent dabf3b7 commit 731f559
Show file tree
Hide file tree
Showing 9 changed files with 465 additions and 123 deletions.
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,51 @@ In addition to the breaking changes above, the following changes are also includ

Note that this behavior does **not** work across files. Each file is still compiled independently so the namespaces in each file are still resolved independently per-file. Implicit namespace cross-references still do not work across files. Getting this to work is counter to esbuild's parallel architecture and does not fit in with esbuild's design. It also doesn't make sense with esbuild's bundling model where input files are either in ESM or CommonJS format and therefore each have their own scope.

* Change output for top-level TypeScript enums

The output format for top-level TypeScript enums has been changed to reduce code size and improve tree shaking, which means that esbuild's enum output is now somewhat different than TypeScript's enum output. The behavior of both output formats should still be equivalent though. Here's an example that shows the difference:

```ts
// Original code
enum x {
y = 1,
z = 2
}

// Old output
var x;
(function(x2) {
x2[x2["y"] = 1] = "y";
x2[x2["z"] = 2] = "z";
})(x || (x = {}));

// New output
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 1] = "y";
x2[x2["z"] = 2] = "z";
return x2;
})(x || {});
```

The function expression has been changed to an arrow expression to reduce code size and the enum initializer has been moved into the variable declaration to make it possible to be marked as `/* @__PURE__ */` to improve tree shaking. The `/* @__PURE__ */` annotation is now automatically added when all of the enum values are side-effect free, which means the entire enum definition can be removed as dead code if it's never referenced. Direct enum value references within the same file that have been inlined do not count as references to the enum definition so this should eliminate enums from the output in many cases:

```ts
// Original code
enum Foo { FOO = 1 }
enum Bar { BAR = 2 }
console.log(Foo, Bar.BAR)

// Old output (with --bundle --minify)
var n;(function(e){e[e.FOO=1]="FOO"})(n||(n={}));var l;(function(e){e[e.BAR=2]="BAR"})(l||(l={}));console.log(n,2);

// New output (with --bundle --minify)
var n=(e=>(e[e.FOO=1]="FOO",e))(n||{});console.log(n,2);
```

Notice how the new output is much shorter because the entire definition for `Bar` has been completely removed as dead code by esbuild's tree shaking.

The output may seem strange since it would be simpler to just have a plain object literal as an initializer. However, TypeScript's enum feature behaves similarly to TypeScript's namespace feature which means enums can merge with existing enums and/or existing namespaces (and in some cases also existing objects) if the existing definition has the same name. This new output format keeps its similarity to the original output format so that it still handles all of the various edge cases that TypeScript's enum feature supports. Initializing the enum using a plain object literal would not merge with existing definitions and would break TypeScript's enum semantics.

* Fix legal comment parsing in CSS ([#1796](https://github.com/evanw/esbuild/issues/1796))

Legal comments in CSS either start with `/*!` or contain `@preserve` or `@license` and are preserved by esbuild in the generated CSS output. This release fixes a bug where non-top-level legal comments inside a CSS file caused esbuild to skip any following legal comments even if those following comments are top-level:
Expand Down
58 changes: 58 additions & 0 deletions internal/bundler/bundler_ts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,64 @@ func TestTSSiblingEnum(t *testing.T) {
})
}

func TestTSEnumTreeShaking(t *testing.T) {
ts_suite.expectBundled(t, bundled{
files: map[string]string{
"/simple-member.ts": `
enum x { y = 123 }
console.log(x.y)
`,
"/simple-enum.ts": `
enum x { y = 123 }
console.log(x)
`,
"/sibling-member.ts": `
enum x { y = 123 }
enum x { z = y * 2 }
console.log(x.y, x.z)
`,
"/sibling-enum-before.ts": `
console.log(x)
enum x { y = 123 }
enum x { z = y * 2 }
`,
"/sibling-enum-middle.ts": `
enum x { y = 123 }
console.log(x)
enum x { z = y * 2 }
`,
"/sibling-enum-after.ts": `
enum x { y = 123 }
enum x { z = y * 2 }
console.log(x)
`,
"/namespace-before.ts": `
namespace x { console.log(x,y) }
enum x { y = 123 }
`,
"/namespace-after.ts": `
enum x { y = 123 }
namespace x { console.log(x,y) }
`,
},
entryPaths: []string{
"/simple-member.ts",
"/simple-enum.ts",
"/sibling-member.ts",
"/sibling-enum-before.ts",
"/sibling-enum-middle.ts",
"/sibling-enum-after.ts",
"/namespace-before.ts",
"/namespace-after.ts",
},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
OutputFormat: config.FormatESModule,
},
})
}

func TestTSEnumJSX(t *testing.T) {
ts_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
26 changes: 10 additions & 16 deletions internal/bundler/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3323,9 +3323,9 @@ var require_es6_ns_export_enum = __commonJS({
"es6-ns-export-enum.ts"(exports) {
var ns;
((ns2) => {
let Foo3;
((Foo4) => {
})(Foo3 = ns2.Foo || (ns2.Foo = {}));
let Foo;
((Foo2) => {
})(Foo = ns2.Foo || (ns2.Foo = {}));
})(ns || (ns = {}));
console.log(exports);
}
Expand All @@ -3336,9 +3336,9 @@ var require_es6_ns_export_const_enum = __commonJS({
"es6-ns-export-const-enum.ts"(exports) {
var ns;
((ns2) => {
let Foo3;
((Foo4) => {
})(Foo3 = ns2.Foo || (ns2.Foo = {}));
let Foo;
((Foo2) => {
})(Foo = ns2.Foo || (ns2.Foo = {}));
})(ns || (ns = {}));
console.log(exports);
}
Expand All @@ -3363,9 +3363,9 @@ var require_es6_ns_export_class = __commonJS({
"es6-ns-export-class.ts"(exports) {
var ns;
((ns2) => {
class Foo3 {
class Foo {
}
ns2.Foo = Foo3;
ns2.Foo = Foo;
})(ns || (ns = {}));
console.log(exports);
}
Expand All @@ -3376,9 +3376,9 @@ var require_es6_ns_export_abstract_class = __commonJS({
"es6-ns-export-abstract-class.ts"(exports) {
var ns;
((ns2) => {
class Foo3 {
class Foo {
}
ns2.Foo = Foo3;
ns2.Foo = Foo;
})(ns || (ns = {}));
console.log(exports);
}
Expand Down Expand Up @@ -3414,15 +3414,9 @@ console.log(void 0);
console.log(void 0);

// es6-export-enum.ts
var Foo;
((Foo3) => {
})(Foo || (Foo = {}));
console.log(void 0);

// es6-export-const-enum.ts
var Foo2;
((Foo3) => {
})(Foo2 || (Foo2 = {}));
console.log(void 0);

// es6-export-module.ts
Expand Down
136 changes: 107 additions & 29 deletions internal/bundler/snapshots/snapshots_ts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,26 +104,26 @@ var foo = bar();
================================================================================
TestTSEnumDefine
---------- /out/entry.js ----------
var a;
((a2) => {
var a = /* @__PURE__ */ ((a2) => {
a2[a2["b"] = 123] = "b";
a2[a2["c"] = 123] = "c";
})(a || (a = {}));
return a2;
})(a || {});

================================================================================
TestTSEnumJSX
---------- /out/element.js ----------
export var Foo;
((Foo2) => {
export var Foo = /* @__PURE__ */ ((Foo2) => {
Foo2["Div"] = "div";
})(Foo || (Foo = {}));
return Foo2;
})(Foo || {});
console.log(/* @__PURE__ */ React.createElement("div", null));

---------- /out/fragment.js ----------
export var React;
((React2) => {
export var React = /* @__PURE__ */ ((React2) => {
React2["Fragment"] = "div";
})(React || (React = {}));
return React2;
})(React || {});
console.log(/* @__PURE__ */ React.createElement("div", null, "test"));

---------- /out/nested-element.js ----------
Expand Down Expand Up @@ -162,6 +162,81 @@ var x;
})(y = x2.y || (x2.y = {}));
})(x || (x = {}));

================================================================================
TestTSEnumTreeShaking
---------- /out/simple-member.js ----------
// simple-member.ts
console.log(123);

---------- /out/simple-enum.js ----------
// simple-enum.ts
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 123] = "y";
return x2;
})(x || {});
console.log(x);

---------- /out/sibling-member.js ----------
// sibling-member.ts
console.log(123, 246);

---------- /out/sibling-enum-before.js ----------
// sibling-enum-before.ts
console.log(x);
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 123] = "y";
return x2;
})(x || {});
var x = /* @__PURE__ */ ((x2) => {
x2[x2["z"] = 246] = "z";
return x2;
})(x || {});

---------- /out/sibling-enum-middle.js ----------
// sibling-enum-middle.ts
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 123] = "y";
return x2;
})(x || {});
console.log(x);
var x = /* @__PURE__ */ ((x2) => {
x2[x2["z"] = 246] = "z";
return x2;
})(x || {});

---------- /out/sibling-enum-after.js ----------
// sibling-enum-after.ts
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 123] = "y";
return x2;
})(x || {});
var x = /* @__PURE__ */ ((x2) => {
x2[x2["z"] = 246] = "z";
return x2;
})(x || {});
console.log(x);

---------- /out/namespace-before.js ----------
// namespace-before.ts
var x;
((x2) => {
console.log(x2, x2.y);
})(x || (x = {}));
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 123] = "y";
return x2;
})(x || {});

---------- /out/namespace-after.js ----------
// namespace-after.ts
var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 123] = "y";
return x2;
})(x || {});
((x2) => {
console.log(x2, 123);
})(x || (x = {}));

================================================================================
TestTSExportDefaultTypeIssue316
---------- /out.js ----------
Expand Down Expand Up @@ -401,10 +476,10 @@ class Foo extends Bar {
================================================================================
TestTSMinifyEnum
---------- /a.js ----------
var Foo;(e=>{e[e.A=0]="A",e[e.B=1]="B",e[e.C=e]="C"})(Foo||(Foo={}));
var Foo=(e=>(e[e.A=0]="A",e[e.B=1]="B",e[e.C=e]="C",e))(Foo||{});

---------- /b.js ----------
export var Foo;(e=>{e[e.X=0]="X",e[e.Y=1]="Y",e[e.Z=e]="Z"})(Foo||(Foo={}));
export var Foo=(e=>(e[e.X=0]="X",e[e.Y=1]="Y",e[e.Z=e]="Z",e))(Foo||{});

================================================================================
TestTSMinifyNamespace
Expand All @@ -417,48 +492,51 @@ export var Foo;(e=>{let a;(p=>{foo(e,p)})(a=e.Bar||(e.Bar={}))})(Foo||(Foo={}));
================================================================================
TestTSSiblingEnum
---------- /out/number.js ----------
export var x;
((x2) => {
export var x = /* @__PURE__ */ ((x2) => {
x2[x2["y"] = 0] = "y";
x2[x2["yy"] = 0] = "yy";
})(x || (x = {}));
((x2) => {
return x2;
})(x || {});
var x = /* @__PURE__ */ ((x2) => {
x2[x2["z"] = 1] = "z";
})(x || (x = {}));
return x2;
})(x || {});
((x2) => {
console.log(0, 1);
})(x || (x = {}));
console.log(0, 1);

---------- /out/string.js ----------
export var x;
((x2) => {
export var x = /* @__PURE__ */ ((x2) => {
x2["y"] = "a";
x2["yy"] = "a";
})(x || (x = {}));
((x2) => {
return x2;
})(x || {});
var x = /* @__PURE__ */ ((x2) => {
x2["z"] = "a";
})(x || (x = {}));
return x2;
})(x || {});
((x2) => {
console.log("a", "a");
})(x || (x = {}));
console.log("a", "a");

---------- /out/propagation.js ----------
export var a;
((a2) => {
export var a = /* @__PURE__ */ ((a2) => {
a2[a2["b"] = 100] = "b";
})(a || (a = {}));
export var x;
((x2) => {
return a2;
})(a || {});
export var x = /* @__PURE__ */ ((x2) => {
x2[x2["c"] = 100] = "c";
x2[x2["d"] = 200] = "d";
x2[x2["e"] = 4e4] = "e";
x2[x2["f"] = 1e4] = "f";
})(x || (x = {}));
((x2) => {
return x2;
})(x || {});
var x = /* @__PURE__ */ ((x2) => {
x2[x2["g"] = 625] = "g";
})(x || (x = {}));
return x2;
})(x || {});
console.log(100, 100, 625, 625);

---------- /out/nested-number.js ----------
Expand Down
Loading

0 comments on commit 731f559

Please sign in to comment.