Build-Zeit-Artefakte
Ein Modul kann ein Verzeichnis ausliefern, das Konsumenten zur Build-Zeit statt
zur Laufzeit verwenden — am nützlichsten ein Paket, gegen das andere Module kompilieren. Wippy
nennt diese Artefakte: gewöhnliche WAPP-Filesystem-Ressourcen, markiert mit
meta.artifact.format.
So erreicht ein geteiltes Paket ein Modul in einem anderen Repository. Ein Pfad-Alias löst nur innerhalb eines Repos auf; ein Artefakt reist mit dem Modul.
Die Design-Schicht erklärt, was in ein solches Paket gehört und was nicht; diese Seite ist der Mechanismus, der es ausliefert.
Ein Artefakt deklarieren
Der Produzent deklariert ein normales fs.directory und markiert es mit einem Format:
# src/_index.yaml
entries:
- name: package_fs
kind: fs.directory
meta:
comment: Das npm-Paket, das Konsumenten zur Build-Zeit materialisieren.
artifact:
format: node-package
directory: ./package
Sonst ändert sich nichts: Die Ressource wird wie jedes andere fs.directory in das WAPP
eingebettet — listen Sie sie unter embed: in wippy.yaml auf oder übergeben Sie --embed an
wippy publish und wippy pack; ein nicht eingebettetes Verzeichnis wird weder gepackt
noch validiert. Deklarierte Artefakte werden beim Veröffentlichen des Moduls und beim
Packen der Anwendung validiert, sodass ein fehlerhaftes Artefakt beim Veröffentlichen
scheitert statt bei einem Konsumenten.
Formate
Ein Format-Adapter entscheidet, wie ein Verzeichnis validiert wird, welche Identität es hat und wo es landet. Wippy liefert eines eingebaut mit:
| Format | Besitzt Teilbaum | Validiert |
|---|---|---|
node-package |
npm/ |
package.json |
node-package erfordert einen name und eine semantische version und lehnt die
Lifecycle-Skripte preinstall, install, postinstall und prepare ab — ein
materialisiertes Paket darf bei der Installation nichts ausführen. Es schreibt nach
npm/<Paketname> unterhalb der Materialisierungswurzel.
Das Format muss in der Binärdatei registriert sein, die die Arbeit erledigt. Hosts können zusätzliche Formate registrieren; doppelte Namen und überlappende Wurzeln werden abgelehnt.
Materialisieren
Meistens führen Sie nichts aus. Materialisierte Ausgaben werden automatisch abgeglichen während:
- vollständigem und gezieltem
wippy installundwippy update - Kaltstart
- Hub-gestützter dynamischer Installation, Aktualisierung und Deinstallation
Vollständige Installation, Aktualisierung, Kaltstart und Laufzeit-Abgleich von Abhängigkeiten sind exakt: veraltete Ausgaben werden entfernt. Eine gezielte Installation überlagert nur die ausgewählten Module und bewahrt Ausgaben, die zu nicht ausgewählten Modulen gehören.
Lokale Modulersetzungen durchlaufen denselben Validierungs- und Materialisierungslebenszyklus wie gepackte Ressourcen, sodass sich das Artefakt eines ersetzten Moduls wie ein veröffentlichtes verhält.
Explizit materialisieren
Für einen Build-Schritt, der das Artefakt braucht, bevor die Laufzeit beteiligt ist, stellt die CLI es direkt bereit:
wippy artifacts materialize <pack.wapp> <namespace:name> [--root <directory>]
--root verwendet standardmäßig .wippy. Die Ressource muss meta.artifact.format deklarieren,
und dieses Format muss in dieser CLI registriert sein.
Machen Sie sich klar, was dieses Kommando bewusst nicht tut: Es löst keine Modulabhängigkeiten
auf, verändert wippy.lock nicht, ruft keine Paketmanager auf und nimmt nicht an der
Laufzeitkomposition teil. Es validiert ein Artefakt aus einem WAPP und schreibt es auf die Festplatte.
Wo die Ausgabe landet
artifact.materialization_root konfiguriert die von der Anwendung besessene Ausgabewurzel.
Der Standardwert ist das übergeordnete Verzeichnis des Vendor-Verzeichnisses für Abhängigkeiten. Jedes Format besitzt
darunter einen nicht überlappenden Teilbaum, sodass die node-package-Ausgabe immer unter
<root>/npm/ liegt.
Materialisierung ist transaktional. Inhalte werden validiert und bereitgestellt, verwaltete Wurzeln werden unter einer Prozesssperre atomar getauscht, ein Fehler rollt mit der umgebenden Registry-Transaktion zurück, und ein unterbrochener Tausch wird beim nächsten Lauf wiederhergestellt.
Ausgearbeitetes Beispiel: ein geteiltes Frontend-Paket
Ein Produzentenmodul, dessen einzige Aufgabe das Veröffentlichen eines Pakets ist — es liefert zur Laufzeit nichts aus:
# platform/ui-kit/src/_index.yaml
version: "1.0"
namespace: kickside.ui_kit
entries:
- name: package_fs
kind: fs.directory
meta:
artifact:
format: node-package
directory: ./package
Ein Konsument materialisiert es in seinen eigenen Baum, bevor er Abhängigkeiten installiert:
wippy artifacts materialize kickside-ui-kit-1.5.0.wapp \
kickside.ui_kit:package_fs --root ./.wippy
Das schreibt ./.wippy/npm/@kickside/ui-kit. Der Konsument greift es mit einem
gewöhnlichen Workspaces-Glob auf, sodass die Auflösung von da an schlichte Node-Auflösung ist:
{
"workspaces": ["./.wippy/npm/@*/*"]
}
npm install
Zwei Dinge lohnen sich, aus dieser Form zu übernehmen:
- Das Paket ist ein eigenes Modul, kein Verzeichnis in einem größeren. Das
Artefakt trägt seine eigene
package.json-Version, und es an ein Modul zu binden, das sich aus unabhängigen Gründen ändert, erzwingt jedes Mal ein Release des einen, wenn das andere sich bewegt. - Der Konsument löst es als normale Abhängigkeit auf. Einmal materialisiert gibt es keinen Wippy-spezifischen Importpfad, und genau das lässt dieselbe Quelle innerhalb des Monorepos und außerhalb davon bauen.
Von Anfang bis Ende: Erstellen, Dev-Loop, CI
Den Produzenten erstellen
Für ein Paket-Artefakt gibt es meist nichts zu bauen — das Verzeichnis ist das Liefergut. Ein CSS-Vokabularpaket besteht nur aus Dateien plus einem Manifest:
platform/ui-kit/
├── src/_index.yaml # deklariert package_fs als Artefakt
└── package/ # das Verzeichnis, das zum npm-Paket wird
├── package.json
├── kx-card.css
└── kx-state.css
{
"name": "@kickside/ui-kit",
"version": "1.5.0",
"type": "module",
"sideEffects": ["*.css"],
"exports": {
"./kx-card.css": "./kx-card.css",
"./kx-state.css": "./kx-state.css"
},
"files": ["kx-card.css", "kx-state.css", "package.json"]
}
sideEffects ist für ein reines CSS-Paket entscheidend: Ohne dieses Feld steht es einem Bundler
frei, ein importiertes Stylesheet als toten Code zu behandeln und zu verwerfen.
Die Paketversion muss der Modulversion entsprechen. wippy publish
validiert das und verweigert eine Abweichung, also erhöhen Sie beide gemeinsam. Das ist auch der
Grund, einem geteilten Paket ein eigenes Modul zu geben, statt es in ein
größeres zu verschachteln — sonst erzwingt jede unabhängige Änderung am Host-Modul ein
Release des Pakets und umgekehrt.
Veröffentlichen
# validieren, ohne zu veröffentlichen
wippy publish --dry-run --version 1.5.0 --embed package_fs
# veröffentlichen
wippy publish --create --module-type library --module-visibility public --version 1.5.0 --embed package_fs
Deklarierte Artefakte werden als Teil des Veröffentlichens validiert, sodass eine package.json, die die Regeln des Formats verletzt, hier abgelehnt wird und nicht erst im Build eines Konsumenten.
Der Dev-Loop
Bei jeder Änderung zu veröffentlichen ist kein Dev-Loop. Packen Sie den Produzenten lokal und richten Sie den Materialisierungsschritt des Konsumenten stattdessen auf diese Datei:
# aus dem Produzentenmodul
wippy pack /tmp/ui-kit-dev.wapp --embed package_fs
# Konsumenten materialisieren aus dem lokalen Pack statt aus dem veröffentlichten
UI_KIT_WAPP=/tmp/ui-kit-dev.wapp make ui-kit MOD=workflows
Behalten Sie diese Überschreibung als einzigen Unterschied zwischen Dev-Pfad und CI — eine Umgebungsvariable, die die Pack-Datei auswählt, während alles Nachgelagerte identisch bleibt. Ein Dev-Loop, der anders materialisiert als CI, sagt CI nicht mehr voraus.
Einbindung in make und CI
Machen Sie den Materialisierungsschritt zu einer Voraussetzung des Konsumenten-Builds, nicht zu etwas, an dessen Ausführung sich jemand erinnern muss:
UI_KIT_WAPP ?=
build:
@case " $(UI_KIT_CONSUMERS) " in *" $(MOD) "*) $(MAKE) ui-kit MOD=$(MOD);; esac
cd $(call fe_dir,$(MOD)) && npm run build
CI braucht dann überhaupt keinen artefaktspezifischen Schritt: Es führt dasselbe make build aus,
UI_KIT_WAPP ist nicht gesetzt, also läuft der Abruf-und-Materialisieren-Pfad gegen die in
build-inputs gepinnte veröffentlichte Version. Ein frischer Checkout kann nicht gegen ein
veraltetes oder fehlendes Paket kompilieren, und ein Mitwirkender, der nie von
Artefakten gehört hat, bekommt trotzdem einen korrekten Build.
Was Sie weiterhin selbst zusammenbauen müssen
wippy artifacts materialize ist bewusst eng gefasst, sodass ein Build, der ein Artefakt
konsumiert, derzeit vier Schritte selbst zusammenklebt. Zu wissen, welche vier,
erspart deren Wiederentdeckung:
1. Das .wapp beschaffen. Das Kommando nimmt einen Pfad zu einer Pack-Datei, keine Modul-Referenz,
und löst keine Abhängigkeiten auf — also muss etwas den Produzenten zuerst holen. Das
praktikable Muster ist ein winziges Wippy-Projekt, dessen einzige Aufgabe es ist, ihn zu pinnen und herunterzuladen:
# build-inputs/wippy.lock — ein Projekt, das nur zum Abrufen existiert
directories:
modules: .wippy
src: ./src
modules:
- name: kickside/ui-kit
version: 1.5.0
hash: be1eafd5…
( cd build-inputs && wippy install )
wapp=$(ls build-inputs/.wippy/vendor/kickside/ui-kit-*.wapp | grep -v sha256 | sort | tail -1)
Es hier statt im Anwendungs-Lock zu pinnen, hält eine Build-Zeit-Eingabe aus dem Laufzeit-Abhängigkeitsgraphen heraus.
2. Einmal pro Konsument materialisieren, in eine Wurzel, die der Paketmanager des Konsumenten sehen kann:
wippy artifacts materialize "$wapp" kickside.ui_kit:package_fs --root ./ui/.wippy
3. Die package.json des Konsumenten verdrahten. Materialisieren schreibt Dateien; es
bearbeitet keine Manifeste. npm verlinkt das Paket nur, wenn der Konsument sowohl
den Workspace-Glob als auch die Abhängigkeit deklariert:
{
"workspaces": ["./.wippy/npm/@*/*"],
"dependencies": { "@kickside/ui-kit": "*" }
}
Die Version ist *, weil das materialisierte Paket seine eigene trägt. Skripten Sie
das und machen Sie es idempotent — fehlt die Verdrahtung, scheitert der Build viel
später mit einem nackten ENOENT auf einem Stylesheet, was sich wie eine fehlende Datei
liest statt wie fehlende Verdrahtung.
4. Den Paketmanager ausführen. materialize ruft keinen auf, also liegt
npm install nach Schritt 3 bei Ihnen.
Zusammen, in einem Target, das das konsumierende Modul als Parameter nimmt:
ui-kit:
@set -e; \
( cd build-inputs && $(WIPPY) install ); \
wapp=$$(ls build-inputs/.wippy/vendor/kickside/ui-kit-*.wapp | grep -v sha256 | sort | tail -1); \
test -n "$$wapp" || { echo "no ui-kit .wapp; is the module published?"; exit 1; }; \
$(WIPPY) artifacts materialize "$$wapp" kickside.ui_kit:package_fs --root $(DIR)/.wippy; \
cd $(DIR) && node ../../scripts/wire-ui-kit.mjs && npm install --no-audit --no-fund
Machen Sie das gesamte Target zu einer Voraussetzung des Konsumenten-Builds, damit ein frischer Checkout nicht gegen ein veraltetes oder fehlendes Paket kompilieren kann.
Nicht im Umfang
Artefakte führen absichtlich keinen zweiten Resolver, keine Paket-Registry, kein Archivformat, kein Lock-Schema, keine Hub-API und kein Modulmanifest ein. Semantik reiner Build-Abhängigkeiten, Weiterverbreitungsrichtlinien und Host-ABI-Validierung sind separate Belange und werden hier nicht gelöst.
Verwandt
- Abhängigkeitsverwaltung — Module und lokale Ersetzungen auflösen
- Veröffentlichen — was ein veröffentlichtes Modul enthält
- Die Design-Schicht — warum ein geteiltes Frontend-Vokabular überhaupt als Paket ausgeliefert wird