webpack4源码学习(6)--打包后文件解析

Posted on By yuanfang

学习参考: 来源于 可鱼不是鱼的 webpack 4 源码主流程分析(十二):打包后文件解析
学习参考: 来源于 可鱼不是鱼的 webpack 4 源码主流程分析(一):前言及总流程概览
webpack中文文档

  1. 调试配置:
    {
      // 使用 IntelliSense 了解相关属性。 
      // 悬停以查看现有属性的描述。
      // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
      "version": "0.2.0",
      "configurations": [
     {
       "type": "node",
       "request": "launch",
       "name": "webpack-demo的调试",
       "cwd": "${workspaceFolder}/webpack-demo",
       "skipFiles": ["<node_internals>/**"],
       "runtimeExecutable": "npm",// node 的可执行程序
       "runtimeArgs": [ // 类似于命令行参数
           "run-script", "debug"
       ],
       "port": 9229,
       // 使用什么终端工具。integratedTerminal 表示使用 Visual Studio Code 内置的终端工具,也可以配置成使用操作系统默认的终端工具。
       "console": "integratedTerminal"
     }
      ]
    }
    // package.json
    "scripts": {
     "build": "webpack",
     "debug": "node --inspect-brk ./node_modules/.bin/webpack"
      },
    
  2. 代码解析— 转载: 来源于 可鱼不是鱼的 webpack 4 源码主流程分析(十二):打包后文件解析
  • demo
    ```js //src/a.js import { add } from ‘Src/b’; import(‘./c.js’).then(m => m.sub(2, 1)); const a = 1; add(3, 2 + a);

//src/b.js import { mul } from ‘@fe_korey/test-loader?number=20!Src/e’; export function add(a, b) { return a + b + mul(10, 5); } export function addddd(a, b) { return a + b * b; }

//src/c.js import { mul } from ‘Src/d’; import(‘./b.js’).then(m => m.add(200, 100)); //require.ensure() 是 webpack 特有的,已经被 import() 取代。 export function sub(a, b) { return a - b + mul(100, 50); } //src/d.js export function mul(a, b) { const d = 10000; return a * b + d; }

//webpack.config.js var path = require(‘path’);

module.exports = { entry: { bundle: ‘./src/a.js’ }, devtool: ‘none’, output: { path: __dirname + ‘/dist’, filename: ‘[name].[chunkhash:4].js’, chunkFilename: ‘[name].[chunkhash:8].js’ }, mode: ‘development’, resolve: { alias: { Src: path.resolve(__dirname, ‘src/’) } }, module: { rules: [ { test: /.js$/, use: [ { loader: ‘babel-loader’ } ] } ] } };


+ bundle 主体结构  
```js
(function(modules) {
  //runtime代码
})({
  './node_modules/@fe_korey/test-loader/loader.js?number=20!./src/d.js': function(module, __webpack_exports__, __webpack_require__) {
    //...模块代码d
  },
  './src/a.js': function(module, __webpack_exports__, __webpack_require__) {
    //...模块代码a
  },
  './src/b.js': function(module, __webpack_exports__, __webpack_require__) {
    //...模块代码b
  }
});
  • runtime 函数主体结构
    ```js function(modules){ function webpackJsonpCallback(data){ //… } // 设置 script src webpack_require.p 即为 output.publicPath 配置 function jsonpScriptSrc(chunkId){ return webpack_require.p + “” + ({}[chunkId]||chunkId) + “.” + {“0”:”d680ffbe”}[chunkId] + “.js” } function webpack_require(moduleId){ //… } // 初始化 installedModules,保存所有创建过的 module,用于缓存判断 var installedModules = {};

    // undefined:chunk未加载, null: chunk通过prefetch/preload提前获取过 // Promise:chunk正在加载, 0:chunk加载完毕 // 数组: 结构为 [resolve Function, reject Function, Promise] 的数组, 代表 chunk 在处于加载中 var installedChunks = { bundle: 0 };

    // 定义一堆挂载在__webpack_require__上的属性 //…

    // jsonp 初始化 // …

    // 执行入口 return webpack_require(webpack_require.s = “./src/a.js”); }


+ __webpack_require__ 属性  
```js
    // 异步处理
    __webpack_require__.e = function requireEnsure(chunkId) {
        //后文单独分析
    };

    // 即为传入的modules:各模块组成的对象
    __webpack_require__.m = modules;

    // 即为installedModules:已经缓存的对象
    __webpack_require__.c = installedModules;

    // 在exports对象上添加属性,即 增加导出
    __webpack_require__.d = function(exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
        Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
    };

    // 在exports对象上添加 __esModule 属性,用于标识 es6 模块
    __webpack_require__.r = function(exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
    };

    // 创建一个伪命名空间对象
    __webpack_require__.t = function(value, mode) {
        //没用上,解释暂时略过
    };

    // 得到 getDefaultExport,即通过 __esModule 属性判断是否是 es6 来确定对应的默认导出方法
    __webpack_require__.n = function(module) {
        var getter =
            module && module.__esModule
            ? function getDefault() {
                return module['default'];
                }
            : function getModuleExports() {
                return module;
                };
        __webpack_require__.d(getter, 'a', getter);
        return getter;
    };

    // 调用 hasOwnProperty,即判断对象上是否有某一属性
    __webpack_require__.o = function(object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    // 即为 publicPath,在output.publicPath配置而来
    __webpack_require__.p = '';

    // 错误处理
    __webpack_require__.oe = function(err) {
        console.error(err);
        throw err;
    };

  • jsonp 初始化
      var jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] || []); //初始化 window['webpackJsonp']对象
      var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); // 暂存 push 方法
      jsonpArray.push = webpackJsonpCallback; //重写 jsonpArray 的 push 方法为 webpackJsonpCallback
      jsonpArray = jsonpArray.slice(); //拷贝 jsonpArray(不带 push 方法)
      for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); //若入口文件加载前,chunks文件先加载了,遍历 jsonpArray 用 webpackJsonpCallback 执行
      var parentJsonpFunction = oldJsonpFunction; //旧的 push 方法存入 parentJsonpFunction
    
  • webpack_require
    ```js function webpack_require(moduleId) { // 判断该module是否已经被缓存到installedModules,如果有,则直接返回它的导出exports if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // 定义module并缓存 var module = (installedModules[moduleId] = { i: moduleId, l: false, exports: {} }); // 执行module代码 modules[moduleId].call(module.exports, module, module.exports, webpack_require);

    // 标志module已经读取完成 module.l = true;

    return module.exports; }


+ 执行各同步模块代码  
```js
/***/ "./src/a.js":
/*!******************!*\
  !*** ./src/a.js ***!
  \******************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

        "use strict";
        __webpack_require__.r(__webpack_exports__);
        /* harmony import */ var Src_b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! Src/b */ "./src/b.js");

        __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/c.js")).then(function (m) {
            return m.sub(2, 1);
        });
        var a = 1;
        Object(Src_b__WEBPACK_IMPORTED_MODULE_0__["add"])(3, 2 + a);

/***/ }),

/***/ "./src/b.js":
/*!******************!*\
  !*** ./src/b.js ***!
  \******************/
/*! exports provided: add, addddd */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

        "use strict";
        __webpack_require__.r(__webpack_exports__);
        /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
        /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addddd", function() { return addddd; });
        /* harmony import */ var _fe_korey_test_loader_number_20_Src_d__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fe_korey/test-loader?number=20!Src/d */ "./node_modules/@fe_korey/test-loader/loader.js?number=20!./src/d.js");

        function add(a, b) {
            return a + b + Object(_fe_korey_test_loader_number_20_Src_d__WEBPACK_IMPORTED_MODULE_0__["mul"])(10, 5);
        }
        function addddd(a, b) {
            return a + b * b;
        }

/***/ })
  • 异步加载模块 webpack_require.e
    __webpack_require__.e = function requireEnsure(chunkId) {
      // promise队列,等待多个异步 chunk都加载完成才执行回调
      var promises = [];
    
      // 先判断是否加载过该 chunk
      var installedChunkData = installedChunks[chunkId];
      if (installedChunkData !== 0) {
      // 0 means "already installed".
    
          // a Promise means "currently loading". 目标 chunk 正在加载,则将 promise push到 promises 数组
          if (installedChunkData) {
              promises.push(installedChunkData[2]);
          } else {
              // 新建一个Promise去异步加载目标chunk
              var promise = new Promise(function(resolve, reject) {
                  installedChunkData = installedChunks[chunkId] = [resolve, reject]; //这里设置 installedChunks[chunkId]
              });
              promises.push((installedChunkData[2] = promise)); // installedChunks[chunkId]  = [resolve, reject, promise]
    
              var script = document.createElement('script');
              var onScriptComplete;
    
              script.charset = 'utf-8';
              script.timeout = 120;
              if (__webpack_require__.nc) {
              script.setAttribute('nonce', __webpack_require__.nc);
              }
              // 设置src
              script.src = jsonpScriptSrc(chunkId);
    
              var error = new Error();
              // 设置加载完成或错误的回调
              onScriptComplete = function(event) {
                  // avoid mem leaks in IE.
                  script.onerror = script.onload = null;
                  clearTimeout(timeout);
                  var chunk = installedChunks[chunkId];
                  if (chunk !== 0) {
                      if (chunk) {
                          var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                          var realSrc = event && event.target && event.target.src;
                          error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                          error.name = 'ChunkLoadError';
                          error.type = errorType;
                          error.request = realSrc;
                          chunk[1](error);
                      }
                      installedChunks[chunkId] = undefined;
                  }
              };
              // 设置超时处理
              var timeout = setTimeout(function() {
              onScriptComplete({ type: 'timeout', target: script });
              }, 120000);
              //script标签的onload事件都是在外部js文件被加载完成并执行完成后(异步不算)才被触发
              script.onerror = script.onload = onScriptComplete;
              // script标签加入文档
              document.head.appendChild(script);
          }
      }
      return Promise.all(promises);
    }
    
  • 加载非入口文件
    ```js // 在模块加载后,就会立即执行的 window[‘webpackJsonp’].push() 。由 jsonp 初始化可知, 即执行 bundle 文件里的 webpackJsonpCallback 方法 (window[‘webpackJsonp’] = window[‘webpackJsonp’] || []).push([ [0], { ‘./src/c.js’: function(module, webpack_exports, webpack_require) { //模块 c },

    ’./src/d.js’: function(module, webpack_exports, webpack_require) { //模块 d } } ]);


+ webpackJsonpCallback  
```js
function webpackJsonpCallback(data) {
    var chunkIds = data[0];
    var moreModules = data[1]; //异步 chunk 的各模块组成的对象

    var moduleId,
        chunkId,
        i = 0,
        resolves = [];
    // 这里收集 resolve 并将所有 chunkIds 标记为已加载
    for (; i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
            resolves.push(installedChunks[chunkId][0]); //将 resolve push 到 resolves 数组中
        }
        installedChunks[chunkId] = 0; //标记为已加载
    }
    // 遍历各模块组成的对象,将每个模块都加到 modules
    for (moduleId in moreModules) {
        if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
            modules[moduleId] = moreModules[moduleId];
        }
    }
    // 执行保存的旧 push 方法,可能是 array.push (即 push 到 window.webpackJsonp),也可能是前一个并行执行了 runtime 的 bundle 的 webpackJsonpCallback,即递归执行 webpackJsonpCallback,如多入口同时 import 同一个 module 的情况。
    if (parentJsonpFunction) parentJsonpFunction(data);

    //循环触发 resolve 回调
    while (resolves.length) {
        resolves.shift()();
    }
}

  • 异步加载小结

    1)通过 webpack_require 加载运行入口 module 模块代码里遇到 import()即执行 webpack_require.e 加载异步 chunk;
    2)webpack_require.e 使用模拟 jsonp 的方式及创建 script 标签来加载异步 chunk,并为每个 chunk 创建一个 promise;
    3)等到异步 chunk 被加载后,会执行 window[‘webpackJsonp’].push,即webpackJsonpCallback 方法;
    4)webpackJsonpCallback 里将异步 chunk 里的 module 加入到 modules, 并触发前面创建 promise 的 resolve 回调,然后执行其 then 方法即 webpack_require 去加载新的 module。