AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

webpack 到底对我们的代码做了什么

  • 2019-09-18
  • 本文字数:4312 字

    阅读完需:约 14 分钟

webpack到底对我们的代码做了什么

webpack 对于每个前端儿来说都不陌生,它将每个静态文件当成一个模块,经过一系列的处理为我们整合出最后的需要的 js,css,图片,字体等文件。你有没有好奇过 webpack 是如何工作的?

这个系列将带大家深入 webpack 源码内部,剖析它的是如何解析资源,如何使用各种不同的装载机来处理各种资源又是如何将各个资源链接起来,打包成最终的文件的。希望通过这个系列文章,能带给大家对 webpack 机制有更深刻的认识,进而更好的辅助大家对 webpack 的使用。


1。写在前面的话

在阅读一个东西的源码之前,首先需要了解这个东西是什么,怎么用。这样在​​阅读源码过程中才能在大脑中形成一副整体的认知。所以,先了解一下 webpack 打包前后代码发生了什么?找一个简单的例子。


入口文件为 main.js,在其中引入了 a.js,b.js


// main.jsimport { A } from './a'import B from './b'console.log(A)B()
复制代码


// a.jsexport const A = 'a'
复制代码


// b.jsexport default function () {    console.log('b')}
复制代码


经过 webpack 的一番蹂躏,最后变成了一个文件:bundle.js.先忽略细节,看最外面的代码结构。


(function(modules){  ...(webpack的函数)  return __webpack_require__(__webpack_require__.s = "./demo01/main.js");})( {   "./demo01/a.js": (function(){...}),   "./demo01/b.js": (function(){...}),   "./demo01/main.js": (function(){...}), })
复制代码


最外层是一个立即执行函数,参数是 modules.a.js,b.js 和 main.js 最后被编译成三个函数(下文将这三个函数称为模函数),关键是文件的相对路径.bundle.js 会执行到__webpack_require__(webpack_require.s = “./demo01/main.js”);即通过__webpack_require__(’./demo01/main.js’)开始主入口函数的执行。


通过 bundle.js 的主接口可以清晰的看出,对于 webpack 每个文件就是一个模块。我们写的 import ‘xxx’,则最终为__webpack_require__函数执行。更多的时候我们使用的是 import A from 'xxx’或者 import { B } from ‘xxx’,可以猜想一下,这个__webpack_require__函数中除了找到对应的’xxx’来执行,还需要一个返回’xxx’中导出来的内容。


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;}
复制代码


调用每一个模块函数时,参数为 module,module.exports,webpack_require。module.exports 用来收集模块中所有的出口 XXX。看” ./demo/a.js“的模块。


(function(module, __webpack_exports__, __webpack_require__) {
// ...__webpack_require__.d(__webpack_exports__, "A", function() { return A; });const A = 'a'
/***/ })
// ...__webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { configurable: false, enumerable: true, get: getter }); }};// Object.prototype.hasOwnProperty.call__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property);};// ...
复制代码


__webpack_require 。d( webpack_exports __,“A”,function(){return A;}); 简单理解就是


__webpack_exports__.A = A;
复制代码


webpack_exports 实际为上面的 webpack_require 中传入的 moule.exports,如此,就将 A 变量收集到了 module.exports 中。如此我们的


  import { A } from './a.js'   console.log(A)
复制代码


就编译为


  var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./demo/a.js");   console.log(_a__WEBPACK_IMPORTED_MODULE_0__["A"])
复制代码


对于 b.js 我们使用的是 export default ,webpack 处理后,会在 module.exports 中增加一个默认属性。


__webpack_exports__["default"] = (function () {    console.log('b')});
复制代码


最后导入 B 来自’./b.js 编译为


var _b__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./demo/b.js")Object(_b__WEBPACK_IMPORTED_MODULE_1__["default"])()
复制代码

2。异步加载

在 webpack 中我们可以很方便的实现异步加载,以简单的 demo 入手。


// c.jsexport default {  key: 'something'}
复制代码


// main.js  import('./c').then(test => {    console.log(test)})
复制代码


打包结果,异步加载的 c.js,最后打包在一个单独的文件 0.js 中。


(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{"./demo/c.js": (function(module, __webpack_exports__, __webpack_require__) {  "use strict";  __webpack_require__.r(__webpack_exports__);  __webpack_exports__["default"] = ({      key2: 'key2'  });  })}]);
复制代码


简化一下,执行的就是:


