Tanuljuk meg, hogyan működnek a modulcsomagolók, majd írjunk egyet

Helló! Üdvözlet, üdvözlet, nagyon jó, hogy itt vagy! Ma egy igazán egyszerű JavaScript modul csomagot fogunk építeni.

Mielőtt nekilátnánk, szeretnék néhány elismerést adni. Ez a cikk nagyban felhasználja a következő forrásokat:

  • A JavaScript modulcsomagoló szétválasztása - Luciano Mammino
  • Minipack - Ronen Amiel

Oké, kezdjük azzal, hogy mi is a modulcsomagoló valójában.

Mi a modulcsomagoló?

A modulcsomagoló olyan eszköz, amely összegyűjti a JavaScript darabjait és azok függőségeit, és egyetlen fájlba csomagolja őket, általában a böngészőben történő felhasználásra. Lehet, hogy olyan eszközöket használt, mint a Browserify, a Webpack, a Rollup vagy a sok más eszközt.

Rendszerint egy bejegyzésfájllal kezdődik, és onnan összegyűjti az adott bejegyzésfájlhoz szükséges összes kódot.

A csomagnak két fő szakasza van:

  1. Függőség feloldása
  2. Csomagolás

Egy belépési ponttól kezdve (például app.jsfent) a függőség-felbontás célja, hogy megkeresse a kód összes függőségét (a működéséhez szükséges egyéb kódrészleteket), és felépítsen egy grafikont (az úgynevezett függőségi grafikont).

Ha ez megtörtént, akkor a függőségi diagramot egyetlen fájlba csomagolhatja vagy konvertálhatja, amelyet az alkalmazás használhat.

Kezdjük a kódunkat néhány importtal (később részletezem az okát).

Függőségmegoldás

Az első dolog, amit meg kell tennünk, hogy kitaláljuk, hogyan akarunk egy modult képviselni a függőség feloldási szakaszában.

Modul ábrázolás

Négy dologra lesz szükségünk:

  • A fájl neve és azonosítója
  • A fájl honnan származik (a fájlrendszerben)
  • A fájl a fájlban
  • Milyen függőségekre van szüksége a fájlnak

A grafikon struktúrája úgy épül fel, hogy rekurzív módon ellenőrzi az egyes fájlokban lévő függőségeket.

A JavaScript-ben az ilyen adatsor reprezentációjának legegyszerűbb módja egy objektum lenne.

A createModuleObjectfenti függvényt tekintve a figyelemre méltó rész az úgynevezett függvény meghívása detective.

Nyomozó egy könyvtár, amely képes megtalálja az összes hívás a require () nem számít, milyen mélyen beágyazott , és arra használják, azt jelenti, hogy kerülje el a saját AST bejárás!

Egy dolgot meg kell jegyeznünk (és ez szinte minden modulcsomagban ugyanaz), hogy ha valami furcsa módon próbálkozik, például:

const libName = 'lodash'const lib = require(libName)

Nem fogja megtalálni (mert ez a kód végrehajtását jelentené).

Tehát mit ad ennek a függvénynek a modul útvonaláról való futtatása?

Mi a következő lépés? Függőség feloldása.

Oké, még nem egészen. Először egy modultérképnek nevezett dologról szeretnék beszélni.

Modultérkép

Ha modulokat importál a Node-ba, akkor relatív importálást, például require('./utils'). Tehát amikor a kód ezt hívja, honnan tudhatja a kötegelő, hogy mi a megfelelő ./utilsfájl, amikor minden csomagolva van?

Ezt a problémát oldja meg a modultérkép.

Modulobjektumunknak egyedi idkulcsa van, amely az „igazság forrása” lesz. Tehát amikor elvégezzük a függőség-feloldást, minden modulhoz vezetünk egy listát a szükséges nevekről, azonosítójukkal együtt. Így futás közben megszerezhetjük a megfelelő modult.

Ez azt is jelenti, hogy az összes modult tárolhatjuk nem beágyazott objektumban, az id kulcsot használva.

Függőségmegoldás

Oké, tehát elég sok összeg folyik a getModulesfüggvényben. Fő célja a root / entry modul indítása, és a függőségek rekurzív keresése és megoldása.

