• 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
  • throughmoduleObject to represent information about a module
  • throughexportsIt is used to export the exposed information of the module
  • throughrequireTo 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..nodeFile 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

  • inpackage.jsonAdded “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 allimportAn 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.gifCode 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