JavaScript modulok 2. rész: Modulok csomagolása

A bejegyzés I. részében arról beszéltem, hogy melyek a modulok, miért használják a fejlesztők, valamint a programjaikba való beépítésük különféle módjairól.

Ebben a második részben kitérek arra, hogy mit is jelent pontosan a modulok „kötegelése”: miért csomagoljuk a modulokat, ennek különféle módjait és a modulok jövőjét a webfejlesztésben.

Mi a modulcsomagolás?

Magas szinten a modulok kötegelése egyszerűen az a folyamat, amikor a modulok csoportját (és azok függőségeit) egyetlen fájlba (vagy fájlcsoportba) összerendezik a megfelelő sorrendben.

Mint az internetes fejlesztés minden aspektusában, az ördög is a részletekben rejlik. :)

Miért kell egyáltalán csomagolni a modulokat?

Amikor a programot modulokra osztja, ezeket a modulokat általában különböző fájlokba és mappákba rendezi. Valószínű, hogy a használt könyvtárakhoz is lesz egy modulcsoport, például az Aláhúzás vagy a Reagálás.

Ennek eredményeként mindegyik fájlt fel kell venni a fő HTML fájlba a pt> tag, amelyet a böngésző akkor tölt be, amikor a felhasználó meglátogatja a kezdőlapot. Ha minden fájlhoz külön arate & l ; script> címkéket használ, az azt jelenti, hogy a böngészőnek külön-külön kell betöltenie az egyes fájlokat: egyet… egyet… egyet.

… Ez rossz hír az oldal betöltési idejéről.

A probléma kiküszöbölése érdekében az összes fájlunkat összegyűjtjük, vagy „összefűzzük” egy nagy fájlba (vagy adott esetben egy pár fájlba) a kérések számának csökkentése érdekében. Amikor azt hallja, hogy a fejlesztők az „építési lépésről” vagy a „gyártási folyamatról” beszélnek, akkor erről beszélnek.

A kötegelt műveletek felgyorsításának másik általános megközelítése a mellékelt kód „kicsinyítése”. A kicsinyítés a felesleges karakterek eltávolítása a forráskódból (pl. Szóköz, megjegyzések, új vonalkarakterek stb.), A tartalom teljes méretének csökkentése érdekében, a kód funkcionalitásának megváltoztatása nélkül.

A kevesebb adat kevesebb böngésző feldolgozási időt jelent, ami viszont csökkenti a fájlok letöltéséhez szükséges időt. Ha látott már olyan fájlt, amelynek „min” kiterjesztése volt, mint például az „underscore-min.js”, akkor valószínűleg észrevette, hogy a tömörített verzió elég apró (és nem olvasható) a teljes verzióhoz képest.

Az olyan feladatfuttatók, mint a Gulp és a Grunt, egyszerűvé teszik az összefűzést és a tömörítést a fejlesztők számára, biztosítva, hogy az ember által olvasható kód továbbra is elérhető legyen a fejlesztők számára, miközben a gépre optimalizált kódot böngészőkhöz kötik.

Milyen módon lehet modulokat csomagolni?

A fájlok összefűzése és tömörítése nagyszerűen működik, ha a szokásos modulminták egyikét használja (az előző bejegyzésben tárgyalták) a modulok meghatározásához. Csak annyit teszel, hogy egy csomó egyszerű vanília JavaScript kódot összeraksz.

Ha azonban nem natív modulrendszereket követ, amelyeket a böngészők nem tudnak értelmezni, mint például a CommonJS vagy AMD (vagy akár natív ES6 modulformátumok ), akkor speciális eszközt kell használnia a modulok megfelelő sorrendű böngészővé történő átalakításához. -barát kód. Itt lépnek színre a Browserify, a RequireJS, a Webpack és más „modulcsomagolók” vagy „modulbetöltők”.

