Sorry, what?
Wir würden gerne verschiedene Pakete anlegen, die Bausteine fürs Frontend enthalten.
Das können zum Beispiel Symfony-Bundles als Composer-Pakete sein, weil Bundles sowohl Twig Components wie auch öffentlich verfügbare Assets enthalten können. Aber auch z. B. SASS-Module könnten dazugehören oder JavaScript-Dateien, die im Rahmen des Build-Prozesses von übergeordneten Modulen als ES6-Modul importiert oder in SASS mit @use o. ä. verwendet werden sollen.
In einem einfacheren Fall wäre der Code eventuell nicht in einem Composer-Paket und ein Symfony-Bundle, sondern nur ein normales NPM-Paket.
Das bringt mehrere Fragen mit sich.
Zum Beispiel für den Fall von Symfony-Bundles mit wiederverwendbaren Twig Components müssen wir Composer verwenden, um die notwendigen Abhängigkeiten bzw. Voraussetzungen zu deklarieren. Das bedeutet, das letztlich Composer entscheidet, welche Version eines solchen Pakets in vendor/ installiert wird.
Je nach der gewählten Version kann es dann aber sein, dass dieses Bundle z. B. noch eine JavaScript-Dependency (sagen wir mal lazysizes oder slick-slider) erfordert. Welche Abhängigkeiten bzw. Versionen genau das sind, hängt von der installierten Version des Composer-Pakets ab.
An dieser Stelle können uns das ab Yarn v2 verfügbare portal:-Protokoll helfen:
{
"packageManager": "[email protected]",
"dependencies": {
"dies-das": "portal:vendor/dies/das"
}
}Diese package.json aktiviert für das Projekt Yarn >= 2 und definiert, dass bei der Auflösung von Dependencies auch die Anforderungen aus der package.json in vendor/dies/das berücksichtigt werden sollen. Dieses Verzeichnis muss vorhanden sein, wenn yarn gestartet wird - d. h. wir müssen zunächst das composer install ausführen.
Yarn wird dann auch die Abhängigkeiten dieses Pakets mit berücksichtigen. Insbesondere wird auch ein yarn install ggf. die gewählten Versionen in der yarn.lock aktualisieren, wenn Composer eine neuere Version des Pakets installiert hat, die andere Anforderungen mitbringt als die vorherige Version.
Im Beispiel in diesem Repo ist das vendor/-Verzeichnis simuliert bzw. eingecheckt, wir brauchen kein Composer ausführen. Die dies/das-Dependency hängt in JavaScript von weiteren NPM-Paketen ab, die automatisch mit installiert werden.
Ein über Composer installiertes Paket, das in der root package.json mit portal: verlinkt wird, kann seinerseits portal:-Links in der eigenen package.json definieren. So kann es auch auf die npm-Dependencies der Composer-Pakete hinweisen, die es selbst als eigene Composer-Abhängigkeit deklariert hat.
Es muss dabei allerdings portal:../../[vendorname]/[paketname] geschrieben werden, damit Yarn und der NPM Module Loader die tatsächliche Verzeichnisstruktur richtig erkennen und z. B. das Hoisting korrekt durchführen.
Möchte man ein solches Composer-Paket stand-alone installieren, muss dieser Pfad angepasst werden - er sollte dann direkt vendor/ statt ../.. enthalten. Das ist eventuell über Composer Install-Skripte machbar, hat aber in jedem Fall den unschönen Nebeneffekt, dass die Datei mit uncommitted changes zurückbleibt.
Alle NPM-Abhägigkeiten können über den Modul-Import-Mechanismus von ES6 aufgelöst werden. Dabei kann es sogar vorkommen, dass an verschiedenen Stellen unterschiedliche Versionen einer Dependency, z. B. lazysizes gefunden und verwendet werden.
Das ist aus JavaScript-Sicht erstmal kein Problem und beunruhigt uns nicht weiter.
Nochmal zu bestätigen wäre, dass auch Webpack beim Bundling solcher Abhängigkeitsstrukturen richtig vorgeht, aber davon möchten wir eigentlich ausgehen.
Ein Nebenthema ist, dass wir es aus Performance-Aspekten nicht gut finden, wenn unnötig mehrere verschiedene Versionen von Dependencies ausgeliefert werden. Das ist aber ein Problem für eine getrennte Suche nach geeignetem Tooling, das auf solche Redundanzen hinweisen könnte und Vorschläge machen könnte, ob/wie es z. B. mit expliziten Yarn set resolution-Anweisungen behoben werden könnte.
In unseren Frontend-Paketen wird die Anfordeurng aufkommen, auch SASS-Dateien mit Mixins, Partials u. ä. zur Verfügung stellen zu können, die dann von anderen Paketen in einem größeren Kontext genutzt und eingebaut werden können. Gleichzeitig müssen sich diese Dateien auch auf Dinge aus anderen Paketen beziehen können, die als Dependencies deklariert wurden.
Eine interessante Lösung scheint das SASS pkg: Protokoll und der darauf basierende Node Package Importer zu sein:
- https://sass-lang.com/blog/announcing-pkg-importers/
- https://sass-lang.com/documentation/js-api/classes/nodepackageimporter/
- sass/sass#2739
Damit können in @use und ähnlichen Befehlen Pfadangaben der Form pkg:paketname/datei gemacht werden. paketname wird dabei auf Basis der Node-Regeln aufgelöst. Der besondere Cloud: Die Auflösung berücksichtigt sogar die Versionen der NPM-Pakete korrekt, wie sie sich aus lokaler Sicht der SASS-Datei ergeben.
Ausprobieren kann man das, indem man node compile.js ausführt. Es ist das minimale Beispiel (ohne Gulp drumherum), das die Datei test.scss kompiliert und anzeigt. Die Dateien vendor/dies/das/style.scss und vendor/foo/bar/style.scss werden über zwei Stufen eingebunden. Beide beziehen sich auf pkg:@webfactoryde/baseline-reset. Sie deklarieren aber unterschiedliche Versionen davon als Abhängigkeit und bekommen jeweils "ihre" Version korrekt aufgelöst.