懒加载模板代码

src目录下文件:
入口文件index.js,点击按钮获取并加载login.js文件

  1. // index.js
  2. let oBtn = document.getElementById('btn')
  3. oBtn.addEventListener('click', function () {
  4. import(/*webpackChunkName: "login"*/'./login.js').then((login) => {
  5. console.log(login)
  6. })
  7. })
  8. console.log('index.js执行了')
  9. // login.js
  10. module.exports = "懒加载导出内容"

打包结果

最终打包结果生成 build.js login.build.js

先看看作为立即执行函数的参数是怎么样的。当点击按钮时,加载login.js

  1. 调用webpack_require.e方法,创建script标签并引入login.js
  2. 调用webpack_require.t方法,执行login.js中的代码
    1. (function (modules) {
    2. ...
    3. })
    4. ({
    5. "./src/index.js":
    6. (function (module, exports, __webpack_require__) {
    7. let oBtn = document.getElementById('btn')
    8. oBtn.addEventListener('click', function () {
    9. __webpack_require__.e(/*! import() | login */ "login").then(__webpack_require__.t.bind(null, /*! ./login.js */ "./src/login.js", 7)).then((login) => {
    10. console.log(login)
    11. })
    12. })
    13. console.log('index.js执行了')
    14. })
    15. });

与以往不同的是,立即执行函数会设置window[“webpackJsonp”],并重写window[“webpackJsonp”]中的push方法

  1. (function (modules) {
  2. ...
  3. // 设置window["webpackJsonp"]
  4. var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
  5. // 保存原有的push方法
  6. var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
  7. // 因为保存的是地址,所以修改jsonpArray.push时候也会修改window["webpackJsonp"].push
  8. // webpackJsonpCallback是懒加载的核心函数
  9. jsonpArray.push = webpackJsonpCallback;
  10. // 深拷贝一份jsonpArray
  11. jsonpArray = jsonpArray.slice();
  12. ...
  13. })
  14. ({
  15. ...
  16. })

通过一步步查看执行函数,了解其逻辑

webpack_require.e函数

  1. __webpack_require__.e = function requireEnsure(chunkId) {
  2. // 申明promises,数组中保存Promise类型的函数
  3. // 最终通过返回Promise.all([promises])
  4. var promises = [];
  5. // 获取installedChunks注册的函数
  6. var installedChunkData = installedChunks[chunkId];
  7. // 0 => 已经执行完毕,undefined => 未加载
  8. // null => 预加载,promise => 加载中
  9. if (installedChunkData !== 0) {
  10. // 如果是加载中
  11. if (installedChunkData) {
  12. promises.push(installedChunkData[2]);
  13. } else {
  14. // 申明promise
  15. // 并设置installedChunkData[2] = promise
  16. // 保存完整的promise
  17. var promise = new Promise(function (resolve, reject) {
  18. installedChunkData = installedChunks[chunkId] = [resolve, reject];
  19. });
  20. promises.push(installedChunkData[2] = promise);
  21. // 创建script标签
  22. var script = document.createElement('script');
  23. // jsonpScriptSrc函数,返回__webpack_require__.p + "" + chunkId + ".built.js"
  24. // __webpack_require__.p => webpack中publicPath配置项
  25. script.src = jsonpScriptSrc(chunkId);
  26. // 往head中添加script标签
  27. document.head.appendChild(script);
  28. }
  29. }
  30. // 返回promise.all
  31. return Promise.all(promises);
  32. }

webpack_require.t函数

主要内容就是执行login.js

  1. // a & b => 通过转换二进制进行比较
  2. __webpack_require__.t = function (value, mode) {
  3. // 执行login.js
  4. if (mode & 1) value = __webpack_require__(value);
  5. // 返回执行的结果
  6. if (mode & 8) return value;
  7. if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  8. var ns = Object.create(null);
  9. __webpack_require__.r(ns);
  10. // 绑定default
  11. Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  12. // 如果是对象类型
  13. if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));
  14. return ns;
  15. };

login.js中的代码

  1. // 在之前window["webpackJsonp"].push已经被重写为webpackJsonpCallback函数
  2. // 调用webpackJsonpCallback函数
  3. (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["login"], {
  4. "./src/login.js":
  5. (function (module, exports) {
  6. module.exports = "懒加载导出内容"
  7. })
  8. }]);

webpackJsonpCallback函数

  1. function webpackJsonpCallback(data) {
  2. // chunkIds => "login"
  3. var chunkIds = data[0];
  4. // moreModules => 用对象包裹的执行函数
  5. var moreModules = data[1];
  6. var moduleId, chunkId, i = 0, resolves = [];
  7. // 循环chunkIds
  8. for (; i < chunkIds.length; i++) {
  9. // 获取每一项
  10. chunkId = chunkIds[i];
  11. // 往resolves中添加Promise中的resolve
  12. if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
  13. resolves.push(installedChunks[chunkId][0]);
  14. }
  15. // 标记为0,即已执行
  16. installedChunks[chunkId] = 0;
  17. }
  18. // 循环moreModules
  19. for (moduleId in moreModules) {
  20. if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
  21. // 设置modules中的moduleId为对应函数
  22. // 方便__webpack_require__调用时,可以获取到对应的函数
  23. modules[moduleId] = moreModules[moduleId];
  24. }
  25. }
  26. if (parentJsonpFunction) parentJsonpFunction(data);
  27. // 递归执行resolve函数
  28. while (resolves.length) {
  29. resolves.shift()();
  30. }
  31. };

由此,webpack懒加载便结束了。核心就是

  1. 重写window[“webpackJsonp”].push方法
  2. 创建并引入script