A modulok kötegelése és / vagy betöltése mellett a modulcsomagolók rengeteg további funkciót kínálnak, mint például az automatikus újrafordítás kódja, amikor változtatásokat hajt végre, vagy forrás térképeket készít a hibakereséshez.

Menjünk át néhány általános modulcsomagolási módszeren:

Csomagolás CommonJS

Mint az 1. részből tudod, a CommonJS szinkron módon tölti be a modulokat, ami rendben lenne, kivéve, hogy ez nem praktikus a böngészők számára. Említettem, hogy ennek van egy megoldása - az egyik egy Browserify nevű modulcsomagoló. A Browserify egy olyan eszköz, amely a CommonJS modulokat állítja össze a böngészőhöz.

Tegyük fel például, hogy rendelkezik ezzel a main.js fájllal, amely egy modult importál egy számtömb átlagának kiszámításához:

Tehát ebben az esetben egy függőségünk van (myDependence). Az alábbi paranccsal a Browserify rekurzívan összegyűjti az összes szükséges modult a main.js-től kezdődően egyetlen fájlba, a bundle.js nevű fájlba:

A böngésző igazolása úgy történik, hogy beugrik elemezni az egyes igényelt hívások AST-jét, hogy bejárja a projekt teljes függőségi grafikonját. Miután kiderült, hogyan épül fel a függősége, az összeset a megfelelő sorrendben egyetlen fájlba foglalja. Ezen a ponton mindössze annyit kell tennie, hogy beilleszt egy kislemezt pt> címkézze meg a „bund le.js” fájlt a html-be, hogy az összes forráskód egy HTTP-kérelemben legyen letöltve. Bam! Csomagolva menni.

Hasonlóképpen, ha több, több függőséggel rendelkező fájlja van, akkor egyszerűen meg kell mondania a Böngészőnek, hogy mi a bejegyzésfájlja, és dőljön hátra, miközben varázslatos.

A végtermék: a csomagba csomagolt fájlok előkészítve és készen állnak az olyan eszközökre, mint a Minify-JS, a tömörített kód csökkentésére.

Az AMD csomagolása

Ha AMD-t használ, akkor érdemes olyan AMD betöltőt használni, mint a RequireJS vagy a Curl. A modulbetöltő (egy csomaghoz képest) dinamikusan betölti azokat a modulokat, amelyekre a programnak futtatnia kell.

Emlékeztetőül: az AMD egyik fő különbsége a CommonJS-sel szemben az, hogy aszinkron módon tölti be a modulokat. Ebben az értelemben az AMD-vel technikailag nincs szükség építési lépésre, ahol a modulokat egy fájlba csomagolja, mivel a modulokat aszinkron módon tölti be - ez azt jelenti, hogy fokozatosan csak azokat a fájlokat tölti le, amelyek feltétlenül szükségesek a programot ahelyett, hogy az összes fájlt egyszerre töltené le, amikor a felhasználó először meglátogatja az oldalt.

A valóságban azonban az összes felhasználói művelet idővel bekövetkező nagy volumenű kéréseinek nincs sok értelme a gyártásban. A legtöbb webfejlesztő továbbra is építőeszközöket használ az AMD-modulok csomagolásához és kicsinyítéséhez az optimális teljesítmény érdekében, például olyan eszközök használatával, mint a RequireJS optimalizer, az r.js.

Összességében elmondható, hogy az AMD és a CommonJS között a különbség a csomagolásban a következő: a fejlesztés során az AMD alkalmazásai megúszhatják a fejlesztési lépést. Legalábbis addig, amíg élesben nem nyomja meg a kódot, ekkor léphetnek be az optimalizálók, mint például az r.js.

Egy érdekes beszélgetést a CommonJS és az AMD kapcsán nézzen meg Tom Dale blogjában :)

Webpack

Ami a csomagokat illeti, a Webpack az új gyerek. Úgy tervezték, hogy agnosztikus legyen az Ön által használt modulrendszerrel szemben, lehetővé téve a fejlesztők számára a CommonJS, AMD vagy ES6 használatát.

