Frontend

TypeScript 5.0 is here! | Frontend Weekly vol. 129

It has been almost three years since the previous release of TypeScript major version. Because of that, the release of TypeScript 5.0 is big event! With this version, we finally got ECMAScript-compliant decorators and some other interesting new features.

Article cover

1. TypeScript 5.0

Migration to modules

The biggest new feature of TypeScript 5.0 is the source code migration from namespaces to modules. It involves a significant slimming down of the package size and a slight improvement in performance. The decision to migrate was dictated by the needs of the community, which has already been using modules for years. TypeScript uses a technique called Dog Feeding, meaning that it compiles itself as part of testing. Although the language has made quite a few innovations in this area in recent years it has not been able to use them itself. Moreover, by not using modules, the team missed the opportunity to test functionality heavily used by the community.

If you started your adventure with TypeScript over the past few years, there’s a good chance you’ve never been exposed to namespaces. This functionality was created in 2014, when JavaScript modules were still in their infancy and several conventions were competing to become the standard. TypeScript urgently needed to solve the problem of modularization and namespace pollution, so it came up with its own implementation od modules. In a nutshell, namespace is a syntax sugar for creating a global object.

// TypeScript input
namespace Example {
  export const name = 'Tomek';
}
// JavaScript output
var Example;
(function (Example) {
    Example.name = 'Tomek';
})(Example || (Example = {}));

In 2015, full-fledged and well-designed modules finally arrived in the ECMAScript standard. They not only solved the problem of namespace pollution, but also gave us tools to control how our application is split into pieces and when those pieces are loaded. Thus, TypeScript namespaces became unnecessary and quickly forgotten by the community.

There are indeed quite a few interesting stories associated with TypeScript migration to modules. From choosing the right bundler, through performance issues related to the use of let and const, to slimming down the package size by reducing indentation. If you feel like digging a little deeper into the topic, I strongly recommend a short case study prepared by Microsoft.

Decorators

The history of decorators in TypeScript is the material for a multi-season tv series full of plot twists and cliffhangers. The adventure began in October 2014, when the first proposal of decorators was presented at the TC39 meeting (reminder: TC39 is a JavaScript standardization group – I like to compare it to the tribal elders deciding where the tribe will go next). Almost in parallel, at the ngEurope conference, it was announced that Angular 2.0 would be written in AtScript. The new language from Google was supposed to be based on TypeScript, but extended it with several new features. Among them were the decorators.

Did you know that one of AtScript’s features was supposed to be support for runtime types? It sounds good on paper, but I wonder how it would affect performance.

It seems reasonable to ask why Google did not simply decide to invest in TypeScript. This happened because of a conflict of interest. The team at Microsoft wanted TypeScript to be as close to JavaScript as possible. Adding functionality that might interfere with the standard in the future was not negotiable. The team at Google needed tools that would allow them to build their dream framework. Without decorators, TypeScipt was not such a tool.

Fortunately, after tumultuous negotiations, the two companies reached an agreement. In May 2015, it was announced that decorators as described in the Stage 1 Proposal would be added to TypeScript (reminder: the JavaSciprt standardization process consists of 4 stages – only functionality in Stage 3 is considered reasonably stable). Around the same time, it was announced that Angular will be written in TypeScript.

It may be an unpopular opinion, but if Microsoft had not made a deal with Google in 2015, the history of TypeScript could have turned out very differently. Maybe today Angular is losing the battle with React, but 8 years ago all eyes were on the framework from Google.

A lot has changed in terms of decorators since 2015. First of all, the proposal broke through to Stage 3. However, it was not without losses. A significant part concerning metadata was cut from the original proposal. This means that the decorators, which will be a part of the JavaScript standard soon, are not compatible with those implemented in TypeScript. Fortunately, TypeScript 5.0 came to the rescue.

If this is the first time you hear about decorators, they are functions that allow us to modify the behavior of other functions or classes. The subject is so complicated and broad that I will not try to explain it in this report. For all of you seeking knowledge, I will recommend to the excellent article by Axel Rauschmayer. After reading it, you can go straight to the note from Microsoft, where you will learn how to type your decorators.

// Decorator definition
function debut(originalMethod: any, _context: ClassMethodDecoratorContext) {
    function replacementMethod(this: any, ...args: any[]) {
        console.log("LOG: Entering method.")
        const result = originalMethod.call(this, ...args);
        console.log("LOG: Exiting method.")
        return result;
    }
    return replacementMethod;
}

// Decorator usage
class Person {
    constructor(private readonly name: string) {}

    @debug greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

new Person("Tomek").greet(); 

// Expected output:
// LOG: Entering method.
// Hello, my name is Tomek
// LOG: Exiting method.

What about projects using the old implementation of decorators (for example Angular, Ember, and MobX)? As long as the TypeScript configuration includes the --experimentalDecorators flag, their behavior will not change. In the long run, all these libraries will face migration.

const Type Parametrs

If you work with TypeScript on a daily basis, you are surely familiar with the as const trick, which allows us to narrow down the inferred type.

// Inferred type: string[]  
const namesA = ["Alice", "Bob", "Eve"];

// Inferred type: readonly ["Alice", "Bob", "Eve"]
const namesB = ["Alice", "Bob", "Eve"] as const; 

We often use this trick, to narrow down the type returned by a function.

function getNames<T extends { name: readonly string[]}>(arg: T): T["names"] {
    return arg.names;
}

// Inferred type: string[]
const namesA = getNames({ names: ["Alice", "Bob", "Eve"]});

// Inferred type: readonly ["Alice", "Bob", "Eve"]         
const namesB = getNames({ names: ["Alice", "Bob", "Eve"]} as const); 

TypeScript 5.0 introduces a mechanism that allows us to move as const to the type definition. This way, we take the burden off the backs of our users.

/*                👇 Hese is the new const type parameter                */
function getNames<const T extends { names: readonly string[] }>(arg: T): T["names"] {
    return arg.names;
}

// Inferred type: readonly ["Alice", "Bob", "Eve"]
const names = getNames{ names: ["Alice", "Bob", "Eve"] });

And much more…

Of course, this is not the end of what’s new in TypeScript 5.0. I refer anyone interested in the details of the latest version of the language to a great memo prepared by Microsoft.

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

2. Lazy Loading in React Router 6.9

React Router is becoming an increasingly powerful library from version to version. In React Router 6.4 we got our hands on loaders, which enable us to parallelize the download for all the necessary components.

const routes = [{
  path: '/',
  loader: () => getUser(),
  element: <Layout />,
  children: [{
    path: 'projects',
    loader: () => getProjects(),
    element: <Projects />,
    children: [{
      path: ':projectId',
      loader: ({ params }) => getProject(params.projectId),
      element: <Project />,
    }],
  }],
}]

In React Router 6.9, released this week, the React.lazy function, which allows component download parallelization, came into our hands. By combining it with the loaders described in the previous paragraph, we can parallelize both data and component downloads.

const routes = [{
  path: '/',
  element: <Layout />,
  children: [{
    path: 'projects',
    loader: () => getProjects(),
    lazy: () => import("./projects"), // 💤 Lazy load!
    children: [{
      path: ':projectId',
      loader: ({ params }) => getProject(params.projectId),
      lazy: () => import("./project"), // 💤 Lazy load!
    }],
  }],
}]

If the topic interests you, an article has been published on the React Router blog that details what download parallelization capabilities the library offers us.

New React Docs

React has finally lived to see the documentation it deserves. After long months in beta, new and rewritten from scratch documentation becomes the default one. The outdated one is not disappearing from the Internet and will be available at legacy.reactjs.org. What distinguishes the new documentation? First of all, it is rich in interactive examples and has a refreshed layout. If you haven’t seen it yet, it’s high time to catch up. Especially since it has been supplemented with some interesting advanced topics (such as why we should avoid useEffect).

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

4. ECMAScript® 2023

In the past weeks a new JavaScript specification has hit the web. As usual, it’s not an easy read, and before you get into it I recommend reading the ECMAScript specification reading guide by Timothy Gu. If reading it nevertheless proves too difficult for you, last week’s Reddit was conquered by Linus Schlumberger’s article, which brilliantly summarizes all the new developments in JavaScript over the past three years.

Source: XKCD