What to Expect From TypeScript 5.0
Authored by Nazar Hussain
Until now, the most widely used Typescript version has been 4.9.5
, but that's changed with newer, recently releasedTypescript 5.0
. With TypeScript 5.0 comes many new features, along with the stated intent to make TypeScript smaller, simpler, and faster.
The details of what we can expect are listed in the following issue. Below we'll go through some of the major features and changes to familiarize ourselves with these updates.
Some highlights:
-
Decorators
-
const
Type
Parameters -
Supporting multiple extended configurations
-
All
enums
as Unionenums
-
Support for
export type *
-
New configuration flags
--allowImportingTsExtensions --resolvePackageJsonExports --resolvePackageJsonImports --moduleResolution bundler --customConditions --verbatimModuleSyntax
-
Passing Emit-Specific Flags Under
--build --declaration --emitDeclarationOnly --declarationMap --soureMap --inlineSourceMap
For each, let's drill into the details.
Decorators
Yes, it's not Python or Java annotation, it's TypeScript, and yes, we are getting decorators' support. We can define decorators from multiple perspectives.
-
Decorators allow users to add new functionality to an existing object without modifying its structure.
-
Decorators allow modifying the functionality of your functions without changing the functions.
-
Decorators allow using declarative programming patterns within functional programs.
There could be many more ways to define decorators, but eventually, this feature will enrich how we define functions, extend those, or compose functions.
Consider the following example.
class Foo {
blah() {
return "blah blah";
}
}
Say we want to log the output of the function blah
whenever it is invoked, without knowing details of it or even without changing any code line inside the function. Here come the decorators.
function Log<T extends (...args: any[]) => any, T2>(
target: T2,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>) : TypedPropertyDescriptor<T> {
const value: T = function (...args: any[]): any {
if(!descriptor.value) return;
console.log(`calling "${propertyKey.toString()}" with args "${args}"`);
const ret = descriptor.value(...args);
console.log(`returned "${ret}"`);
return ret;
} as T;
return {...descriptor, value };
}
That's a decorator, but basically, a function that is extending a method. We can use it in the following way.
class Foo {
@Log
blah(d: string) {
return "blah blah " + d;
}
@Log
sum(a: number, b: number) {
return a + b;
}
}
You may be concerned about the types here, and yes, the types of the function are not modified. In the future, there may be such a feature available.
const Type Parameters
There is a feature in TypeScript called Type inferring. Most of us have used it often without thinking about its details. Consider the following example.
TypeScript tries to infer the type of the obj.type
from its parameter value, and you are right, it guessed it a string
.
function getObjectType<T extends {readonly type: string}>(obj: T): T["type"] {
return obj.type;
}
const objType = getObjectType({type: "MyObject"}); // string
But if we wanted it to be a literal type, we have to specify the value as const
function getObjectType<T extends {readonly type: string}>(obj: T): T["type"] {
return obj.type;
}
const objType = getObjectType({type: "MyObject"} as const); // MyObject
It's redundant to use as const
, and we may forget it a lot. So the new TypeScript allows you to specify parameter type as const
by adding const
before T
function getObjectType<const T extends {readonly type: string}>(obj: T): T["type"] {
return obj.type;
}
const objType = getObjectType({type: "MyObject"}); // MyObject
Supporting multiple extend configurations
For any TypeScript project, the tsconfig.json
is the entry point. It's where we start configuring our projects. And we usually don't have one tsconfig.json
but instead multiple e.g. tsconfig.build.json
or tsconfig.test.json
. We usually don't configure in every case but rather extends
from existing ones. e.g.
{
"extends": "./tsconfig.build.json",
"compilerOptions": {
"emitDeclarationOnly": false,
"incremental": false,
"noEmit": true
}
}
Earlier, TypeScript allowed only to extend from one configuration. Now it allows configuring from multiple configuration files.
{ "extends": ["basic", "extended"], "compilerOptions": { } }
It's the same approach used in the configuration of another famous project eslint
. And it uses the same precedence approach as ESLint.
All enums
as Union enums
In TypeScript, enums
have quite a history. In earlier versions, these were just numerical const, in later ones, we could also assign string values to enums. TypeScript formally had been treating enums differently based on the value assigned.
Consider the following example.
enum E {
Foo = 10,
Bar = 20,
Baz = "value"
}
const obj1: E = "value"; // Raise error
const obj2: E = 30; // WIll pass fine
Will both values raise an error as we are not using correct enum-relevant values? No, TypeScript will only raise errors on the first because the string values define E.Baz
as a literal value, so you can only assign E.Baz
to the value. For the numeric values, E.Foo
and E.Bar
it defines those as number
and E
was actually a union number | E.Baz
In TypeScript5, all enums will be considered unions of constant type and literal type values. So in Typescript 5, the above enum will be E.Foo | E.Bar | E.Baz
where E.Foo
and E.Bar
will be literal types. If you compile the above code in TypeScript 5, it will raise an error on both lines.
Support for export type *
Earlier syntax export * as namespace from "./module"
only supported the values, but on the other hand, the import type
was supported, which was contradicting.
So now we can merge the types into namespaces and re-export those as export type * as namespace from "./module"
New configuration flags
There are quite a few new flags added to TypeScript 5. Most of those are self-explanatory by name. We will go here for a few very important and useful.
The --customConditions
is linked to the exports
and imports
attributes of the package.json
. You can also set it in the configuration for compilerOptions.customConditions: []
. It allows you to enable custom conditions for exports/imports. Say we have the following package.json
, which enlists an export with a specific name, say that is a custom device we want to build our project for.
{
// ...
"exports": {
".": {
"my-device": "./my-device-module.mjs",
"node": "./my-module.mjs",
}
}
}
While compiling for that custom device, we can use --customConditions my-device
, and TypeScriptwill import my-device-module.mjs
instead of my-module.mjs
.
The --importsNotUsedAsValues
and --preserveValueImports
is deprecated in favor of --verbatimModuleSyntax
. The new approach allows easy, consistent, and faster decisions for filtering the emitting code. Not that much use if you are already using ECMAScript code, but if you are writing a mix of CommonJS and ECMAScript, then you could try that flag and notice the difference in the output.
Passing Emit-Specific Flags Under
The following new flags are available to be used along with tsc --build.
Earlier, these were only available in the configuration. This will help to modify the build with specific needs without changing the configuration.
-
--declaration
-
--soureMap
-
--inlineSourceMap
-
--declarationMap
-
--emitDeclarationOnly
Summary
With this update, a lot changed internally, and massive refactoring has been done in TypeScript 5.0.
The result is that it's possible to reduce around 26 MB of its package size - bringing it to 39.2 MB
from 66.8 MB
. The refactoring also enables achieving a higher build time.
Courtesy: https://devblogs.microsoft.com/
If you want to explore more details of each change, check out the following blog post or GitHub issue.
About ChainSafe
ChainSafe is a leading blockchain research and development firm specializing in infrastructure solutions for web3. Alongside its contributions to major ecosystems such as Ethereum, Polkadot, Filecoin, Mina, and more, ChainSafe creates solutions for developers and teams across the web3 space utilizing expertise in gaming, bridging, NFTs, and decentralized storage. As part of its mission to build innovative products for users and improved tooling for developers, ChainSafe embodies an open source and community-oriented ethos to advance the future of the internet.
Website | Twitter | Linkedin | GitHub | Discord | YouTube | Newsletter