Hello, Typescript!

Hier ist beschrieben, wie man ein TypeScript-Projekt mit Kompilierung (TypeScript zu JavaScript), Formatierung, Linting und Testing aufsetzt.

Note

Die Anleitung wurde angepasst, sodass neu (Stand: 20.03.2025) konsequent das Modulsystem von ECMAScript zum Einsatz kommt und nicht mehr eine Hybrid-Lösung mit CommonJS und ECMAScript-Modulen. Dies betrifft die Dateien package.json und tsconfig.js.

Voraussetzungen

Du hast die Git Bash für Windows oder für Linux/macOS Git installiert.

Du hast Node.js in einer aktuellen Version (>= 22) installiert, was du folgendermassen prüfen kannst:

$ node --version
v.22.14.0

Du hast den TypeScript-Compiler tsc in einer aktuellen Version (>=5.7.0) installiert:

$ tsc --version
Version 5.7.3

Ansonsten installiere es folgendermassen:

$ npm install --global typescript@~5.7.3

Das ~-Zeichen vor der Versionsnummer bewirkt, dass auch eine neuere Patch-Version installiert werden kann.

Projekt

Öffne die Git-Bash (oder eine vergleichbare Shell) und navigiere mit cd in das Verzeichnis, in welchem die Übungen für dieses Modul ablegen willst.

Erstelle ein neues Verzeichnis namens hello-typescript:

$ mkdir hello-typescript

Wechsle in das neu erstellte Verzeichnis:

$ cd hello-typescript

Initialisiere nun ein Node-Projekt mit Standardeinstellungen:

$ npm init --yes

Hierdurch wird eine Datei package.json erstellt. Ersetze in dieser bei der Angabe type den Wert commonjs durch module:

{
  ...
  "type": "module"
}

Dadurch wird das Modulsystem von ECMAScript und nicht dasjenige von Node.js verwendet.

Erstelle nun zwei Verzeichnisse namens src und dist mit je einer leeren Datei namens .keep:

$ mkdir src dist
$ touch src/.keep dist/.keep

Dadurch können die (sonst leeren) Verzeichnisse mit Git verwaltet werden.

Repository

Initialisiere ein Git-Repository:

$ git init

Füge die Datei package.json sowie die beiden Verzeichnisse dem Repository hinzu:

$ git add package.json src dist
$ git commit -m 'initial commit: project setup'

Lege eine Datei namens .gitignore mit folgendem Inhalt an:

dist/**/*.js
node_modules/

Nimm die Datei ebenfalls ins Repository auf:

$ git add .gitignore
$ git commit -m 'ignore compiled JavaScript code'

Es empfiehlt sich, auf GitHub ein gleichnamiges Repository zu erstellen und es auf den Server zu kopieren. (Die Instruktionen hierzu erhälst du, wenn du das Repository erstellt hast.)

TypeScript

Erstelle eine Datei namens tsconfig.json mit folgendem Inhalt:

