-
Parse
const
type parameters from TypeScript 5.0The TypeScript 5.0 beta announcement adds
const
type parameters to the language. You can now add theconst
modifier on a type parameter of a function, method, or class like this:type HasNames = { names: readonly string[] }; const getNamesExactly = <const T extends HasNames>(arg: T): T["names"] => arg.names; const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
The type of
names
in the above example isreadonly ["Alice", "Bob", "Eve"]
. Marking the type parameter asconst
behaves as if you had writtenas const
at every use instead. The above code is equivalent to the following TypeScript, which was the only option before TypeScript 5.0:type HasNames = { names: readonly string[] }; const getNamesExactly = <T extends HasNames>(arg: T): T["names"] => arg.names; const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] } as const);
You can read the announcement for more information.
-
Make parsing generic
async
arrow functions more strict in.tsx
filesPreviously esbuild's TypeScript parser incorrectly accepted the following code as valid:
let fn = async <T> () => {};
The official TypeScript parser rejects this code because it thinks it's the identifier
async
followed by a JSX element starting with<T>
. So with this release, esbuild will now reject this syntax in.tsx
files too. You'll now have to add a comma after the type parameter to get generic arrow functions like this to parse in.tsx
files:let fn = async <T,> () => {};
-
Allow the
in
andout
type parameter modifiers on class expressionsTypeScript 4.7 added the
in
andout
modifiers on the type parameters of classes, interfaces, and type aliases. However, while TypeScript supported them on both class expressions and class statements, previously esbuild only supported them on class statements due to an oversight. This release now allows these modifiers on class expressions too:declare let Foo: any; Foo = class <in T> { }; Foo = class <out T> { };
-
Update
enum
constant folding for TypeScript 5.0TypeScript 5.0 contains an updated definition of what it considers a constant expression:
An expression is considered a constant expression if it is
- a number or string literal,
- a unary
+
,-
, or~
applied to a numeric constant expression, - a binary
+
,-
,*
,/
,%
,**
,<<
,>>
,>>>
,|
,&
,^
applied to two numeric constant expressions, - a binary
+
applied to two constant expressions whereof at least one is a string, - a template expression where each substitution expression is a constant expression,
- a parenthesized constant expression,
- a dotted name (e.g.
x.y.z
) that references aconst
variable with a constant expression initializer and no type annotation, - a dotted name that references an enum member with an enum literal type, or
- a dotted name indexed by a string literal (e.g.
x.y["z"]
) that references an enum member with an enum literal type.
This impacts esbuild's implementation of TypeScript's
const enum
feature. With this release, esbuild will now attempt to follow these new rules. For example, you can now initialize anenum
member with a template literal expression that contains a numeric constant:// Original input const enum Example { COUNT = 100, ERROR = `Expected ${COUNT} items`, } console.log( Example.COUNT, Example.ERROR, ) // Old output (with --tree-shaking=true) var Example = /* @__PURE__ */ ((Example2) => { Example2[Example2["COUNT"] = 100] = "COUNT"; Example2[Example2["ERROR"] = `Expected ${100 /* COUNT */} items`] = "ERROR"; return Example2; })(Example || {}); console.log( 100 /* COUNT */, Example.ERROR ); // New output (with --tree-shaking=true) console.log( 100 /* COUNT */, "Expected 100 items" /* ERROR */ );
These rules are not followed exactly due to esbuild's limitations. The rule about dotted references to
const
variables is not followed both because esbuild's enum processing is done in an isolated module setting and because doing so would potentially require esbuild to use a type system, which it doesn't have. For example:// The TypeScript compiler inlines this but esbuild doesn't: declare const x = 'foo' const enum Foo { X = x } console.log(Foo.X)
Also, the rule that requires converting numbers to a string currently only followed for 32-bit signed integers and non-finite numbers. This is done to avoid accidentally introducing a bug if esbuild's number-to-string operation doesn't exactly match the behavior of a real JavaScript VM. Currently esbuild's number-to-string constant folding is conservative for safety.
-
Forbid definite assignment assertion operators on class methods
In TypeScript, class methods can use the
?
optional property operator but not the!
definite assignment assertion operator (while class fields can use both):class Foo { // These are valid TypeScript a? b! x?() {} // This is invalid TypeScript y!() {} }
Previously esbuild incorrectly allowed the definite assignment assertion operator with class methods. This will no longer be allowed starting with this release.