Pročitajte ažuriranu verziju ovog sadržaja i više o Nodeu na jscomplete.com/node-beyond-basics .
Čvor koristi dva temeljna modula za upravljanje ovisnostima o modulima:
require
Modul, koji, kako se čini dostupan na globalnom okviru - nema potrebe zarequire('require')
.module
Modul, koji također čini se da je dostupan na globalnom okviru - nema potrebe zarequire('module')
.
O require
modulu možete razmišljati kao o naredbi, a o module
modulu kao o organizatoru svih potrebnih modula.
Zahtjev za modulom u Nodeu nije toliko kompliciran koncept.
const config = require('/path/to/file');
Glavni objekt koji require
modul izvozi je funkcija (kao što je korišteno u gornjem primjeru). Kada Node pozove tu require()
funkciju s lokalnom stazom datoteke kao jedini argument funkcije, Node prolazi kroz sljedeći slijed koraka:
- Rješavanje : pronaći apsolutni put datoteke.
- Učitavanje : za određivanje vrste sadržaja datoteke.
- Omotavanje : Da bi datoteka dobila svoj privatni opseg. To je ono što objekte
require
imodule
objekte čini lokalnim za svaku datoteku koju trebamo. - Procjena : To je ono što VM na kraju radi s učitanim kodom.
- Predmemoriranje : Tako da kad ponovno zatražimo ovu datoteku, ne prelazimo sve korake drugi put.
U ovom ću članku pokušati objasniti na primjerima ove različite faze i kako oni utječu na način na koji pišemo module u Nodeu.
Prvo da kreiram direktorij za hostiranje svih primjera pomoću mog terminala:
mkdir ~/learn-node && cd ~/learn-node
Sve naredbe u ostatku ovog članka pokrenut će se iznutra ~/learn-node
.
Rješavanje lokalnog puta
Dopustite mi da vas upoznam s module
objektom. Možete ga provjeriti u jednostavnoj REPL sesiji:
~/learn-node $ node > module Module { id: '', exports: {}, parent: undefined, filename: null, loaded: false, children: [], paths: [ ... ] }
Svaki objekt modula dobiva id
svojstvo da ga identificira. To id
je obično puni put do datoteke, ali u REPL sesiji to je jednostavno.
Čvorni moduli imaju jedan-na-jedan odnos s datotekama u datotečnom sustavu. Trebamo modul učitavanjem sadržaja datoteke u memoriju.
Međutim, budući da Node dopušta mnogo načina za zahtijevanje datoteke (na primjer, relativnom stazom ili unaprijed konfiguriranom stazom), prije nego što sadržaj datoteke možemo učitati u memoriju, trebamo pronaći apsolutno mjesto te datoteke.
Kada trebamo 'find-me'
modul, bez navođenja puta:
require('find-me');
Čvor će tražiti find-me.js
na svim stazama navedenim module.paths
- redom.
~/learn-node $ node > module.paths [ '/Users/samer/learn-node/repl/node_modules', '/Users/samer/learn-node/node_modules', '/Users/samer/node_modules', '/Users/node_modules', '/node_modules', '/Users/samer/.node_modules', '/Users/samer/.node_libraries', '/usr/local/Cellar/node/7.7.1/lib/node' ]
Popis staza je u osnovi popis direktorija node_modules ispod svakog direktorija od trenutnog direktorija do korijenskog direktorija. Također uključuje nekoliko naslijeđenih direktorija čija se uporaba ne preporučuje.
Ako Node ne može pronaći ni find-me.js
na jednom od ovih putova, izbacit će poruku "ne može pronaći pogrešku modula."
~/learn-node $ node > require('find-me') Error: Cannot find module 'find-me' at Function.Module._resolveFilename (module.js:470:15) at Function.Module._load (module.js:418:25) at Module.require (module.js:498:17) at require (internal/module.js:20:19) at repl:1:1 at ContextifyScript.Script.runInThisContext (vm.js:23:33) at REPLServer.defaultEval (repl.js:336:29) at bound (domain.js:280:14) at REPLServer.runBound [as eval] (domain.js:293:12) at REPLServer.onLine (repl.js:533:10)
Ako sada napravite lokalni node_modules
direktorij i tamo stavite znak find-me.js
, require('find-me')
redak će ga pronaći.
~/learn-node $ mkdir node_modules ~/learn-node $ echo "console.log('I am not lost');" > node_modules/find-me.js ~/learn-node $ node > require('find-me'); I am not lost {} >
Ako je find-me.js
, na primjer, postojala druga datoteka u bilo kojem od drugih putova, ako imamo node_modules
direktorij ispod početnog direktorija i tamo imamo drugu find-me.js
datoteku:
$ mkdir ~/node_modules $ echo "console.log('I am the root of all problems');" > ~/node_modules/find-me.js
Kada uđemo require('find-me')
iz learn-node
direktorija - koji ima svoj node_modules/find-me.js
, find-me.js
datoteka pod matičnim direktorijem uopće se neće učitati:
~/learn-node $ node > require('find-me') I am not lost {} >
Ako uklonimo lokalni node_modules
direktorij iz ~/learn-node
i pokušamo zatražiti find-me
još jedanput, node_modules
koristila bi se datoteka ispod direktorija doma :
~/learn-node $ rm -r node_modules/ ~/learn-node $ node > require('find-me') I am the root of all problems {} >
Zahtijeva mapu
Moduli ne moraju biti datoteke. Također možemo stvoriti find-me
mapu ispod node_modules
i tamo smjestiti index.js
datoteku. Isti require('find-me')
će redak koristiti index.js
datoteku te mape :
~/learn-node $ mkdir -p node_modules/find-me ~/learn-node $ echo "console.log('Found again.');" > node_modules/find-me/index.js ~/learn-node $ node > require('find-me'); Found again. {} >
Primijetite kako je ponovno ignorirao put kućnog direktorija node_modules
jer sada imamo lokalni.
index.js
Datoteka će se prema zadanim postavkama kada tražimo mapu, ali možemo kontrolirati ono što naziv datoteke za početak u mapu pomoću main
nekretninu u package.json
. Na primjer, da bi se require('find-me')
linija razlučila u drugu datoteku u find-me
mapi, sve što trebamo učiniti je dodati package.json
datoteku tamo i odrediti koju datoteku treba koristiti za rješavanje ove mape:
~/learn-node $ echo "console.log('I rule');" > node_modules/find-me/start.js ~/learn-node $ echo '{ "name": "find-me-folder", "main": "start.js" }' > node_modules/find-me/package.json ~/learn-node $ node > require('find-me'); I rule {} >
zahtijevati.razriješiti
Ako želite samo riješiti modul, a ne izvršiti ga, možete koristiti require.resolve
funkciju. Ovo se ponaša potpuno isto kao glavna require
funkcija, ali ne učitava datoteku. I dalje će izbaciti pogrešku ako datoteka ne postoji i vratit će puni put do datoteke kad je pronađena.
> require.resolve('find-me'); '/Users/samer/learn-node/node_modules/find-me/start.js' > require.resolve('not-there'); Error: Cannot find module 'not-there' at Function.Module._resolveFilename (module.js:470:15) at Function.resolve (internal/module.js:27:19) at repl:1:9 at ContextifyScript.Script.runInThisContext (vm.js:23:33) at REPLServer.defaultEval (repl.js:336:29) at bound (domain.js:280:14) at REPLServer.runBound [as eval] (domain.js:293:12) at REPLServer.onLine (repl.js:533:10) at emitOne (events.js:101:20) at REPLServer.emit (events.js:191:7) >
To se može koristiti, na primjer, za provjeru je li instaliran dodatni paket ili ne i koristiti ga samo kad je dostupan.
Relativni i apsolutni putovi
Osim rješavanja modula iz node_modules
direktorija, modul također možemo smjestiti gdje god želimo i zahtijevamo ga ili relativnim stazama ( ./
i ../
) ili apsolutnim stazama koje počinju sa /
.
Ako se, primjerice, find-me.js
datoteka nalazila u lib
mapi umjesto u node_modules
mapi, možemo je zatražiti s:
require('./lib/find-me');
Odnos roditelja i djeteta između datoteka
Stvorite lib/util.js
datoteku i dodajte console.log
liniju tamo da je prepoznate. Također, console.log
sam module
objekt:
~/learn-node $ mkdir lib ~/learn-node $ echo "console.log('In util', module);" > lib/util.js
Učinite isto za index.js
datoteku, što ćemo izvršiti pomoću naredbe node. Neka ova index.js
datoteka zahtijeva lib/util.js
:
~/learn-node $ echo "console.log('In index', module); require('./lib/util');" > index.js
Sada izvršite index.js
datoteku s čvorom:
~/learn-node $ node index.js In index Module { id: '.', exports: {}, parent: null, filename: '/Users/samer/learn-node/index.js', loaded: false, children: [], paths: [ ... ] } In util Module { id: '/Users/samer/learn-node/lib/util.js', exports: {}, parent: Module { id: '.', exports: {}, parent: null, filename: '/Users/samer/learn-node/index.js', loaded: false, children: [ [Circular] ], paths: [...] }, filename: '/Users/samer/learn-node/lib/util.js', loaded: false, children: [], paths: [...] }
Imajte na umu kako je glavni index
modul (id: '.')
sada naveden kao nadređeni lib/util
modulu. Međutim, lib/util
modul nije naveden kao podređeno mjesto index
modula. Umjesto toga, tamo imamo [Circular]
vrijednost jer je ovo kružna referenca. Ako Node ispiše lib/util
objekt modula, ući će u beskonačnu petlju. Zato lib/util
referencu jednostavno zamjenjuje s [Circular]
.
Sada je još važnije, što se događa ako lib/util
modul zahtijeva glavni index
modul? Tu ulazimo u ono što je poznato kao kružna modularna ovisnost, koja je dopuštena u Nodeu.
Da bismo ga bolje razumjeli, prvo shvatimo nekoliko drugih koncepata na objektu modula.
izvozi, module.exports i sinkrono učitavanje modula
U bilo kojem modulu izvoz je poseban objekt. Ako ste primijetili gore, svaki put kad smo ispisali objekt modula, on je imao svojstvo export, koje je do sada bilo prazan objekt. Ovom posebnom izvozu možemo dodati bilo koji atribut. Na primjer, izvezimo atribut id za index.js
i lib/util.js
:
// Add the following line at the top of lib/util.js exports.id = 'lib/util'; // Add the following line at the top of index.js exports.id = 'index';
Kada sada izvršimo index.js
, vidjet ćemo ove atribute kako se upravljaju na objektu svake datoteke module
:
~/learn-node $ node index.js In index Module { id: '.', exports: { id: 'index' }, loaded: false, ... } In util Module { id: '/Users/samer/learn-node/lib/util.js', exports: { id: 'lib/util' }, parent: Module { id: '.', exports: { id: 'index' }, loaded: false, ... }, loaded: false, ... }
Uklonio sam neke atribute u gornjem izlazu kako bi bio kratak, ali imajte na umu kako exports
objekt sada ima atribute koje smo definirali u svakom modulu. Na taj objekt izvoza možete staviti onoliko atributa koliko želite, a zapravo možete cijeli objekt promijeniti u nešto drugo. Na primjer, da bismo promijenili objekt izvoza u funkciju umjesto u objekt, radimo sljedeće:
// Add the following line in index.js before the console.log module.exports = function() {};
Kad index.js
sada pokrenete , vidjet ćete kako je exports
objekt funkcija:
~/learn-node $ node index.js In index Module { id: '.', exports: [Function], loaded: false, ... }
Imajte na umu kako nismo učinili exports = function() {}
da exports
objekt pretvorimo u funkciju. To zapravo ne možemo učiniti, jer je exports
varijabla unutar svakog modula samo referenca na module.exports
koju se upravlja izvoženim svojstvima. Kad dodijelimo exports
varijablu, ta se referenca gubi i umjesto promjene module.exports
objekta uveli bismo novu varijablu .
module.exports
Predmet u svakom modulu je ono što je require
funkcija vraća kada tražimo taj modul. Na primjer, promijenite require('./lib/util')
redak u index.js
:
const UTIL = require('./lib/util'); console.log('UTIL:', UTIL);
Gore će uhvatiti svojstva izvozi u lib/util
u UTIL
konstantu. Kad index.js
sada pokrenemo , izvest će se posljednji redak:
UTIL: { id: 'lib/util' }
Let’s also talk about the loaded
attribute on every module. So far, every time we printed a module object, we saw a loaded
attribute on that object with a value of false
.
The module
module uses the loaded
attribute to track which modules have been loaded (true value) and which modules are still being loaded (false value). We can, for example, see the index.js
module fully loaded if we print its module
object on the next cycle of the event loop using a setImmediate
call:
// In index.js setImmediate(() => { console.log('The index.js module object is now loaded!', module) });
The output of that would be:
The index.js module object is now loaded! Module { id: '.', exports: [Function], parent: null, filename: '/Users/samer/learn-node/index.js', loaded: true, children: [ Module { id: '/Users/samer/learn-node/lib/util.js', exports: [Object], parent: [Circular], filename: '/Users/samer/learn-node/lib/util.js', loaded: true, children: [], paths: [Object] } ], paths: [ '/Users/samer/learn-node/node_modules', '/Users/samer/node_modules', '/Users/node_modules', '/node_modules' ] }
Note how in this delayed console.log
output both lib/util.js
and index.js
are fully loaded.
The exports
object becomes complete when Node finishes loading the module (and labels it so). The whole process of requiring/loading a module is synchronous. That’s why we were able to see the modules fully loaded after one cycle of the event loop.
This also means that we cannot change the exports
object asynchronously. We can’t, for example, do the following in any module:
fs.readFile('/etc/passwd', (err, data) => { if (err) throw err; exports.data = data; // Will not work. });
Circular module dependency
Let’s now try to answer the important question about circular dependency in Node: What happens when module 1 requires module 2, and module 2 requires module 1?
To find out, let’s create the following two files under lib/
, module1.js
and module2.js
and have them require each other:
// lib/module1.js exports.a = 1; require('./module2'); exports.b = 2; exports.c = 3; // lib/module2.js const Module1 = require('./module1'); console.log('Module1 is partially loaded here', Module1);
When we run module1.js
we see the following:
~/learn-node $ node lib/module1.js Module1 is partially loaded here { a: 1 }
We required module2
before module1
was fully loaded, and since module2
required module1
while it wasn’t fully loaded, what we get from the exports
object at that point are all the properties exported prior to the circular dependency. Only the a
property was reported because both b
and c
were exported after module2
required and printed module1
.
Node keeps this really simple. During the loading of a module, it builds the exports
object. You can require the module before it’s done loading and you’ll just get a partial exports object with whatever was defined so far.
JSON and C/C++ addons
We can natively require JSON files and C++ addon files with the require function. You don’t even need to specify a file extension to do so.
If a file extension was not specified, the first thing Node will try to resolve is a .js
file. If it can’t find a .js
file, it will try a .json
file and it will parse the .json
file if found as a JSON text file. After that, it will try to find a binary .node
file. However, to remove ambiguity, you should probably specify a file extension when requiring anything other than .js
files.
Requiring JSON files is useful if, for example, everything you need to manage in that file is some static configuration values, or some values that you periodically read from an external source. For example, if we had the following config.json
file:
{ "host": "localhost", "port": 8080 }
We can require it directly like this:
const { host, port } = require('./config'); console.log(`Server will run at //${host}:${port}`);
Running the above code will have this output:
Server will run at //localhost:8080
If Node can’t find a .js
or a .json
file, it will look for a .node
file and it would interpret the file as a compiled addon module.
The Node documentation site has a sample addon file which is written in C++. It’s a simple module that exposes a hello()
function and the hello function outputs “world.”
You can use the node-gyp
package to compile and build the .cc
file into a .node
file. You just need to configure a binding.gyp file to tell node-gyp
what to do.
Once you have the addon.node
file (or whatever name you specify in binding.gyp
) then you can natively require it just like any other module:
const addon = require('./addon'); console.log(addon.hello());
We can actually see the support of the three extensions by looking at require.extensions
.

Looking at the functions for each extension, you can clearly see what Node will do with each. It uses module._compile
for .js
files, JSON.parse
for .json
files, and process.dlopen
for .node
files.
All code you write in Node will be wrapped in functions
Node’s wrapping of modules is often misunderstood. To understand it, let me remind you about the exports
/module.exports
relation.
We can use the exports
object to export properties, but we cannot replace the exports
object directly because it’s just a reference to module.exports
exports.id = 42; // This is ok. exports = { id: 42 }; // This will not work. module.exports = { id: 42 }; // This is ok.
How exactly does this exports
object, which appears to be global for every module, get defined as a reference on the module
object?
Let me ask one more question before explaining Node’s wrapping process.
In a browser, when we declare a variable in a script like this:
var answer = 42;
That answer
variable will be globally available in all scripts after the script that defined it.
This is not the case in Node. When we define a variable in one module, the other modules in the program will not have access to that variable. So how come variables in Node are magically scoped?
The answer is simple. Before compiling a module, Node wraps the module code in a function, which we can inspect using the wrapper
property of the module
module.
~ $ node > require('module').wrapper [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ] >
Node does not execute any code you write in a file directly. It executes this wrapper function which will have your code in its body. This is what keeps the top-level variables that are defined in any module scoped to that module.
This wrapper function has 5 arguments: exports
, require
, module
, __filename
, and __dirname
. This is what makes them appear to look global when in fact they are specific to each module.
All of these arguments get their values when Node executes the wrapper function. exports
is defined as a reference to module.exports
prior to that. require
and module
are both specific to the function to be executed, and __filename
/__dirname
variables will contain the wrapped module’s absolute filename and directory path.
You can see this wrapping in action if you run a script with a problem on its first line:
~/learn-node $ echo "euaohseu" > bad.js ~/learn-node $ node bad.js ~/bad.js:1 (function (exports, require, module, __filename, __dirname) { euaohseu ^ ReferenceError: euaohseu is not defined
Note how the first line of the script as reported above was the wrapper function, not the bad reference.
Moreover, since every module gets wrapped in a function, we can actually access that function’s arguments with the arguments
keyword:
~/learn-node $ echo "console.log(arguments)" > index.js ~/learn-node $ node index.js { '0': {}, '1': { [Function: require] resolve: [Function: resolve], main: Module { id: '.', exports: {}, parent: null, filename: '/Users/samer/index.js', loaded: false, children: [], paths: [Object] }, extensions: { ... }, cache: { '/Users/samer/index.js': [Object] } }, '2': Module { id: '.', exports: {}, parent: null, filename: '/Users/samer/index.js', loaded: false, children: [], paths: [ ... ] }, '3': '/Users/samer/index.js', '4': '/Users/samer' }
The first argument is the exports
object, which starts empty. Then we have the require
/module
objects, both of which are instances that are associated with the index.js
file that we’re executing. They are not global variables. The last 2 arguments are the file’s path and its directory path.
The wrapping function’s return value is module.exports
. Inside the wrapped function, we can use the exports
object to change the properties of module.exports
, but we can’t reassign exports itself because it’s just a reference.
What happens is roughly equivalent to:
function (require, module, __filename, __dirname) { let exports = module.exports; // Your Code... return module.exports; }
If we change the whole exports
object, it would no longer be a reference to module.exports
. This is the way JavaScript reference objects work everywhere, not just in this context.
The require object
There is nothing special about require
. It’s an object that acts mainly as a function that takes a module name or path and returns the module.exports
object. We can simply override the require
object with our own logic if we want to.
For example, maybe for testing purposes, we want every require
call to be mocked by default and just return a fake object instead of the required module exports object. This simple reassignment of require will do the trick:
require = function() { return { mocked: true }; }
After doing the above reassignment of require
, every require('something')
call in the script will just return the mocked object.
The require object also has properties of its own. We’ve seen the resolve
property, which is a function that performs only the resolving step of the require process. We’ve also seen require.extensions
above.
There is also require.main
which can be helpful to determine if the script is being required or run directly.
Say, for example, that we have this simple printInFrame
function in print-in-frame.js
:
// In print-in-frame.js const printInFrame = (size, header) => { console.log('*'.repeat(size)); console.log(header); console.log('*'.repeat(size)); };
The function takes a numeric argument size
and a string argument header
and it prints that header in a frame of stars controlled by the size we specify.
We want to use this file in two ways:
- From the command line directly like this:
~/learn-node $ node print-in-frame 8 Hello
Passing 8 and Hello as command line arguments to print “Hello” in a frame of 8 stars.
2. With require
. Assuming the required module will export the printInFrame
function and we can just call it:
const print = require('./print-in-frame'); print(5, 'Hey');
To print the header “Hey” in a frame of 5 stars.
Those are two different usages. We need a way to determine if the file is being run as a stand-alone script or if it is being required by other scripts.
This is where we can use this simple if statement:
if (require.main === module) { // The file is being executed directly (not with require) }
So we can use this condition to satisfy the usage requirements above by invoking the printInFrame function differently:
// In print-in-frame.js const printInFrame = (size, header) => { console.log('*'.repeat(size)); console.log(header); console.log('*'.repeat(size)); }; if (require.main === module) { printInFrame(process.argv[2], process.argv[3]); } else { module.exports = printInFrame; }
When the file is not being required, we just call the printInFrame
function with process.argv
elements. Otherwise, we just change the module.exports
object to be the printInFrame
function itself.
All modules will be cached
Caching is important to understand. Let me use a simple example to demonstrate it.
Say that you have the following ascii-art.js
file that prints a cool looking header:

We want to display this header every time we require the file. So when we require the file twice, we want the header to show up twice.
require('./ascii-art') // will show the header. require('./ascii-art') // will not show the header.
The second require will not show the header because of modules’ caching. Node caches the first call and does not load the file on the second call.
We can see this cache by printing require.cache
after the first require. The cache registry is simply an object that has a property for every required module. Those properties values are the module
objects used for each module. We can simply delete a property from that require.cache
object to invalidate that cache. If we do that, Node will re-load the module to re-cache it.
However, this is not the most efficient solution for this case. The simple solution is to wrap the log line in ascii-art.js
with a function and export that function. This way, when we require the ascii-art.js
file, we get a function that we can execute to invoke the log line every time:
require('./ascii-art')() // will show the header. require('./ascii-art')() // will also show the header.
That’s all I have for this topic. Thanks for reading. Until next time!
Learning React or Node? Checkout my books:
- Learn React.js by Building Games
- Node.js Beyond the Basics