Wenn Sie schon länger mit JavaScript arbeiten, sind Sie wahrscheinlich schon einmal auf den folgenden Fehler gestoßen:
SyntaxError: Unexpected token 'export'
Alle paar Monate taucht dieser Fehler auf und ich fange an, mit Jest, TypeScript und ES-Modulen zu kämpfen, indem ich mysteriöse Konfigurationslösungen anwende, die ich online finde, ohne wirklich zu verstehen, warum. Diese Lösungen beinhalteten immer "kryptische" Konfigurationsänderungen, aber diese Lösungen enthielten nie eine Erklärung für das zugrunde liegende Problem.
Als ich kürzlich mit einem d3-delaunay -Import kämpfte, der mit ts-node perfekt funktionierte, aber in Jest explodierte, entdeckte ich schließlich sowohl die Ursache als auch eine viel bessere Lösung. Noch wichtiger ist, dass ich etwas darüber gelernt habe, wann man als Ingenieur tiefer graben muss und wann man einfach das Tool verwenden sollte, das funktioniert.
JavaScript ES Module vs. CommonJS: Der Hauptunterschied
Die Wurzel all dieser Schmerzen liegt in einem grundlegenden Unterschied, der selten klar erklärt wird:
CommonJS (das ursprüngliche Modulsystem von Node) lädt Module synchron zur Laufzeit:
const fs = require('fs'); // This actually executes code to load the module
ES-Module sind statisch - Importeund Exporte werden analysiert , bevor irgendein Code ausgeführt wird:
import fs from 'fs'; // This is just a declaration, resolved at compile time
Dies ist nicht nur ein Unterschied in der Syntax. Es handelt sich um ein anderes Ausführungsmodell. Wenn Node.js im CommonJS-Modus auf eine export Anweisung stößt, versucht es, diese Codezeile auszuführen, aber export ist nicht ausführbar, sondern eine statische Deklaration, die während der Parsing-Phase analysiert werden sollte.
Deshalb können Sie nicht einfach require gegen import austauschen und die Sache abhaken. Auch die Laufzeitumgebung muss den Modus wechseln.
Warum Tools damit zu kämpfen haben
Wenn Sie diesen Unterschied zwischen statisch und dynamisch verstehen, ergeben die meisten dieser "kryptischen" Konfigurationsänderungen plötzlich einen Sinn. Einige gängige Lösungen und was sie wirklich bedeuten:
"type": "module"in package.json → "Hey Node, behandle dieses Paket als statische ES-Module".- Transformationsmuster in Bundlern → "ES-Module zur Erstellungszeit in CommonJS konvertieren".
--experimental-vm-modules→ "Verwenden Sie eine andere Ausführungsmaschine, die beides versteht".
Bei all diesen Änderungen geht es im Grunde darum, den Tools mitzuteilen, wie sie die beiden grundlegend unterschiedlichen Welten von CommonJS und ES Modules überbrücken können.
Jests Nachrüstungsproblem vs. Vitests Neustart
An dieser Stelle kommt mein Problem ins Spiel. Ich hatte einen NodeJS-Backend-Server erstellt, der bei der Ausführung mit ts-node problemlos lief. Als Nächstes wollte ich einige Unit-Tests dafür schreiben und dazu das JavaScript-Testing-Framework verwenden: Jest. Nachdem ich Tests für meine Funktionen geschrieben hatte, die die npm-Bibliothek d3-delaunay verwendeten, stieß ich auf einige seltsame Fehler.
Bei der Fehlersuche habe ich festgestellt, dass d3-delaunay ein ES-Modul ist. Das schien die Ursache für meine Probleme zu sein. Ich habe viele der oben beschriebenen "magischen" Konfigurationsänderungen ausprobiert, aber keine davon hat funktioniert. Dann wechselte ich zu Vitest, und alles funktionierte einfach.
Das macht irgendwie Sinn. Jest wurde während der CommonJS-Ära entwickelt und hat seither die Unterstützung für ES-Module nachgerüstet. Deshalb fühlt sich die Arbeit mit ES-Modulen in Jest wie ein Kampf gegen das Framework an, denn das tun Sie im Grunde auch. Sie bitten ein CommonJS-eigenes Tool, so zu tun, als ob es ES-Module durch eine komplexe Reihe von Transformationen und Workarounds versteht.
Vitest hingegen wurde von Anfang an mit ES-Modulen entwickelt. Wenn Sie d3-delaunay in Vitest importieren, funktioniert es einfach. Keine Konfiguration, keine Transformationsmuster, keine experimentellen Flags. Vitest geht auch nahtlos mit CommonJS-Modulen um. Sie erhalten das Beste aus beiden Welten, ohne dass Sie sich um die Konfiguration kümmern müssen. Es ist keine Magie, sondern nur ein Tool, das für das von Ihnen verwendete Modulsystem entwickelt wurde.
Dies ist ein perfektes Beispiel dafür, wie Legacy in Software entsteht. Der frühe Erfolg von Jest bedeutete eine hohe Akzeptanz, was wiederum bedeutete, dass der Druck, die Abwärtskompatibilität aufrechtzuerhalten, groß war, was wiederum bedeutete, dass ES-Module eher ein nachträglicher Einfall als eine Designentscheidung waren.
Das Kaninchenloch-Dilemma: Wann man nicht tiefer graben sollte
An dieser Stelle geht es um mehr als nur um JavaScript-Module.
Als Ingenieure haben wir oft den Instinkt, alles genau zu verstehen. Der Gedankengang geht so: "Ich sollte wohl lernen, wie Bundler funktionieren" oder "Vielleicht sollte ich meinen eigenen Modullader schreiben, um das wirklich zu verstehen." Diese Neugier ist zwar im Allgemeinen eine Superkraft, aber sie kann auch ein Produktivitätskiller für mich sein.
Die Wahrheit ist: Sie müssen nicht jede Abstraktion verstehen, die Sie verwenden. Sie müssen nur genug verstehen, um effektiv arbeiten zu können und um zu erkennen, wann Sie tiefer gehen müssen und wann Sie einfach das Werkzeug verwenden sollten, das funktioniert.
Heben Sie sich die tiefgehenden Informationen für den Fall auf, dass Sie Zeit haben oder wenn ein Projekt dieses Maß an Verständnis erfordert. Es gibt immer mehr faszinierende Kaninchenlöcher als Zeit, sie zu erforschen!
In diesem Fall reicht das Wissen, dass ES-Module statisch (und nicht dynamisch) sind, aus, um zu verstehen, warum Tools Schwierigkeiten haben. Der Schlüssel liegt darin, gerade so viel zu verstehen, dass Sie eine fundierte Entscheidung für ein Tool treffen können, und nicht darin, ein Experte für jede Schicht des Stacks zu werden.
Zusammenfassung
Das Modul-Ökosystem von JavaScript kann sich wie ein Minenfeld anfühlen, aber das Kernproblem ist eigentlich ganz einfach: CommonJS- und ES-Module sind grundlegend unterschiedliche Ausführungsmodelle, nicht nur eine unterschiedliche Syntax.
Tools, die für ein System entwickelt wurden, haben mit dem anderen zu kämpfen. Jest hat die Unterstützung für ES-Module nachgerüstet; Vitest wurde von Anfang an für ES entwickelt. Deshalb fühlt sich das eine an, als ob es mit dem Framework kämpft, während das andere einfach funktioniert.
Als Ingenieure müssen wir nicht jedes Detail der Implementierung verstehen. Wir müssen genug verstehen, um gute Entscheidungen treffen zu können. Manchmal ist es die richtige Entscheidung, die Tools zu wechseln, anstatt mit der Konfiguration zu kämpfen.
Und manchmal ist es die beste technische Entscheidung, sich dem eigentlichen Problem zuzuwenden, das Sie zu lösen versuchen, anstatt sich in dem Versuch zu verlieren, die Werkzeuge zu verstehen, die eigentlich dazu gedacht waren, die Komplexität zu verbergen. Die Ironie.
Nachdem ich diesen Beitrag geschrieben habe, habe ich meinen NodeJS-Backend-Starter aktualisiert, um Vitest anstelle von Jest zu verwenden. Für mich bedeutet das: keine Kopfschmerzen mehr bei der Modulkonfiguration für zukünftige Projekte!
Verfasst von

Simon Karman
Simon Karman is a cloud engineer and consultant that builds effective cloud solutions ranging from serverless applications, to apis, to CICD pipelines, and to infrastructure as code solutions in both AWS and GCP.
Contact