Lehet, hogy kíváncsi arra, miért van szükségünk Webpack-re, amikor már vannak olyan csomagjaink, mint a Browserify és a RequireJS, akik elvégzik a munkát, és nagyon baromi jó munkát végeznek rajta. Nos, egyrészről a Webpack olyan hasznos funkciókat kínál, mint a „kódfelosztás” - egy mód arra, hogy a kódbázist igény szerint feltöltött „darabokra” oszthassa.

Például, ha van webalkalmazása olyan kódblokkokkal, amelyekre csak bizonyos körülmények között van szükség, akkor nem biztos, hogy hatékony a teljes kódbázist egyetlen masszív kötegelt fájlba helyezni. Ebben az esetben használhatja a kódfelosztást, hogy kibontsa a kódot igény szerint betölthető csomagokba, elkerülve ezzel a nagy előre megterhelhető terheket, amikor a legtöbb felhasználónak csak az alkalmazás magjára van szüksége.

A kódfelosztás csak egy a sok kényszerítő szolgáltatás közül, amelyet a Webpack kínál, és az Internet tele van erős véleményekkel arról, hogy a Webpack vagy a Browserify jobb-e. Íme csak néhány a többszintű beszélgetések közül, amelyeket hasznosnak találtam a fejem köré fonásához:

  • //gist.github.com/substack/68f8d502be42d5cd4942
  • //mattdesl.svbtle.com/browserify-vs-webpack
  • //blog.namangoel.com/browserify-vs-webpack-js-drama

ES6 modulok

Már vissza? Jó! Mert legközelebb az ES6 modulokról szeretnék beszélni, amelyek bizonyos szempontból csökkenthetik a jövőben a csomagolók igényét. (pillanatok alatt meglátja, mire gondolok.) Először értsük meg, hogyan töltődnek be az ES6 modulok.

A jelenlegi JS modulformátumok (CommonJS, AMD) és az ES6 modulok közötti legfontosabb különbség az, hogy az ES6 modulokat statikus elemzést szem előtt tartva tervezték. Ez azt jelenti, hogy amikor modulokat importál, akkor az import fordításkor megoldódik - vagyis még mielőtt a parancsfájl elindulna. Ez lehetővé teszi számunkra, hogy a program futtatása előtt eltávolítsuk azokat az exportokat, amelyeket más modulok nem használnak. A fel nem használt export eltávolítása jelentős helymegtakarításhoz vezethet, csökkentve a böngésző terhelését.

Az egyik felmerülő kérdés a következő: miben különbözik ez a holt kód megszüntetésétől, amely akkor történik, amikor valami hasonlót használ az UglifyJS-hez a kód tömörítéséhez? A válasz, mint mindig, "ez attól függ".

(MEGJEGYZÉS: A holtkód eltávolítása egy optimalizálási lépés, amely eltávolítja a fel nem használt kódot és változókat - gondoljon arra, hogy eltávolítja a felesleges poggyászt, amelyet a csomagban lévő programnak nem kell futtatnia, * miután * csomagba került).

Előfordul, hogy a holt kódok megszüntetése ugyanúgy működhet az UglifyJS és az ES6 modulok között, máskor pedig nem. Van egy jó példa a Rollup wiki oldalán), ha meg akarja nézni.

Az ES6 modulokat a különbség az, hogy a holt kódok kiküszöbölésére más megközelítést alkalmaznak, az úgynevezett „fa remegését”. A fa megrázása lényegében megfordult a holtkód-eliminációval. Csak azt a kódot tartalmazza, amelyre a csomagnak futtatnia kell, ahelyett, hogy kizárná azt a kódot, amelyre a csomagnak nincs szüksége. Nézzünk meg egy példát a fa remegésére:

Tegyük fel, hogy van egy utils.js fájlunk az alábbi függvényekkel, amelyek mindegyikét az ES6 szintaxisa segítségével exportáljuk:

