1. An overview of the
It is often said that Node is not a new programming language, it is simply a runtime for javascript, which you can simply think of as the environment in which you run javascript. Most of the time you run javascript in the browser, but with Node you can run javascript in Node, which means you can run javascript wherever you have Node or browser installed.
2. Modular implementation of Node
Node has its own modularity mechanism, each file is a separate module, and it follows the CommonJS specification, that is, import modules by require and export modules by module.export.
The operation mechanism of Node module is also very simple. In fact, each module is wrapped with a layer of functions, which can achieve scope isolation between codes.
You might say, WELL, I didn’t write the code with a function wrapped around it. Yes, I did. Node implements this layer of functions automatically, so you can test it.
Create a new js file and print a variable in the first line that does not exist, such as window. There is no window in Node.
console.log(window);
Copy the code
If you execute the file through Node, the following error message is displayed. Use the default CMD command.
(function (exports, require, module, __filename, __dirname) { console.log(window); ReferenceError: window is not defined at Object.<anonymous> (/Users/choice/Desktop/node/main.js:1:75) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions.. js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:279:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)Copy the code
Exports, require, module, __filename, __dirname, exports, require, module, __filename, __dirname, etc.
I covered this in my previous post on the Development of Front-end Modularity. Self-executing function is also one of the implementation schemes of front-end modularization. In the early era when there was no modular system in the front-end, self-executing function can solve the problem of namespace very well, and other modules that the module depends on can be passed in by parameters. The CMD and AMD specifications also rely on self-executing functions.
In the module system, each file is a module, and each module will automatically set a function outside, and define the export mode module. Exports or exports, and also define the import mode require.
let moduleA = (function() {
module.exports = Promise;
return module.exports; }) ();Copy the code
3. Require loading modules
Require relies on the FS module in Node to load the module file. Fs. readFile reads a string.
Javascript can convert a string to JAVASCRIPT code by means of eval or new Function.
1. eval
const name = 'yd';
const str = 'const a = 123; console.log(name)';
eval(str); // yd;
Copy the code
2. new Function
The new Function receives a string to be executed and returns a new Function that will be executed when called. If the Function needs to pass arguments, the arguments can be passed in the new Function sequence, followed by the string to be executed. Let’s say we pass in the argument B, the string STR to execute.
const b = 3;
const str = 'let a = 1; return a + b';
const fun = new Function('b', str);
console.log(fun(b, str)); / / 4
Copy the code
As you can see eval and Function instantiation can both be used to execute javascript strings, it seems that they can both be used to implement the Require module load. They are not chosen for modularity in Node, however, simply because they have the fatal problem of being affected by variables that do not belong to them.
The following string does not define a, but it can use the a variable defined above. This is clearly not correct. In a modularization mechanism, STR should have its own running space, and variables that do not exist can not be used directly.
const a = 1;
const str = 'console.log(a)';
eval(str);
const func = new Function(str);
func();
Copy the code
Node has the concept of a VM virtual environment for running additional JS files, which ensures that javascript execution is independent from external influences.
3. Vm built-in modules
Although hello is defined externally, STR is a separate module and is not in the Hello variable, so an error is reported directly.
// Introduce vm module, no installation required, Node built module
const vm = require('vm');
const hello = 'yd';
const str = 'console.log(hello)';
wm.runInThisContext(str); / / an error
Copy the code
So Node can use VM to execute javascript modules. This ensures that the module is independent.
4. Require code implementation
Before introducing the require code implementation, let’s review the use of the two Node modules, as we’ll see below.
1. The path module
Used to handle file paths.
Basename: base path. If there is a file path, it is not a base path. The base path is 1.js
Extname: obtains the extension name
Dirname: parent road strength
Join: indicates the join path
Resolve: Specifies the absolute path of the current folder. Do not add a slash at the end
__dirname: indicates the path of the current file folder
__filename: indicates the absolute path of the current file
const path = require('path'.'s');
console.log(path.basename('1.js'));
console.log(path.extname('2.txt'));
console.log(path.dirname('2.txt'));
console.log(path.join('a/b/c'.'d/e/f')); // a/b/c/d/e/
console.log(path.resolve('2.txt'));
Copy the code
2. The fs module
Used to operate files or folders, such as reading and writing files, adding files, deleting files, etc. Common methods include readFile and readFileSync, which read files asynchronously and synchronously, respectively.
const fs = require('fs');
const buffer = fs.readFileSync('./name.txt'.'utf8'); // If no encoding is passed in, the output is binary
console.log(buffer);
Copy the code
Fs. access: Determines whether a file exists. The exists method provided by node10 is deprecated because it does not comply with the Node specification, so access is used to determine whether a file exists.
try {
fs.accessSync('./name.txt');
} catch(e) {
// File does not exist
}
Copy the code
5. Manually implement the Require module loader
First import the dependent modules PATH, fs, and VM, and create a Require function that takes a modulePath argument indicating the path of the file to be imported.
// Import dependencies
const path = require('path'); // Path operation
const fs = require('fs'); // File read
const vm = require('vm'); // File execution
// Define the import class with the module path
function Require(modulePath) {... }Copy the code
In Require, get the absolute path of the Module, which is easy to load using FS. Here, the Module content is abstracted using new Module, and the Module content is loaded using tryModuleLoad. Module and tryModuleLoad will be implemented later. The return value of Require should be the contents of the module, which is module.exports.
// Define the import class with the module path
function Require(modulePath) {
// Get the current absolute path to load
let absPathname = path.resolve(__dirname, modulePath);
// Create Module, create Module instance
const module = new Module(absPathname);
// Load the current module
tryModuleLoad(module);
// Returns the exports object
return module.exports;
}
Copy the code
The implementation of Module is very simple: create an exports object for the Module. When tryModuleLoad is executed, add the content to exports. The id is the absolute path of the Module.
// Define module, add file ID identifier and exports property
function Module(id) {
this.id = id;
// The contents of the read file are placed in exports
this.exports = {};
}
Copy the code
Module exports, Module Require, Module exports, Module Require, Module exports, Module Require, Module exports, Module Require, and Module exports. __dirname, __filename, are global variables commonly used in modules. Notice that the Require parameter passed here is defined as Require.
The second argument is the end of the function. Both parts are strings, so wrap them around the module’s string.
Module.wrapper = [
"(function(exports, module, Require, __dirname, __filename) {"."})"
]
Copy the code
The _extensions are used to use different loading methods for different module extensions, such as JSON and javascript. JSON is run using json.parse.
If you run javascript using vm.runInThisContext, you can see that fs.readfilesync passes in module.id, which is the absolute path to the module when the module is defined, and reads the content as a string, Wrapping a Module with module.wrapper is like wrapping a function around the Module, thus implementing a private scope.
Exports, module, Require, __dirname, and __filename are all wrapped around this function.
Module._extensions = {
'.js'(module) {
const content = fs.readFileSync(module.id, 'utf8');
const fnStr = Module.wrapper[0] + content + Module.wrapper[1];
const fn = vm.runInThisContext(fnStr);
fn.call(module.exports, module.exports, module, Require,_filename,_dirname);
},
'.json'(module) {
const json = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(json); // Place the result of the file on the exports property}}Copy the code
The tryModuleLoad function receives the Module object, gets the extension name of the Module through path.extName, and loads the Module using module._extensions.
// Define the module loading method
function tryModuleLoad(module) {
// Get the extension
const extension = path.extname(module.id);
// Load the current module with a suffix
Module._extensions[extension](module);
}
Copy the code
At this point the Require loading mechanism is basically written. Resolve (__dirname, modulePath) in the Require method to get the absolute path to the file. Then create a Module object by instantiating a New Module, store the absolute path of the Module in the Module ID property, and create a JSON object in the Module exports property.
The tryModuleLoad method is used to load the module. In tryModuleLoad, path.extname is used to obtain the file extension, and the module loading mechanism is implemented based on the extension.
The module that will eventually be loaded is mounted in module.exports. Module. exports already exists after tryModuleLoad is executed.
// Import dependencies
const path = require('path'); // Path operation
const fs = require('fs'); // File read
const vm = require('vm'); // File execution
// Define the import class with the module path
function Require(modulePath) {
// Get the current absolute path to load
let absPathname = path.resolve(__dirname, modulePath);
// Create Module, create Module instance
const module = new Module(absPathname);
// Load the current module
tryModuleLoad(module);
// Returns the exports object
return module.exports;
}
// Define module, add file ID identifier and exports property
function Module(id) {
this.id = id;
// The contents of the read file are placed in exports
this.exports = {};
}
// Define the function that wraps the contents of the module
Module.wrapper = [
"(function(exports, module, Require, __dirname, __filename) {"."})"
]
// Define the extension, different extension, loading mode is different, implement JS and JSON
Module._extensions = {
'.js'(module) {
const content = fs.readFileSync(module.id, 'utf8');
const fnStr = Module.wrapper[0] + content + Module.wrapper[1];
const fn = vm.runInThisContext(fnStr);
fn.call(module.exports, module.exports, module, Require,_filename,_dirname);
},
'.json'(module) {
const json = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(json); // Place the result of the file on the exports property}}// Define the module loading method
function tryModuleLoad(module) {
// Get the extension
const extension = path.extname(module.id);
// Load the current module with a suffix
Module._extensions[extension](module);
}
Copy the code
6. Add cache to the module
Adding cache is also relatively simple, that is, when the file is loaded, put the file into cache, and then load the module to see whether there is in the cache, if there is, use it directly, if there is no to re-love, and then put into cache after loading.
// Define the import class with the module path
function Require(modulePath) {
// Get the current absolute path to load
let absPathname = path.resolve(__dirname, modulePath);
// Read from the cache, if present, return the result directly
if (Module._cache[absPathname]) {
return Module._cache[absPathname].exports;
}
// Create Module, create Module instance
const module = new Module(absPathname);
// Add cache
Module._cache[absPathname] = module;
// Load the current module
tryModuleLoad(module);
// Returns the exports object
return module.exports;
}
Copy the code
7. Omit the module name extension
Automatically add the suffix to the module, realize the omission of the suffix to load the module, in fact, if the file does not have a suffix when traversing all the suffix name to see whether the file exists.
// Define the import class with the module path
function Require(modulePath) {
// Get the current absolute path to load
let absPathname = path.resolve(__dirname, modulePath);
// Get all suffix names
const extNames = Object.keys(Module._extensions);
let index = 0;
// Store the original file path
const oldPath = absPathname;
function findExt(absPathname) {
if (index === extNames.length) {
return throw new Error('File does not exist');
}
try {
fs.accessSync(absPathname);
return absPathname;
} catch(e) {
constext = extNames[index++]; findExt(oldPath + ext); }}// Recursively appends the suffix name to determine whether the file exists
absPathname = findExt(absPathname);
// Read from the cache, if present, return the result directly
if (Module._cache[absPathname]) {
return Module._cache[absPathname].exports;
}
// Create Module, create Module instance
const module = new Module(absPathname);
// Add cache
Module._cache[absPathname] = module;
// Load the current module
tryModuleLoad(module);
// Returns the exports object
return module.exports;
}
Copy the code