We had to wait almost 2 years for the next major TypeScript version. However, the patience paid off, as TC39-compliant decorators and a big package of optimizations made their way into TypeScript 5.0.
1. TypeScript 5.0 beta
The first beta of TypeScript 5.0 was released this week. Before you proceed further, remember that TypeScript does not follow Semantic Versioning. As a result, this version brings new features rather than breaking changes (although there will be a few of those as well). Today we will look at the 3 most interesting new features, but if you want to explore the details on your own, I refer you to the brilliant note from 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.
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.
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.
/* 👇 Here 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"] });
Optimizations
TypeScript 5.0 is also a milestone when it comes to performance. This is due to the fact that the language has been entirely rewritten from archaic namespaces to modules. This paved the way for the use of a whole range of tools and mechanisms to optimize both compilation and the size of the final package.
We will still have to wait until March 14 for the stable version of TypeScript 5.0, but I’m already waiting with baked goods on my face. Are you waiting for a new version of TypeScript too, or have you long since stopped keeping track of what’s new?
2. Astro 2.0
Astro is a server-side rendering framework that debuted in 2021 and lived to see its first stable release in 2022. It is distinguished from its competitors by two big features: island architecture and framework independence.
Island architecture assumes that on the site we create, among the sea of static content (e.g. navigation menu, article) there are small islands of interactive components (e.g. newsletter sign-up form). Thanks to such an assumption, you can significantly slim down the process of hydration (reminder: hydration is a process of connecting the HTML structure rendered on the server with the framework running on the client side)
Astro is also one of the few tools that allow us to mix frameworks. At the moment React, Preact, Svelte, Vue, SolidJS and Lit are available, but the list is likely to grow in the future.
—
import HomeLayout from '../layouts/HomeLayout.astro'
import MyReactComponent from '../components/MyReactComponent.jsx';
import MySvelteComponent from '../components/MySvelteComponent.svelte';
const data = await fetch('API_URL').then(r => r.json());
---
<HomeLayout>
<MyReactComponent client:load name={data.name}>
<MySvelteComponent avatar={data.avatar}/>
</MyReactComponent>
</HomeLayout>
The year 2022 was full of successes for Astro. In the State of JS 2022 in the server-side rendering category in terms of developer satisfaction, Astro ranked 1st. In the JavaScript Rising Star ranking, which measures GitHub stars’ growth, Astro ranked 7th in the overall category and 3rd in the server-side framework category. For a rookie -this is really impressive!
The team behind Astro wants to continue the good fortune and this week Astro 2.0 was released. This time, we are sticking with Semantic Versioning, so quite a few dependencies have been updated (including Vite 4.0!). However, the developers have also included quite a few new features.
Type-safe Markdown & MDX
Astro is ideal for building Markdown and MDX-based blogs and documentation. On pages like this, repetitive patterns like headings and metadata are crucial for SEO. If a developer makes even the smallest typo, it can lead to a situation where a subpage is indexed badly or even not at all. Proper integration of Markdown and MDX with typed data sources is a killer feature.
The mechanism presented in Astro 2.0 is based on the popular Zod library. With its help, we define the input data schema. Then, if the programmer makes a mistake, it will be detected at the build stage.
const blog = defineCollection({
schema: z.object({
// Define your expected frontmatter properties
title: z.string(),
// Mark certain properties as optional
draft: z.boolean().optional(),
// Transform datestrings to full Date objects
publishDate: z.string().transform((val) => new Date(val))
// Improve SEO with descriptive warnings
description: z.string().max(160, 'Short descriptions have better SEO!')
// ...
}),
});
Hybrid Rendering
Until now, when using Astro, we have been forced to choose between server-side rendering and client-side rendering. Since Astro 2.0, Hybrid Rendering is also available. The right configuration of these three strategies can give really impressive results.
As a reminder – Hybrid Rendering is a technique that involves rendering pages at the build stage and then serving them in a static way. By applying such a strategy, the client no longer waits until the server fetches the necessary data and renders the HTML structure, but instead immediately downloads the static HTML file. Of course, this approach also has its drawbacks – it significantly increases the build time of the application, requires all possible pages definition up-front and can’t be applied if we need a personalized page for each user.
If you want to find out what else changed in Astro 2.0, I recommend the blog post announcing version 2.0 and the dedicated blog post on typed Markdown and MDX. If you have a little more time and want to see how to put the new features of Astro 2.0 into practice, Jack Herrington has prepared a nearly half-hour demo on how he builds a simple blog page.