News

Vorgriff auf ECMAScript 2020: Das sind die spannendsten neuen Features in TypeScript 3.8

TypeScript-Code. (Bild via Instacode)

Lesezeit: 6 Min.
Artikel merken

Seit Mitte Januar ist die Beta von Version 3.8 der von Microsoft entwickelten Programmiersprache TypeScript zum Testen verfügbar – jetzt wurde das finale Update veröffentlicht. Version 3.8 kommt mit neuen Features aus ECMAScript 2020 sowie einer neuen Syntax für den Im- und Export von Typen.  

TypeScript basiert auf Vorschlägen zum ES6-Standard. Viele Konzepte der Sprache wie Klassen, Vererbung oder Module sind daher deckungsgleich mit ES6. Syntax und Semantik der Sprache sind stark an JavaScript angelehnt. Die Lernkurve ist für Entwickler, die von JavaScript zu TypeScript wechseln, relativ moderat. Wer mit TypeScript arbeitet, kann dabei bereits existenten JavaScript-Code und die meisten Frameworks und Libraries aus dem JavaScript-Ökosystem nutzen. TypeScript-Code kann über JavaScript aufgerufen werden. Am Ende wird TypeScript zu JavaScript-Code kompiliert, der in allen Browsern, in Node.js und in jeder JavaScript-Engine läuft, die ECMAScript ab Version 3 unterstützt. Version 3.8 von TypeScript kommt mit einigen Neuerungen.

Type-only Im- und Exporte

Für das Referenzieren von Typen macht TypeScript Anleihen bei JavaScripts Import-Syntax. So war es bisher möglich, JavaScript-Values zusammen mit TypeScript-Typen zu importieren, etwa so:

// ./foo.ts
interface Options {
// ...
}
export function doThing(options: Options) {
//
}

// ./bar.ts
import { doThing, Options } from "./foo.js";
// doThing is a JS value, Options is purely a TypeScript type

function doThingBetter(options: Options) {
// do something twice as good
doThing(options);
doThing(options);
}

*Alle Code-Snippets wurden, soweit nicht anders gekennzeichnet, aus Michael Rosenwassers Blogpost für den Microsoft-Blog übernommen.

Das ist bequem, weil es meistens weniger eine Rolle spielt, ob ein Type oder Value importiert wird. Wichtig ist, überhaupt etwas zu importieren. In manchen Fällen ist es aber wichtig, zu unterscheiden, was importiert wird. TypeScripts Import Elision löscht Import Statements, deren Importe nur als Types genutzt werden. Das führt etwa bei Modulen mit Side-Effects zu abweichendem Verhalten – um Side Effects sicherzustellen, war bisher ein zweites Import Statement nötig.

Dieses Problem kam zum Beispiel in Angular in Version 1.x regelmäßig auf, wo Services global registriert werden mussten, diese aber überhaupt nur wegen der Types importiert wurden.

