阅读 190

webpack编译结果如何运行?

webpack在前端开发中很常用,但很多人对于webpack的知识仅仅停留在了解阶段,以及一些配置操作,对于webpack更多的运行过程都不甚了解。希望本文带你更多的知识。

一般情况下,webpack打包只会生成被称为bundle的js文件。bundle文件可能会很长,并且可能被混淆了,所以一般开发者都不看这个文件的具体内容,只知道加载这个文件可以运行就是了。

那么bundle文件中的逻辑是什么样的呢?

直接看bundle的话确实不太明晰,可以修改一些配置(mode=development,单独分出manifest文件),让代码更加清晰

这样配置的情况下,除了生成一些bundle文件外,还有一个manifest文件,文件比较小,并且因为使用了development模式,这个manifest文件是可以阅读的。

manifest文件是bundle运行的入口文件,以下是对于manifest中逻辑的解读,读者们也可以参考代码自行阅读和理解,推荐自己梳理一遍代码。

关于文中出现的代码片段,webpack版本为4,在配置为mode: development情况下的编译结果,//是代码原本的注释,////是笔者写的注释,注释内容包括对于代码逻辑的注解、阅读中疑惑和思考的问题。

主体逻辑

整个manifest文件是一个自执行函数IIFE。大部分的内容是对于变量和函数的定义。

在IIFE中的直接运行的代码如下