{
  "compilerOptions": {
    "target": "ES2024",
    "outDir": "./dist",
    "rootDir": "./src",
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

Damit wird der TypeScript-Code aus src/ zu JavaScript-Code nach dist/ kompiliert. Der resultierende Code verwendet den Standard ECMAScript 2024 (benötigt mindestens TypeScript 5.7) und das neueste ECMAScript-Modulsystem (esnext). Die Einstellungen moduleResolution und esModuleInterop sorgen dafür, dass Node.js, welches nativ ein anderes Modulsystem verwendet, mit dem ECMAScript-Modulsystem im Projekt umgehen kann.

Erstelle nun eine Datei namens src/index.ts mit folgendem Inhalt:

let lang: string = "TypeScript";
let greeting: string = `Hello, ${lang}!`;
console.log(greeting);

Der TypeScript-Code reichert das ansonsten dynamisch typisierte JavaScript um Typannotationen an.

Kompiliere den Code nun von TypeScript nach JavaScript:

$ tsc

Dadurch sollte eine Datei dist/index.js mit folgendem Inhalt erstellt worden sein:

let lang = "TypeScript";
let greeting = `Hello, ${lang}!`;
console.log(greeting);

Dadurch sind die Typannotationen verschwunden.

Führe den kompilierten Code nun aus:

$ node dist/index.js
Hello, TypeScript!

Die Kompilierung mittels tsc kann in package.json als Skript definiert werden:

{
  ...
  "scripts": {
    "build": "tsc"
  },
  ...
}

Anschliessend lässt sie sich als Node-Befehl ausführen:

$ npm run build

Formatierung

Installiere das Formatierungswerkzeug Prettier global:

$ npm install --global prettier@^3.5.3

Durch die Angabe ^3.5.3 kann auch eine neuere Minor- oder Patch-Version installiert werden.

Formatiere nun damit alle Dateien im lokalen Verzeichnis:

$ prettier -w .

Dadurch wird der Code einheitlich formatiert.

Die Änderungen können ins Git-Repository aufgenommen werden:

$ git add src/index.ts tsconfig.json
$ git commit -m 'TypeScript config and demo code'

Linting

Mithilfe eines Linters (“Entfussler”) lassen sich potenzielle Probleme im Programmcode erkennen und teilweise sogar automatisch korrigieren.

Installiere ESLint als Entwicklungs-Abhängigkeit (development dependency):

$ npm install --save-dev eslint@^9.22.0

Initialisiere die projektweiten Einstellungen nun folgendermassen:

$ npm init @eslint/config@latest

Beantworte die Fragen folgendermassen:

  • ESLint soll zur Syntaxprüfung und Problemfindung verwendet werden.
  • Es sollen JavaScript-Module (import/export) verwendet werden.
  • Es soll kein Framework zum Einsatz kommen.
  • Das Projekt verwendet TypeScript.
  • Der Code soll mit Node ausgeführt werden.
  • Die Abhängigkeiten (@eslint/js, typescript-eslint) sollen installiert werden.
  • Als Paketmanager kommt npm zum Einsatz.

Führe nun ESLint auf den bestehenden Code aus:

$ npx eslint src/index.ts

Betrachte dir die gemeldeten Probleme und korrigiere sie, sodass ESLint beim nächsten Durchlauf nichts mehr zu beanstanden hat.

Die Änderungen können ins Repository aufgenommen werden:

$ git add package.json package-lock.json eslint.config.mjs src/index.ts
$ git commit -m 'installed, configured, and applied ESLint'

Testing

Installiere das Test-Framework Jest:

$ npm install --save-dev jest@^29.7.0 ts-jest@^29.2.6 @types/jest@^29.5.14

Dadurch wird Jest mitsamt TypeScript-Unterstütung und Typannotationen installiert.

Erstelle eine Datei jest.config.js mit folgendem Inhalt:

export const roots = ["src"];
export const transform = { "^.+\\.tsx?$": "ts-jest" };

Dadurch werden die Testfälle im Quellcodeverzeichnis src gesucht und wie gewünscht umgewandelt.

Erstelle eine Datei src/rounding.ts mit folgendem Inhalt:

export function roundTo(x: number, granularity: number): number {
  const factor = 1.0 / granularity;
  return Math.round(x * factor) / factor;
}

Die Funktion rundet eine gegebene Zahl x auf eine gewünschte Genauigkeit. Beispielsweise ergibt roundTo(10/3, 0.05) den Wert 3.35.

Erstelle nun eine Datei src/rounding.test.ts mit folgendem Inhalt:

import { roundTo } from "./rounding";

test("check round to nickels", () => {
  expect(roundTo(10.0 / 3.0, 0.05)).toBe(3.35);
});

Führe den Test nun folgendermassen aus:

$ npx jest

Verändere den Testfall nun, sodass er den Wert 3.33 erwartet (toBe(3.33)).

Führe den Test erneut aus:

$ npx jest

Betrachte die Ausgabe und versuche sie zu verstehen.

Korrigiere den Test anschliessend wieder.

Auch das Testen kann als Skript in package.json definiert werden:

{
  ...
  "scripts": {
    "build": "tsc",
    "test": "npx jest"
  },
  ...
}

Anschliessend lassen sich die Tests folgendermassen ausführen:

$ npm run test

Schreibe zwei weitere Testfälle für “cents” und “dimes”, welche die Rundung auf die Granularität 0.01 bzw. 0.1 testen. Führe die Testfälle aus.

Füge am Schluss alle Dateien dem Repository hinzu und pushe es auf GitHub.

Debugger

Da wir TypeScript-Code schreiben, jedoch JavaScript-Code ausführen, stimmen die Zeilennummern vom geschriebenen und kompilierten Code nicht immer miteinander überein. Damit man trotzdem einen Debugger verwenden kann, muss man sogenannte Source Maps generieren, welche die Zeilennummern korrekt zuordnen können. Dies erreicht man, indem man die Compiler-Option sourceMap in tsconfig.json auf true setzt:

{
  "compilerOptions": {
    ...
    "sourceMap": true
  }
}

Beim nächsten Kompiliervorgang werden Source Maps mit der Endung .js.map generiert:

$ tsc
$ ls dist/*.map
dist/index.js.map
dist/rounding.js.map
dist/rounding.test.js.map

Die Regeln in .gitignore sollten entsprechend erweitert werden:

dist/**/*.js
dist/**/*.js.map
node_modules/

Füge nun der Datei src/index.ts ein debugger-Statement hinzu:

const lang: string = "TypeScript";
const greeting: string = `Hello, ${lang}!`;
debugger
console.log(greeting);

Kompiliere den Code und führe ihn mit node inspect aus:

$ tsc
$ node inspect dist/index.js

Mit c wird die Ausführung bis zum Breakpoint fortgesetzt. Anschliessend kann man die Variablen (auf eine etwas umständliche Weise) inspizieren:

> exec('lang')
'TypeScript'
> exec('greeting')
'Hello, TypeScript!'

Komfortabler funktioniert das Debuggen mit einer Entwicklungsumgebung wie Visual Studio Code.

Entferne den Breakpoint wieder aus src/index.ts und nehme die Änderung ins Repository auf:

$ git add .gitignore tsconfig.json
$ git commit -m 'generate (and ignore) source maps'

Visual Studio Code

Das beschriebene Setup funktioniert unabhängig von einer Entwicklungsumgebung. Wer etwas mehr Komfort haben möchte, kann mit Visual Studio Code arbeiten.

Hierzu sind folgende Erweiterungen hilfreich:

Diese Erweiterungen arbeiten mit den installierten Node-Packages (prettier, eslint, jest) zusammen.

Wie man das Debugging in Visual Studio konfiguriert, wird im Video erklärt.

Zusatzaufgabe: Pythagoräische Tripel

Erstelle eine neue Datei src/pythagoras.ts mit einer Funktion isTriplet, welche drei Parameter a, b und c erwartet. Die Funktion soll true zurückgeben, wenn die Gleichung a²+b²=c² erfüllt ist, und false andernfalls.

Erstelle nun einen Testfall in src/pythagoras.test.ts, welche je einen positiven (Tipp: 3²+4²=5²) und einen negativen Test enthält. Führe den Test aus und prüfe, ob isTriplet wie gewünscht funktioniert.

Passe nun src/index.ts so an, dass es weitere pythagoräische Triplets findet. Tipp: Verwende drei verschachtelte Schleifen, welche jeweils die Variable a, b, oder c hochzählen und die Zahlenkombination durchprobieren (Brute Force). Gebe alle gefundenen Triplets aus.