Node.js原始碼解析-啟動-js部分

xiedacon發表於2019-02-16

Node.js原始碼解析-啟動-js部分

歡迎來我的部落格閱讀:《Node.js原始碼解析-啟動-js部分》

Node.js 版本 8.x

Node.js 程式啟動時,首先執行 c / c++ 程式碼,然後 c / c++ 載入並執行 lib/internal/bootstrap_node.js 並給予一個 process 引數( 執行上下文 )

// lib/internal/bootstrap_node.js 概覽

// Hello, and welcome to hacking node.js!
//
// This file is invoked by node::LoadEnvironment in src/node.cc, and is
// responsible for bootstrapping the node.js core. As special caution is given
// to the performance of the startup process, many dependencies are invoked
// lazily.

`use strict`;

// 這裡的 process 物件來自 c / c++,屬於原始資料
(function(process) {
  // ...

  startup();
})

載入 lib/internal/bootstrap_node.js 後,直接執行 startup() 函式

startup()

// lib/internal/bootstrap_node.js

  function startup() {
    // 下面幾行程式碼使 process 具有 EventEmitter 的特性,比如說 on,emit
    // BEGIN 
    const EventEmitter = NativeModule.require(`events`);
    process._eventsCount = 0;

    const origProcProto = Object.getPrototypeOf(process);
    Object.setPrototypeOf(process, Object.create(EventEmitter.prototype, {
      constructor: Object.getOwnPropertyDescriptor(origProcProto, `constructor`)
    }));
    // 相當於 new EventEmitter() ,不過上下文是 process
    EventEmitter.call(process);
    // END

    // 一些初始化操作
    // BEGIN
    setupProcessObject();
    // do this good and early, since it handles errors.
    setupProcessFatal();
    setupProcessICUVersions();
    setupGlobalVariables();
    if (!process._noBrowserGlobals) {
      setupGlobalTimeouts();
      setupGlobalConsole();
    }
    // END

    // process 物件的初始化操作
    // BEGIN
    // 這裡的 internal/process 模組用於初始化 process 
    const _process = NativeModule.require(`internal/process`);
    _process.setup_hrtime();
    _process.setup_cpuUsage();
    _process.setupMemoryUsage();
    _process.setupConfig(NativeModule._source);
    NativeModule.require(`internal/process/warning`).setup();
    NativeModule.require(`internal/process/next_tick`).setup();
    NativeModule.require(`internal/process/stdio`).setup();
    _process.setupKillAndExit();
    _process.setupSignalHandlers();
    if (global.__coverage__)
      NativeModule.require(`internal/process/write-coverage`).setup();

    if (process.argv[1] !== `--debug-agent`)
      _process.setupChannel();

    _process.setupRawDebug();
    
    NativeModule.require(`internal/url`);

    Object.defineProperty(process, `argv0`, {
      enumerable: true,
      configurable: false,
      value: process.argv[0]
    });
    process.argv[0] = process.execPath;
    // ...
    // END

    // 下面的 if-else 用來判斷 node 的執行模式,我們只關注 node xx.js 的執行模式

    // if () {
    // ...
    } else {
      // 執行使用者程式碼

      // cluster 模組的 hook
      if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
        const cluster = NativeModule.require(`cluster`);
        cluster._setupWorker();

        // Make sure it`s not accidentally inherited by child processes.
        delete process.env.NODE_UNIQUE_ID;
      }

      if (process._eval != null && !process._forceRepl) {
        // ...
      } else if (process.argv[1] && process.argv[1] !== `-`) {
        // node app.js

        // make process.argv[1] into a full path
        const path = NativeModule.require(`path`);
        // 變為絕對路徑
        process.argv[1] = path.resolve(process.argv[1]);

        const Module = NativeModule.require(`module`);

        // check if user passed `-c` or `--check` arguments to Node.
        if (process._syntax_check_only != null) {
          const fs = NativeModule.require(`fs`);
          // read the source
          const filename = Module._resolveFilename(process.argv[1]);
          var source = fs.readFileSync(filename, `utf-8`);
          checkScriptSyntax(source, filename);
          process.exit(0);
        }

        preloadModules();
        Module.runMain();
      } else {
        // REPL 或其他
      }
    }
  }

startup() 最後呼叫 Module.runMain() 函式來載入並執行使用者程式碼。在執行 startup() 函式的過程中,多次用到了 NativeModule.require() 來載入模組

NativeModule

NativeModule.require() 是專門用來載入 Node.js 內建模組的

// lib/internal/bootstrap_node.js

  function NativeModule(id) {
    this.filename = `${id}.js`;
    this.id = id;
    this.exports = {};
    this.loaded = false;
    this.loading = false;
  }

  NativeModule._source = process.binding(`natives`);
  NativeModule._cache = {};

  NativeModule.require = function(id) {
    if (id === `native_module`) {
      return NativeModule;
    }

    const cached = NativeModule.getCached(id);
    if (cached && (cached.loaded || cached.loading)) {
      return cached.exports;
    }

    if (!NativeModule.exists(id)) {
      // Model the error off the internal/errors.js model, but
      // do not use that module given that it could actually be
      // the one causing the error if there`s a bug in Node.js
      const err = new Error(`No such built-in module: ${id}`);
      err.code = `ERR_UNKNOWN_BUILTIN_MODULE`;
      err.name = `Error [ERR_UNKNOWN_BUILTIN_MODULE]`;
      throw err;
    }

    process.moduleLoadList.push(`NativeModule ${id}`);

    const nativeModule = new NativeModule(id);

    nativeModule.cache();
    nativeModule.compile();

    return nativeModule.exports;
  };

  NativeModule.getCached = function(id) {
    return NativeModule._cache[id];
  };

  NativeModule.exists = function(id) {
    return NativeModule._source.hasOwnProperty(id);
  };

  // ...

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

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

  NativeModule.prototype.compile = function() {
    var source = NativeModule.getSource(this.id);
    source = NativeModule.wrap(source);

    this.loading = true;

    try {
      const fn = runInThisContext(source, {
        filename: this.filename,
        lineOffset: 0,
        displayErrors: true
      });
      fn(this.exports, NativeModule.require, this, this.filename);

      this.loaded = true;
    } finally {
      this.loading = false;
    }
  };

  NativeModule.prototype.cache = function() {
    NativeModule._cache[this.id] = this;
  };

NativeModule 有幾個重要的屬性和方法:

  • id: NativeModule 的識別符號,例如 eventsinternal/process

  • filename: NativeModule 對應原始碼檔案

  • exports: 預設值是 {}

  • loaded / loading: NativeModule 狀態

  • _cache: 簡單的模組快取

  • _source: 模組原始碼資源

  • require(): 先查詢快取,快取沒有則新建 NativeModule 並編譯,返回 exports

  • wrap()/wrapper: wrapper 是對模組上下文的包裹,如下:

    (function (exports, require, module, __filename, __dirname) { 
      xxx
    });
  • compile(): 將模組原始碼用 wrapper 包裹後,使用 runInThisContext()(類似 eval())生成 js 函式,再執行之

Module.runMain()

Node.js 啟動完成後,呼叫 Module.runMain(),原始碼如下:

// bootstrap main module.
Module.runMain = function() {
  // Load the main module--the command line argument.
  Module._load(process.argv[1], null, true);
  // Handle any nextTicks added in the first tick of the program
  process._tickCallback();
};

Module._load() 載入並執行使用者程式碼。至此 啟動-js部分 已經全部完成,後續模組載入部分,見 Node.js原始碼解析-require背後

End

啟動只是 Node.js 原始碼的一小部分,除此之外還有大量的內建模組和 c / c++ 原始碼

參考:

相關文章