文章webpack版本为3.6.0css
随着掌握的前端基础知识愈来愈多,对技术的要求逐渐不知足于实现便可,技术到了瓶颈期,本身也曾尝试写过一些开源库,不过不多有满意的做品,一般没迭代几个版本就没有耐心继续维护了。一般是面临的情形是前期设计思路太过简单致使后期扩展的时候须要重构大量的代码(GG吧~),就比如一坨屎,再怎么装点,都很难把它当成蛋糕吃下去。前端
我认为,突破这个瓶颈的关键就是学会深刻理解优秀开源库背后的思路。有人可能会说,我用xxx已经好久了,可以熟练使用它解决各类棘手问题,对于它,我已经充分理解了。我想说的是,即使你对于它的使用已经达到了炉火纯青的程度,可是站在使用者角度理解再“深”能有多深呢,不过是坐井观天罢了。webpack
目前为止,Webpack已经拥有39.9k的star,在前端代码打包器领域内应该算是无敌的存在了吧。Webpack强大的生态圈和丰富的解决方案使得咱们在平常开发中很难逃脱它的魔爪。若是能学习到它背后的思路,对于技能树的完善和水平层次的提升应该是很是有好处的。web
若是要全面总结webpack的实现,估计写10篇文章都不必定够。为了更加清晰地get到webpack的设计思路,会隐去webpack的大部分功能实现。编程
以实现简单的js模块打包功能为背景,文章分为3部分:架构
相信你在阅读完本文后会对Webpack的架构有个大概的了解,这应该会对你继续深刻理解webpack其它功能的实现以及编写插件会有所帮助。框架
简单到不能再简单的js模块打包器函数
这个接下来依次讲解webpack中几个重要对象之间的关系,会以各自的视角描述几个重要的过程。固然,就单单这几个对象还不能彻底地描述流程上的全部内容。学习
webpack 4.0的插件系统已经彻底重作并将Tapable更新到了1.0.0ui
在正式介绍几个核心对象以前,你须要了解一下Tapable类。
简单来讲,Tapable为一个对象提供了插件功能。若是你用过Vue.js
或者React.js
之类的框架,Tapable就是为某个对象提供了至关于组件的生命周期功能,在外部你能够经过调用这些生命周期钩子监听该对象。
固然,你还能够在外部手动触发对象的某个生命周期。
若是你想详细了解Tapable的API能够参考这里(文中版本为0.2.8)
最宏观的视角
1. 合并外部与默认配置
2. 配置并建立compiler
3. 在compiler启动前触发compiler上的若干生命周期
其中生命周期包括:environment,after-environment,entry-option,after-plugins,after-resolvers
4. 启动compiler
5. 将compiler运行后获得的状态信息打印出来
1. 正式运行前依次触发before-run和run生命周期
2. 建立params对象并触发before-compile生命周期
3. 触发compile生命周期并建立compilation对象
4. 触发this-compilation和compilation生命周期
5. 触发make生命周期并调用compilation.finish()
在make阶段调用了compilation.addEntry(),开始构建模块树,构建完毕后调用compilation.finish(),记录报错信息
6. 调用compilation.seal()并触发after-compile生命周期
compilation在seal过程当中作了不少工做,在compilation视角部分会讲到,如今只需知道seal事后compilation生成了assets对象以供compiler生成文件
7. 拿到assets并在生成每一个assets对应的文件
8. 将警告信息和文件大小信息合成为stats状态信息
9. 触发done生命周期并将stats状态信息交给webpack主函数
当compiler命令compilation构建模块树以后compilation都作了些什么
1. 使用moduleFactory建立空module
2. 命令module自行构建自身属性,好比依赖的子模块信息(dependency)
调用module.build()进行构建模块自身属性
3. 递归地重复1和2的操做,生成模块树
4. 将模块树记录到chunk中
1. 配置chunk
2. 将所处模块树深度和引用顺序等信息记录在每一个模块上
3. 将全部模块按照引用顺序排序
4. 触发optimize-module-order生命周期并按照排序后的模块顺序为每一个模块编号
5. 使用template对象渲染出chunk的内容source
6. 拿到source后生成asset,添加到assets中
1. 存在的问题
能够看到,BundleBuilder的架构中彻底没有为第三方提供接口,后期固然也能够作成根据不一样的外部配置项来实现一些有限的定制化需求。
可是,这样为了保证功能的多样性,会频繁修改打包器的内部实现。这种作法会使得整个打包器的稳定性不足,最终很是臃肿,维护困难。
2. webpack的作法
反观webpack,它使用了一种很是聪明的方式。在保证基本架构的前提下,为主流程上的大部分对象都引入插件系统,使用者能够获取到这些对象,而且在一些特定的时候运行使用者提供的代码。这样一来,社区的逐渐壮大保证了功能的多样性,还把稳定性不足的风险留给用户去处理,提升了整个打包器的可维护性。
1. 存在的问题
能够看到,BundleBuilder最终生成文件内容只有一个过程,就是调用ModuleResolver获取字符串。当这个过程当中的某一阶段须要独立进行的时候,不免会要重构代码。若是内部实现是比较松耦合的,那么重构的工做会比较轻松,可是像如今BundleBuilder这种实现,显然要作的工做并很多。
2. webpack的作法
从接收配置到生成文件内容,从比较宏观的角度,分为构建,封装,生成文件内容,三部分。
1. 存在的问题
在BundleBuilder中,对于每一个模块仅仅是经过路径读取它的文件内容,而后分析其子模块的信息,最后生成处理后的模块内容。这些都是过程。若是后面迭代时须要在打包后输出一些log,如模块警告,模块路径等与模块相关的信息。以面向过程的编程方式固然也能够实现,但这样不免会增长实现难度,下降代码可读性。
2. webpack的作法
稍微搜索一下,不包括自带插件,webpack总共有200多个用Class
声明的类。
因为webpack过于庞大,看源码的过程感受是在修行。写这篇文章之初准备深刻到一些技术细节,后来感受意义不大。也尝试过列举在简单js模块打包流程上涉及到的默认插件,写出来像API手册,若是彻底写完,体量可能都接近半本书了。最后,决定拿小学3年级画画水平,将最基本的架构关系画出来。
最大的感觉就是:当你真的准备设计一个库的时候,应该在实现以前充分列举可能的应用场景,将充分抽象出稳定的基本架构,而后将难办的部分,复杂度很高的部分,或者说定制化需求比较多的部分,采用开放插件的方式扔给使用者去解决。