Workshop | NPM-Paket erstellen und via GitLab bereitstellen
Von Lukas Gruber am 28.02.2024
Ziel ist es ein npm-Paket zu erstellen, welches TypeScript verwendet und in einer Node.js-Anwendung eingebettet werden kann. Dazu wird es über GitLab für private Projekte zur Verfügung gestellt.
Ein paar Infos vorweg …
Es gibt mehrer Gründe dafür ein NPM-Paket zu erstellen. Diese sind unter anderem:
- Module … Module sind eine Sammlung von JavaScript Funktionen und Objekten, die von externen Anwendungen verwendet werden können. –> API
- Typdefinitionen … Teilen von TypeScript Typdefinitionen zwischen mehreren Anwendungen
- Programme/Scripts … Am Terminal ausführbare Binärdateien (z.B. nest new)
- Styling & Assets … Teilen von Styles (Tailwind, Bootstrap, …), und anderen Assets (Bilder, Schriftarten, etc.)
In diesem Artikel soll ein simples Modul inklusive Typdefinitionen erstellt werden. Dazu muss man zunächst zwischen JavaScript, ECMAScript und TypeScript unterscheiden.
JS – ECMA – TypeScript
JavaScript (JS) ist einerseits eine eingetragene Marke von Oracle und andererseits als Programmiersprache die Implementierung des ECMAScript Standards (https://ecma-international.org/publications-and-standards/standards/ecma-262/). Die Namensabweichung von ECMA zu Java besteht aufgrund dieser bestehenden Markenrechte seitens Oracle. Als Referenzquelle zu JavaScript können die Mozilla Developer Docs (https://developer.mozilla.org/) herangezogen werden. Als dritten im Bunde steht das von Microsoft entwickelte TypeScript (https://www.typescriptlang.org/) für eine Erweiterung dieser Sprache um Typdefintionen. Zur Ausführung wird TypeScript jedoch zu ECMAScript transpiliert.
Damit kann es losgegen!
Anlegen des Projekts
Im ersten Schritt wird mithilfe von VS-Code ein neues Projekt angelegt. Zunächst wird einfach ein neuer Ornder angelegt und dieser als Workspace geöffnet.
Anschließend wird im Terminal in den angelegten Ordner gewechselt und dort mit dem Befehl “npm init” das Modul initialisiert. Wichtig dabei ist der Name des Pakets. Hier wurde “@mit231519/mypackage” verwendet. Der Teil zwischen dem @ und dem Schrägstrich bezeichnet den Geltungsbereich des Pakets. Der Teil hinter dem Schrägstrich ist der Name des Pakets. Üblicherweise gehören alle Pakete im selben Geltungsbereich zusammen. Beispielsweise @angular/cli, @angular/core und @angular/material. Zudem enstpricht unser “mit231519” der Bezeichnung der GitLab-Gruppe die später angelegt wird. Also je nach dem wie die Gruppe benannt wird, dieser Name ist dann auch dem Paket-Geltungsbereich zu vergeben. Um Konflikte zu vermeiden sollte hier eine individuelle Bezeichnung gewählt werden.
Nach der Installation der notwendigen Paketet wird mit “npx tsc –init” die TypeScript Konfiguration erstellt. In der Konfiguration selbst sind dann noch einige Einstellungen zu treffen, siehe Video. Das .gitignore ist ebenfalls nicht zu vergessen da sonst der Node_Modules Folder mit am Repository landet.
Als Plattform wird das OnPremise FHSTP-GitLab verwendet. Dort wird eine neue Gruppe erstellt, die wie bereits erwähnt unserem Geltungsbereich entspricht. Und darin wiederum ein Projekt mit dem Namen des Pakets als Bezeichung. Vorraussetzung für den nächsten Schritt ist, das eine SSH Verbindung zum GitLab aufgesetzt wurde (Siehe: https://docs.gitlab.com/ee/user/ssh.html). Dann kann der existierende Ordner mittels der angezeigten Befehle gepush werden.
Der letzte Schritt besteht daraus mit “nest new” ein neues Testprojekt anzulegen in dem unser neues Paket getestet werden kann. Nest fragt uns danach welchen Paketmanager wir verwenden wollen. Der Einfachheit halber verwenden wir npm.
npm – yarn – pnpm
NPM (Node Package Manager) ist der Standard-Paketmanager von Node.js. Als Manager von Paketen erlaubt es NPM diese zu installieren, löschen sowie veröffentlichen. Für letzteres stellt NPM eine öffentliche Registry (https://npmjs.com) bereit, die als Standard-Abrufort fungiert wenn keine anderen Destinationen für Pakete angegeben werden. Yarn (Yet another resource negotiator) und PNPM (Performant NPM) sind zwei Paketmanager die später erstellt wurden um einige schwächen von NPM auszumerzen. So sind diese etwa um einiges schneller, verbrauchen weniger Speicher und sind sicherer indem sie die Zugriffe zwischen den Paketen nicht ermöglichen, wenn sie diese nicht im package.json File definiert haben.
Modul erstellen
Oft empfiehlt es sich bei anderen npm-Paketen zu prüfen wie diese geschrieben sind und dieses Format auf das eigene anzuwenden. Als Beispiel zeigt das Video TypeORM. Das Modul enthält ein index.ts File welches zentral alle zu exportierenden Funktionen, Objekte etc. enthält. Weiters werden im package.json File die Einstiegspunkte des Pakets definiert.
Als Testinhalt werden dem Paket eine “example” Testfunktion sowie ein Interface hinzugefügt und das erstellte index File als Einstiegspunkt im package.json File angegeben. Das Files Attribut lässt uns zudem definieren welche Files schlussendlich Teil des Pakets sein sollen.
Der Befehl “npm link” lässt uns schließlich einen System-Link zu unserem Paket herstellen. Das Paket ist dann unter den global installierten npm-Modulen einsehbar. Um es in der Test-Nestjs-App nutzen zu können wird im Terminal ein link in den lokalen node_modules erstellt.
Wollen wird die erstellte example-Funktion nun ausprobieren und die Nest-Applikation starten erhalten wir eine Fehlermeldung da unser Paket im es-Module Format geschrieben ist, während das Nest-Projekt das commonjs-Format nutzt.
Node.js Modulsystem CJS/ESM
Seit 2020 besitzt Node.js eine stabile Unterstützung des ES-Modulsystems. Dieses ist das offizielle JavaScript Modulsystem seit ES6 (ECMA 2015). Damit es global aktiviert wird muss im package.json das “type” Attribut auf “module” gesetzt werden. Alternativ können einzelne Files mit der Endung “.mjs” bzw. “.mts” deklariert werden um diese als ES-Module zu behandeln. Standardmäßig ist jedoch das bereits zuvor existente CommonJS (CJS)-Modulsystem aktiviert. Größtes Unterscheidungsmerkmal ist wohl der Import. Bei ES-Modulen erfolgt der Import asynchron und mit export/import. Bei CJS hingegen erfolgt dieser synchron mit module.exports/require. In neueren Node.js Versionen können Files explizit mit “.cjs” bzw. “.cts” als CJS-Modul markiert werden.
ESM/CJS-Bundle mit Tsup
Das Video zeigt zu Beginn das es funktionieren würde das Nest-Projekt auf das ES-System umzustellen um die Fehlermeldung zu umgehen. Da uns aber das endgültige System unbekannt ist, wollen wir das Paket sowohl im ESM als auch im CJS Format ausliefern. Dazu kann das npm-Paket Tsup verwendet werden.
Dazu erstellen wir im package.json ein neues build-Script und passen die Einstiegspunkte so an das bei einem “require” der commonjs Build verwendet wird und bei einem “import” der esmodule Build.
Wir versuchen die Nest-Applikation erneut zu starten –> und siehe da, es funktioniert ! 🙂
Manuell auf GitLab registieren
Um den Build in die GitLab registry zu pushen erstellen wir uns zunächst einen Access Token in der GitLab Gruppe. Das .npmrc File lässt npm feststellen woher ein Paket bzw. unser Geltungsbereich @mit231519 bezogen werden soll. Die selbe Url muss ebenfalls im package.json File angegeben werden um das Ziel zu definieren. Wichtig ist dabei die Projektnummer sowie den Scope entsprechend anzupassen. Der Access Token wird als Umgebungsvariable “CI_JOB_TOKEN” übergeben. Mit “npm puplish” wird das Paket gepusht. Anschließend sollte in der Registry auf GitLab das Paket aufscheinen. Wenn es geöffnet wird sollten alle Files darin enthalten sein die im package.json im Files Attribut angegeben wurden.
GitLab CI/CD
Um den Access Token nicht manuell angeben und das ebenso manuell bauen zu müssen, kann die GitLab CI/CD herangezogen werden. “npm version patch|minor|major” erlaubt es und die Paketversion automatisch auf die nächst höhere Stufe zu stellen und einen Commit zu erzeugen.
Das .gitlab-ci.yml File enthält die Konfiguration der Pipeline. Sie wird so konfiguriert das sie nur getriggert wird wenn ein Commit auf den Hauptpfad erfolgt und die Commit-Nachricht in Form von Semantic-Version erfolgt. Wird die Pipeline getriggert werden in einem Node.js 20 Image zunächst die npm-Module installiert. Danach wird das Paket mit “npm publish” in der Registry registiert. Der Token wird dabei automatisch aufgelöst. Der Build erfolgt als prebulish Hook im package.json File.
Damit die CI/CD am Fhstp Gitlab funktioniert müssen zudem die Shared Runners aktiviert sein und der entsprechende Tag muss dem Pipeline Job hinzugefügt werden.
Schlusswort
Schlussendlich haben wir ein funktionierendes npm-Paket erstellt welches automatisiert über GitLab für private Projekte zur Verfügung gestellt wird. Ein weiterer Schritt könnte es sein mit TypeDoc (https://typedoc.org/) automatisiert eine Dokumentation des Moduls zu erstellen. Interressant könnte es zudem sein ein ausführbares Programm mit dem Paket mitzuliefern (https://dev.to/orkhanhuseyn/making-your-npm-package-executable-1j0b). Jedoch gibt es im JavaScript-Universum auch noch andere Möglichkeiten der Paketbereitstellung. Deno (https://deno.com/) – eine alternative JavaScript Laufzeitumgebung – nutzt dazu etwa einen viel offeneren – browserorientierten – Ansatz.
* Titelbild wurde mit Bing Image KI erstellt
The comments are closed.