NodeJS執行js文件流程

在終端執行node test.js來運行test.js文件,通過拋出的異常日誌,來分析nodejs的執行流程

D:\AliOS\HelloWorldTS>node test.js
internal/modules/cjs/loader.js:800
    throw err;
    ^

Error: Cannot find module 'D:\AliOS\HelloWorldTS\test.js'
[90m    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:797:15)[
[90m    at Function.Module._load (internal/modules/cjs/loader.js:690:27)[39m
[90m    at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)[39m
[90m    at internal/main/run_main_module.js:17:11[39m {
  code: [32m'MODULE_NOT_FOUND'[39m,
  requireStack: []
}

先看 internal/main/run_main_module.js

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

prepareMainThreadExecution(true);

require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);

最後一句,加載internal/modules/cjs/loader並運行Module.runMain方法,傳遞的參數就是test.js

但是它首先加載了internal/bootstrap/pre_execution的prepareMainThreadExecution方法,並執行。

接下來就看看internal/bootstrap/pre_execution

function prepareMainThreadExecution(expandArgv1 = false) {
    ...
    initializeCJSLoader();
    ...
}

function initializeCJSLoader() {
  const CJSLoader = require('internal/modules/cjs/loader');
  CJSLoader.Module._initPaths();
  // TODO(joyeecheung): deprecate this in favor of a proper hook?
  CJSLoader.Module.runMain =
    require('internal/modules/run_main').executeUserEntryPoint;
}
    
module.exports = {

  ...

  prepareMainThreadExecut
};

到此我們知道,internal/modules/cjs/loader中的Module.runMain方法,是internal/modules/run_main模塊的executeUserEntryPoint方法

看下internal/modules/run_main

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); // 只需要知道,最終走這裏就可以了
  }
}

module.exports = {
  executeUserEntryPoint
};

接下來就是執行internal/modules/cjs/loader中的Module._load方法

Module._extensions = ObjectCreate(null);

// 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;
  if (parent) {
    ... // 這裏parent=null,不執行此處代碼
  }

  // 開頭報錯就是這裏拋出的
  const filename = Module._resolveFilename(request, parent, isMain);

  // 查找緩存
  const cachedModule = Module._cache[filename];
  if (cachedModule !== undefined) {
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
  }
   
  // 加載Native模塊
  const mod = loadNativeModule(filename, request, experimentalModules);
  if (mod && mod.canBeRequiredByUsers) return mod.exports;

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

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

  Module._cache[filename] = module;
  if (parent !== undefined) {
    relativeResolveCache[relResolveCacheIdentifier] = filename;
  }

  let threw = true;
  try {
    // Intercept exceptions that occur during the first tick and rekey them
    // on error instance rather than module instance (which will immediately be
    // garbage collected).
    if (enableSourceMaps) {
      try {
        module.load(filename);
      } catch (err) {
        rekeySourceMap(Module._cache[filename], err);
        throw err; /* node-do-not-add-exception-line */
      }
    } else {
      module.load(filename);
    }
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
      if (parent !== undefined) {
        delete relativeResolveCache[relResolveCacheIdentifier];
      }
    }
  }

  return module.exports;
};

// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {
  this.filename = filename;
  this.paths = Module._nodeModulePaths(path.dirname(filename));

  const extension = findLongestRegisteredExtension(filename);
  // allow .mjs to be overridden
  if (filename.endsWith('.mjs') && !Module._extensions['.mjs']) {
    throw new ERR_REQUIRE_ESM(filename);
  }
  // 這裏是調用對應的擴展名方法
  Module._extensions[extension](this, filename);
  this.loaded = true;
  ...
}  
    
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
  if (filename.endsWith('.js')) {
    const pkg = readPackageScope(filename);
    // Function require shouldn't be used in ES modules.
    if (pkg && pkg.data && pkg.data.type === 'module') {
      const parentPath = module.parent && module.parent.filename;
      const packageJsonPath = path.resolve(pkg.path, 'package.json');
      throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
    }
  }
  const content = fs.readFileSync(filename, 'utf8');
  module._compile(content, filename);
};
    
// 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) {
  ...

  maybeCacheSourceMap(filename, content, this);
  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;
  ...
    result = compiledWrapper.call(thisValue, exports, require, module,
                                  filename, dirname);
  ...
  hasLoadedAnyUserCJSModule = true;
  
  return result;
}; 

    
function wrapSafe(filename, content, cjsModuleInstance) {
  if (patched) {
    ...//這裏patched爲false,表示沒有修改過wrapper數組
  }

  let compiled;
  try {
    compiled = compileFunction(
      content,
      filename,
      0,
      0,
      undefined,
      false,
      undefined,
      [],
      [
        'exports',
        'require',
        'module',
        '__filename',
        '__dirname',
      ]
    );
  } catch (err) {
    if (experimentalModules && process.mainModule === cjsModuleInstance)
      enrichCJSError(err);
    throw err;
  }
  return compiled.function;
}    
    
const { compileFunction } = internalBinding('contextify');    

internalBinding就是nodejs的C++代碼層暴露到js層的方法,用來加載內置的C++模塊

接着看下internal/main/run_main_module方法是在哪裏調用的,搜索文件名後發現在node.cc代碼中使用到了

// node.cc文件

MaybeLocal<Value> StartMainThreadExecution(Environment* env) {
  // To allow people to extend Node in different ways, this hook allows
  // one to drop a file lib/_third_party_main.js into the build
  // directory which will be executed instead of Node's normal loading.
  if (NativeModuleEnv::Exists("_third_party_main")) {
    return StartExecution(env, "internal/main/run_third_party_main");
  }

  std::string first_argv;
  if (env->argv().size() > 1) {
    first_argv = env->argv()[1];
  }
  ...
  if (!first_argv.empty() && first_argv != "-") {
    return StartExecution(env, "internal/main/run_main_module");
  }
  ...
  return StartExecution(env, "internal/main/eval_stdin");
}


void LoadEnvironment(Environment* env) {
  CHECK(env->is_main_thread());
  // TODO(joyeecheung): Not all of the execution modes in
  // StartMainThreadExecution() make sense for embedders. Pick the
  // useful ones out, and allow embedders to customize the entry
  // point more directly without using _third_party_main.js
  USE(StartMainThreadExecution(env));
}

LoadEnvironment方法是在node_main_instance.cc方法中執行的

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

  int exit_code = 0;
  // TODO 
  std::unique_ptr<Environment> env = CreateMainEnvironment(&exit_code);

  CHECK_NOT_NULL(env);
  Context::Scope context_scope(env->context());

  if (exit_code == 0) {
    {
      InternalCallbackScope callback_scope(
          env.get(),
          Local<Object>(),
          { 1, 0 },
          InternalCallbackScope::kAllowEmptyResource |
              InternalCallbackScope::kSkipAsyncHooks);
      // TODO             
      LoadEnvironment(env.get());
    }

    env->set_trace_sync_io(env->options()->trace_sync_io);

    {
      SealHandleScope seal(isolate_);
      bool more;
      env->performance_state()->Mark(
          node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
      // TODO chengzhen libuv庫的試下Nodejs主循環
      do {
        uv_run(env->event_loop(), UV_RUN_DEFAULT);

        per_process::v8_platform.DrainVMTasks(isolate_);

        more = uv_loop_alive(env->event_loop());
        if (more && !env->is_stopping()) continue;

        if (!uv_loop_alive(env->event_loop())) {
          EmitBeforeExit(env.get());
        }

        // Emit `beforeExit` if the loop became alive either after emitting
        // event, or after running some callbacks.
        more = uv_loop_alive(env->event_loop());
      } while (more == true && !env->is_stopping());
      env->performance_state()->Mark(
          node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
    }

    env->set_trace_sync_io(false);
    exit_code = EmitExit(env.get());
  }

  env->set_can_call_into_js(false);
  env->stop_sub_worker_contexts();
  ResetStdio();
  env->RunCleanup();

  // 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

  RunAtExit(env.get());

  per_process::v8_platform.DrainVMTasks(isolate_);

#if defined(LEAK_SANITIZER)
  __lsan_do_leak_check();
#endif

  return exit_code;
}


作者:orgcheng
鏈接:https://www.jianshu.com/p/250fb5aeb098
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章