var t = window["webpackJsonp"] = window["webpackJonsp"] || [])t.push([[0], {function(){...}}])
复制代码


执行导入(’./ c.js’)时,实际上通过在 HTML 中插入一个脚本标签加载 0.js.0.js 加载后会执行窗口[“webpackJsonp”]。推方法。在 main.js 在还有一段:


var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];jsonpArray.push = webpackJsonpCallback;
复制代码


这里篡改了一下,window [“webpackJsonp”]的推方法,将推方法外包装了一层 webpackJonspCallback 的逻辑。当 0.js 加载后,会执行 window[“webpackJsonp”].push,这时便会进入 webpackJsonpCallback 的执行逻辑。


function webpackJsonpCallback(data) {    var chunkIds = data[0];    var moreModules = data[1];    // 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];        if(installedChunks[chunkId]) {            resolves.push(installedChunks[chunkId][0]);        }        installedChunks[chunkId] = 0;    }    for(moduleId in moreModules) {        if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {            modules[moduleId] = moreModules[moduleId];        }    }    if(parentJsonpFunction) parentJsonpFunction(data);    while(resolves.length) {        resolves.shift()();    }};
复制代码


在 webpackJsonpCallback 中会将 0.js 中的 chunks 和 modules 保存到全局的模块变量中,并设置 installedChunks 的标志位。


有两点需要详细说明的:

1。

我们知道 import(‘xxx.js’)会返一个 Promise 实例 promise,在 webpack 打包出来的最终文件中是如何处理这个 promise 的?


在加载 0.js 之前会在全局 installedChunks 中先存入了一个 promise 对象。


installedChunks[chunkId] = [resolve, reject, promise]
复制代码


解决这个值在 webpackJsonpCallback 中会被用到,这时就会进入到我们写的导入(’./ c.js’)。然后() 的然后语句中了。

2。

在 main.js 中处理 webpackJsonp 过程中还有一段特殊的逻辑:


jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);...jsonpArray = jsonpArray.slice();for(var i = 0; i < jsonpArray.length; i++)  webpackJsonpCallback(jsonpArray[i]);var parentJsonpFunction = oldJsonpFunction;
复制代码


也就是说如果之前已经存在全局的 窗口[“webpackJsonp”] 那么在替换 其推送函数之前会将原有的推方法保存为 oldJsonpFunction,同时将已存在于 窗口[“webpackJsonp”] 中的内容,一一执行 webpackJsonpCallback 。并且在 webpackJsonpCallback 中也将异步加载的内容也会在 parentJsonpFunction 中同样执行一次。


if(parentJsonpFunction) parentJsonpFunction(data);
复制代码


这样的同步意义何在?试想下面的场景,webpack 中多入口情况下,例如如下配置:


{entry: {   bundle1: 'bundle1.js',   bundle2: 'bundle2.js' }}
复制代码


并且 bundle1 和 bundle2 中都用到了异步加载了 0.js.而且在同一个页面中同时加载了 bundle1 和 bundle2。那么由于上面的逻辑,执行的流程如下图:



通过上图可以看到,这样设计对于多入口的地方,可以将 bundle1.js 和 bundle2.js 中异步模块进行同步,这样不仅保证了 0.js 可以同时在两个文件中被引用,而且不会重复加载。


异步加载中,有两个需要注意的地方:


  • 诺言


在 webpack 异步加载使用了 Promise。要兼容低版本的安卓,比如 4.x 的代码来说,需要有全局的 Promise polyfill。


  • 窗口[ “webpackJsonp”]


如果一个 HTML 页面中,会加载多个 webpack 独立打包出来的文件。那么这些文件异步加载的回调函数,默认都叫“webpackJonsp”,会相互冲突。需要通过 output.jsonpFunction 配置修改这个默认的函数名称。

3。webpack 编译总流程

知道上面的产出,根据产出看 webpack 的总流程。这里我们暂时不考虑 webpack 的缓存,错误处理,看等逻辑,只看主流程。首先会有一个入口文件写在配置文件中,确定 webpack 从哪个文件开始处理。


  • step1:webpack 配置文件处理


我们在写配置文件中条目的时候,肯定写过./main.js 这时一个相对目录,所以会有一个将相对目录变成绝对目录的处理


  • step2 : 文件位置解析


webpack 需要从入口文件开始,顺藤摸瓜找到所有的文件。那么会有一个


  • step3 : 加载文件 step4 文件解析 step5 从解析结果中找到文件引入的其他文件


