Rumah > hujung hadapan web > tutorial js > Terokai kod sumber Node.js dan terangkan proses pemuatan modul cjs secara terperinci

Terokai kod sumber Node.js dan terangkan proses pemuatan modul cjs secara terperinci

青灯夜游
Lepaskan: 2022-09-07 20:13:53
ke hadapan
2232 orang telah melayarinya

Terokai kod sumber Node.js dan terangkan proses pemuatan modul cjs secara terperinci

Saya percaya semua orang tahu cara memuatkan modul dalam Nod:

const fs = require('fs');
const express = require('express');
const anotherModule = require('./another-module');
Salin selepas log masuk

Ya, require sedang memuatkan cjs API modul, tetapi V8 sendiri tidak mempunyai sistem modul cjs, jadi bagaimana nod mencari modul melalui require dan memuatkannya? [Tutorial berkaitan yang disyorkan: tutorial video nodejs]

Hari ini kita akan meneroka kod sumber Node.js dan mendapat pemahaman yang mendalam tentang proses pemuatan modul cjs. Versi kod nod yang kami baca ialah v17.x:

  • git head: 881174e016d6c27b20c70111e6eae2296b6c6293
  • Pautan kod: Pautan kod:
github.>

🎜>

Bacaan kod sumber

Modul terbina dalam

require[src/node_main.cc](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node_main.cc#L105)Untuk mengetahui logik kerja daripada [node::Start](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node.cc#L1134), kami Anda perlu terlebih dahulu memahami cara modul terbina dalam dimuatkan ke dalam nod (seperti 'fs', 'path', 'child_process', yang juga termasuk beberapa modul dalaman yang tidak boleh dirujuk oleh pengguna menyediakan kod, kita perlu bermula dari nod Mula membaca. Fungsi utama nod adalah dalam

dan memulakan tika nod dengan memanggil kaedah
int Start(int argc, char** argv) {
  InitializationResult result = InitializeOncePerProcess(argc, argv);
  if (result.early_return) {
    return result.exit_code;
  }

  {
    Isolate::CreateParams params;
    const std::vector<size_t>* indices = nullptr;
    const EnvSerializeInfo* env_info = nullptr;
    bool use_node_snapshot =
        per_process::cli_options->per_isolate->node_snapshot;
    if (use_node_snapshot) {
      v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob();
      if (blob != nullptr) {
        params.snapshot_blob = blob;
        indices = NodeMainInstance::GetIsolateDataIndices();
        env_info = NodeMainInstance::GetEnvSerializeInfo();
      }
    }
    uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);

    NodeMainInstance main_instance(&params,
                                   uv_default_loop(),
                                   per_process::v8_platform.Platform(),
                                   result.args,
                                   result.exec_args,
                                   indices);
    result.exit_code = main_instance.Run(env_info);
  }

  TearDownOncePerProcess();
  return result.exit_code;
}
Salin selepas log masuk
:

NodeMainInstancemain_instanceGelung peristiwa dicipta di sini dan tika dicipta dan Kaedah

Run
int NodeMainInstance::Run(const EnvSerializeInfo* env_info) {
  Locker locker(isolate_);
  Isolate::Scope isolate_scope(isolate_);
  HandleScope handle_scope(isolate_);

  int exit_code = 0;
  DeleteFnPtr<Environment, FreeEnvironment> env =
      CreateMainEnvironment(&exit_code, env_info);
  CHECK_NOT_NULL(env);

  Context::Scope context_scope(env->context());
  Run(&exit_code, env.get());
  return exit_code;
}
Salin selepas log masuk
Salin selepas log masuk
nya dipanggil:

Run[CreateMainEnvironment](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node_main_instance.cc#L170)

Panggil
Environment* CreateEnvironment(
    IsolateData* isolate_data,
    Local<Context> context,
    const std::vector<std::string>& args,
    const std::vector<std::string>& exec_args,
    EnvironmentFlags::Flags flags,
    ThreadId thread_id,
    std::unique_ptr<InspectorParentHandle> inspector_parent_handle) {
  Isolate* isolate = context->GetIsolate();
  HandleScope handle_scope(isolate);
  Context::Scope context_scope(context);
  // TODO(addaleax): This is a much better place for parsing per-Environment
  // options than the global parse call.
  Environment* env = new Environment(
      isolate_data, context, args, exec_args, nullptr, flags, thread_id);
#if HAVE_INSPECTOR
  if (inspector_parent_handle) {
    env->InitializeInspector(
        std::move(static_cast<InspectorParentHandleImpl*>(
            inspector_parent_handle.get())->impl));
  } else {
    env->InitializeInspector({});
  }
#endif

  if (env->RunBootstrapping().IsEmpty()) {
    FreeEnvironment(env);
    return nullptr;
  }

  return env;
}
Salin selepas log masuk
dalam kaedah untuk mencipta dan memulakan persekitaran:

EnvironmentenvBuat[RunBootstrapping](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node.cc#L398) Objek

dan panggil kaedah
MaybeLocal<Value> Environment::RunBootstrapping() {
  EscapableHandleScope scope(isolate_);

  CHECK(!has_run_bootstrapping_code());

  if (BootstrapInternalLoaders().IsEmpty()) {
    return MaybeLocal<Value>();
  }

  Local<Value> result;
  if (!BootstrapNode().ToLocal(&result)) {
    return MaybeLocal<Value>();
  }

  // Make sure that no request or handle is created during bootstrap -
  // if necessary those should be done in pre-execution.
  // Usually, doing so would trigger the checks present in the ReqWrap and
  // HandleWrap classes, so this is only a consistency check.
  CHECK(req_wrap_queue()->IsEmpty());
  CHECK(handle_wrap_queue()->IsEmpty());

  DoneBootstrapping();

  return scope.Escape(result);
}
Salin selepas log masuk
nya:

[BootstrapInternalLoaders](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node.cc#L298)[internal/bootstrap/loaders.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.js#L326) [nativeModulerequire](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.js#L332) di sini melaksanakan langkah yang sangat penting dalam proses pemuatan modul nod: Dengan membungkus dan melaksanakan [internalBinding](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.js#L164), fungsi [NativeModule](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/loaders.js#L191) modul terbina dalam digunakan untuk memuatkan modul js terbina dalam, dan

digunakan untuk memuatkan modul C terbina dalam ialah a sistem modul kecil yang khusus digunakan untuk modul terbina dalam.
function nativeModuleRequire(id) {
  if (id === loaderId) {
    return loaderExports;
  }

  const mod = NativeModule.map.get(id);
  // Can't load the internal errors module from here, have to use a raw error.
  // eslint-disable-next-line no-restricted-syntax
  if (!mod) throw new TypeError(`Missing internal module '${id}'`);
  return mod.compileForInternalLoader();
}

const loaderExports = {
  internalBinding,
  NativeModule,
  require: nativeModuleRequire
};

return loaderExports;
Salin selepas log masuk

requirePerlu diambil perhatian bahawa fungsi require('module')._cache ini hanya akan digunakan untuk memuatkan modul terbina dalam dan ia tidak akan digunakan untuk memuatkan modul pengguna. (Ini juga sebab kita boleh melihat semua modul pengguna dengan mencetak fs, tetapi bukan modul terbina dalam seperti

, kerana kaedah pemuatan dan penyelenggaraan cache kedua-duanya berbeza).

Modul Pengguna

[NodeMainInstance::Run](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node_main_instance.cc#L127)Seterusnya mari kita kembalikan perhatian kepada fungsi

:
int NodeMainInstance::Run(const EnvSerializeInfo* env_info) {
  Locker locker(isolate_);
  Isolate::Scope isolate_scope(isolate_);
  HandleScope handle_scope(isolate_);

  int exit_code = 0;
  DeleteFnPtr<Environment, FreeEnvironment> env =
      CreateMainEnvironment(&exit_code, env_info);
  CHECK_NOT_NULL(env);

  Context::Scope context_scope(env->context());
  Run(&exit_code, env.get());
  return exit_code;
}
Salin selepas log masuk
Salin selepas log masuk

CreateMainEnvironmentKami Objek env telah dibuat melalui fungsi Environment Contoh NativeModule ini sudah mempunyai sistem modul Run untuk mengekalkan modul terbina dalam. Kod itu kemudiannya akan dijalankan ke versi terlampau beban fungsi yang lain

:
void NodeMainInstance::Run(int* exit_code, Environment* env) {
  if (*exit_code == 0) {
    LoadEnvironment(env, StartExecutionCallback{});

    *exit_code = SpinEventLoop(env).FromMaybe(1);
  }

  ResetStdio();

  // TODO(addaleax): Neither NODE_SHARED_MODE nor HAVE_INSPECTOR really
  // make sense here.
#if HAVE_INSPECTOR && defined(__POSIX__) && !defined(NODE_SHARED_MODE)
  struct sigaction act;
  memset(&act, 0, sizeof(act));
  for (unsigned nr = 1; nr < kMaxSignal; nr += 1) {
    if (nr == SIGKILL || nr == SIGSTOP || nr == SIGPROF)
      continue;
    act.sa_handler = (nr == SIGPIPE) ? SIG_IGN : SIG_DFL;
    CHECK_EQ(0, sigaction(nr, &act, nullptr));
  }
#endif

#if defined(LEAK_SANITIZER)
  __lsan_do_leak_check();
#endif
}
Salin selepas log masuk

[LoadEnvironment](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/api/environment.cc#L403) memanggil

di sini:
MaybeLocal<Value> LoadEnvironment(
    Environment* env,
    StartExecutionCallback cb) {
  env->InitializeLibuv();
  env->InitializeDiagnostics();

  return StartExecution(env, cb);
}
Salin selepas log masuk

[StartExecution](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/src/node.cc#L455) dan kemudian laksanakan

MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
  // 已省略其他运行方式,我们只看 `node index.js` 这种情况,不影响我们理解模块系统
  if (!first_argv.empty() && first_argv != "-") {
    return StartExecution(env, "internal/main/run_main_module");
  }
}
Salin selepas log masuk

StartExecution(env, "internal/main/run_main_module")这个调用中,我们会包装一个 function,并传入刚刚从 loaders 中导出的 require 函数,并运行 [lib/internal/main/run_main_module.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/main/run_main_module.js) 内的代码:

'use strict';

const {
  prepareMainThreadExecution
} = require('internal/bootstrap/pre_execution');

prepareMainThreadExecution(true);

markBootstrapComplete();

// Note: this loads the module through the ESM loader if the module is
// determined to be an ES module. This hangs from the CJS module loader
// because we currently allow monkey-patching of the module loaders
// in the preloaded scripts through require('module').
// runMain here might be monkey-patched by users in --require.
// XXX: the monkey-patchability here should probably be deprecated.
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);
Salin selepas log masuk

所谓的包装 function 并传入 require,伪代码如下:

(function(require, /* 其他入参 */) {
  // 这里是 internal/main/run_main_module.js 的文件内容
})();
Salin selepas log masuk

所以这里是通过内置模块require 函数加载了 [lib/internal/modules/cjs/loader.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/modules/cjs/loader.js#L172) 导出的 Module 对象上的 runMain 方法,不过我们在 loader.js 中并没有发现 runMain 函数,其实这个函数是在 [lib/internal/bootstrap/pre_execution.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/bootstrap/pre_execution.js#L428) 中被定义到 Module 对象上的:

function initializeCJSLoader() {
  const CJSLoader = require('internal/modules/cjs/loader');
  if (!noGlobalSearchPaths) {
    CJSLoader.Module._initPaths();
  }
  // TODO(joyeecheung): deprecate this in favor of a proper hook?
  CJSLoader.Module.runMain =
    require('internal/modules/run_main').executeUserEntryPoint;
}
Salin selepas log masuk

[lib/internal/modules/run_main.js](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/modules/run_main.js#L74) 中找到 executeUserEntryPoint 方法:

function executeUserEntryPoint(main = process.argv[1]) {
  const resolvedMain = resolveMainPath(main);
  const useESMLoader = shouldUseESMLoader(resolvedMain);
  if (useESMLoader) {
    runMainESM(resolvedMain || main);
  } else {
    // Module._load is the monkey-patchable CJS module loader.
    Module._load(main, null, true);
  }
}
Salin selepas log masuk

参数 main 即为我们传入的入口文件 index.js。可以看到,index.js 作为一个 cjs 模块应该被 Module._load 加载,那么 _load干了些什么呢?这个函数是 cjs 模块加载过程中最重要的一个函数,值得仔细阅读:

// `_load` 函数检查请求文件的缓存
// 1. 如果模块已经存在,返回已缓存的 exports 对象
// 2. 如果模块是内置模块,通过调用 `NativeModule.prototype.compileForPublicLoader()`
//    获取内置模块的 exports 对象,compileForPublicLoader 函数是有白名单的,只能获取公开
//    内置模块的 exports。
// 3. 以上两者皆为否,创建新的 Module 对象并保存到缓存中,然后通过它加载文件并返回其 exports。

// request:请求的模块,比如 `fs`,`./another-module`,'@pipcook/core' 等
// parent:父模块,如在 `a.js` 中 `require('b.js')`,那么这里的 request 为 'b.js',
           parent 为 `a.js` 对应的 Module 对象
// isMain: 除入口文件为 `true` 外,其他模块都为 `false`
Module._load = function(request, parent, isMain) {
  let relResolveCacheIdentifier;
  if (parent) {
    debug('Module._load REQUEST %s parent: %s', request, parent.id);
    // relativeResolveCache 是模块路径缓存,
    // 用于加速父模块所在目录下的所有模块请求当前模块时
    // 可以直接查询到实际路径,而不需要通过 _resolveFilename 查找文件
    relResolveCacheIdentifier = `${parent.path}\x00${request}`;
    const filename = relativeResolveCache[relResolveCacheIdentifier];
    if (filename !== undefined) {
      const cachedModule = Module._cache[filename];
      if (cachedModule !== undefined) {
        updateChildren(parent, cachedModule, true);
        if (!cachedModule.loaded)
          return getExportsForCircularRequire(cachedModule);
        return cachedModule.exports;
      }
      delete relativeResolveCache[relResolveCacheIdentifier];
    }
  }
	// 尝试查找模块文件路径,找不到模块抛出异常
  const filename = Module._resolveFilename(request, parent, isMain);
  // 如果是内置模块,从 `NativeModule` 加载
  if (StringPrototypeStartsWith(filename, 'node:')) {
    // Slice 'node:' prefix
    const id = StringPrototypeSlice(filename, 5);

    const module = loadNativeModule(id, request);
    if (!module?.canBeRequiredByUsers) {
      throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
    }

    return module.exports;
  }
	// 如果缓存中已存在,将当前模块 push 到父模块的 children 字段
  const cachedModule = Module._cache[filename];
  if (cachedModule !== undefined) {
    updateChildren(parent, cachedModule, true);
    // 处理循环引用
    if (!cachedModule.loaded) {
      const parseCachedModule = cjsParseCache.get(cachedModule);
      if (!parseCachedModule || parseCachedModule.loaded)
        return getExportsForCircularRequire(cachedModule);
      parseCachedModule.loaded = true;
    } else {
      return cachedModule.exports;
    }
  }
	// 尝试从内置模块加载
  const mod = loadNativeModule(filename, request);
  if (mod?.canBeRequiredByUsers) return mod.exports;
	
  // Don't call updateChildren(), Module constructor already does.
  const module = cachedModule || new Module(filename, parent);

  if (isMain) {
    process.mainModule = module;
    module.id = '.';
  }
	// 将 module 对象加入缓存
  Module._cache[filename] = module;
  if (parent !== undefined) {
    relativeResolveCache[relResolveCacheIdentifier] = filename;
  }

  // 尝试加载模块,如果加载失败则删除缓存中的 module 对象,
  // 同时删除父模块的 children 内的 module 对象。
  let threw = true;
  try {
    module.load(filename);
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
      if (parent !== undefined) {
        delete relativeResolveCache[relResolveCacheIdentifier];
        const children = parent?.children;
        if (ArrayIsArray(children)) {
          const index = ArrayPrototypeIndexOf(children, module);
          if (index !== -1) {
            ArrayPrototypeSplice(children, index, 1);
          }
        }
      }
    } else if (module.exports &&
               !isProxy(module.exports) &&
               ObjectGetPrototypeOf(module.exports) ===
                 CircularRequirePrototypeWarningProxy) {
      ObjectSetPrototypeOf(module.exports, ObjectPrototype);
    }
  }
	// 返回 exports 对象
  return module.exports;
};
Salin selepas log masuk

module 对象上的 [load](https://github.com/nodejs/node/blob/881174e016d6c27b20c70111e6eae2296b6c6293/lib/internal/modules/cjs/loader.js#L963) 函数用于执行一个模块的加载:

Module.prototype.load = function(filename) {
  debug('load %j for module %j', filename, this.id);

  assert(!this.loaded);
  this.filename = filename;
  this.paths = Module._nodeModulePaths(path.dirname(filename));

  const extension = findLongestRegisteredExtension(filename);
  // allow .mjs to be overridden
  if (StringPrototypeEndsWith(filename, '.mjs') && !Module._extensions['.mjs'])
    throw new ERR_REQUIRE_ESM(filename, true);

  Module._extensions[extension](this, filename);
  this.loaded = true;

  const esmLoader = asyncESM.esmLoader;
  // Create module entry at load time to snapshot exports correctly
  const exports = this.exports;
  // Preemptively cache
  if ((module?.module === undefined ||
       module.module.getStatus() < kEvaluated) &&
      !esmLoader.cjsCache.has(this))
    esmLoader.cjsCache.set(this, exports);
};
Salin selepas log masuk

实际的加载动作是在 Module._extensions[extension](this, filename); 中进行的,根据扩展名的不同,会有不同的加载策略:

  • .js:调用 fs.readFileSync 读取文件内容,将文件内容包在 wrapper 中,需要注意的是,这里的 requireModule.prototype.require 而非内置模块的 require 方法。
const wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});',
];
Salin selepas log masuk
  • .json:调用 fs.readFileSync 读取文件内容,并转换为对象。
  • .node:调用 dlopen 打开 node 扩展。

Module.prototype.require 函数也是调用了静态方法 Module._load实现模块加载的:

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--;
  }
};
Salin selepas log masuk

总结

看到这里,cjs 模块的加载过程已经基本清晰了:

  • 初始化 node,加载 NativeModule,用于加载所有的内置的 js 和 c++ 模块

  • 运行内置模块 run_main

  • run_main 中引入用户模块系统 module

  • 通过 module_load 方法加载入口文件,在加载时通过传入 module.requiremodule.exports 等让入口文件可以正常 require 其他依赖模块并递归让整个依赖树被完整加载。

在清楚了 cjs 模块加载的完整流程之后,我们还可以顺着这条链路阅读其他代码,比如 global 变量的初始化,esModule 的管理方式等,更深入地理解 node 内的各种实现。

更多node相关知识,请访问:nodejs 教程

Atas ialah kandungan terperinci Terokai kod sumber Node.js dan terangkan proses pemuatan modul cjs secara terperinci. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:juejin.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan