Webpack基本架构浅析

文章webpack版本为3.6.0css

前言

随着掌握的前端基础知识愈来愈多,对技术的要求逐渐不知足于实现便可,技术到了瓶颈期,本身也曾尝试写过一些开源库,不过不多有满意的做品,一般没迭代几个版本就没有耐心继续维护了。一般是面临的情形是前期设计思路太过简单致使后期扩展的时候须要重构大量的代码(GG吧~),就比如一坨屎,再怎么装点,都很难把它当成蛋糕吃下去。前端

我认为,突破这个瓶颈的关键就是学会深刻理解优秀开源库背后的思路。有人可能会说,我用xxx已经好久了,可以熟练使用它解决各类棘手问题,对于它,我已经充分理解了。我想说的是,即使你对于它的使用已经达到了炉火纯青的程度,可是站在使用者角度理解再“深”能有多深呢,不过是坐井观天罢了。webpack

为何Webpack

目前为止,Webpack已经拥有39.9k的star,在前端代码打包器领域内应该算是无敌的存在了吧。Webpack强大的生态圈和丰富的解决方案使得咱们在平常开发中很难逃脱它的魔爪。若是能学习到它背后的思路,对于技能树的完善和水平层次的提升应该是很是有好处的。web

概要

若是要全面总结webpack的实现,估计写10篇文章都不必定够。为了更加清晰地get到webpack的设计思路,会隐去webpack的大部分功能实现。编程

以实现简单的js模块打包功能为背景,文章分为3部分:架构

  1. BundleBuilder基本架构
  2. Webpack基本架构
  3. 学到了些什么

相信你在阅读完本文后会对Webpack的架构有个大概的了解,这应该会对你继续深刻理解webpack其它功能的实现以及编写插件会有所帮助。框架

BundleBuilder基本架构

简单到不能再简单的js模块打包器函数

示意图

BundleBuilder.JPG-26.6kB

BundleBuilder对象

  1. BundleBuilder对象接收并处理外部配置
  2. 根据配置选择不一样的ModuleResolver
  3. 使用ModuleResolver接收配置获得最终文件内容
  4. 生成打包后的文件

ModuleResolver对象

  1. 接收从BundleBuilder传进的配置
  2. 解析入口文件内容
  3. 提取子模块路径,并递归地解析子模块
  4. 将引用的模块路径替换为模块id最终生成模块文件

webpack基本架构

这个接下来依次讲解webpack中几个重要对象之间的关系,会以各自的视角描述几个重要的过程。固然,就单单这几个对象还不能彻底地描述流程上的全部内容。学习

Tapable插件功能

webpack 4.0的插件系统已经彻底重作并将Tapable更新到了1.0.0ui

在正式介绍几个核心对象以前,你须要了解一下Tapable类。

简单来讲,Tapable为一个对象提供了插件功能。若是你用过Vue.js或者React.js之类的框架,Tapable就是为某个对象提供了至关于组件的生命周期功能,在外部你能够经过调用这些生命周期钩子监听该对象。

固然,你还能够在外部手动触发对象的某个生命周期。

若是你想详细了解Tapable的API能够参考这里(文中版本为0.2.8)

Webpack主函数视角

最宏观的视角

1. 合并外部与默认配置

1-1.PNG-220.1kB

2. 配置并建立compiler

1-1.PNG-125kB

3. 在compiler启动前触发compiler上的若干生命周期

其中生命周期包括:environment,after-environment,entry-option,after-plugins,after-resolvers

1-3.PNG-122.2kB

4. 启动compiler

1-4.PNG-107.2kB

5. 将compiler运行后获得的状态信息打印出来

1-5.PNG-201.8kB

Compiler视角

1. 正式运行前依次触发before-run和run生命周期

2-1.JPG-17.1kB

2. 建立params对象并触发before-compile生命周期

2-2.JPG-16.2kB

3. 触发compile生命周期并建立compilation对象

2-3.JPG-17.6kB

4. 触发this-compilation和compilation生命周期

2-4.JPG-17.9kB

5. 触发make生命周期并调用compilation.finish()

在make阶段调用了compilation.addEntry(),开始构建模块树,构建完毕后调用compilation.finish(),记录报错信息

2-5.JPG-20.6kB

6. 调用compilation.seal()并触发after-compile生命周期

compilation在seal过程当中作了不少工做,在compilation视角部分会讲到,如今只需知道seal事后compilation生成了assets对象以供compiler生成文件

2-6.JPG-17.1kB

7. 拿到assets并在生成每一个assets对应的文件

2-7.JPG-22.2kB

8. 将警告信息和文件大小信息合成为stats状态信息

2-8.JPG-20kB

9. 触发done生命周期并将stats状态信息交给webpack主函数

2-9.JPG-17.4kB

Compilation构建模块树视角

当compiler命令compilation构建模块树以后compilation都作了些什么

1. 使用moduleFactory建立空module

3-1.JPG-16.6kB

