Webpack 模块打包原理

824 阅读1分钟

通过分析webpack打包后的文件,理解webpack到底做了什么。

举个简单的例子:

// webpack.config.js
const path = require('path');

module.exports = {
    mode: 'development',
  // JavaScript 执行入口文件
  entry: './src/main.js',
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 输出文件都放到 dist 目录下
    path: path.resolve(__dirname, './dist'),
  }
};

// src/add
export default function(a, b) {
    return a + b
}

// src/main.js
import Add from './add'
console.log(Add, Add(1, 2))

上述打包后的bundle.js文件精简内容如下:

// modules是存放所有模块的数组,数组中每个元素存储{ 模块路径: 模块导出代码函数 }
(function (modules) {
  // 模块缓存作用,已加载的模块可以不用再重新读取,提升性能
  var installedModules = {};

  // 关键函数,加载模块代码
  // 形式有点像Node的CommonJS模块,但这里是可跑在浏览器上的es5代码
  function __webpack_require__(moduleId) {
    // 判断是否已经加载过,加载过不在重新读取
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    // 先创建一个空模块,塞入缓存中
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {},
    });

    // 运行模块导出的代码函数,this设定为module.exports,即将模块内容挂载到module.exports上
    // es5规范中最终导出的都是module.exports = xxx 语句,这里直接将module传入,那么module中的exports就包含了所有的导出内容
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __webpack_require__
    );

    // 当前函数已经被加载过
    module.l = true;

    // 返回加载的模块,调用方直接调用即可
    return module.exports;
  }

  return __webpack_require__((__webpack_require__.s = "./src/main.js"));
})({
  // add模块 # sourceURL= xxx 为注释内容,直接忽略
  "./src/add.js": function (module, exports) {
    eval(
      "function add(a, b) {\r\n  return a + b;\r\n}\r\nmodule.exports = add;\r\n\n\n//# sourceURL=webpack:///./src/add.js?"
    );
  },

  // 入口模块
  
  "./src/main.js": function (module, exports, __webpack_require__) {
    eval(
      'let Add = __webpack_require__(/*! ./add */ "./src/add.js");\r\nconsole.log(Add, Add(1, 2));\r\n\n\n//# sourceURL=webpack:///./src/main.js?'
    );
  },
});