Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

看Node源码学Node模块加载 #7

Open
gamebody opened this issue Apr 2, 2019 · 0 comments
Open

看Node源码学Node模块加载 #7

gamebody opened this issue Apr 2, 2019 · 0 comments

Comments

@gamebody
Copy link
Owner

gamebody commented Apr 2, 2019

看Node源码学Node模块加载

↓↓↓↓该符号为断点进入的地方

// a.js
module.exports = {
    str: 'hello'
}
// b.js
const a = ↓↓↓↓require('./a'); // 在这打断点

console.log(a)

然后vscode断点调试

// require方法
  function require(path) {
    try {
      exports.requireDepth += 1;
      return  mod.require(path); ↓↓↓↓
    } finally {
      exports.requireDepth -= 1;
    }
  }

Module.prototype.require = function(path) {
  assert(path, 'missing path');
  assert(typeof path === 'string', 'path must be a string');
  return Module._load(path, this, /* isMain */ false);↓↓↓↓
};
// 该模块有没有缓存,有缓存返回缓存,没有创建一个新的模块,并且load该模块
Module._load = function(request, parent, isMain) {
  if (parent) {
    debug('Module._load REQUEST %s parent: %s', request, parent.id);
  }

  if (isMain && experimentalModules) {

  }

  var filename = Module._resolveFilename(request, parent, isMain);

  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
  }

  if (NativeModule.nonInternalExists(filename)) {
    debug('load native module %s', request);
    return NativeModule.require(filename);
  }

  // Don't call updateChildren(), Module constructor already does.
  var module = new Module(filename, parent);

  if (isMain) {
    process.mainModule = module;
    module.id = '.';
  }

  Module._cache[filename] = module;

  tryModuleLoad(module, filename);↓↓↓↓

  return module.exports;
};
// load文件
function tryModuleLoad(module, filename) {
  var threw = true;
  try {
    module.load(filename);↓↓↓↓
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
    }
  }
}
// 执行不同后缀的文件解析,当前用的.js文件加载方法
Module.prototype.load = function(filename) {
  // ...
  var extension = path.extname(filename) || '.js';
  if (!Module._extensions[extension]) extension = '.js';
  Module._extensions[extension](this, filename);↓↓↓↓
  // ....
};
// Native extension for .js
// 读取文件,去除BOM
Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(internalModule.stripBOM(content), filename);↓↓↓↓
};
// `_compile`的作用就是调用`vm.runInThisContext`虚拟机,来执行包起来的字符串代码
Module.prototype._compile = function(content, filename) {

  content = internalModule.stripShebang(content);

  // create wrapper function
  var wrapper = Module.wrap(content);↓↓↓↓

  var compiledWrapper = vm.runInThisContext(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true
  });
  
  //...

  // 执行编译好的javascript代码
  result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);
  if (depth === 0) stat.cache = null;
  return result;
};

把读取的文件源码,用function包起来`.

  // ...
  Module.wrapper = NativeModule.wrapper;
  Module.wrap = NativeModule.wrap;
  // ...
  NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

  NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
  ];

最后看一下module.js的api

(function (exports, require, module, __filename, __dirname) {

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  updateChildren(parent, this, false);
  this.filename = null;
  this.loaded = false;
  this.children = [];
}

Module.builtinModules = builtinModules;

Module._cache = Object.create(null);
Module._pathCache = Object.create(null);
Module._extensions = Object.create(null);

Module.wrapper = NativeModule.wrapper;
Module.wrap = NativeModule.wrap;
Module._debug = util.debuglog('module');
Module._findPath = function(request, paths, isMain) {};
Module._nodeModulePaths = function(from) {}
Module._resolveLookupPaths = function(request, parent, newReturn) {};
Module._load = function(request, parent, isMain) {};
Module._resolveFilename = function(request, parent, isMain, options) {};
Module.prototype.load = function(filename) {};
Module.prototype.require = function(path) {};
Module.prototype._compile = function(content, filename) {};


// Native extension for .js
Module._extensions['.js'] = function(module, filename) {};
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {};
//Native extension for .node
Module._extensions['.node'] = function(module, filename) {};


// bootstrap main module.
Module.runMain = function() {};
Module._initPaths = function() {};
Module._preloadModules = function(requests) {};

Module._initPaths();
// backwards compatibility
Module.Module = Module;

});

总结:

加载一个自己写的模块,原理其实很简单,就是:

  1. 判断该模块有没有cache,有返回cache
  2. 没有,创建一个新的模块 new Module()
  3. 读取该模块,得到一个字符串
  4. 调用vm.runInThisContext(code[, options])进行编译,
  5. 执行返回结果
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant