esbuild 0.14.30
-
Change the context of TypeScript parameter decorators (#2147)
While TypeScript parameter decorators are expressions, they are not evaluated where they exist in the code. They are moved to after the class declaration and evaluated there instead. Specifically this TypeScript code:
class Class { method(@decorator() arg) {} }becomes this JavaScript code:
class Class { method(arg) {} } __decorate([ __param(0, decorator()) ], Class.prototype, "method", null);This has several consequences:
-
Whether
awaitis allowed inside a decorator expression or not depends on whether the class declaration itself is in anasynccontext or not. With this release, you can now useawaitinside a decorator expression when the class declaration is either inside anasyncfunction or is at the top-level of an ES module and top-level await is supported. Note that the TypeScript compiler currently has a bug regarding this edge case: https://github.com/microsoft/TypeScript/issues/48509.// Using "await" inside a decorator expression is now allowed async function fn(foo: Promise<any>) { class Class { method(@decorator(await foo) arg) {} } return Class }Also while TypeScript currently allows
awaitto be used like this inasyncfunctions, it doesn't currently allowyieldto be used like this in generator functions. It's not yet clear whether this behavior withyieldis a bug or by design, so I haven't made any changes to esbuild's handling ofyieldinside decorator expressions in this release. -
Since the scope of a decorator expression is the scope enclosing the class declaration, they cannot access private identifiers. Previously this was incorrectly allowed but with this release, esbuild no longer allows this. Note that the TypeScript compiler currently has a bug regarding this edge case: https://github.com/microsoft/TypeScript/issues/48515.
// Using private names inside a decorator expression is no longer allowed class Class { static #priv = 123 method(@decorator(Class.#priv) arg) {} } -
Since the scope of a decorator expression is the scope enclosing the class declaration, identifiers inside parameter decorator expressions should never be resolved to a parameter of the enclosing method. Previously this could happen, which was a bug with esbuild. This bug no longer happens in this release.
// Name collisions now resolve to the outer name instead of the inner name let arg = 1 class Class { method(@decorator(arg) arg = 2) {} }Specifically previous versions of esbuild generated the following incorrect JavaScript (notice the use of
arg2):let arg = 1; class Class { method(arg2 = 2) { } } __decorateClass([ __decorateParam(0, decorator(arg2)) ], Class.prototype, "method", 1);This release now generates the following correct JavaScript (notice the use of
arg):let arg = 1; class Class { method(arg2 = 2) { } } __decorateClass([ __decorateParam(0, decorator(arg)) ], Class.prototype, "method", 1);
-
-
Fix some obscure edge cases with
superproperty accessThis release fixes the following obscure problems with
superwhen targeting an older JavaScript environment such as--target=es6:-
The compiler could previously crash when a lowered
asyncarrow function contained a class with a field initializer that used asuperproperty access:let foo = async () => class extends Object { bar = super.toString } -
The compiler could previously generate incorrect code when a lowered
asyncmethod of a derived class contained a nested class with a computed class member involving asuperproperty access on the derived class:class Base { foo() { return 'bar' } } class Derived extends Base { async foo() { return new class { [super.foo()] = 'success' } } } new Derived().foo().then(obj => console.log(obj.bar)) -
The compiler could previously generate incorrect code when a default-exported class contained a
superproperty access inside a lowered static private class field:class Foo { static foo = 123 } export default class extends Foo { static #foo = super.foo static bar = this.#foo }
-