Ezután tegyük fel, hogy nem tudjuk, hogy milyen utils függvényeket szeretnénk használni a programunkban, ezért folytatjuk, és importáljuk a main.js összes modulját, így:

Aztán később csak az egyes függvényeket használjuk:

A main.js fájl „fa rázta” változata a modulok betöltése után így néz ki:

Figyelje meg, hogy az egyedüli exportot melyeket használjuk: mindegyiket .

Eközben, ha úgy döntünk, hogy az egyes funkciók helyett a szűrőfunkciót alkalmazzuk, valami ilyesmit nézünk ki:

A fa megrázott változata a következőképpen néz ki:

Figyelje meg, hogyan tartalmazzák ezúttal mind az egyes, mind a szűrőket . Ennek oka, hogy a szűrő mindegyik használatához meg van határozva , ezért mindkét modulra szükségünk van a modul működéséhez.

Elég sima, mi?

Kihívom, hogy játsszon körül és fedezze fel a fák remegését a Rollup.js élő bemutatójában és szerkesztőjében.

ES6 modulok építése

Oké, tehát tudjuk, hogy az ES6 modulok másképp vannak betöltve, mint a többi modulformátum, de még mindig nem beszéltünk az ES6 modulok használatának építési lépéseiről.

Sajnos az ES6 modulok még mindig némi extra munkát igényelnek, mivel még nincs natív megvalósítás arra vonatkozóan, hogy a böngészők miként töltik be az ES6 modulokat.

Íme néhány lehetőség az ES6 modulok böngészőben történő működésének felépítésére / konvertálására, az 1. számú manapság a legelterjedtebb megközelítés:

  1. Használjon transzpiler (pl. Babel vagy Traceur) segítségével az ES6 kódot CommonJS, AMD vagy UMD formátumban ES5 kódra. Ezután csatolja át az áttelepített kódot egy modulcsomagolón, például a Browserify vagy a Webpack segítségével, hogy létrehozzon egy vagy több kötegelt fájlt.
  2. Használja a Rollup.js fájlt, amely nagyon hasonlít az 1. opcióhoz, azzal a különbséggel, hogy az ES6-modulok erejéből származó összesítés az ES6-kód és a függőségek statikus elemzéséhez a kötegelés előtt. A „fa rázásával” használja a minimumot a csomagba. Összességében a Rollup.js legfőbb előnye a Browserify vagy a Webpack felett az ES6 modulok használata során az, hogy a fa rázásával kisebbek lehetnek a kötegek. A figyelmeztetés az, hogy a Rollup többféle formátumot kínál a kód csomagolásához, beleértve az ES6-ot, a CommonJS-t, az AMD-t, az UMD-t vagy az IIFE-t. Az IIFE és az UMD csomagok ugyanúgy működnének a böngészőben, de ha az AMD, a CommonJS vagy az ES6 csomagot választja, más módszereket kell találnia arra, hogy a kódot a böngésző által értett formátumúra konvertálja (pl. A Browserify, Webpack, RequireJS stb.).

Ugrás a karikákon

Webfejlesztőként sok karikán kell átugrani. Nem mindig könnyű átalakítani gyönyörű ES6 moduljainkat böngészők által értelmezhetővé.

A kérdés az, hogy mikor futnak az ES6 modulok a böngészőben, ennyi általános költség nélkül?

A válasz szerencsére „előbb-utóbb”.

Az ECMAScript jelenleg rendelkezik az ECMAScript 6 modul betöltő API nevű megoldás specifikációjával. Röviden, ez egy programozott, Promise alapú API, amelynek állítólag dinamikusan be kell töltenie a moduljait és gyorsítótárba kell tennie őket, hogy a későbbi importálás ne töltse be újra a modul új verzióját.

Valahogy így fog kinézni:

myModule.js

main.js

Alternatív megoldásként meghatározhat modulokat úgy is, hogy a „type = module” elemet közvetlenül megadja a parancsfájl címkéjében, így:

Ha még nem nézte meg a modulbetöltő API polifill kitöltését, bátorítom, hogy legalább nézzen be.

Sőt, ha tesztelni szeretné ezt a megközelítést, nézze meg a SystemJS-t, amely az ES6 Module Loader polifill tetejére épül. A SystemJS dinamikusan tölt be bármilyen modulformátumot (ES6 modulok, AMD, CommonJS és / vagy globális szkriptek) a böngészőben és a Node-ban. A „modul-nyilvántartásban” nyomon követi az összes betöltött modult, hogy elkerülje a korábban betöltött modulok újratöltését. Arról nem is beszélve, hogy automatikusan áttölti az ES6 modulokat is (ha egyszerűen beállít egy opciót), és képes bármilyen típusú modult betölteni bármilyen más típusból! Nagyon szép.

Szükségünk lesz még bundlerekre, miután rendelkezünk natív ES6 modulokkal?

Az ES6 modulok növekvő népszerűségének érdekes következményei vannak:

A HTTP / 2 elavítja a modulcsomagolókat?

A HTTP / 1 használatával TCP-kapcsolatonként csak egy kérés engedélyezett. Ezért több erőforrás betöltése több kérelmet igényel. A HTTP / 2 használatával minden megváltozik. A HTTP / 2 teljesen multiplexelt, vagyis több kérés és válasz történhet párhuzamosan. Ennek eredményeként egyszerre több kérést is kiszolgálhatunk egyetlen kapcsolattal.

Mivel a HTTP kérésenkénti költség lényegesen alacsonyabb, mint a HTTP / 1, egy csomó modul betöltése hosszú távon nem lesz hatalmas teljesítményprobléma. Néhányan azt állítják, hogy ez azt jelenti, hogy a modulokra már nem lesz szükség. Ez minden bizonnyal lehetséges, de nagyon függ a helyzettől.

Egyrészt a modulcsomagolás olyan előnyöket kínál, amelyeket a HTTP / 2 nem vesz figyelembe, például helytakarékosság érdekében eltávolíthatja a fel nem használt exportokat. Ha olyan weboldalt épít, ahol a teljesítmény minden aprósága számít, a csomagolás hosszú távon növekményes előnyökkel járhat. Ez azt jelenti, hogy ha a teljesítményigénye nem olyan szélsőséges, akkor időt takaríthat meg minimális költséggel, ha teljesen kihagyja a gyártási lépést.

Összességében még mindig messze vagyunk attól, hogy a webhelyek többsége a kódját HTTP / 2-n keresztül szolgáltassa. Hajlamos vagyok megjósolni, hogy az összeállítási folyamat itt van, hogy legalább a közeljövőben megmaradjon .

PS: A HTTP / 2-vel is vannak más különbségek, és ha kíváncsi vagy, íme egy remek forrás.

Elavulnak a CommonJS, az AMD és az UMD?

Miután ES6 válik a modul szabványos, valóban szükségünk van más, nem natív modul formátum?

Kétlem.

A webfejlesztés számára nagy haszon származik, ha egyetlen szabványos módszert követünk a modulok importálására és exportálására JavaScript-ben, közvetítői lépések nélkül. Mennyi ideig tart elérni azt a pontot, ahol az ES6 a modul szabványa?

Valószínűleg jó ideig;)

Ráadásul sok olyan ember van, aki szereti, ha „ízek” közül választhat, így az „egy igaz megközelítés” soha nem válhat valósággá.

Következtetés

Remélem, hogy ez a kétrészes bejegyzés segített tisztázni azokat a szakzsargonokat, amelyeket a fejlesztők használnak, amikor modulokról és modulcsomagolásról beszélnek. Folytassa, és nézze meg az I. részt, ha a fenti kifejezések bármelyikét zavarosnak találta.

Mint mindig, beszéljen velem a megjegyzésekben, és nyugodtan tegyen fel kérdéseket!

Boldog csomagolást :)