webpack打包文件分析

打包后:

  1. (function(modules){
  2. // The module cache
  3. var installedModules = {};
  4. // The require function
  5. function __webpack_require__(moduleId){...}
  6. // expose the modules object (__webpack_modules__)
  7. __webpack_require__.m = modules;
  8. // expose the module cache
  9. __webpack_require__.c = installedModules;
  10. // Object.prototype.hasOwnProperty.call
  11. __webpack_require__.o = function (object, property) {
  12. return Object.prototype.hasOwnProperty.call(object, property);
  13. };
  14. // __webpack_public_path__
  15. __webpack_require__.p = "";
  16. // 其他函数 d r n t
  17. // Load entry module and return exports
  18. return __webpack_require__(__webpack_require__.s = "./src/index.js"); //入口文件
  19. ...
  20. })({
  21. // 模块对象 key值为源文件路径
  22. });

webpack_require

  1. // The require function
  2. function __webpack_require__(moduleId) {
  3. // Check if module is in cache
  4. if (installedModules[moduleId]) {
  5. return installedModules[moduleId].exports;
  6. }
  7. // Create a new module (and put it into the cache)
  8. var module = installedModules[moduleId] = {
  9. i: moduleId,
  10. l: false,
  11. exports: {}
  12. };
  13. // Execute the module function
  14. modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  15. // Flag the module as loaded
  16. module.l = true;
  17. // Return the exports of the module
  18. return module.exports;
  19. }

module、module.exports 的作用和 CommonJS 中的 module、module.exports 的作用是一样的
webpack_require 相当于 CommonJS 中的 require。

r 函数

表示此对象是一个ES6模块对象

  1. // define __esModule on exports
  2. __webpack_require__.r = function (exports) {
  3. // 方便判断 exports 的类型为 Module
  4. if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  5. Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  6. }
  7. Object.defineProperty(exports, '__esModule', { value: true });
  8. };

webpack_exports 添加一个 __esModule 为 true 的属性,表示这是一个 ES6 module。

d 函数

为 ES6模块定义getter的方法

  1. // define getter function for harmony exports
  2. __webpack_require__.d = function (exports, name, getter) {
  3. // 判断是否有 name 属性
  4. if (!__webpack_require__.o(exports, name)) {
  5. Object.defineProperty(exports, name, { enumerable: true, get: getter });
  6. }
  7. };

n 函数

获取此对象的默认导出
webpack_require.n 分析该 export 对象是否是 ES6 module

  • 如果是则返回 module[‘default’] 即 export default 对应的变量。
  • 如果不是 ES6 module 则直接返回 module

    1. // getDefaultExport function for compatibility with non-harmony modules
    2. __webpack_require__.n = function (module) {
    3. var getter = module && module.__esModule ?
    4. function getDefault() { return module['default']; } :
    5. function getModuleExports() { return module; };
    6. __webpack_require__.d(getter, 'a', getter);
    7. return getter;
    8. };

    t 函数

  • 把任意模块包装成ES6模块

  • mode & 1: value is a module id, require it 表示传的是模块ID
  • mode & 2: merge all properties of value into the ns 需要合并属性
  • mode & 4: return value when already ns object 如果是 ES6 模块直接返回
  • mode & 8|1: behave like require 等同于 require 方法 ```javascript // create a fake namespace object // mode & 1: value is a module id, require it // mode & 2: merge all properties of value into the ns // mode & 4: return value when already ns object // mode & 8|1: behave like require webpack_require.t = function (value, mode) { if (mode & 1) value = webpack_require(value); if (mode & 8) return value; if ((mode & 4) && typeof value === ‘object’ && value && value.esModule) return value; var ns = Object.create(null); webpackrequire.r(ns); Object.defineProperty(ns, ‘default’, { enumerable: true, value: value }); if (mode & 2 && typeof value != ‘string’) for (var key in value) webpackrequire.d(ns, key, function (key) { return value[key]; }.bind(null, key)); return ns; };
  1. <a name="ezyl4"></a>
  2. # harmony import/export
  3. > CJS = common.js
  4. > ESM = ES6 module
  5. <a name="bwECF"></a>
  6. ## CJS加载CJS
  7. ```javascript
  8. // c.js
  9. exports.name = 'tom';
  10. exports.age = '18';
  11. // index.js
  12. const c = require('./c');
  13. console.log(c.name);
  14. console.log(c.age);
  1. {
  2. "./src/c.js":
  3. /*! no static exports found */
  4. (function (module, exports) {
  5. exports.name = 'tom';
  6. exports.age = '18';
  7. }),
  8. "./src/index.js":
  9. /*! no static exports found */
  10. (function (module, exports, __webpack_require__) {
  11. const c = __webpack_require__(/*! ./c */ "./src/c.js");
  12. console.log(c.name);
  13. console.log(c.age);
  14. })
  15. }

可以看出,webpack对CJS的模块没有处理,通过实现的require方法加载,webpack是采用的 CJS 的模块方案

CJS加载ESM

  1. // c.js
  2. export default name = 'tom';
  3. export const age = '18';
  4. // index.js
  5. const c = require('./c');
  6. console.log(c.name);
  7. console.log(c.age);
  8. console.log(c.default);
  1. {
  2. "./src/c.js":
  3. /*! exports provided: default, age */
  4. (function (module, __webpack_exports__, __webpack_require__) {
  5. "use strict";
  6. __webpack_require__.r(__webpack_exports__);
  7. /* harmony export (binding) */
  8. __webpack_require__.d(__webpack_exports__, "age", function () { return age; });
  9. /* harmony default export */
  10. __webpack_exports__["default"] = (name = 'tom');
  11. const age = '18';
  12. }),
  13. "./src/index.js":
  14. (function (module, exports, __webpack_require__) {
  15. let e = __webpack_require__(/*! ./c */ "./src/e.js");
  16. console.log(e);
  17. console.log(e.default);
  18. console.log(e.age);
  19. })
  20. }

可以看出:

  • ESM 的导出对象首先通过r函数处理,标识为 __esModule
  • 给 ESM 的导出通过d函数设置 getter(ESM输出的是引用),如果是 default 就直接赋值(将default赋值到模块上)

    ESM加载ESM

    ```javascript // a.js export default name = ‘tom’; export const age = ‘18’;

// index.js import name, { age } from ‘./a’;

console.log(name); console.log(age);

  1. ```javascript
  2. {
  3. "./src/a.js":
  4. /*! exports provided: default, age */
  5. (function (module, __webpack_exports__, __webpack_require__) {
  6. "use strict";
  7. __webpack_require__.r(__webpack_exports__);
  8. /* harmony export (binding) */
  9. __webpack_require__.d(__webpack_exports__, "age", function () { return age; });
  10. /* harmony default export */
  11. __webpack_exports__["default"] = (name = 'tom');
  12. const age = '18';
  13. }),
  14. "./src/index.js":
  15. /*! no exports provided */
  16. (function (module, __webpack_exports__, __webpack_require__) {
  17. "use strict";
  18. __webpack_require__.r(__webpack_exports__);
  19. /* harmony import */
  20. var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
  21. console.log(_a__WEBPACK_IMPORTED_MODULE_0__["default"]);
  22. console.log(_a__WEBPACK_IMPORTED_MODULE_0__["age"]);
  23. })
  24. }

可以看出:

  • 导入ESM使用default时,webpack 会编译成 module["default"]进行访问

    ESM加载CJS

    ```javascript // b.js module.exports = { home: ‘beijing’ }; module.exports.name = ‘tom’; module.exports.age = ‘18’;

// index.js import home, { name, age } from ‘./b’; console.log(name); console.log(age); console.log(home);

  1. ```javascript
  2. "./src/b.js":
  3. (function(module, exports) {
  4. module.exports = { home: 'beijing' };
  5. module.exports.name = 'tom';
  6. module.exports.age = '18';
  7. }}),
  8. "./src/index.js":
  9. (function(module, __webpack_exports__, __webpack_require__) {
  10. "use strict";
  11. __webpack_require__.r(__webpack_exports__);
  12. var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/b.js");
  13. var _b__WEBPACK_IMPORTED_MODULE_0___default =
  14. __webpack_require__.n(_b__WEBPACK_IMPORTED_MODULE_0__);
  15. console.log(_b__WEBPACK_IMPORTED_MODULE_0__["name"]);
  16. console.log(_b__WEBPACK_IMPORTED_MODULE_0__["age"]);
  17. console.log(_b__WEBPACK_IMPORTED_MODULE_0___default.a);
  18. })

可以看出:

  • ESM 加载 CJS 时,如果用到默认导出,要通过n函数获取

异步加载

改变index.js

  1. test()
  2. import('./b') // 按需加载

则会打包成两个文件

  1. (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], {
  2. "./src/b.js":
  3. (function (module, exports) {
  4. module.exports = function test() {
  5. console.log('test')
  6. }
  7. })
  8. }]);
  1. (function (modules) { // 启动函数
  2. //安装一个为了加载额外代码块的JSON回调函数
  3. function webpackJsonpCallback(data) {
  4. var chunkIds = data[0];//代码块ID
  5. var moreModules = data[1];//更多的模块
  6. //向模块对象上增加更多的模块,然后把所有的chunkIds设置为已经加载并触发回调
  7. var moduleId, chunkId, i = 0, resolves = [];
  8. for (; i < chunkIds.length; i++) {
  9. chunkId = chunkIds[i];
  10. if (installedChunks[chunkId]) {
  11. resolves.push(installedChunks[chunkId][0]);
  12. }
  13. installedChunks[chunkId] = 0;//标识这个代码块为已经OK
  14. }
  15. for (moduleId in moreModules) {//把新拉下来的模块合并到模块对象上
  16. if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
  17. modules[moduleId] = moreModules[moduleId];
  18. }
  19. }
  20. if (parentJsonpFunction) parentJsonpFunction(data);//如果有父JSONP函数就调用
  21. while (resolves.length) {
  22. resolves.shift()();//让所有的promise都OK
  23. }
  24. };
  25. // 模块缓存
  26. var installedModules = {};
  27. //用来存放加载完成或加载中的代码块对象
  28. // undefined = 代码块未加载, null = 代码块正在预加载或者预获取
  29. // Promise = 代码块更在加载中, 0 = 代码块已经加载
  30. var installedChunks = {
  31. "main": 0
  32. };
  33. //JSON加载的路径
  34. function jsonpScriptSrc(chunkId) {
  35. return __webpack_require__.p + "" + chunkId + ".bundle.js"
  36. }
  37. function __webpack_require__(moduleId) {
  38. if (installedModules[moduleId]) {
  39. return installedModules[moduleId].exports;
  40. }
  41. var module = installedModules[moduleId] = {
  42. i: moduleId,
  43. l: false,
  44. exports: {}
  45. };
  46. modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  47. module.l = true;
  48. return module.exports;
  49. }
  50. //这个文件只包含入口代码块
  51. //用来加载额外的代码块的函数
  52. __webpack_require__.e = function requireEnsure(chunkId) {
  53. var promises = [];
  54. //JSONP代码块加载
  55. var installedChunkData = installedChunks[chunkId];
  56. if (installedChunkData !== 0) { // 0的意思是已经安装
  57. // a Promise means "currently loading". 如果是一个Promise的话表示正在加载
  58. if (installedChunkData) {
  59. promises.push(installedChunkData[2]);//如果已经在加载中了,则添加Promise
  60. } else {
  61. //在代码块缓存中放置Promise
  62. var promise = new Promise(function (resolve, reject) {
  63. installedChunkData = installedChunks[chunkId] = [resolve, reject];
  64. });
  65. promises.push(installedChunkData[2] = promise);
  66. // 开始加载代码块
  67. var script = document.createElement('script');
  68. var onScriptComplete;
  69. script.charset = 'utf-8';
  70. script.timeout = 120;
  71. //// HTMLElement 接口的 nonce 属性返回只使用一次的加密数字,被内容安全政策用来决定这次请求是否被允许处理。
  72. if (__webpack_require__.nc) {
  73. script.setAttribute("nonce", __webpack_require__.nc);
  74. }
  75. //设置源文件路径
  76. script.src = jsonpScriptSrc(chunkId);
  77. //在栈展开之前创建错误以获取有用的堆栈信息
  78. var error = new Error();
  79. onScriptComplete = function (event) {
  80. script.onerror = script.onload = null;
  81. clearTimeout(timeout);
  82. var chunk = installedChunks[chunkId];
  83. if (chunk !== 0) {
  84. if (chunk) {
  85. var errorType = event && (event.type === 'load' ? 'missing' : event.type);
  86. var realSrc = event && event.target && event.target.src;
  87. error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
  88. error.name = 'ChunkLoadError';
  89. error.type = errorType;
  90. error.request = realSrc;
  91. chunk[1](error);
  92. }
  93. installedChunks[chunkId] = undefined;
  94. }
  95. };
  96. var timeout = setTimeout(function () {
  97. onScriptComplete({ type: 'timeout', target: script });
  98. }, 120000);
  99. script.onerror = script.onload = onScriptComplete;
  100. document.head.appendChild(script);
  101. }
  102. }
  103. return Promise.all(promises);
  104. };
  105. __webpack_require__.m = modules;
  106. __webpack_require__.c = installedModules;
  107. __webpack_require__.d = function (exports, name, getter) {
  108. if (!__webpack_require__.o(exports, name)) {
  109. Object.defineProperty(exports, name, { enumerable: true, get: getter });
  110. }
  111. };
  112. __webpack_require__.r = function (exports) {
  113. if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  114. Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  115. }
  116. Object.defineProperty(exports, '__esModule', { value: true });
  117. };
  118. __webpack_require__.t = function (value, mode) {
  119. if (mode & 1) value = __webpack_require__(value);
  120. if (mode & 8) return value;
  121. if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  122. var ns = Object.create(null);
  123. __webpack_require__.r(ns);
  124. Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  125. if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));
  126. return ns;
  127. };
  128. __webpack_require__.n = function (module) {
  129. var getter = module && module.__esModule ?
  130. function getDefault() { return module['default']; } :
  131. function getModuleExports() { return module; };
  132. __webpack_require__.d(getter, 'a', getter);
  133. return getter;
  134. };
  135. __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  136. __webpack_require__.p = "";
  137. //异步加载中的错误处理函数
  138. __webpack_require__.oe = function (err) { console.error(err); throw err; };
  139. //刚开始的时候会把数组赋给window["webpackJsonp"],并且赋给jsonpArray
  140. var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
  141. //绑定push函数为oldJsonpFunction
  142. var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
  143. //狸猫换太子,把webpackJsonpCallback赋给了jsonpArray.push方法
  144. jsonpArray.push = webpackJsonpCallback;
  145. //把数组进行截取得到一个新的数组
  146. jsonpArray = jsonpArray.slice();
  147. //如果数组不为空,就把全部安装一次
  148. for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
  149. //把oldJsonpFunction赋给parentJsonpFunction
  150. var parentJsonpFunction = oldJsonpFunction;
  151. return __webpack_require__(__webpack_require__.s = "./src/index.js");
  152. })
  153. ({
  154. "./src/index.js":
  155. (function (module, exports, __webpack_require__) {
  156. var button = document.createElement("button");
  157. button.innerHTML = "点我";
  158. button.onclick = function () {
  159. __webpack_require__.e("title").then(__webpack_require__.t.bind(null, "./src/title.js", 7)).then(function (result) {
  160. console.log(result["default"]);
  161. });
  162. };
  163. document.body.appendChild(button);
  164. })
  165. });
  1. 定义了一个对象 installedChunks,作用是缓存动态模块。
  2. 定义了一个辅助函数 jsonpScriptSrc(),作用是根据模块 ID 生成 URL。
  3. 定义了两个新的核心函数 webpack_require.e() 和 webpackJsonpCallback()。
  4. 定义了一个全局变量 window[“webpackJsonp”] = [],它的作用是存储需要动态导入的模块。
  5. 重写 window[“webpackJsonp”] 数组的 push() 方法为 webpackJsonpCallback()。也就是说 window[“webpackJsonp”].push() 其实执行的是 webpackJsonpCallback()。

    1. __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))

    e函数

  6. 先查看该模块 ID 对应缓存的值是否为 0,0 代表已经加载成功了,第一次取值为 undefined。

  7. 如果不为 0 并且不是 undefined 代表已经是加载中的状态。然后将这个加载中的 Promise 推入 promises 数组。
  8. 如果不为 0 并且是 undefined 就新建一个 Promise,用于加载需要动态导入的模块。
  9. 生成一个 script 标签,URL 使用 jsonpScriptSrc(chunkId) 生成,即需要动态导入模块的 URL。
  10. 为这个 script 标签设置一个 2 分钟的超时时间,并设置一个 onScriptComplete() 函数,用于处理超时错误。
  11. 然后添加到页面中 document.head.appendChild(script),开始加载模块。
  12. 返回 promises 数组。

当 JS 文件下载完成后,会自动执行文件内容。
也就是说下载完 0.js 后,会执行 window[“webpackJsonp”].push()。

由于 window[“webpackJsonp”].push() 已被重置为 webpackJsonpCallback() 函数。所以这一操作就是执行 webpackJsonpCallback()

对这个模块 ID 对应的 Promise 执行 resolve(),同时将缓存对象中的值置为 0,表示已经加载完成了。相比于 webpack_require.e(),这个函数还是挺好理解的。

深入了解 webpack 模块加载原理—谭光志