2017. dec. 12. - Szerző: Zentai Norbert

Böngésző oldali csomagkezelés jspm segítségével

A mai világban, ahol a single page application (SPA) egyre elterjedtebb, egyre nagyobb igény van dinamikus, kliens oldalon futó komponensekre és egyre bonyolultabb kódokat és könyvtárakat kell használni, elkerülhetetlen a dependency hell-nek nevezett jelenség.

"Milyen library-ből milyen verzió kell és milyen sorrendben kell betölteni?"

Egy modulbetöltő és csomagkezelő azt a kérdést hivatott nem csak megválaszolni, hanem egyenesen levenni a fejlesztő válláról.

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.

Konkluzió?

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!

Ha tetszett a cikk oszd meg másokkal is.