What's new in TypeScript 5.0?

On the 1st of March, the team behind TypeScript released TypeScript 5.0 RC. That means the version is pretty stable and we can have a look at some new features, Let's roll!
Decorators
Decorators are part of TypeScript for a long time. However, they are still experimental and non-standard (meaning decorators are not part of the ECMAScript standard) features.
Version 5.0 brings us the implementation of the Stage 3 proposal. At this stage, the standard is almost completed and no further changes should be made.
experimentalDecorators No More
From now on, decorators are a valid part of the code. This means, that you no longer need to set experimentalDecorators to true. The flag stays in, however, new decorators are emitted and type-checked differently. As a result, old decorators won't work properly! In addition, flag emitDecoratorMetadata is not compatible at all.
How do they look?
TypeScript's Decorator type signature looks as follows:
type Decorator = (
value: DecoratedValue,
context: {
kind: string;
name: string | symbol;
addInitializer(initializer: () => void): void;
static: boolean;
private: boolean;
access: {get: () => unknown, set: (value: unknown) => void};
}
) => void | ReplacementValue;
As you can see, a decorator is just a function, that takes two parameters - value and context.
Decorated entity
value contains the decorated entity. That may be class, method, getter/setter, field, or accessor.
Context
context adds additional data about the decorated entity:
kindtells us the type of decorated entity. It may contain the following values:class,method,getter,setter,field, andaccessornameis the name of the decorated entity (class name, method name, ...)accesscontains get/set methods for the entity.privateif set to true, decorated entity is privatestaticif set to true, decorated entity is staticaddInitializerallows the decorator to add additional initialization logic.
Not all fields are accessible all the time. The context object changes based on the type of decorated entity:
type Decorator =
| ClassDecorator
| ClassMethodDecorator
| ClassGetterDecorator
| ClassSetterDecorator
| ClassAutoAccessorDecorator
| ClassFieldDecorator
const Type Parameters
When you don't provide a return type, TS has to guess (infer) it. In the previous versions, TypeScript always chose the closest "general" type:
type RequestStatus = {
code: readonly number
message: readonly string
}
function getRequestCode<T extends RequestStatus>(status: T): T['code'] {
return status.code
}
const code = getRequestCode({ code: 404, message: 'Not Found' })
// ^ inferred type: number ❌
Sometimes it's desirable, to get a more specific (in this case readonly) type. One way to do so is by adding as const when passing the parameter into getRequestCode function:
const code = getRequestCode({ code: 404, message: 'Not Found` } as const )
// ^ inferred type: 404 ✅
But that shifts responsibility to the user of your code and is easy to forget to do. Instead, with TS 5.0, you can set the parameter type as const:
function getRequestCode<const T extends RequestStatus>(status: T): T['code'] {
return status.code
}
const code = getRequestCode({ code: 404, message: 'Not Found` })
// ^ inferred type: 404 ✅
At the same time, const modifier does not require immutable values:
function stillReturnsMutable<const T extends readonly string[]>(param: T): T {
return param
}
const values = ['Hoy', 'Holla']
// ^ mutable
const result = stillReturnsMutable(values)
// ^ inferred type: string[]
bundler Module Resolution
module options nodenext and node16 required all relative imports to include a file extension:
// index.mjs
import * from './otherFile' // ❌ TS is missing file extension
import * from './otherFile.mjs' // ✅ works as expected
Most bundlers (Vite, Parcel, esbuild ...) support a hybrid lookup strategy, which finds both files with and without the extension. That conflicted with the TS way described above.
The new bundler option for module allows TypeScript to do the same as bundlers - finding the file even without the extension.
Further reading
This post was by no means an extensive list of all changes but a brief introduction. If you are interested in the full list of changes, see the following links.
https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-rc


