- Writer: Slow down
Modularity Background
JavaScript in the early days was designed to implement simple page interaction logic, but as the era has progressed, browsers have become more than just simple interaction, and websites of all kinds have begun to shine. As websites became more complex and front-end code increased, JavaScript’s lack of modularity compared to other static languages began to reveal its drawbacks, such as naming conflicts. So in order to facilitate the maintenance and management of front-end code, the community began to define modular specifications. In this process, a lot of modular specifications appeared, such as CommonJS, AMD, CMD, ES Modules, this article mainly explains Node according to CommonJS modular implementation.
CommonJS specification
First of all, in the Node world, the module system complies with the CommonJS specification, which is defined as:
- Each file is a module
- through
module
Object to represent information about a module - through
exports
It is used to export the exposed information of the module - through
require
To reference a module
Node Module Classification
- Core modules: modules such as FS, HTTP, PATH, etc. These modules do not need to be installed and are already loaded in memory at runtime
- Third-party modules: installed and stored in node_modules
- Custom module: mainly refers to the file module, which is imported by absolute path or relative path
The Module object
A module object should have the following properties: -id: indicates the id of the current module. -path: indicates the path of the current module. -parent: also a module object, representing the parent module of the current module, that is, the module that called the current module. -filename: The name of the file (absolute path) of the current module, which can be used to add the loaded module to the global module cache when the module is imported. The value can be directly obtained from the cache when the module is imported. -loaded: indicates whether the current module is loaded. – Paths: An array that looks for node_modules starting from the current module and working up to node_modules in the root directory
The module exports and exports
Module. exports and exports
First, let’s do a simple validation with a new module
console.log(module.exports === exports); // true
Copy the code
Module. exports and epxorts refer to the same reference variable.
demo1
/ / a module
module.exports.text = 'xxx';
exports.value = 2;
// b module code
let a = require('./a');
console.log(a); // {text: 'xxx', value: 2}
Copy the code
Exports and exports exist when the module is introduced, because both end up adding attributes to the same reference variable. From the demo, it can be concluded that module.exports and exports refer to the same reference variable
demo2
/ / a module
module.exports = {
text: 'xxx'
}
exports.value = 2;
// b module code
let a = require('./a');
console.log(a); // {text: 'xxx'}
Copy the code
Module. exports () {module.exports () {module.exports () {module.exports () {module.exports () {module.exports (); Noed’s module ultimately exports module.exports, and exports is just a reference to module.exports, similar to the following code:
exports = module.exports = {};
(function (exports.module) {
// the code inside the a module
module.exports = {
text: 'xxx'
}
exports.value = 2;
console.log(module.exports === exports); // false}) (exports.module)
Copy the code
Exports is only a reference to the original module.exports variable. When module.exports is assigned, the corresponding variable is not the same as the latest module.exports variable
Method the require
The process of requiring module introduction is divided into the following steps:
- Resolve the file path to an absolute path
- Check whether the current module to be loaded has a cache. If so, use the cache directly
- Check whether node has a built-in module, such as HTTP,fs, and so on
- Create a module object based on the file path
- Add the module to the module cache
- Parses and compiles files using the corresponding file parsing method. (Node supports only parsing by default
.js
..json
..node
File with suffix) - Returns the loaded module Exports object
Module.prototype.require = function(id) {
// ...
try {
// The Module is loaded mainly through the static method of Module _load
return Module._load(id, this./* isMain */ false);
} finally {}
// ...
};
// ...
Module._load = function(request, parent, isMain) {
let relResolveCacheIdentifier;
// ...
// Resolve the file path to an absolute path
const filename = Module._resolveFilename(request, parent, isMain);
// Check whether the module to be loaded already has a cache
const cachedModule = Module._cache[filename];
// If there is a cache, use the cache directly
if(cachedModule ! = =undefined) {
// ...
return cachedModule.exports;
}
// Check whether node has a built-in module, such as HTTP,fs, etc
const mod = loadNativeModule(filename, request);
if (mod && mod.canBeRequiredByUsers) return mod.exports;
// Initialize a module based on the file path
const module = cachedModule || new Module(filename, parent);
// ...
// Add the module to the module cache
Module._cache[filename] = module;
if(parent ! = =undefined) {
relativeResolveCache[relResolveCacheIdentifier] = filename;
}
// ...
// Load the module
module.load(filename);
return module.exports;
};
Copy the code
At this point, the module principle flow of Node is basically over. Node V13.2.0 supports the ESM feature.
__filename, __dirname
When working with Node, have you ever wondered where __filename, __dirname comes from, and why they exist? Read this chapter carefully and you will have a systematic understanding of these.
- Following the require source code above, when a module is loaded, the contents of the module are read
- Wrap the content into a function body
- Compile a concatenated function string into a function
- Executes the compiled function, passing in the corresponding arguments
Module.prototype._compile = function(content, filename) {
// ...
const compiledWrapper = wrapSafe(filename, content, this);
//
result = compiledWrapper.call(thisValue, exports.require.module,
filename, dirname);
// ...
return result;
};
Copy the code
function wrapSafe(filename, content, cjsModuleInstance) {
// ...
const wrapper = Module.wrap(content);
// ...
}
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { '.'\n}); '
];
ObjectDefineProperty(Module, 'wrap', {
get() {
return wrap;
},
set(value) {
patched = true; wrap = value; }});Copy the code
The module contains __dirname,__filename, module exports, require, and so on
ES Modules are used in NodeJS
- in
package.json
Added “type”: “module” configuration
// test.mjs
export default {
a: 'xxx'
}
// import.js
import a from './test.mjs';
console.log(a); // {a: 'xxx'}
Copy the code
The difference between import and require mechanisms
The obvious difference lies in the timing of execution:
- The ES module will execute with all
import
An imported module is pre-parsed before any other module in the module
// entry.js
console.log('execute entry');
let a = require('./a.js')
console.log(a);
// a.js
console.log('-----a--------');
module.exports = 'this is a';
// The final output order is:
// execute entry
// -----a--------
// this is a
Copy the code
// entry.js
console.log('execute entry');
import b from './b.mjs';
console.log(b);
// b.mjs
console.log('-----b--------');
export default 'this is b';
// The final output order is:
// -----b--------
// execute entry
// this is b
Copy the code
- Import can only be at the top level of a module, not in a code block (e.g
if
Code block), needed if dynamic import is requiredimport()
Dynamic loading;
ES module compared to CommonJS module, there are the following differences:
-
No require, exports or module.exports
In most cases, you can load CommonJS modules using ES module import. If you need to import CommonJS modules with the.js suffix, you can construct require functions in the ES module using module.createrequire ()
// test.cjs
export default {
a: 'xxx'
}
// import.js
import a from './test.cjs';
console.log(a); // {a: 'xxx'}
Copy the code
// test.cjs
export default {
a: 'xxx'
}
// import.js
import a from './test.cjs';
console.log(a); // {a: 'xxx'}
Copy the code
// test.cjs
export default {
a: 'xxx'
}
// import.mjs
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// Test.js is a CommonJS module.
const siblingModule = require('./test');
console.log(siblingModule); // {a: 'xxx'}
Copy the code
-
There is no __filename or __dirname
These CommonJS variables are not available in the ES module.
-
No JSON module is loaded
JSON imports are still experimental and are only supported with the — experimental-Json-modules flag.
-
No require. Resolve
-
There is no NODE_PATH
-
No require. Extensions
-
No require. The cache
ES module and CommonJS reference to each other
Introduce ES module in CommonJS
Because ES Modules load, parse, and execute asynchronously, and require() is synchronous, you can’t reference an ES6 module by require().
The import() function proposed by ES6 will return a Promise that completes after the ES Modules load. We can use this to import ES Modules asynchronously in CommonJS:
// b.mjs
export default 'esm b'
// entry.js
(async() = > {let { default: b } = await import('./b.mjs');
console.log(b); // esm b}) ()Copy the code
Introduce CommonJS in the ES module
It is convenient to use import to reference a CommonJS module in ES6 modules because asynchronous loading is not required in ES6 modules:
// a.cjs
module.exports = 'commonjs a';
// entry.js
import a from './a.cjs';
console.log(a); // commonjs a
Copy the code
So far, here are two demos for you to test whether you have mastered the above knowledge points, if not, you can come back to read.
demo module.exports&exports
/ / a module
exports.value = 2;
// b module code
let a = require('./a');
console.log(a); // {value: 2}
Copy the code
demo module.exports&exports
/ / a module
exports = 2;
// b module code
let a = require('./a');
console.log(a); / / {}
Copy the code
The require&_cache module cache mechanism
// origin.js
let count = 0;
exports.addCount = function () {
count++
}
exports.getCount = function () {
return count;
}
// b.js
let { getCount } = require('./origin');
exports.getCount = getCount;
// a.js
let { addCount, getCount: getValue } = require('./origin');
addCount();
console.log(getValue()); / / 1
let { getCount } = require('./b');
console.log(getCount()); / / 1
Copy the code
require.cache
In the example above, the module will add the cache object require.cache when require is introduced. If you need to remove the cache, consider clearing it, and the next time the require module will reload the module.
let count = 0;
exports.addCount = function () {
count++
}
exports.getCount = function () {
return count;
}
// b.js
let { getCount } = require('./origin');
exports.getCount = getCount;
// a.js
let { addCount, getCount: getValue } = require('./origin');
addCount();
console.log(getValue()); / / 1
delete require.cache[require.resolve('./origin')];
let { getCount } = require('./b');
console.log(getCount()); / / 0
Copy the code
conclusion
So far, this article mainly introduces the Node based on CommonJS modular mechanism, and through the source code of the modular process is analyzed, there is a module introduction can see the following resources. If you have any questions, please leave them in the comments section. Thank you.
The resources
CommonJS module
ES module