Mit értek a „függőségek megoldása” alatt? A Node-ban van egy úgynevezett dolog require.resolve, és a Node hogyan tudja kitalálni, hogy hol van a kívánt fájl. Ennek oka az, hogy viszonylag vagy node_modulesmappából importálhatunk .

Szerencsénk, van egy npm modul, resolveamely ezt az algoritmust valósítja meg számunkra. Csak át kell adnunk a függőség és az alap URL argumentumokat, és ez mindent megtesz nekünk.

Ezt a felbontást a projekt minden moduljának minden függőségére el kell végeznünk.

Készítjük a mapkorábban említett modultérképet is.

A függvény végén marad egy megnevezett tömb, modulesamely a projektünk minden moduljához / függőségéhez modulobjektumokat tartalmaz.

Most, hogy ez megvan, folytathatjuk az utolsó lépéssel: a csomagolással!

Csomagolás

A böngészőben nincs olyan modul (fajta). De ez azt jelenti, hogy nincs szükség funkcióra, és nincs module.exports. Tehát annak ellenére, hogy minden függőségünk megvan, jelenleg nincs módunk modulként használni őket.

Gyári modul funkció

Adja meg a gyári funkciót.

A gyári függvény egy olyan függvény (ez nem konstruktor), amely egy objektumot ad vissza. Ez egy objektum-orientált programozás mintája, amelynek egyik felhasználása a kapszulázás és a függőségi injekció.

Jól hangzik?

A gyári függvény segítségével mind a saját requirefunkciónkat, mind module.exportsa csomagban felhasználható objektumokat be tudjuk injektálni, és megadhatjuk a modulnak a saját hatókörét.

Csomagolás

A következő a csomagoláshoz használt csomagolási funkció.

Ennek nagy része csak a JavaScript sablonszövege, szóval beszéljük meg, mit csinál.

Először fel modulesSource. Itt mindegyik modult átnézzük és átalakítjuk őket egy sor forráská.

Tehát milyen a kimenet egy modul objektumnál?

Most egy kicsit nehezen olvasható, de láthatja, hogy a forrás be van zárva. A gyári funkciót biztosítjuk modulesés requirehasználjuk, amint azt korábban említettem.

Bevezetjük azokat a modultérképeket is, amelyeket a függőségi felbontás során készítettünk.

Ezután a függvényben ezeket mind összekapcsoljuk, hogy az összes függőség nagy objektumát hozzuk létre.

A következő kódsor egy IIFE, ami azt jelenti, hogy amikor ezt a kódot futtatja a böngészőben (vagy bárhol másutt), a funkció azonnal lefut. Az IIFE egy másik minta a hatókör beágyazására, és itt használatos, így nem szennyezzük a globális hatókört a mi requireés a moduljainkkal.

Láthatja, hogy két szükséges funkciót definiálunk, requireés localRequire.

A Require elfogadja a modulobjektum azonosítóját, de a forráskódot természetesen nem id-ek segítségével írják. Ehelyett a másik függvényt használjuk localRequirearra, hogy a modulok által igényelt argumentumokat megkapjuk, és a helyes azonosítóvá konvertáljuk. Ez a modultérképeket használja.

Ezek után definiáljuk azt, module objectamelyet a modul feltölthet, és mindkét funkciót átadjuk a gyárnak, majd visszatérünk module.exports.

Végül felhívjuk require(0)a 0 azonosítójú modult, amely a belépési fájlunk.

És ez az! A modulcsomagolónk 100% -ban teljes!

Gratulálunk! ?

Tehát van egy működő modulcsomagolónk.

Ezt valószínűleg nem szabad használni a termelésben, mert hiányzik belőle rengeteg szolgáltatás (például a körfüggőségek kezelése, annak biztosítása, hogy minden fájlt csak egyszer elemezzenek, az es-modulokat stb.), De ez remélhetőleg jó ötletet adott Önnek arról, hogyan a modulcsomagolók valóban működnek.

Valójában ez körülbelül 60 sorban működik, ha eltávolítja az összes forráskódot.

Köszönjük, hogy elolvastad, és remélem, hogy örömmel nézted meg egyszerű modulcsomagolónk működését. Ha mégis, feltétlenül tapsolj? és ossza meg.

Ezt a cikket eredetileg a blogomon tették közzé.

Nézze meg a //github.com/adamisntdead/wbpck-bundler forrást