var installedModules = {} var installedChunks = {   // 默认manifest是加载的   "webpackManifest": 0 }; var deferredModules = []; //// 定义/获取webpackJsonp,绑定push函数 var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); //// 重定义push jsonpArray.push = webpackJsonpCallback; //// 复制数组 jsonpArray = jsonpArray.slice(); //// 预计是模块的加载逻辑 for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); var parentJsonpFunction = oldJsonpFunction; // run deferred modules from other chunks checkDeferredModules(); 复制代码

代码逻辑如下(后文称为逻辑A):

  1. 定义一些变量,获取名为window.webpackJsonp的数组

  2. 在保存webpackJsonp的旧push方法之后,将webpackJsonpCallback函数作为新的push函数

  3. 对webpackJsonp中的内容进行循环,调用webpackJsonpCallback()

  4. 定义parentJsonpFunction

  5. 调用**checkDeferredModules()**函数

其中第1、2、3步主要逻辑是定义一些变量获取webpackJsonp,并且循环内容进行处理,第4、5步在这里暂时看不出作用,或者不知道是做什么的。

看到这里会有一个疑问:webpackJsonp数组里面究竟装的是什么?

webpackJsonp变量

为了了解webpackJsonp中的内容,需要找到它在哪里内使用了。 让我们来看看bundle代码,以下是名为main的bundle中的代码片段,隐去了大量的代码细节。

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([   ["main"],   {     '/7QA': function(module, __webpack_exports__, __webpack_require__) {       eval('模块具体代码')     },     'edgnb': function(module, __webpack_exports__, __webpack_require__) {       eval('模块具体代码')     },     // ...其他类似代码   },   [["/7QA","webpackManifest","vendors"]] ]); 复制代码

可以看到,为了系统健壮性考虑,首先确保webapckJsonp变量的存在。其次是调用push方法并且传入一个数组,该数组中主要包含三个元素(之后称为三元组)。这三个元素具体是做什么用的暂且不管。 如果细看的话,三元组中第二个元素内存储着大量模块代码,即业务代码,或者是引用的包代码,或者是依赖图结构中的包代码。

再看看其他的bundle文件,其中内容也是相似的。

webpackJsonpCallback函数

在上一小节中调用了push方法,但在逻辑A的第2步中,使用webpackJsonpCallback函数替换了原本的push方法,所以来看下其中的实现

//// data即三元组 function webpackJsonpCallback(data) {   //// 以webpackJsonp小节中的代码为例   //// chunkIds = ["main"]   var chunkIds = data[0];   //// moreModules = { "/7QA": function() {} }   var moreModules = data[1];   //// executeModules = [["/7QA","webpackManifest","vendors"]]   var executeModules = data[2];      // add "moreModules" to the modules object,   // then flag all "chunkIds" as loaded and fire callback   var moduleId, chunkId, i = 0, resolves = [];   for(;i < chunkIds.length; i++) {    chunkId = chunkIds[i];    //// 理解为,如果installChunks存在chunkId,并且对应的内容存在    //// 但是默认情况下,应该都是没有的,所以这里会跳过       //// 也可能installChunks后来会是chunks内容    if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {    resolves.push(installedChunks[chunkId][0]);    }    //// webpackManifest这个模块也会被标记为0,但是这和上面的 installedChunks[chunkId][0] 是否冲突,因为这里是一个数字,而上面是一个数组? 可能是为了兼容以前的逻辑?    installedChunks[chunkId] = 0;   }   //// 写入到modules中了   for(moduleId in moreModules) {    if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {      //// modules应该是一个数组,但是moduleId感觉是一个奇怪的字符,例如/7QA这样,这个是否会有问题    modules[moduleId] = moreModules[moduleId];    }   }   //// 重新再调用push函数   if(parentJsonpFunction) parentJsonpFunction(data);      //// 弹出元素,并且运行   while(resolves.length) {    resolves.shift()();   }      // add entry modules from loaded chunk to deferred list   //// 三元组最后一个元素的内容会被放在deferredModules   deferredModules.push.apply(deferredModules, executeModules || []);      // run deferred modules when all chunks ready   return checkDeferredModules(); } 复制代码

代码逻辑如下(后文称为逻辑B):

  1. 对于chunkIds,在installedChunks对象中将其标记为0

  2. 对于moreModules,将其中的模块存储在modules变量中

  3. 调用parentJsonpFunction函数

  4. 对于deferredModules,将其放入executeModules中

  5. 调用checkDeferredModules函数

对于1、2、4来说,要么是进行一些标记,要么是存储数据,具体为什么要存,存下来怎么用,暂且不管。

对于第3步,和逻辑A 第4步呼应,即重新调用原本的push函数,将三元组加入

对于第5步,和逻辑A 第5步相同。

细心的读者可能会发现,在第2步中,第一次出现了名为modules的变量,那么这个变量是哪里来的?或者是在哪里定义的?

modules变量

回到开头细看IIFE代码,就会发现modules其实是IIFE的参数。IIFE代码如下:

//// 初始时是一个空数组 (function(modules) {   //// 主体逻辑 })([]); 复制代码

有时候也可以看到这样的代码,直接将模块放入modules

(function(modules) {   //// 主体逻辑 })({   //// 省略细节   "/7QA": function() {},   "edgnb": function() {}, }); 复制代码

checkDeferredModules函数

逻辑A逻辑B中剩下没弄懂的逻辑都是第5步,函数中的具体代码如下:

function checkDeferredModules() {   var result;   //// 根据之前的代码,这里deferredModules = [["/7QA","webpackManifest","vendors"]]   for(var i = 0; i < deferredModules.length; i++) {     //// deferredModule = ["/7QA","webpackManifest","vendors"]    var deferredModule = deferredModules[i];    var fulfilled = true;    for(var j = 1; j < deferredModule.length; j++) {    var depId = deferredModule[j];    //// 如果有存在不是0情况下    if(installedChunks[depId] !== 0) fulfilled = false;    }    if(fulfilled) {      //// 不再检查    deferredModules.splice(i--, 1);    //// __webpack_require__(__webpack_require__.s = '/7QA')    result = __webpack_require__(__webpack_require__.s = deferredModule[0]);    }   }      return result; } 复制代码

代码逻辑如下:

  1. 检查三元组中第三个元素的数组中,从下标为1开始,是否在installChunks中都标记为0。

  2. 如果第一个的结果为真,那么就调用**__webpack_require__函数**,参数是下标为0所对应的模块,最后返回结果。

在之前的代码中,即webpackManifestvendors这两个chunk都标记的情况下,将调用__webpack_require__(__webpack_require__.s = '/7QA')

__webpack_require__函数

这个函数也是在IIFE中定义,具体代码如下

function __webpack_require__(moduleId) {   // Check if module is in cache   if(installedModules[moduleId]) {    return installedModules[moduleId].exports;   }   // Create a new module (and put it into the cache)   var module = installedModules[moduleId] = {    i: moduleId,    l: false,    exports: {}   };   // Execute the module function   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);   // Flag the module as loaded   module.l = true;   // Return the exports of the module   return module.exports; } 复制代码

代码逻辑如下:

  1. 如果installModules已经有moduleId对应内容,那么直接返回。

  2. 反之,没有内容时,先向installModules中添加内容,并且通过call函数的方式调用对应的模块,最后将结果返回。

到这一步,业务模块就已经运行了。

总结

通过对于manifest代码的阅读,可以了解到webpack bundle代码的部分运行逻辑,除此之外,IIFE中还有很多其他代码没有列出,读者可以自行阅读。


作者:forzoom
链接:https://juejin.cn/post/7028168019465469959


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