Mi az a csomagkezelő és modulbetöltő?
A csomagkezelő feladata, hogy a különböző library függőségeit feloldja és telepítse. A fejlesztőnek nem kell azzal törődnie, hogy az adott library milyen más függőségekkel rendelkezik. Ez a függőségfeloldás fejlesztési időben releváns, futási időben nem. A modulbetöltő futási időben felelős azért, hogy a megfelelő library a megfelelő függőségét megkapja, a betöltés sorrendje helyes legyen, ne legyen duplán betöltve ugyan az a függőség.
Miért nem jó ide az npm?
A node_modules
mappát be kell járni. Nem egyértelmű azonnal, hogy egy hivatkozott dependencia hová települt. Lehet, hogy egy függőség a fájl melletti node_modules
mappában található, lehet, hogy egy mappával fentebb van a node_modules
. Ezt a mappabejárást nem tudjuk HTTP-n keresztül megtenni, egyértelműen kell tudni, mi hol helyezkedik el. A másik, kisebb probléma, amennyiben egy függőségből több verzió is kell. Ha az alkalmazásom a legfrissebb, forrón a sütőből kikerült 2.0.0-beta
verziót használja a some-amazing-lib
csomagból, ugyanakkor van két függőségem is, amik csak a normál 1.x.x
verziót használják, akkor az npm kétszer fogja letelepíteni az 1.x.x
verziót. Nem tudja felhozni a "global" területre, ugyanis ott az én szuperfriss verzióm van.
node_modules
some-amazing-lib (2.0.0-beta)
[modulfájlok]
pretty-good-package (1.0.1)
[modulfájlok]
node_modules
some-amazing-lib (1.2.3)
good-for-you (1.0.0)
[modulfájlok]
node_modules
some-amazing-lib (1.2.3)
my-app.js
Hogy oldja ezt meg a jspm?
A bejárási problémát a jspm (https://jspm.io/) úgy oldja fel, hogy minden telepítés után ír egy speciális konfigurációs fájlt SystemJS számára. SystemJS a jspm csomagkezelő modulbetöltője. SystemJS képes ezt a konfigurációs fájlt értelmezni, és innen tudja, hogy milyen csomagok hova vannak telepítve.
A duplikált függőséget úgy oldja meg a jspm, hogy minden telepített csomag egy szintre kerül, és a mappák neve határozza meg a library-k verzióját. Vagyis a fent telepített struktúra jspm alatt így néz ki:
jspm_packages
npm (az npm repository-ból jött ez, nem pl.: github-ról)
some-amazing-lib@2.0.0-beta
[modulfájlok]
some-amazing-lib@1.2.3
[modulfájlok]
pretty-good-package@1.0.1
[modulfájlok]
good-for-you@1.0.0
[modulfájlok]
jspm.config.js
Oké, de most, ha a my-app.js
kéri a some-amazing-lib
-et, akkor honnan fogja tudni SystemJS, hogy melyiket adja át? A jspm.config.js
nem csak a jspm_packages
mappa tartalmát írja le, hanem a csomagok közti függőséget is. Ha belenézünk, egy az alábbihoz hasonló felépítést láthatunk:
SystemJS.config({
baseURL: "/",
paths: {
"npm:": "jspm_packages/npm/"
},
packageConfigPaths: [
"npm:*.json"
]
map: {
"some-amazing-lib": "npm:some-amazing-lib@2.0.0-beta",
"pretty-good-package": "npm:pretty-good-package@1.0.1",
"good-for-you": "npm:good-for-you@1.0.0"
},
packages: {
"npm:pretty-good-package@1.0.1": {
map: {
"some-amazing-lib": "npm:some-amazing-lib@1.2.3",
}
},
"npm:good-for-you@1.0.0": {
map: {
"some-amazing-lib": "npm:some-amazing-lib@1.2.3",
}
}
}
});
Bontsuk egy kicsit szét mindezt. Első lépés, hogy megismerkedjünk a canonical névvel. A canonical név az a név, ami egyértelműen azonosít egy csomagot vagy fájlt. A jspm által létrehozott konfigurációban ez a registry:csomagnév@verzió
. Azaz minden csomagra canonical névvel kéne hivatkoznunk. Természetesen nem valami kecsegtető az, hogy függőség verzióváltásakor a forrásfájljainkban manuálisan kell kicserélni a hivatkozásainkat. Itt jön be a képbe a map
. A map
leírja, hogy amennyiben bizonyos névvel hivatkozunk egy függőségre, azt pontosan miként értelmezze SystemJS, mielőtt elkezdi betölteni.
Mi a helyzet az eltérő verziós csomagunknál? A jspm ismeri a csomag fogalmát úgy, hogy egy bizonyos canonical név alatt lévő fájlok nevéhez képes hozzátenni/maszkolni a map
részt. A fenti példában bármilyen fájl, ami az npm:pretty-good-package@1.0.1
alatt van és hivatkozik a some-amazing-lib
-re, az már a neki megfelelő, 1.2.3
verziót fogja megkapni.
A packageConfigPaths
rész SystemJS-nek nyújt extra infót, hogy a telepített csomag package.json
-féle fájlját hol találja meg. Ez a fájl írja le, hogy például mi a main
fájl.
A canonical név ezek után URL-lé alakul a baseURL
és paths
segítségével.
Konklúzió?
Konklúziót még nem szeretnék írni, ugyanis hátra maradt egy csomó megválaszolatlan részkérdés: Hogyan működik egy NodeJS-re írt library SystemJS-en? Hogyan működik a require
, ha a böngésző csak aszinkronosan képes fájlokat betölteni, de a require
szinkronos? Mit tudunk tenni, ha egy csomag nem viselkedik megfelelően böngészős környezetben? Ezekre a kérdésekre egy későbbi blogbejegyzés fog kitérni, addig is ajánlom, hogy próbáljátok ki a SystemJS - jspm párost!