PRE

This is the third day of my participation in Gwen Challenge

This is the source code analysis column

The analysis content is the process of require in Node.js

Understanding this section requires understanding the CommonJS specification in Node

The following is a brief introduction to the key implementation points of modularity, to get a general impression of the output of Module

Analyze the abstract output and concrete process of require again

Untangling this process is important to understand the execution environments of Module.exports and Node.js

Key to modular implementation

CommonJS specification

  • Module reference require
  • Module definition export
  • Module mark

Module Data structure of Module information

Require accepts the identifier and loads the target module

Exports. The module exports export

Module

// 01.js
const m = require('./m')

// m.js
require('./child')

module.exports = {
  add: function () {},
  foo: 'foo',}console.log(module)

// child.js
exports.name = 'child'
exports.age = '12'
Copy the code
Module {
  id: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/m.js'.path: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module'.exports: { add: [Function: add], foo: 'foo' },
  parent: Module {
    id: '. '.path: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module'.exports: {},
    parent: null.filename: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/01.js'.loaded: false.children: [ [Circular *1]],paths: [
      '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/node_modules'.'/Users/sedationh/workspace/current/class-notes/07Node/00/node_modules'.'/Users/sedationh/workspace/current/class-notes/07Node/node_modules'.'/Users/sedationh/workspace/current/class-notes/node_modules'.'/Users/sedationh/workspace/current/node_modules'.'/Users/sedationh/workspace/node_modules'.'/Users/sedationh/node_modules'.'/Users/node_modules'.'/node_modules']},filename: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/m.js'.loaded: false.children: [
    Module {
      id: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/child.js'.path: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module'.exports: [Object].parent: [Circular *1].filename: '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/child.js'.loaded: true.children: [].paths: [Array]}],paths: [
    '/Users/sedationh/workspace/current/class-notes/07Node/00/01module/node_modules'.'/Users/sedationh/workspace/current/class-notes/07Node/00/node_modules'.'/Users/sedationh/workspace/current/class-notes/07Node/node_modules'.'/Users/sedationh/workspace/current/class-notes/node_modules'.'/Users/sedationh/workspace/current/node_modules'.'/Users/sedationh/workspace/node_modules'.'/Users/sedationh/node_modules'.'/Users/node_modules'.'/node_modules']}Copy the code

In the abstract, the data is as follows

interface Module {
  id: string
  path: string
  exports: any
  parent: Module | null
  filename: string
  loaded: boolean
  children: Array<Module>
  paths: Array<string>}Copy the code

Module. exports and exports

Exports are used for easy export

Realization equivalent

exports = module.exports
Copy the code

Module. Exports is used when require imports

Require. main gets the entry file

Module classification and loading process

  • Built-in module
    • Source code, do not go through the complete loading process
  • File module
    • Path analysis
    • The file type
      • (m)
        • m.js m.json m.node
        • Json -> “main”: value
        • index
    • Compile implementation
      • js
        • Fs get plainText
        • wrap plainText -> Function -> executable function
        • function args get exports module reuqire… equivalent
      • json
        • JSON.parse

Cache priority

Add index to loaded modules and use caching when reintroduced

Source code require process analysis

vscode lanch.json

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0"."configurations": [{"type": "pwa-node"."request": "launch"."name": "Launch Program"."skipFiles": [
        // "<node_internals>/**"]."program": "${workspaceFolder}/07Node/00/01module/02debug.js"."outFiles": ["${workspaceFolder}/**/*.js"]]}}Copy the code

The require function

// Loads a module at the given file path. Returns that module's
// `exports` property.
Module.prototype.require = function(id) {
  validateString(id, 'id');
  if (id === ' ') {
    throw new ERR_INVALID_ARG_VALUE('id', id,
                                    'must be a non-empty string');
  }
  requireDepth++;
  try {
    return Module._load(id, this./* isMain */ false);
  } finally{ requireDepth--; }};Copy the code

The internal load function