在加载文件的时候,我们会在 webpack 中配置很多的装载机来处理 js 文件的 babel 转化等等,还应该有文件对应的装载机解析,装载机执行。


  • step3.1 : 找到所有对应的 loader,然后逐个执行


处理完整入口文件之后,得到依赖的其他文件,递归进行处理。最后得到了所有文件的模块。最终输出的是打包完成的包文件。所以会有


  • step4 : module 合并成 chunk 中输出最终文件


根据 webpack 的使用和结果,我们猜测了一下 webpack 中大概的流程。然后看一下 webpack 的源码,并和我们脑中的流程对比一下。实际的 webpack 流程图如下:



本文转载自公众号滴滴技术(ID:didi_tech)。


原文链接:


https://mp.weixin.qq.com/s/mNuhgXsCuyOAFk8q5bnOtg


2019-09-18 23:222747

评论

发布
暂无评论
发现更多内容

OpenHarmony设备开发环境搭建/源码获取/编译/烧录

拓维信息

OpenHarmony 烧录

python pandas loc布尔索引(指定条件下的索引),你花了多久弄明白架构设计

程序媛可鸥

Python 程序员 面试

案例研究:亚马逊广告使用 PyTorch 和 Amazon Inferentia 扩展广告处理模型

亚马逊云科技 (Amazon Web Services)

模型 PyTorch

自从用了这个APP,我的身体状况....

IT蜗壳-Tango

极客时间 IT蜗壳

Cloud RedTeam视角下元数据服务攻防实践

火线安全

云原生 云安全

欧拉的奇异之旅·共赴开源时代

脑极体

OceanBase 在线体验环境,现已上线!

OceanBase 数据库

oceanbase OceanBase 社区版 在线体验站

条码WMS系统与ERP接口实现方法

源字节1号

后端开发 WMS系统 ERP接口

攻击面管理(ASM)技术详解和实现

风向标

网络安全 asm 安全419 零零信安

python sorted()函数及sort()方法,零基础Python开发

程序媛可鸥

Python 程序员 面试

Python 实现 ZeroMQ 的三种基本工作模式,贼好用的Python学习路线集合

程序媛可鸥

Python 程序员 面试

golang并发控制设计中的“流式模型”

不登山的小鲁

golang

2022年,人工智能和数据发展呈现哪五大趋势?

澳鹏Appen

人工智能 机器学习 深度学习 训练数据

网络安全 kali Web安全之CSRF攻击

学神来啦

网络安全 CSRF WEB安全 kali kali Linux

踏雪痕项目管理学习笔记

踏雪痕

项目管理 PMP Certification 3月程序媛福利 3月月更

Python 实现七大排序算法,面试竟然被这31道Python基础题难倒了

程序媛可鸥

Python 程序员 面试

低调不了!最佳体验尽在 Erda 2.0 版本

尔达Erda

云计算 云原生 设计 发布 界面改版

红黑树的原理以及实现

Linux服务器开发

数据结构 B+树 红黑树 Linux服务器开发 Linux后台开发

2021物联之星评选结果重磅出炉!AIoT产业核心玩家已经浮现

dgiot

微博评论高性能高可用架构设计

随欣所遇

架构训练营5期

【模块五】设计微博系统中”微博评论“的高性能高可用计算架构

yhjhero

架构 #架构训练营

Python 下载的9种方法,Python开发技巧

程序媛可鸥

Python 程序员 面试

阿里云 OSS对象存储攻防

火线安全

云安全 阿里云;

关于帮助中心,你需要知道的一切

小炮

python 列表 remove()函数使用详解,最新手淘Python高级面试题及答案

程序媛可鸥

Python 程序员 面试

使用APICloud平台实现朋友圈功能

YonBuilder低代码开发平台

html5 css3 APP开发 APICloud JavaScrip

微软云对象存储攻防

火线安全

云原生 云原生应用 云安全攻防 云安全研究

低代码如何助力化学材料行业数字化升级?

TOBESOFT特碧软件

低代码 数字化转型 MES系统 制造业 TOBESOFT

python Excel数据表格转为HTML网页数据表格,阿里快手拼多多等7家大厂Python面试真题

程序媛可鸥

Python 程序员 面试

腾讯云COS对象存储攻防

火线安全

云原生 云安全 云原生应用

《第四期(2021-2022)传统行业云原生技术落地调研报告——金融篇》重磅发布!

York

容器 DevOps 云原生 金融科技 金融行业

webpack到底对我们的代码做了什么_文化 & 方法_崔静_InfoQ精选文章