A kódbazis.hu (Bolgár Máté) Node.js tutorial alapján.
A Node.js egy futtató környezet, ami lehetőséget nyújt JavaScriptben írt programok futtatására. JavaScriptet leginkább kliens oldalon szoktak használni a böngészőn keresztül. A Google Chrome böngésző JavaScriptet futtató motorja a V8 engine, aminek a fejlesztése 2006-ban kezdődött. Neve játékos utalás a V8 benzinmotorok teljesítményére (a V8 motorokban 8 henger van két sorban elhelyezve, melyek ‘V’ betűt formázó szöget zárnak be). A Node.js is ezt a motort használja némi módosítással, továbbá beépített könyvtárakat tartalmaz. Alapvetően webszerverek készítésére hozták létre.
Nincs GUI-ja, csak CLI-ből használható. Használata adott mappán megnyitott parancssorból vagy VS Code termináljából: készítünk egy pl. index.js fájlt, majd beírjuk a parancssorba, hogy node index.js, akkor a node lefuttatja az index.js fájlt.
A wrapper function
A a parancssorban a node paranccsal lefuttatott kód tkp. egy wrapper function-be van csomagolva, azaz mintha a kód körül lenne véve ezzel a wrapper function-nel, s futtatáskor ez hívódna meg. Ez a function kap paramétereket, amelyeket a forráskódban tudunk használni. Ezek: __dirname, __filename; module, require, exports, ezeket ki is lehet logolni.
- __dirname: a forrásfájl operációs rendszeren belüli abszolút útvonala
- __filename: ugyanez, csak még hozzáfűzve a fájl neve
- module, require, exports: fájlok között funkcionalitás megosztása; ha van egy másik fájlunk, s abban egy function, amit szeretnénk megosztani, akkor a module object export kulcsa alá kell bekötni a megosztani kívánt adatot, ami leginkább object vagy function szokott lenni. Pl.: a fájl neve legyen calculator.js.
function add(num1,num2){ return num1+num2 } module.exports = add
- require: Innentől kezdve ez a function elérhető a többi fájl részére is. Abban a fájlban, ahova meg akarjuk hívni a függvényt, ott a require() függvényben kell megadni a behúzni kívánt fájl relatív útvonalát. A fájlkiterjesztés JavaScript fájlok esetén (is) elhagyható, ill. egészen pontosan, ha nem adunk meg fájlkiterjesztést, akkor a node.js először a .js, majd a .json, majd a .node fájlok között fogja keresni az adott fájlt. Ez keveredést okozhat, így jobb odaírni a fájlkiterjesztéseket.
const add = require('./calculator.js') console.log(add(4,5) //9
- require.resolve: szerver oldalon gyakrabban van szükség egy modul elérési útvonalára. Ez az utasítás nem húzza be a függvényt, így azt nem tudjuk használni, csak a függvény elérési útvonalával tér vissza. Pl.:
const add = require.resolve('./calculator.js'); console.log(add) //C:\Users\Kevin\Desktop\new_map\calculator.js
A Node.js-ben tehát a module.exports utasítással exportálunk és a require utasítással importálunk. Egy fájlból ezen a módon csak egy adatot tudunk exportálni, s importáláskor az az adat lesz behúzva. Egy fájlon belül több module.exports=… utasítás esetén az utolsó lesz érvényes. Hogy hogyan lehet másként egy modulból több adatot exportálni, arra később visszatérünk.
Ez a Node.js esetén rákényszerít a modular code technikára, azaz az egy függvény – egy modul eljárásra.
Viszont a require függvény és a module object a Node.js-en belül globális scope-pal rendelkezik, így nincs szükség a require(‘require’), ill. a require(‘module’) utasításokra.
Az exports és a module.exports
Az adatok exportálása tehát a module.export utasítással történik, ami tkp. a module object egy export nevű kulcsa, s ez alá írunk be valamit. Ugyanakkor a Node.js-en belül az export egy olyan object, amelyet deklarálás nélkül használhatunk. Pl. deklarálás nélkül beírhatjuk egy fájlba:
exports.kulcs="hello" console.log(exports.kulcs) // hello
Azaz a wrapper function exports paramétere és a modules.export két különböző dolog!
Mi a module?
[Irodalom: developpaper.com] Írok két JS fájlt, az index1.js-et és az index2.js-t, s mindkettőt a Desktopon helyezem el.
//index1.js function add(num1,num2){ console.log(num1,num2); return num1+num2}; module.exports=add; console.log(module);
//index2.js add = require('./index1.js'); console.log(add(2,5)); console.log(module);
Az index1.js module-ja ez lesz:
C:\Users\Kevin\Desktop>node index1.js Module { id: '.', path: 'C:\Users\Kevin\Desktop', exports: [Function: add], parent: null, filename: 'C:\Users\Kevin\Desktop\index1.js', loaded: false, children: [], paths: [ 'C:\Users\Kevin\Desktop\node_modules', 'C:\Users\Kevin\node_modules', 'C:\Users\node_modules', 'C:\node_modules' ] }
Az index2.js module-ja ez lesz:
Module { id: '.', path: 'C:\Users\Kevin\Desktop', exports: {}, parent: null, filename: 'C:\Users\Kevin\Desktop\index2.js', loaded: false, children: [ Module { id: 'C:\Users\Kevin\Desktop\index1.js', path: 'C:\Users\Kevin\Desktop', exports: [Function: add], parent: [Circular *1], filename: 'C:\Users\Kevin\Desktop\index1.js', loaded: true, children: [], paths: [Array] } ], paths: [ 'C:\Users\Kevin\Desktop\node_modules', 'C:\Users\Kevin\node_modules', 'C:\Users\node_modules', 'C:\node_modules' ] }
- id attributum: a fájl abszolút elérési útvonala, ennek az alapján azonosítja a node.js a fájlt.
- paths attributum: láthatóan egy halom – létező és nem létező – abszlút útvoval az operációs rendszerel belül. Az index2.js a require függvénnyel behúzza az index1.js-t, amelynek az elérési útvonalát adjuk meg a függvény paramétereként: add = require(‘./index1.js’). Ehelyett írhatjuk ezt is: add = require(‘index1.js’), azaz itt csak a file nevét adtuk meg, nem adtunk hozzá elérési útvonalat. Ekkor a node.js a paths attributum alatt megadott elérési útvonalakon próbálja megtalálni a fájlunkat. S így már nem fogja megtalálni, mivel a C:\Users\Kevin\Desktop nem szerepel az útvonalak között. De ha készítünk egy node_modules mappát a Desktopon, s abba helyezzük el az index1.js fájlt, akkor meg fogja találni, mivel a C:\Users\Kevin\Desktop\node_modules szerepel a paths útvonalai között.
- children, parent: az index2.js a require eljárással behúzza az index1.js-t, így az szerepel a children tömbjében. Az már nem húz be semmit, ezért annak nincs gyereke. A parent-nál viszont azt látjuk, hogy [Circular *1], mivel itt egy körkörös – önmagába visszatérő – függőségről van szó, hiszen az index1.js-t az index1.js-ből húztuk be, így saját maga a szülöje.
- export: a module object export kulcsa alá kerül be az, amit kiexportáltunk a fájlból.
- loaded: jelzi, hogy a module ba van-e töltve. Akkor lesz az értéke true, ha a module teljesen be lett töltve. Így a fentiek mellett a node index2.js utasításra a kiírt module-ban azt látjuk, hogy az index2.js-nál ez false, mivel a kiíratáskor a fájl még fut, de az index1.js-re true, mivel a kiíratáskor az már be lett töltve a require utasítással mindjárt a fájl elején.
A kód futtatása a következőképpen történik (kép forrása):
- resolving: a célmodul megtalálása és az abszolút út generálása
- loading: annak meghatározása, hogy a modul JS, JSON vagy Node fájl-e
- wrapping: becsomagolás privát scope-ba
- evaluating: futtatás
- caching: cache-elés, hogy későbbi használatnál ne kelljen a fenti lépéseket megismételni
Wrapping
A node.js tehát nem direkt módon futtatja az adott js kódot, hanem előtte becsomagolja a kódot egy wrapper függvénybe. Ez a function így néz ki (nodejs.org dokumentációja):
(function (exports, require, module, __filename, __dirname) { //module code });
Ezzel a következő dolgok valósulnak meg:
- Mivel itt függvény scope-ról van szó, így a modulon belül használt var, let, const kulcsszavakkal deklarált változók scope-ja a modul lesz, így azok nem lesznek globálisan elérhető változók.
- Minden modulnak megvan a saját wrapper függvénye, s a __dirname, __filename; module, require, exports változók minden modul esetében lokálisak.
Az index2.js fájlon belül console.log(arguments) utasítással kiírathatjuk a wrapper function paramétereit:
[Arguments] { '0': {}, '1': [Function: require] { resolve: [Function: resolve] { paths: [Function: paths] }, main: Module { id: '.', path: 'C:\Users\Kevin\Desktop', exports: {}, parent: null, filename: 'C:\Users\Kevin\Desktop\index2.js', loaded: false, children: [Array], paths: [Array] }, extensions: [Object: null prototype] { '.js': [Function (anonymous)], '.json': [Function (anonymous)], '.node': [Function (anonymous)] }, cache: [Object: null prototype] { 'C:\Users\Kevin\Desktop\index2.js': [Module], 'C:\Users\Kevin\Desktop\node_modules\index1.js': [Module] } }, '2': Module { id: '.', path: 'C:\Users\Kevin\Desktop', exports: {}, parent: null, filename: 'C:\Users\Kevin\Desktop\index2.js', loaded: false, children: [ [Module] ], paths: [ 'C:\Users\Kevin\Desktop\node_modules', 'C:\Users\Kevin\node_modules', 'C:\Users\node_modules', 'C:\node_modules' ] }, '3': 'C:\Users\Kevin\Desktop\index2.js', '4': 'C:\Users\Kevin\Desktop' }
Az exportálás három módja
(Irodalom: Srishti Gupta – medium.com) A legegyszerűbb módra fenntebb láttunk példát:
function add(num1,num2){
return num1+num2
}
module.exports=add
A module.export tkp így néz ki (kép forrása, mondjuk a helyesírás-ellenőrőt kikapcsolhatta volna – Srishti Gupta):
S mivel a module.export eredetileg egy object, ezért használhatunk rajta kulcsokat, azaz különböző kulcsok alatt különböző adatokat adhatunk meg, majd a require utasítással ezt az object-et kapjuk meg, aminek hivatkozhatunk a kulcsaira.
//index1.js const add = function(num1,num2){ console.log(num1+num2); return num1+num2}; module.exports.add=add; function mult(num1,num2){ console.log(num1num2); return num1num2}; module.exports.mult=mult; //index2.js const add = require('./index1.js').add; add(2,5) const mult = require('./index1.js').mult; mult(2,5) //7 //10
A másik megoldás ugyanerre épül, csak ott az object-et máshogy adjuk meg:
const add = function(num1,num2){ console.log(num1+num2); return num1+num2} function mult(num1,num2){ console.log(num1num2); return num1num2} module.exports = {add:add, mult:mult}
(Még tömörebben az uolsó sor: module.exports = {add, mult}
require.cache
Egy fájlon belül a console.log(require) utasítással kiírathatjuk a require objectumot. Ekkor az objectumon belül látni fogunk egy chache kulcsot, ami alatt az eddig betöltött modulok lesznek eltárolva, s így az ismételt modulhívás esetén tkp. csak cache-elés történik.