// Check the cache for the requested file.
// 1. If a module already exists in the cache: return its exports object.
// 2. If the module is native: call
// `NativeModule.prototype.compileForPublicLoader()` and return the exports.
// 3. Otherwise, create a new module for the file and save it to the cache.
// Then have it load the file contents before returning its exports
// object.
Module._load = function(request, parent, isMain) {
  let relResolveCacheIdentifier;
Copy the code

For now, assume adding a new Module

Module._load = function(request, parent, isMain) {...constfilename = Module._resolveFilename(request, parent, isMain); .const module = cachedModule || newModule(filename, parent); . Module._cache[filename] =module; .module.load(filename);
Copy the code
// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {... Module._extensions[extension](this, filename);
Copy the code

Policy mode, using different loaders for different files

// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
  if (filename.endsWith('.js')) {... content = fs.readFileSync(filename,'utf8'); .module._compile(content, filename);
Copy the code

The file content in Content is the source code for Requrie

// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {...const compiledWrapper = wrapSafe(filename, content, this);
Copy the code

Get the wrapped function. Note that the wrapped function takes several arguments

exports require module __dirname __filename

For this reason, modules can get them directly during execution

// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {...const compiledWrapper = wrapSafe(filename, content, this); .const dirname = path.dirname(filename);
  const require = makeRequireFunction(this, redirects);
  let result;
  const exports = this.exports;
  const thisValue = exports;
  const module = this;
  if (requireDepth === 0) statCache = new Map(a);if (inspectorWrapper) {
    result = inspectorWrapper(compiledWrapper, thisValue, exports.require.module, filename, dirname);
  } else {
    result = compiledWrapper.call(thisValue, exports.require.module, filename, dirname); }...return result
Copy the code

Module.exports: this exports module.exports

Perform compilerWrapper. Call…

One part of this process is not clear

That’s where the wrapSafe function is

This uses the compileFunction method

const { compileFunction } = internalBinding('contextify');

This is an internal method whose key function is to create a virtual box to execute the code

The requirement here is that the namespace of the code requiring require should be separated from the namespace calling require

CompileFunction analysis

Wrap functions, separate scopes, execute functions

  1. From textual content to executable functions

Use function for concatenation

"(function (exports, require, module, __filename, __dirname){" + content + "})""
Copy the code
  1. Separate scope

Use the vm. RunInThisContext ()

vm.runInThisContext() compiles code, runs it within the context of the current global and returns the result. Running code does not have access to local scope, but does have access to the current global object.

If options is a string, then it specifies the filename.

The following example illustrates using both vm.runInThisContext() and the JavaScript eval() function to run the same code:

const vm = require('vm');
let localVar = 'initial value';

const vmResult = vm.runInThisContext('localVar = "vm"; ');
console.log(`vmResult: '${vmResult}', localVar: '${localVar}'`);
// Prints: vmResult: 'vm', localVar: 'initial value'

const evalResult = eval('localVar = "eval"; ');
console.log(`evalResult: '${evalResult}', localVar: '${localVar}'`);
// Prints: evalResult: 'eval', localVar: 'eval'
Copy the code

Debug View the execution process

debug -> const vmResult = vm.runInThisContext(‘localVar = “vm”; ‘)

function runInThisContext(code, options) {
  if (typeof options === 'string') {
    options = { filename: options };
  }
  return createScript(code, options).runInThisContext(options);
}
Copy the code

Create scirpt and call runInThisContext from the created script

This points to script

Scirpt’s runInThisContext overrides the runInThisContext inherited from ContextifyScript

class Script extends ContextifyScript {...runInThisContext(options) {
    const { breakOnSigint, args } = getRunInContextArgs(options);
    if (breakOnSigint && process.listenerCount('SIGINT') > 0) {
      return sigintHandlersWrap(super.runInThisContext, this, args);
    }
    return super.runInThisContext(... args); }Copy the code
const {
  ContextifyScript,
  MicrotaskQueue,
  makeContext,
  isContext: _isContext,
  constants,
  compileFunction: _compileFunction,
  measureMemory: _measureMemory,
} = internalBinding('contextify');
Copy the code

Execute here to enter the VM environment

return super.runInThisContext(... args);Copy the code

And the context in eval

Strict mode is not used here

If we use strict

The eval VO environment is created for eval execution

END

Welcome to SedationH

Source really is the best teacher | | document ~