2. 命令module自行构建自身属性,好比依赖的子模块信息(dependency)

调用module.build()进行构建模块自身属性

3-2.JPG-24.4kB

3. 递归地重复1和2的操做,生成模块树

3-3.JPG-18.8kB

4. 将模块树记录到chunk中

3-4.JPG-19.9kB

Compilation的seal视角

1. 配置chunk

4-1.JPG-25.4kB

2. 将所处模块树深度和引用顺序等信息记录在每一个模块上

4-2.JPG-37.1kB

3. 将全部模块按照引用顺序排序

4-3.JPG-27.5kB

4. 触发optimize-module-order生命周期并按照排序后的模块顺序为每一个模块编号

4-4.JPG-28.8kB

5. 使用template对象渲染出chunk的内容source

4-5.JPG-29.2kB

6. 拿到source后生成asset,添加到assets中

4-6.JPG-34.4kB

学到了什么

引入插件系统

1. 存在的问题

能够看到,BundleBuilder的架构中彻底没有为第三方提供接口,后期固然也能够作成根据不一样的外部配置项来实现一些有限的定制化需求。

可是,这样为了保证功能的多样性,会频繁修改打包器的内部实现。这种作法会使得整个打包器的稳定性不足,最终很是臃肿,维护困难。

2. webpack的作法

反观webpack,它使用了一种很是聪明的方式。在保证基本架构的前提下,为主流程上的大部分对象都引入插件系统,使用者能够获取到这些对象,而且在一些特定的时候运行使用者提供的代码。这样一来,社区的逐渐壮大保证了功能的多样性,还把稳定性不足的风险留给用户去处理,提升了整个打包器的可维护性。

过程粒度细化

1. 存在的问题

能够看到,BundleBuilder最终生成文件内容只有一个过程,就是调用ModuleResolver获取字符串。当这个过程当中的某一阶段须要独立进行的时候,不免会要重构代码。若是内部实现是比较松耦合的,那么重构的工做会比较轻松,可是像如今BundleBuilder这种实现,显然要作的工做并很多。

2. webpack的作法

从接收配置到生成文件内容,从比较宏观的角度,分为构建,封装,生成文件内容,三部分。

  1. 保证了内部修改的灵活性。若是要对过程再细分或者添加过程,实现起来会比较方便。
  2. 丰富了对外扩展的接口。很显然,因为webpack引入了插件系统,细化过程粒度应该是必然选择,这样会有效地增长用户对整个打包过程的自定义能力。
  3. 提高了代码的可维护性。当打包器在运行时出现了bug,粒度越小,越加方便定位问题。

更多类的抽象

1. 存在的问题

在BundleBuilder中,对于每一个模块仅仅是经过路径读取它的文件内容,而后分析其子模块的信息,最后生成处理后的模块内容。这些都是过程。若是后面迭代时须要在打包后输出一些log,如模块警告,模块路径等与模块相关的信息。以面向过程的编程方式固然也能够实现,但这样不免会增长实现难度,下降代码可读性。

2. webpack的作法

稍微搜索一下,不包括自带插件,webpack总共有200多个用Class声明的类。

  1. 结构化的数据。建立一个类就意味着咱们能统一不少有相同抽象含义的对象建立一样的属性,好比Module类,它能够记录不少与模块相关的信息。
  2. 方便扩展不一样种类的对象。好比模块类,能够经过继承的方式衍生出,普通js文件模块,css文件模块等等。
  3. 多类意味着有承担不一样职责的对象。明确的职责分工,好比compiler仅仅负责compilation的建立,文件的生成和信息状态的合成。ModuleFactory负责建立Module。一旦出了问题方便定位到责任人,下降了各个工做的耦合度。
  4. 对象间的解耦。好比compilation和Module两个类,webpack其实也能够直接使用compilation来直接建立Module,可是一旦Module的种类增长,不可避免地须要在compilation中写一些条件语句,这样,建立Module这部分的代码会让原本就有不少事情要作的compilation变得更加庞大。因此webpack引入了ModuleFactory,compilation只需调用ModuleFactory来建立Module就好,建立部分的逻辑则被分布在了ModuleFactory中,将compilation与Module解耦,二者中一方发生变化,只需在ModuleFactory中增长逻辑便可。

感觉

因为webpack过于庞大,看源码的过程感受是在修行。写这篇文章之初准备深刻到一些技术细节,后来感受意义不大。也尝试过列举在简单js模块打包流程上涉及到的默认插件,写出来像API手册,若是彻底写完,体量可能都接近半本书了。最后,决定拿小学3年级画画水平,将最基本的架构关系画出来。

最大的感觉就是:当你真的准备设计一个库的时候,应该在实现以前充分列举可能的应用场景,将充分抽象出稳定的基本架构,而后将难办的部分,复杂度很高的部分,或者说定制化需求比较多的部分,采用开放插件的方式扔给使用者去解决。

相关文章
相关标签/搜索