// ./service.ts
export class Service
{    // ...}
register("globalServiceId", Service);

// ./consumer.ts
import { Service } from "./service.js";
inject("globalServiceId", function (service: Service) {
// do stuff with Service
});

Im Beispiel wird ./service.js aufgrund diesen Verhaltens nie ausgeführt. Die Folge: Während der Laufzeit crasht die App.

Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Um derartige Probleme zu vermeiden, wurde in Version 3.8 eine Syntax für Type-only Im- und Exporte eingeführt. So sieht sie aus:

import type { SomeThing } from "./some-module.js";
export type { SomeThing };

Über import type werden nur Deklarationen, die ausschließlich für Typen-Annotationen und Deklarationen genutzt werden, importiert. Ein solches Statement wird beim Kompilieren immer komplett gelöscht, sodass während der Laufzeit nichts mehr davon im Code vorhanden ist.

export type kann nur für type-Kontexte genutzt werden und wird beim Kompilieren ebenfalls aus dem Output gelöscht.

Neue Compiler-Flag

In Version 3.8 wurde außerdem eine Compiler Flag hinzugefügt, über die ihr kontrollieren könnt, was aus Imports wird, die zur Laufzeit nicht verwendet werden. importsNotUsedAsValues heißt sie. Die Flag nimmt drei verschiedene Werte an:

remove: ist derzeit der Default und wird der Default bleiben – Importe, die nicht verwendet werden, werden gedroppt.

preserve: führt zum Erhalt aller Importe, deren Values nicht verwendet werden. So können zum Beispiel side effects, wo Services nur wegen des Typs registriert werden, erhalten bleiben.

error: Wie die preserve-Flag erhält die error-Flag alle Importe. Im Fall des Imports eines Values, der nur als Type verwendet wird, bekommt ihr mit dieser Flag allerdings eine Fehlermeldung. Das ist zum Beispiel nützlich, um sicherzustellen, dass ihr Values nicht aus Versehen importiert, Importe von Nebeneffekten aber trotzdem explizit machen wollt.

ECMAScript Private Fields

TypeScript 3.8 unterstützt die Private Fields des ECMAScript Stage-3 Class Fields Proposals. Private Fields schreibt ihr mit einem vorangestellten #. Sie werden auch private names genannt.

class Person {
#name: string
constructor(name: string){
this.#name = name;
} greet() {
console.log(`Hello, my name is ${this.#name}!`);    }}

let jeremy = new Person("Jeremy Bearimy"); jeremy.#name
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

Private Fields sind ausschließlich innerhalb der Klasse gültig, in der sie enthalten sind.

TypeScript-eigene Accessibility-Modifiers wie public oder private könnt ihr in Private Fields nicht verwenden. Und: Außerhalb der Klasse, in der sie enthalten sind, kann auf Private Fields weder zugegriffen noch können sie entdeckt werden. Das wird auch Hard Privacy genannt. Reguläre Property-Deklarationen kollidieren oft mit Klassennamen von irgendwelchen Unterklassen, mit Private Fields passiert das nicht – jeder Field Name betrifft nur die Klasse, in der er enthalten ist.

Wer das neue Feature in reinen JavaScript-Dateien nutzt: Diese müssen immer bestimmt werden, bevor sie einer Entität zugewiesen werden. In JavaScript war es bisher erlaubt, auf nicht deklarierte Properties zuzugreifen. Mit Private Fields ist das nicht möglich.

class C {
/** @type {number} */
#foo;
constructor(foo: number) {
// This works.
this.#foo = foo;
}
}

Ob ihr TypeScripts Private Modifiers nutzt oder die neuen # private fields aus ECMAScript, hängt vom jeweiligen Einsatzweck ab. TypeScripts private Modifiers werden, wenn sie auf Properties angewendet werden, komplett gelöscht. Die Daten sind zwar da, im kompilierten JavaScript-Output sind aber keine Informationen darüber, wie die Property deklariert wurde, hinterlegt – während der Laufzeit verhält sie sich wie eine normale Property. Das heißt, Privacy ist nur während der Kompilierzeit sichergestellt.

Diese „softe“ Privacy bietet Clients einen Workaround, zeitweise fehlenden Zugriff auf eine API auszugleichen. Und sie funktioniert in jeder Laufzeitumgebung. Auf ECMAScrips #privates kann im Gegensatz dazu außerhalb der Klasse, in der sie definiert wurden, nicht zugegriffen werden. Deren strikte Privacy ist hilfreich, wenn ihr wirklich sicherstellen wollt, dass niemand auf Internals in eurem Code zugreift. Außerdem macht das Feature – wie bereits erwähnt – Sub-Classing einfacher, Kollisionen von Field-Benennungen sind damit ausgeschlossen – anders bei TypeScripts private-Keyword.

Allerdings unterstützt TypeScript das Feature bisher nur bei ES6+ Targets. Im Gegensatz dazu funktioniert das private-Keyword mit allen Targets ab ES3.

Neue export * -Syntax

Aus ECMAScript 2020 wurde in TypeScript 3.8 außerdem eine neue Syntax übernommen, mit der alle in einem Modul verwendeten Komponenten in einer Zeile exportiert werden können. Anstatt über

import * as utilities from "./utilities.js"
export { utilities }

alle Module einzeln zu exportieren, etwa so:

export { Accordion } from './Accordion';
export { Alert, AlertText } from './Alert';
export { AuthorCard } from './AuthorCard';
export { Avatar } from './Avatar';
export { Badge } from './Badge';
export { Box } from './Box';
export { Breadcrumbs, BreadcrumbsItem } from './Breadcrumbs';
Codebeispiel: t3n

, könnt ihr jetzt äquivalent zur Syntax des Import-Statements über

export * as utilities from "./utilities.js";

alle Module mit dieser einen Zeile exportieren – weniger Schreibarbeit, yay!

Top Level await

Die meisten Umgebungen mit Input/Output in JavaScript sind asynchron. Die meisten API geben Promises zurück. Das hat den Vorteil, dass Vorgänge parallel ausgeführt werden können. Beim Laden externer Inhalte ist diese Asynchronität aber unerwünscht.


fetch("...")
.then(response => response.text())
.then(greeting => { console.log(greeting) });

then-Ketten wie im Beispiel sind ein Weg, Vorgänge trotzdem nacheinander auszuführen.

Um then-Ketten zu vermeiden, nutzen JavaScript-Entwickler oft eine async-Funktion, um über await sicherzustellen, dass response und greeting nacheinander ausgeführt werden.

async function main() {
const response = await fetch("...");
const greeting = await response.text();
console.log(greeting);
}
main()
.catch(e => console.error(e))

Mit einem neuen Feature aus ECMAScript 2020 namens top Level await ist es in TypeScript 3.8 möglich, await auf dem obersten Level eines Moduls zu nutzen, die async-Funktion braucht es damit an dieser Stelle nicht mehr.

const response = await fetch ("...")
const greeting = await response.text();
console.log(greeting);
// Make sure we're a module
export {};

Top level await funktioniert aber nur auf dem obersten Level eines Moduls. Dateien gelten nur dann als Module, wenn ein import oder export vorhanden ist. Um das sicherzustellen, müsst ihr in manchen Fällen wie im Beispiel export {} ausschreiben.
Achtung: Im Moment funktioniert das Feature nur für Target Compiler Options ES17+.

es2020-Option für target und module

Um sicherzustellen, dass neue ECMAScript-Features wie die dynamische import(...)-Syntax oder export * as ns auch funktionieren, gibt es in V 3.8 die Option es2020 für target und module.

JSDoc Property Modifiers

Über eine neue Flag namens allowJS könnt ihr in TypeScript 3.8 JavaScript-Dateien nutzen und sie über die checkJS-Option type-checken – alternativ könnt ihr euren JavaScript-Dateien auch // @ts-check voranstellen. In JavaScript gibt es keine eigene Syntax für die Typüberprüfung. Um Type-Checking in JavaScript-Files zu ermöglichen, macht TypeScript 3.8 Anleihen bei JSDoc, es versteht die Accessibility-Modifier @public, @private und @protected, die äquivalent zu public, private und protected in TypeScript funktionieren. Außerdem neu ist der @readonly-Modifier, der dafür sorgt, dass einer Property nur bei der Initialisierung ein Value zugewiesen werden kann.

Neue watch-Optionen für Directories

TypeScript 3.8 kommt mit einer neuen Strategie für die Überwachung von Directories, über die ihr Änderungen von Node-Modulen besser im Blick behalten könnt. Damit ihr das entsprechend der Anforderungen eurer einzelner Projekte individuell konfigurieren könnt, wurde mit TypeScript 3.8 ein watchOptions-Field in euren tsconfig.json beziehungsweise jsconfig.json-Dateien eingeführt. Für die Konfiguration stehen euch vier verschiedene Optionen zur Auswahl; wer sich für die Details interessiert, sei auf den Microsoft-Blog oder auch auf den Pull-Request auf GitHub verwiesen.

„Fast & Loose“ Incremental Checking

TypeScript verfügt über zwei Modi, --watch und --incremental, über die TypeScript Dateien und deren Auswirkung auf andere Dateien trackt und Information im Speicher so weit wie möglich wiederverwendet.

Über eine neue Compiler-Option namens OnlyAffectDirectDependencies werden nur jene Dateien gecheckt, die von einer Änderung betroffen sind, sowie jene, die diese Dateien importieren. Das bringt vor allem einen Performance-Zuwachs. Dieses Feature ist vielleicht nicht für alle Codebases empfehlenswert, wer nach dem „move fast & fix things later“-Prinzip arbeiten will, findet dafür aber eventuell Verwendung.

Außerdem kommt Version 3.8 mit einigen kleineren Breaking Changes, die vor allem strikteres Type Checking betreffen und neuen Editor-Features. Alle Details zu allen Neuerungen könnt ihr im Blogpost von Microsoft oder im Projekt auf GitHub  nachlesen.

Das könnte dich auch interessieren:

Das könnte dich auch interessieren

Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Schreib den ersten Kommentar!

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!

Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Digitales High Five
Holger Schellkopf (Chefredakteur t3n)

Anleitung zur Deaktivierung

Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder