关于webpack:webpack从一个简化后的webpac异步加载打包代码了解webpack异步加载原理

前言

本文剖析webpack5的异步加载原理,代码是简化后的,原代码大略200行,简化后100行左右,然而性能仍旧能够失常实现。

注释
首先贴出所有的代码,而后剖析。
这是未打包的代码:

index.js代码,引入了test.js,然而是通过import异步引入。

// index.js
import("./test").then(val => {
    console.log(val)
})

test.js, 默认导出了一个字符串。

// test.js
export default "test代码"

这是打包后的代码。

// index.js打包代码
var webpackModules = {};
var webpackModuleCache = {};
function webpackRequire(moduleId) {
  var cachedModule = webpackModuleCache[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = webpackModuleCache[moduleId] = {
    exports: {}
  };
  webpackModules[moduleId](module, module.exports, webpackRequire);
  return module.exports;
}
webpackRequire.m = webpackModules;
(() => {
  webpackRequire.d = (exports, definition) => {
    for (var key in definition) {
      if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
        Object.defineProperty(exports, key, {
          enumerable: true,
          get: definition[key]
        });
      }
    }
  };
})();

(() => {
  webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
  webpackRequire.r = exports => {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, {
        value: 'Module'
      });
    }
    Object.defineProperty(exports, 'esmodule', {
      value: true
    });
  };
})();



var webpackJsonpCallback = ([chunkIds, moreModules]) => {
  var resolves = []
  for (var i = 0; i < chunkIds.length; i++) {
    var resolve = installedChunks[chunkIds[i]][0]
    resolves.push(resolve)
    // 0示意加载结束
    installedChunks[chunkIds[i]] = 0
  }
  for (var key in moreModules) {
    webpackRequire.m[key] = moreModules[key];
  }
  // 模块加载结束,执行resolve。
  while (resolves.length > 0) {
    resolves.shift()()
  }
}

webpackRequire.f = {}

var installedChunks = {
  main: 0
}

webpackRequire.p = "";

webpackRequire.u = (chunkId) => chunkId + ".js";

// 3
webpackRequire.l = (url) => {
  var script = document.createElement("script");
  script.src = url;
  document.head.appendChild(script);
}

// 2
webpackRequire.f.j = (chunkId, promises) => {
  var installedChunkData = installedChunks[chunkId];
  // 阐明这个chunk曾经加载过了
  if (installedChunkData !== undefined) { return }
  var promise = new Promise((resolve, reject) => {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
  })
  installedChunkData[2] = promise;
  promises.push(promise);
  var url = webpackRequire.p + webpackRequire.u(chunkId);
  webpackRequire.l(url)
}

// 1
webpackRequire.e = (chunkId) => {
  var promises = [];
  webpackRequire.f.j(chunkId, promises);
  return Promise.all(promises)
}


// 4
var wepackLoadingGlobal = window.webpackChunkwebpack2 = [];
// 重写push
wepackLoadingGlobal.push = webpackJsonpCallback
webpackRequire.e("src_test_js").then(webpackRequire.bind(webpackRequire, "./src/test.js")).then(val => {
  console.log(val);
});

这个是test.js打包后的代码

(self["webpackChunkwebpack2"] = self["webpackChunkwebpack2"] || []).push([["src_test_js"], {
  "./src/test.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
    webpackRequire.r(webpackExports);
    webpackRequire.d(webpackExports, {
      "default": () => webpackDefaultExport
    });
    const webpackDefaultExport = "test代码";
  }
}]);

咱们先来看这段代码比拟重要的初始化。

// 这个是寄存所有的模块。
var webpackModules = {};
// 模块的缓存。
var webpackModuleCache = {};
// 模块引入
function webpackRequire(moduleId) {
    var cachedModule = webpackModuleCache[moduleId];
    if (cachedModule !== undefined) {
        return cachedModule.exports;
    }
    var module = webpackModuleCache[moduleId] = {
        exports: {}
    };
    webpackModules[moduleId](module, module.exports, webpackRequire);
    return module.exports;
}
webpackRequire.m = webpackModules;

对象 webpackModules示意的是所有的模块的汇合,无论咱们是通过esModule还是commonjs导出的模块都在这里寄存,这里之所以是一个空的对象是因为,我尽管导出了test模块,然而这个模块是被异步加载的,webpack会在前面执行import("./test.js")的时候把对应的模块放进 webpackModules,这个前面会说。
而后就是webpackModuleCache,这个对象是一个用于缓存的对象,当咱们应用过某个模块后,这个模块就会被放进webpackModuleCache
函数webpackRequire是一个很重要的函数,这个函数的作用是用来获取webpackModules外面的模块的,要想晓得webpackRequire是怎么获取模块的,咱们就要先看懂导出后的模块打包好的代码是什么样子的。
来看下面的test.js打包后的代码:

(self["webpackChunkwebpack2"] = self["webpackChunkwebpack2"] || []).push([["src_test_js"], {
    // 临时只看这个对象字段
  "./src/test.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
    webpackRequire.r(webpackExports);
    webpackRequire.d(webpackExports, {
      "default": () => webpackDefaultExport
    });
    const webpackDefaultExport = "test代码";
  }
}]);

咱们先不看下面的函数调用,就看 "./src/test.js"这个字段,这个字段就是test.js打包后的代码,webapck把它封装成为了一个函数,这样做的起因就是为了让外界能拿到这个模块导出的内容,这个函数承受了三个参数,别离是:unusedWebpackModulewebpackExportswebpackRequire,第一个参数就是commonjs的全局module对象,之所以翻译过去叫“没有应用的模块”,是因为在下面的webpackRequire函数外面查看了模块是否被缓存,有缓存就从缓存外面去取,就不会走到这个函数了

    // webpackRequire的过滤
if (cachedModule !== undefined) {
     return cachedModule.exports;
}

第二个参数webpackExports示意的是commonjs的module.exports,这个参数其实也是esModule导出变量的办法,然而commonjs和esModule有很大的不同,这个我上面说。
第三个参数是webpackRequire,这个参数的作用是提供一些工具办法。
函数剖析:
webpackRequire.r(webpackExports);的作用是把以后模块标记为esmodule
简略看看webpackRequire.r的实现:

    webpackRequire.r = exports => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
          value: 'Module'
        });
      }
      Object.defineProperty(exports, 'esmodule', {
        value: true
      });
    };

代码很简略,就是增加了两个字段,表明是esModule。这个工具函数只有在模块是esModule的时候才会被应用。

webpackRequire.d(webpackExports, {
      "default": () => webpackDefaultExport
    });
    const webpackDefaultExport = "test代码";

这段代码是导出的核局部,这段代码通过 webpackRequire.d,把test.js导出的代码给增加到了 webpackExports上,函数的第二个参数蕴含了模块导出的代码。
来看 webpackRequire.d的实现:

    webpackRequire.d = (exports, definition) => {
        for (var key in definition) {
            if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
                Object.defineProperty(exports, key, {
                    enumerable: true,
                    get: definition[key]
                });
            }
        }
    };

这段代码的实现很简略,这里的webpackRequire.o的作用是用来判断参数1是否蕴含参数2。
webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
做完判断之后,函数对definition进行了遍历,并且把每个值都以 Object.defineProperty的形式定义到了exports上,这也就解释了刚刚为什么模块导出的参数是写成了一个函数,就是为了这里不便增加到get办法上。这同样解释了为什么commonjs导入的值能够批改,然而esModule导入的值不能批改,因为esModule导出的值基本没有set办法。webpack把值放在了exports上,最初webpackRequire返回了这个值,这样就实现了模块对值的导出。

累了累了,今天接着写

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理