Module parse
Relevant code located roughly node/lib/internal/modules/CJS/loader. Js
The default is js, JSON, and Node (v13 has updated the fourth one and is experimenting with it).
> require('module')._extensions
[Object: null prototype] {
'.js': [Function (anonymous)],
'.json': [Function (anonymous)],
'.node': [Function (anonymous)]
}
Copy the code
Node uses CommonJS mode instead of JSON by default when the suffix is not resolved, which is the only parsing mode covered here. (has been misled by the common format of various configuration files.)
The specific steps are as follows:
-
Example Query the absolute path of a file
-
The type of file to read
-
Create private scopes through encapsulation (function shells).
-
Read from the VM and execute the code
-
After loading, the corresponding absolute path cache code
The key module
Node has two key global modules for module resolution and does not need to be loaded for module reference and resolution.
-
Require
-
Module
It can be understood that require acts as an interactive instruction and module acts as the manager of all reference modules. Dependent references are accomplished through the cooperation of the two.
Load order
-
Built-in modules are preferred, if they have the same name (such as require and module).
-
The query cache
-
Query path
-
The query file
-
Querying folders
-
-
Query node_modules
-
An error
The order of address queries in Part 3 can be viewed by printing the Module. This is the order in which node_modules is queried and the corresponding path.
// console.log(module)
Module {
id: '. ',
path: '/Users/arius/Lib/node-test',
exports: {},
parent: null,
filename: '/Users/arius/Lib/node-test/modules.js',
loaded: false,
children: [],
paths: [
'/Users/arius/Lib/node-test/node_modules'.'/Users/arius/Lib/node_modules'.'/Users/arius/node_modules'.'/Users/node_modules'.'/node_modules']}Copy the code
Folder handling
If the file cannot be found in the path, the system queries the folder in the path.
Module._resolveFilename checks whether it is a relative path.
If it starts with a., it is a relative path, in which case the file or directory index is queried.
If it is not a relative path, node_modules is queried, with the path and order shown above.
This is the only time that package.json retrieval is triggered.
The loaded package.json is stored in the global packageJSONCache as {key: path, value: configJSON}.
The realization of the query in the lib/internal/modules/esm/resolve js: : packageMainResolve
So if the file does not exist, the execution sequence is as follows:
-
The query cache
-
Determine if it is a relative path
-
If yes, query the index in the path
-
If not, traverse the Paths query for package.json and cache it
-
Exports (2020-04 update)
-
Then check whether main exists
-
Then check whether the path contains index
-
-
-
An error
Module parse
In the lib/internal/modules/CJS/loader. The js: : Module. _compile.
When the extension is c? Js or default processing, go JS parsing.
Each JS file can be thought of simply as a function.
For this reason, each reference to the outermost code is executed, but only the corresponding Module is printed.
The Node interpreter is the tool that executes this function, passing the corresponding arguments to it to ensure the scope of execution.
> node Welcome to node.js v14.9.0.type".help" for more information.
> require('module').wrapper
Proxy [
[
'(function (exports, require, module, __filename, __dirname) { '.'\n}); '] and {set: [Function: set], defineProperty: [Function: defineProperty] }
]
>
Copy the code
Among them
function(
exports.// References to current module exports are the same thing as module.exports
require./ / it is commonly the Module. The prototype. The require namely _Module. Prototype. The require. _load
module.// The current module, also this
__filename, // The absolute path of the file
__dirname // File relative path
) {}
Copy the code
An anonymous function is generated and thrown to the VM to execute this string of code by adding a front and back shell for function execution and managing the input file access parameters with the Module.
Because Node uses functions to execute the entire file, you can print information about the entire module directly.
// args.js
console.log(arguments)
// run it
node args.js
[Arguments] {
'0': {},
'1': [Function: require] {
resolve: [Function: resolve] { paths: [Function: paths] },
main: Module {
id: '. ',
path: '/Users/arius/Lib/node-test/require-test',
exports: {},
parent: null,
filename: '/Users/arius/Lib/node-test/require-test/args.js',
loaded: false,
children: [],
paths: [Array]
},
extensions: [Object: null prototype] {
'.js': [Function (anonymous)],
'.json': [Function (anonymous)],
'.node': [Function (anonymous)]
},
cache: [Object: null prototype] {
'/Users/arius/Lib/node-test/require-test/args.js': [Module]
}
},
'2': Module {
id: '. ',
path: '/Users/arius/Lib/node-test/require-test',
exports: {},
parent: null,
filename: '/Users/arius/Lib/node-test/require-test/args.js',
loaded: false,
children: [],
paths: [
'/Users/arius/Lib/node-test/require-test/node_modules'.'/Users/arius/Lib/node-test/node_modules'.'/Users/arius/Lib/node_modules'.'/Users/arius/node_modules'.'/Users/node_modules'.'/node_modules']},'3': '/Users/arius/Lib/node-test/require-test/args.js'.'4': '/Users/arius/Lib/node-test/require-test'
}
Copy the code
Module. exports refers to the same thing as exports. Exports are references. Exports = XXX does not work.
module.exports = {
a: '123'.// 最终依然是 123
}
exports.a = '321';
Copy the code
Depend on the loop
When requiring a package or file, it is not the package that executes (or relies on) it directly. The module corresponding to the entire object is cached in the global object module. _cache[filename].
If the execution of A.js.
// a.js
const b = require('./b')
console.log(b) // 'b'
module.exports = 'a'
// b.js
const a = require('./a')
console.log(a) // {}, module. _cache a is still an empty Module {} object
module.exports = 'b'
Copy the code
In dependency processing, CJS and ES run in the same order (default, submodules are undefined), but ES can dynamically replace references. As long as the reference is called after the load is complete, there should be no problem. So pay attention to the order in which the outermost functions need to be executed.
// a.js
import { b } from '.. / ';
console.log(b)
export const a = 'a'
// b.js
import { a } from '.. / ';
console.log(a) // undefined
export const callA = () = > {
console.log(a) // 'a'
}
export const b = 'b'
// index
export * from './a';
export * from './b';
Copy the code
If you are MJS, you will get an error if you encounter cyclic dependencies, which may not yet be supported or the new specification.
ReferenceError: Cannot access 'a' before initialization
Copy the code
So what is MJS?
ECMAScript module
Official experimental ES6 specification, ending with the MJS suffix. The main difference between CJS and ESM is that require + module.exports corresponds to import and export.
// there should be an ExperimentalWarning (node:55066) ExperimentalWarning: The ESM Module loader is experimental. // Warning: To load an ES module,set "type": "module" in the package.json or use the .mjs extension.
Copy the code
Node requires esM modules to use the.mjs suffix and start strict mode by default.
Or set type to module in package.json so that js scripts executed in the directory will be resolved to ES6 modules. This parsing is limited to the JS suffix; other (non-JS) files using the CJS or MJS suffix will be parsed in the same way.
Unlike Babel, require cannot load MJS, and require cannot be used in MJS.
Call each other
When CJS calls MJS, it needs to be asynchronous. Asynchronous calls may be intended to inherit the esM dynamic reference mechanism, but circular dependencies are not currently allowed. One of the reasons require does not support ES6 modules is that it is loaded synchronously, and ES6 modules can use top-level await commands internally, so it cannot be loaded synchronously.
(async() = > {await import('./my-app.mjs'); }) ();Copy the code
When MJS calls CJS, it can only be called as a whole, not parsed.
Support for multiple modes
In ESM mode, export default is required for an overall interface. In CommonJS mode you need to wrap a layer and split a separate file.
import cjsModule from '.. /index.cjs';
export const{... } = cjsModule;Copy the code
Or use package.json, where exports can choose the invocation mode in addition to relative path substitution. Details are as follows:
// ./index.js
require('find-me');// Found you
// ./index.mjs
import 'find-me'; // Found me!
// ./node_modules/find-me/package.json
{
"exports": {
"import": "./me.mjs"
"require": "./you.js"}}// ./node_modules/find-me/you.js
console.log('Found you')
// ./node_modules/find-me/me.mjs
console.log('Found me! ')
Copy the code
FAQ
1. The number of incorrect lines in Node
Node itself does not handle error functions. All error handling comes from the VM. Historically, Node failed to report the stack because Node 4 did not set the displayErrors parameter at that time.
Github.com/nodejs/node…
The wrapper itself does not have a newline on the first line, and the code is not compressed and concatenated directly, so there is no line count problem.
Refers
-
Node repo, debug builds are a bit slow
-
Node MJS API documentation
-
Drill into node.js’s module loading mechanism by writing the require function. Very good article
-
Require something you need to know