webpack最終生成bundle原理(主要是js模組化的原理)

ArthurHsing發表於2020-12-25

這是生成的依賴樹

{
  './src/index.js': {
    code: '"use strict";\n' +
      '\n' +
      'var _add = _interopRequireDefault(require("./add.js"));\n' +
      '\n' +
      'var _count = _interopRequireDefault(require("./count.js"));\n' +
      '\n' +
      'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
      '\n' +
      'console.log((0, _add["default"])(1, 2));\n' +
      'console.log((0, _count["default"])(3, 1));',
    deps: {
      './add.js': 'D:\\webpack5進階\\05.myWebpack\\src\\add.js',
      './count.js': 'D:\\webpack5進階\\05.myWebpack\\src\\count.js'
    }
  },
  'D:\\webpack5進階\\05.myWebpack\\src\\add.js': {
    code: '"use strict";\n' +
      '\n' +
      'Object.defineProperty(exports, "__esModule", {\n' +
      '  value: true\n' +
      '});\n' +
      'exports["default"] = void 0;\n' +
      '\n' +
      'var _new = _interopRequireDefault(require("./new.js"));\n' +
      '\n' +
      'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
      '\n' +
      'function add(x, y) {\n' +
      '  return x + y;\n' +
      '}\n' +
      '\n' +
      'console.log(_new["default"]);\n' +
      'var _default = add;\n' +
      'exports["default"] = _default;',
    deps: { './new.js': 'D:\\webpack5進階\\05.myWebpack\\src\\new.js' }
  },
  'D:\\webpack5進階\\05.myWebpack\\src\\count.js': {
    code: '"use strict";\n' +
      '\n' +
      'Object.defineProperty(exports, "__esModule", {\n' +
      '  value: true\n' +
      '});\n' +
      'exports["default"] = void 0;\n' +
      '\n' +
      'function count(x, y) {\n' +
      '  return x - y;\n' +
      '}\n' +
      '\n' +
      'var _default = count;\n' +
      'exports["default"] = _default;',
    deps: {}
  },
  'D:\\webpack5進階\\05.myWebpack\\src\\new.js': {
    code: '"use strict";\n' +
      '\n' +
      'Object.defineProperty(exports, "__esModule", {\n' +
      '  value: true\n' +
      '});\n' +
      'exports["default"] = void 0;\n' +
      'var _default = 123;\n' +
      'exports["default"] = _default;',
    deps: {}
  }
}

這是簡單版的bundle生成方法

  generate(depsGraph) {
    const bundle = `
      (function(depsGraph){
        // require目的:為了載入入口檔案
        function require(module){
          // 定義模組內部的require函式
          function localRequire(relativePath){
            // 為了找到要引入模組的絕對路徑,通過require載入
            return require(depsGraph[module].deps[relativePath]);
          }
          // 定義暴露物件(將來我們要暴露的內容)
          var exports = {};
          (function(require,exports,code){
            eval(code)
          })(localRequire, exports, depsGraph[module].code);
          // 作為require函式的返回值返回出去
          // 後面的require函式能得到暴露的內容
          return exports;
        }
        // 載入入口檔案
        require('${this.options.entry}');
      })(${JSON.stringify(depsGraph)})
    `;
    // 生成輸出檔案的絕對路徑
    const filePath = path.resolve(this.options.output.path, this.options.output.filename);
    // 寫入檔案
    fs.writeFileSync(filePath, bundle, 'utf-8');
  }

首先可以從依賴樹裡面看到,es6的模組語法都已經被轉為了require和exports之類的。

關於require:

執行generate方法時,首先會從入口檔案開始執行require('${this.options.entry}'),然後require函式裡面,有一個立即執行方法,這個方法可以執行入口檔案裡面的程式碼:

          (function(require,exports,code){
            eval(code)
          })(localRequire, exports, depsGraph[module].code);

如果這些程式碼裡面有模組引入(require),那麼又會繼續遞迴呼叫require函式,並且根據依賴樹把模組引入的相對路徑轉為絕對路徑,這樣就可以在依賴樹裡面找到該模組內部的程式碼了,那麼require函式的立即執行方法又可以繼續執行引入模組內部的程式碼了。

關於exports:

但是僅僅遞迴呼叫require是不夠的,因為require返回的是一個模組,外層模組必須基於require的返回值來進行下一步操作,所以要對exports進行處理,其實處理很簡單,對於有exports語法的模組,在require方法內部先定義一個空的exports物件var exports = {};,也就是我們的暴露物件,然後再在這個物件上新增要暴露出去的屬性或方法,最後返回這個物件即可。那麼當我外層模組require時,就可以拿到內層模組的想要暴露出來的值了。

總結:

所謂的模組化其實最後都生成了一段程式碼,最重要的有三點,第一點是依賴樹,第二點是require的遞迴呼叫,第三點是exports的返回值。

相關文章