Moduly v JavaScripte
Časť 2: CommonJS a AMD
Milan Herda, 10/2016, úpravy 08/2022
CommonJS
CommonJS
- 2009, Mozilla, ekosystém pre server-side JavaScript
- 2013 - Node.js opúšťa CommonJS, ale ponecháva si formát pre moduly
CommonJS nie je súčasťou špecifikácie JavaScriptu a tak nefunguje natívne v prehliadačoch!
CommonJS
Všetko vo vnútri modulu je lokálne iba pre modul.
Von z modulu sa dostane iba to, čo explicitne povolíme.
CommonJS
require
module.exports
Premenná dostupná v každom module. Jej obsah je jediná vec, ktorá sa z modulu exportuje.
Funkcia, pomocou ktorej importujeme iné moduly.
const foo = require('./foo.js');
const bar = () => {
// ...
};
module.exports = bar;
Stiahnite si zdrojáky
git clone https://bitbucket.org/perungrad/js-modules-2.git
Na najvyššej úrovni máme adresáre pre štyri scenáre, ktoré dnes budeme riešiť:
- server-commonjs
- web-oldschool
- web-commonjs
- web-amd
V každom z týchto adresárov je ešte adresár src a v ňom sú uložené funkcie unitFactory a ranged z predchádzajúceho školenia
Úloha
- pracujte v adresári server-commonjs
- prepíšte oba moduly do CommonJS formátu
- načítajte ich v index.js
- vytvorte inštancie pre kopijníka, jazda a lukostrelca
- "zobrazte" obsah inštancií (napr. cez console.log)
- require - funkcia pre načítanie modulu
- module.exports - premenná, do ktorej zapíšete, čo váš modul exportuje
Riešenie
const unitFactory = function() {
// ...
};
module.exports = unitFactory;
src/unit/unitFactory.js
Riešenie
const extendByRange = function(unit) {
// ...
};
module.exports = extendByRange;
src/unit/ranged.js
Riešenie
const unitFactory = require('./src/unit/unitFactory.js');
const extendByRange = require('./src/unit/ranged.js');
const pikeman = unitFactory()
.setName('Pikeman')
.setSpeed(2)
.setStrength(3)
.setHealth(4);
const horseman = /* ... */
const archer = extendByRange(unitFactory())
.setName('Archer')
.setSpeed(2)
.setStrength(2)
.setHealth(4)
.setRange(4);
console.log({ pikeman });
console.log({ horseman });
console.log({ archer });
index.js
Toto bolo na serveri.
Ako je to v prehliadači?
Old School prístup
- dá sa vidieť v adresári web-oldschool
- "moduly" sa načítavajú pomocou obyčajného script tagu
- všetky funkcie sú v globálnom priestore
Old School prístup
const unitFactory = function(unit) {
// ...
};
src/unit/unitFactory.js
Old School prístup
const extendByRange = function(unit) {
// ...
};
src/unit/ranged.js
Old School prístup
<script src="src/unit/unitFactory.js"></script>
<script src="src/unit/ranged.js"></script>
<script>
const pikeman = unitFactory();
const horseman = unitFactory();
const archer = extendByRange(unitFactory());
archer.setName('Robin Hood').setSpeed(1).setStrength(5).setHealth(10).setRange(5);
console.log({ pikeman });
console.log({ horseman });
console.log({ archer });
</script>
index.html
Aké problémy vidíte v tomto prístupe?
- kód "modulov" je v globálnom mennom priestore
- načítavanie cez script tag blokuje rendering stránky
- vo väčšom projekte priveľa súborov na načítanie
Možné riešenie?
1. kód "modulov" je v globálnom mennom priestore
2. načítavanie cez script tag blokuje rendering stránky
3. vo väčšom projekte priveľa súborov na načítanie
Môžeme skúsiť obaliť hlavný skript a načítavanie modulov do funkcie (Module Pattern)
Môžeme napr. presunúť script tag na koniec stránky
Spojíme všetky súbory do jedného
CommonJS
v prehliadači
CommonJS nie je súčasťou JS, takže nie je prítomné v prehliadačoch.
Potrebujeme knižnicu, nástroj, čokoľvek, čo nám ho dodá.
webpack
webpack je "module bundler" pre web, ktorý vie pracovať aj s CommoJS formátom
Funguje tak, že si prečíta vaše zdrojové súbory a pretransformuje ich do cieľovej formy vhodnej pre prehliadače.
Tieto pretransformované súbory sa potom nasadzujú do produkcie.
webpack
Inštalácia
cd web-commonjs
npm init
npm install --dev --save webpack webpack-cli
# alebo
yarn init
yarn add -D webpack webpack-cli
webpack
Adresárová štruktúra projektu
-
src/ - zdrojový adresár
-
unit/
- unitFactory.js - CommonJS modul
- ranged.js - CommonJS modul
- index.js - hlavný skript
-
unit/
- www/ - cieľový adresár
- index.html
- webpack.config.js
- package.json
- www/js/app.js bude náš nový vstupný bod do aplikácie
- bude sa načítavať v html cez jediný script tag
- app.js sa bude generovať webpackom
- zdrojový súbor pre vygenerovanie app.js bude src/index.js
webpack
Konfigurácia, webpack.config.js
const path = require('path');
module.exports = {
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'www/js'),
filename: '[name].js',
},
mode: 'production',
};
webpack
src/index.js
const unitFactory = require('./unit/unitFactory.js');
const extendByRange = require('./unit/ranged.js');
const pikeman = unitFactory()
.setName('Pikeman')
.setSpeed(2)
.setStrength(3)
.setHealth(4);
const horseman = /* ... */
const archer = extendByRange(unitFactory())
.setName('Archer')
.setSpeed(2)
.setStrength(2)
.setHealth(4)
.setRange(4);
console.log({ pikeman });
console.log({ horseman });
console.log({ archer });
webpack
src/unit/unitFactory.js
const unitFactory = function() {
// ...
};
module.exports = unitFactory;
webpack
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>CommonJS module example</title>
<script src="www/js/app.js"></script>
</head>
<body></body>
</html>
webpack
Spustenie
npx run webpack
# alebo
yarn webpack
webpack
Pozrieme sa na vygenerovaný súbor
a objavíme
Module Pattern a IIFE!
AMD
AMD
Asynchronous Module Definition
Problémy Old School prístupu
1. kód "modulov" je v globálnom mennom priestore
2. načítavanie cez script tag blokuje rendering stránky
3. vo väčšom projekte priveľa súborov na načítanie
Môžeme skúsiť obaliť hlavný skript a načítavanie modulov do funkcie (Module Pattern)
Môžeme skúsiť moduly načítavať asynchrónne
Spojíme všetky súbory do jedného
AMD
require
- Funkcia, pomocou ktorej importujeme iné moduly
- Načítanie sa deje asynchrónne
- Po načítaní všetkých požadovaných modulov sa spustí callback
require(
['path/to/module1', 'path/to/module2'],
function (module1, module2) {
// kód spustený po načítaní modulov
}
);
AMD
define
- Funkcia, pomocou ktorej definujeme modul
- Ak má modul závislosti, načítavajú sa asynchrónne
- Po načítaní všetkých požadovaných modulov sa spustí callback definujúci modul
- Návratová hodnota callbacku reprezentuje API modulu
define(
'nazov', // názov je voliteľný
['path/to/module1', 'path/to/module2'], // závislosti
function (module1, module2) { // kód modulu, povinné
// kód spustený po načítaní modulov
}
);
Názov modulu je nepovinný, pokiaľ je modul v samostatnom súbore
Závislosti sú nepovinné a pokiaľ nie sú, tak sa nemusí uviesť ani prázdne pole
Závislosť môžeme uviesť ako cestu k súboru s modulom alebo ako názov modulu
AMD
v prehliadači
AMD nie je súčasťou JS, takže nie je prítomné v prehliadačoch.
Potrebujeme knižnicu, nástroj, čokoľvek, čo nám ho dodá.
RequireJS
RequireJS je "module loader" pre moduly napísané v AMD formáte
Je možné ho nakonfigurovať tak, aby vedel pracovať aj s nie-AMD formátom
RequireJS
Inštalácia
cd web-amd
npm init
npm install --dev --save requirejs
# alebo
yarn init
yarn add -D requirejs
AMD
src/index.js
require(['unit/unitFactory', 'unit/ranged'], (unitFactory, extendByRange) => {
const pikeman = unitFactory()
.setName('Pikeman')
.setSpeed(2)
.setStrength(3)
.setHealth(4);
const horseman = /* ... */
const archer = extendByRange(unitFactory())
.setName('Archer')
.setSpeed(2)
.setStrength(2)
.setHealth(4)
.setRange(4);
console.log({ pikeman });
console.log({ horseman });
console.log({ archer });
});
AMD
src/unit/unitFactory
define(() => {
const unitFactory = function() {
// ...
}
return unitFactory;
});
AMD
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>AMD module example</title>
</head>
<body>
<script
src="node_modules/requirejs/require.js"
data-main="src/index"
></script>
</body>
</html>
AMD formát nám umožňuje mať viacero modulov v jednom súbore.
Vďaka tomu vieme optimalizovať súbory pre produkciu a spájať moduly do jedného alebo viac súborov.
RequireJS poskytuje pre tento účel utilitu nazvanú r.js
RequireJS
Adresárová štruktúra projektu
-
src/ - zdrojový adresár
-
unit/
- unitFactory.js - AMD modul
- ranged.js - AMD modul
- index.js - hlavný skript
-
unit/
- www/ - cieľový adresár pre build z r.js
- index.html
- package.json
- www/js/app.js bude náš nový vstupný bod do aplikácie
- bude sa načítavať v html cez jediný script tag
- app.js sa bude generovať pomocou r.js
- zdrojový súbor pre vygenerovanie app.js bude src/index.js
RequireJS
package.json
{
"name": "web-amd",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"requirejs": "^2.3.6"
},
"scripts": {
"build": "r.js -o baseUrl=./src paths.requireLib=../node_modules/requirejs/require include=requireLib name=index out=www/js/app.js"
}
}
/*
* r.js
* -o baseUrl=./src
* paths.requireLib=../node_modules/requirejs/require
* include=requireLib
* name=index
* out=www/js/app.js
*/
RequireJS
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>AMD module example</title>
</head>
<body>
<script src="www/js/app.js"></script>
</body>
</html>
Zhrnutie
CommonJS alebo AMD?
- ✅ AMD bolo vytvárané pre web
- ❌ síce je použiteľné aj v prostredí Node.js, ale je to trochu meh
- ✅ v AMD sú závislosti viditeľné
- ✅ v AMD sa pekne aplikuje Dependency Injection
- ✅ AMD modul sa jednoduchšie testuje, lebo závislosti dostáva ako argumenty funkcie
CommonJS alebo AMD?
- ✅ CommonJS bolo vytvárané pre server
- ❌ CommonJS nie je použiteľné v browseroch a tak sa tam implementuje ako Module Pattern
- ❌ závislosti nie sú viditeľné, lebo require sa môže použiť aj uprostred kódu
- ❌ CommonJS modul sa ťažšie testuje, lebo nie sú viditeľné závislosti a treba zložito mockovať importy
CommonJS alebo AMD?
CommonJS alebo AMD?
Historicky vyhral CommonJS
- bol dostatočne dobrý
- vývojári naň boli zvyknutí z prostredia Node.js
- webpack prevalcoval ostatné nástroje pre bundlovanie
Súčasnosť a budúcnosť
- CommonJS sa stáva minulosťou
- natívne JS moduly sú podporvané v Node.js, prehliadačoch a v nových serverových runtimeoch (Deno, Bun)
- webpack sa stáva zbytočným (alternatívy: Rollup, Parcel, Vite)
Nabudúce
Stručne o UMD
a obšírnejšie o natívnych JS moduloch
Ďakujem za pozornosť
Moduly v JavaScripte, časť 2
By Milan Herda
Moduly v JavaScripte, časť 2
- 432