What's new in TypeScript 5.0?

Search for a command to run...

No comments yet. Be the first to comment.
TL;DR, summarize! WebMCP = API for LLM living in your browser Website exposes tools, that Agent can run No separate MCP server or screen/DOM scraping is necessary. Makes accessing auth-protected websites simpler by reusing browser’ session Why ...

Hey, what's last month's revenue?

So, somewhere in the middle of February, Anthropic released Claude 3.7 Sonnet model, which people immediately jumped onto and used it in Cursor (or the editor of choice). However, there was another tool released that day - Claude Code. It was just a ...

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 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 MoreFrom 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.
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.
value contains the decorated entity. That may be class, method, getter/setter, field, or accessor.
context adds additional data about the decorated entity:
kind tells us the type of decorated entity. It may contain the following values: class, method, getter, setter, field, and accessor
name is the name of the decorated entity (class name, method name, ...)
access contains get/set methods for the entity.
private if set to true, decorated entity is private
static if set to true, decorated entity is static
addInitializer allows 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 ParametersWhen 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 Resolutionmodule 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.
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