TypeScript 5.3
Import Attributes
TypeScript 5.3 supports the latest updates to theimport attributes proposal.
One use-case of import attributes is to provide information about the expected format of a module to the runtime.
ts// We only want this to be interpreted as JSON,// not a runnable/malicious JavaScript file with a `.json` extension.importobjfrom"./something.json"with {type: "json" };
The contents of these attributes are not checked by TypeScript since they’re host-specific, and are simply left alone so that browsers and runtimes can handle them (and possibly error).
ts// TypeScript is fine with this.// But your browser? Probably not.import*asfoofrom"./foo.js"with {type: "fluffybunny" };
Dynamicimport() calls can also use import attributes through a second argument.
tsconstobj =awaitimport("./something.json", {with: {type:"json" }});
The expected type of that second argument is defined by a type calledImportCallOptions, which by default just expects a property calledwith.
Note that import attributes are an evolution of an earlier proposal called“import assertions”, which were implemented in TypeScript 4.5.The most obvious difference is the use of thewith keyword over theassert keyword.But the less-visible difference is that runtimes are now free to use attributes to guide the resolution and interpretation of import paths, whereas import assertions could only assert some characteristics after loading a module.
Over time, TypeScript will be deprecating the old syntax for import assertions in favor of the proposed syntax for import attributes.Existing code usingassert should migrate towards thewith keyword.New code that needs an import attribute should usewith exclusively.
We’d like to thankOleksandr Tarasiuk forimplementing this proposal!And we’d also like to call outWenlu Wang for their implementation ofimport assertions!
Stable Supportresolution-mode in Import Types
In TypeScript 4.7, TypeScript added support for aresolution-mode attribute in/// <reference types="..." /> to control whether a specifier should be resolved viaimport orrequire semantics.
ts/// <reference types="pkg" resolution-mode="require" />// or/// <reference types="pkg" resolution-mode="import" />
A corresponding field was added to import assertions on type-only imports as well;however, it was only supported in nightly versions of TypeScript.The rationale was that in spirit, importassertions were not intended to guide module resolution.So this feature was shipped experimentally in a nightly-only mode to get more feedback.
But given thatimport attributes can guide resolution, and that we’ve seen reasonable use-cases, TypeScript 5.3 now supports theresolution-mode attribute forimport type.
ts// Resolve `pkg` as if we were importing with a `require()`importtype {TypeFromRequire }from"pkg"with {"resolution-mode": "require"};// Resolve `pkg` as if we were importing with an `import`importtype {TypeFromImport }from"pkg"with {"resolution-mode": "import"};exportinterfaceMergedTypeextendsTypeFromRequire,TypeFromImport {}
These import attributes can also be used onimport() types.
tsexporttypeTypeFromRequire =import("pkg", {with: {"resolution-mode":"require" } }).TypeFromRequire;exporttypeTypeFromImport =import("pkg", {with: {"resolution-mode":"import" } }).TypeFromImport;exportinterfaceMergedTypeextendsTypeFromRequire,TypeFromImport {}
For more information,check out the change here
resolution-mode Supported in All Module Modes
Previously, usingresolution-mode was only allowed under themoduleResolution optionsnode16 andnodenext.To make it easier to look up modules specifically for type purposes,resolution-mode now works appropriately in all othermoduleResolution options likebundler,node10, and simply doesn’t error underclassic.
For more information,see the implementing pull request.
switch (true) Narrowing
TypeScript 5.3 now can perform narrowing based on conditions in eachcase clause within aswitch (true).
tsfunctionf(x:unknown) {switch (true) {casetypeofx ==="string":// 'x' is a 'string' hereconsole.log(x.toUpperCase());// falls through...caseArray.isArray(x):// 'x' is a 'string | any[]' here.console.log(x.length);// falls through...default:// 'x' is 'unknown' here.// ...}}
This feature was spearheadedinitial work byMateusz BurzyńskiWe’d like to extend a “thank you!” for this contribution.
Narrowing On Comparisons to Booleans
Occasionally you may find yourself performing a direct comparison withtrue orfalse in a condition.Usually these are unnecessary comparisons, but you might prefer it as a point of style, or to avoid certain issues around JavaScript truthiness.Regardless, previously TypeScript just didn’t recognize such forms when performing narrowing.
TypeScript 5.3 now keeps up and understands these expressions when narrowing variables.
tsinterfaceA {a:string;}interfaceB {b:string;}typeMyType =A |B;functionisA(x:MyType):xisA {return"a"inx;}functionsomeFn(x:MyType) {if (isA(x) ===true) {console.log(x.a);// works!}}
We’d like to thankMateusz Burzyński forthe pull request that implemented this.
instanceof Narrowing ThroughSymbol.hasInstance
A slightly esoteric feature of JavaScript is that it is possible to override the behavior of theinstanceof operator.To do so, the value on the right side of theinstanceof operator needs to have a specific method named bySymbol.hasInstance.
jsclassWeirdo {static [Symbol.hasInstance](testedValue) {// wait, what?returntestedValue ===undefined;}}// falseconsole.log(newThing()instanceofWeirdo);// trueconsole.log(undefinedinstanceofWeirdo);
To better model this behavior ininstanceof, TypeScript now checks if such a[Symbol.hasInstance] method exists and is declared as a type predicate function.If it does, the tested value on the left side of theinstanceof operator will be narrowed appropriately by that type predicate.
tsinterfacePointLike {x:number;y:number;}classPointimplementsPointLike {x:number;y:number;constructor(x:number,y:number) {this.x =x;this.y =y;}distanceFromOrigin() {returnMath.sqrt(this.x **2 +this.y **2);}static [Symbol.hasInstance](val:unknown):valisPointLike {return !!val &&typeofval ==="object" &&"x"inval &&"y"inval &&typeofval.x ==="number" &&typeofval.y ==="number";}}functionf(value:unknown) {if (valueinstanceofPoint) {// Can access both of these - correct!value.x;value.y;// Can't access this - we have a 'PointLike',// but we don't *actually* have a 'Point'.value.distanceFromOrigin();}}
As you can see in this example,Point defines its own[Symbol.hasInstance] method.It actually acts as a custom type guard over a separate type calledPointLike.In the functionf, we were able to narrowvalue down to aPointLike withinstanceof, butnot aPoint.That means that we can access the propertiesx andy, but not the methoddistanceFromOrigin.
For more information, you canread up on this change here.
Checks forsuper Property Accesses on Instance Fields
In JavaScript, it’s possible to access a declaration in a base class through thesuper keyword.
jsclassBase {someMethod() {console.log("Base method called!");}}classDerivedextendsBase {someMethod() {console.log("Derived method called!");super.someMethod();}}newDerived().someMethod();// Prints:// Derived method called!// Base method called!
This is different from writing something likethis.someMethod(), since that could invoke an overridden method.This is a subtle distinction, made more subtle by the fact that often the two can be interchangeable if a declaration is never overridden at all.
jsclassBase {someMethod() {console.log("someMethod called!");}}classDerivedextendsBase {someOtherMethod() {// These act identically.this.someMethod();super.someMethod();}}newDerived().someOtherMethod();// Prints:// someMethod called!// someMethod called!
The problem is using them interchangeably is thatsuper only works on members declared on the prototype —not instance properties.That means that if you wrotesuper.someMethod(), butsomeMethod was defined as a field, you’d get a runtime error!
tsclassBase {someMethod = ()=> {console.log("someMethod called!");}}classDerivedextendsBase {someOtherMethod() {super.someMethod();}}newDerived().someOtherMethod();// 💥// Doesn't work because 'super.someMethod' is 'undefined'.
TypeScript 5.3 now more-closely inspectssuper property accesses/method calls to see if they correspond to class fields.If they do, we’ll now get a type-checking error.
This check was contributed thanks toJack Works!
Interactive Inlay Hints for Types
TypeScript’s inlay hints now support jumping to the definition of types!This makes it easier to casually navigate your code.

See more atthe implementation here.
Settings to Prefertype Auto-Imports
Previously when TypeScript generated auto-imports for something in a type position, it would add atype modifier based on your settings.For example, when getting an auto-import onPerson in the following:
tsexportletp:Person
TypeScript’s editing experience would usually add an import forPerson as:
tsimport {Person }from"./types";exportletp:Person
and under certain settings likeverbatimModuleSyntax, it would add thetype modifier:
tsimport {typePerson }from"./types";exportletp:Person
However, maybe your codebase isn’t able to use some of these options; or you just have a preference for explicittype imports when possible.
With a recent change, TypeScript now enables this to be an editor-specific option.In Visual Studio Code, you can enable it in the UI under “TypeScript › Preferences: Prefer Type Only Auto Imports”, or as the JSON configuration optiontypescript.preferences.preferTypeOnlyAutoImports
Optimizations by Skipping JSDoc Parsing
When running TypeScript viatsc, the compiler will now avoid parsing JSDoc.This drops parsing time on its own, but also reduces memory usage to store comments along with time spent in garbage collection.All-in-all, you should see slightly faster compiles and quicker feedback in--watch mode.
The specific changes can be viewed here.
Because not every tool using TypeScript will need to store JSDoc (e.g. typescript-eslint and Prettier), this parsing strategy has been surfaced as part of the API itself.This can enable these tools to gain the same memory and speed improvements we’ve brought to the TypeScript compiler.The new options for comment parsing strategy are described inJSDocParsingMode.More information is availableon this pull request.
Optimizations by Comparing Non-Normalized Intersections
In TypeScript, unions and intersections always follow a specific form, where intersections can’t contain union types.That means that when we create an intersection over a union likeA & (B | C), that intersection will be normalized into(A & B) | (A & C).Still, in some cases the type system will maintain the original form for display purposes.
It turns out that the original form can be used for some clever fast-path comparisons between types.
For example, let’s say we haveSomeType & (Type1 | Type2 | ... | Type99999NINE) and we want to see if that’s assignable toSomeType.Recall that we don’t really have an intersection as our source type — we have a union that looks like(SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE).When checking if a union is assignable to some target type, we have to check ifevery member of the union is assignable to the target type, and that can be very slow.
In TypeScript 5.3, we peek at the original intersection form that we were able to tuck away.When we compare the types, we do a quick check to see if the target exists in any constituent of the source intersection.
For more information,see this pull request.
Consolidation Betweentsserverlibrary.js andtypescript.js
TypeScript itself ships two library files:tsserverlibrary.js andtypescript.js.There are certain APIs available only intsserverlibrary.js (like theProjectService API), which may be useful to some importers.Still, the two are distinct bundles with a lot of overlap, duplicating code in the package.What’s more, it can be challenging to consistently use one over the other due to auto-imports or muscle memory.Accidentally loading both modules is far too easy, and code may not work properly on a different instance of the API.Even if it does work, loading a second bundle increases resource usage.
Given this, we’ve decided to consolidate the two.typescript.js now contains whattsserverlibrary.js used to contain, andtsserverlibrary.js now simply re-exportstypescript.js.Comparing the before/after of this consolidation, we saw the following reduction in package size:
| Before | After | Diff | Diff (percent) | |
|---|---|---|---|---|
| Packed | 6.90 MiB | 5.48 MiB | -1.42 MiB | -20.61% |
| Unpacked | 38.74 MiB | 30.41 MiB | -8.33 MiB | -21.50% |
| Before | After | Diff | Diff (percent) | |
|---|---|---|---|---|
lib/tsserverlibrary.d.ts | 570.95 KiB | 865.00 B | -570.10 KiB | -99.85% |
lib/tsserverlibrary.js | 8.57 MiB | 1012.00 B | -8.57 MiB | -99.99% |
lib/typescript.d.ts | 396.27 KiB | 570.95 KiB | +174.68 KiB | +44.08% |
lib/typescript.js | 7.95 MiB | 8.57 MiB | +637.53 KiB | +7.84% |
In other words, this is over a 20.5% reduction in package size.
For more information, you cansee the work involved here.
Breaking Changes and Correctness Improvements
lib.d.ts Changes
Types generated for the DOM may have an impact on your codebase.For more information,see the DOM updates for TypeScript 5.3.
Checks forsuper Accesses on Instance Properties
TypeScript 5.3 now detects when the declaration referenced by asuper. property access is a class field and issues an error.This prevents errors that might occur at runtime.
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Dec 16, 2025