Marvin-Dem / js-playground

0 stars 0 forks source link

Aufsetzen von TypeScript Hotloader #18

Open p-runge opened 3 weeks ago

p-runge commented 3 weeks ago

Ziel

Ziel dieser Aufgabe ist es eine Umgebung zu haben, in der man, sobald eine Änderung in einer TypeScript Datei gemacht wird, die Auswirkungen direkt beim Speichern im Browser sieht.

Wir werden dabei eine Ansammlung an Tools benutzen: TSC (TypeScript Compiler), Nodemon (Node Runtime mit Watcher auf .js Dateien) und Express (Lokaler Webserver). Das hier wird dabei der Flow des Ganzen:

Image

Der Weg

1. Strukturieren des Projekts

Viele Projekte in der Webentwicklung haben mindestens folgende Ordner im Top Level:

Der Überischtlichkeit halber wäre es hilfreich alle Dateien, die ähnlich heißen, entsprechend unbenennen.

2. Einbinden der .js Datei in index.html

Um JavaScript im Browser ausführen zu können, müssen wir eine Script Datei einbinden. Wie das geht, lässt sich einfach ergooglen. Der Pfad zur main.js aus Sicht der index.html lautet übrigens ../src/main.js (also gehe ein Level hoch, dann in src, und darin die main.js). Um das zu testen erstelle in main.js ein Objekt person mit den Props givenName und lastName und logge dies in die Konsole. Wenn das Script richtig eingebunden wurde, wird nun die entsprechende Ausgabe in der Browser Konsole ausgegeben (Cmd+Option+I -> Tab: "Console").

3. Umstellen der Source auf TypeScript

Wir wollen in unserem Sourcecode TypeScript verwenden. Deshalb müssen wir nun also main.js in main.ts umbenennen, und einen Typen Person definieren, der als Vorlage für das bereits definierte Objekt dient.

Das Problem ist hier, dass der Browser kein TypeScript versteht. Die src des Scripts im .html File also einfach auf src/main.ts umzustellen funktioniert nicht. Um das zu lösen, werden wir eine CLI verwenden, die aus unserem Sourcecode (TypeScript) browser-kompatiblen Code (natives JavaScript) kompiliert. Die besagt CLI dafür heißt tsc. Damit die CLI anständig funktioniert, müssen ein paar Ändeurngen in der tsconfig.json vorgenommen werden. In den compilerOptions müssen folgende Einträge enthalten sein:

        "outDir": "./dist",
        "rootDir": "./src"

Zudem muss im Top level "include": ["src/**/*.ts"] definiert sein.

Fertig konfiguriert lässt sich das Ganze dann einfach über npx tsc ausführen.

Mit diesem Command wird der Code in src/main.ts gelesen, zu JavaScript Code kompiliert, und in einem Verzeichnis dist ausgespuckt. Es wurde nach dem Ausführen des Commands nun also ein Ordner dist mit einer Datei main.js darin erstellt.

Wenn wir nun in der .html Datei sagen, dass das Script nicht mehr aus src/main.js sondern aus dist/main.js gelesen werden soll, sieht man, dass alles wieder funktioniert.

Das dist Verzeichnis nimmt man übrigens üblicherweise in die .gitignore Datei mit auf, weil darin nur automatisch generierter Code liegt. In Git soll aber nur der Sourcecode liegen.

4. Aufsetzen eines lokalen Webservers

Aktuell ist es nun so, dass wir nach jeder Änderung in main.ts erstmal den TypeScript Compiler mit npx tsc drüberjagen müssen. Das wollen wir natürlich nicht. Zudem müssen wir jedes Mal die Seite neu laden, damit wir unsre kompilierten Änderungen im .js File sehen. Auch das wollen wir nicht.

Damit der .js File sich automatisch updated, wenn wir etwas in main.ts ändern, können wir den TypeScript Compiler mit der Flag -w aufrufen, also so: npx tsc -w. -w steht für Watcher, und dies bewirkt, dass der Terminal Tab quasi "blockiert" und permanent auf Änderungen in in der konfigurierten rootDir ("./src") lauscht und diese direkt neu kompiliert – Also unser .js File updatet.

Damit Änderungen im Quellcode nun auch sofort ein Neuladen im Browser verursachen, können wir unsre index.html nicht mehr lokal aufrufen. Stattdessen werden wir auf unsrem Rechner einen "lokalen Webserver" aufsetzen, der – ähnlich wie der tsc Watcher – auf Änderungen im /dist Verzeichnis lauscht, und dem Browser sagt: "Wenn sich hier was ändert, lade die Seite neu".

Dafür installieren wir neue Dependencies:

npm i express
npm i -D nodemon livereload connect-livereload

nodemon ist ähnlich wie node eine JavaScript Runtime CLI, mit der sich ein Node Server hochfahren lässt. Anders als node merkt nodemon aber, wenn sich Dateien ändern, und startet dann den Express Server neu. express ist eine Library, die es ermöglicht dann lokal den eigentlichen Webserver aufzusetzen. livereload und conntect-livereload sind Libraries, die in Express eingebunden werden können um ein Refresh-Signal an den Browser zu senden.

Dafür brauchen wir einmal eine neue Datei server.js in unsrer Root Dir (parallel zur package.json) mit folgendem Inhalt:

const express = require("express");
const app = express();
const livereload = require("livereload");
const connectLiveReload = require("connect-livereload");

const liveReloadServer = livereload.createServer();
liveReloadServer.server.once("connection", () => {
    setTimeout(() => {
        liveReloadServer.refresh("/");
    }, 100);
});

app.use(connectLiveReload());

app.use(express.static("public"));
app.use("/dist", express.static("dist"));

app.listen(3000, function () {
    console.log("App is listening on port 3000!");
});

Wenn wir nun npx nodemon server.js in der Konsole ausführen, starten wir mit Nodemon dieses Script, welches effektiv den lokalen Webserver unter http://localhost:3000 hostet. Unter dieser Adresse können wir nun unsere Seite aufrufen.

Als letzte Anpassung muss nun noch in der .html Datei der Pfad zur .js Datei geändert werden. Auf einem Webserver kann man nämlich nicht mit ../ über die Root Directory hinaus nach oben raus gehen. Deshalb haben wir in server.js auch explizit den dist Ordner unter /dist zur Verfügung gestellt. Wenn wir nun also den Pfad zu /dist/main.js ändern, funktioniert wieder alles.

Und wenn nun etwas in main.ts geändert wird, aktualisiert sich direkt der Browser, weil durch den TSC Watcher direkt der .js File in /dist geupdatet wird, und durch Nodemon das Update in /dist einen Refresh im Browser triggert.

5. Scripts

Um uns alles noch ein wenig einfacher zu machen, können wir zuletzt in unsrer package.json Scripts definieren. Aktuell schreiben wir also jedes Mal npx tsc -w und npx nodemon server.js um unsre Entwicklungsumgebung zu starten. npm lässt uns mit Hilfe des run Befehls aber auch Scripts aus der package.json auszuführen. Heißt also, wenn wir in der package.json folgendes einfügen:

    "scripts": {
        "ts": "tsc -w",
        "dev": "nodemon server.js"
    },

können wir einfach npm run ts und npm run dev benutzen, was gerade wenn man komplexere Startbefehle verwendet, eine wahre Erleichterung sein kann.