Webpack 构建流程简介
webpack的核心概念
- Entry: 入口,webpack执行构建的第一入口,可以抽象理解为input
- Module: 模块, 在webpack里面一切皆是模块,一个模块对应一个文件,webpack会从配置的Entry开始,递归找到所有依赖的模块。
- Chunk:代码块, 一个chunk是由多个模块组合而成,用于代码合并与分割。
- Loader: 模块转换器,用于将模块的元内容按照需求转换成新内容。
- Plugin: 扩展插件,在webpack构建流程中的特定时机会广播对应的事件,插件可以监听这些事件,在特定的时机做对应的事情。
webpack 中,module,chunk 和 bundle 的区别是什么?
先上一篇参考文章中的图片
对于一份同逻辑的代码,当我们手写下一个一个的文件,它们无论是 ESM 还是 commonJS 或是 AMD,他们都是 module ;
当我们写的 module 源文件传到 webpack 进行打包时,webpack 会根据文件引用关系生成 chunk 文件,webpack 会对这个 chunk 文件进行一些操作;
webpack 处理好 chunk 文件后,最后会输出 bundle 文件,这个 bundle 文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。
一般来说一个 chunk 对应一个 bundle,比如上图中的 utils.js -> chunks 1 -> utils.bundle.js
;但也有例外,比如说上图中,我就用 MiniCssExtractPlugin
从 chunks 0 中抽离出了 index.bundle.css
文件。
webpack构建流程分析
Webpack 源码是一个插件的架构,很多功能都是通过诸多的内置插件实现的。Webpack为此专门自己写一个插件系统,叫 Tapable
主要提供了注册和调用插件的功能。
webpack的流程
webpack是一个串行的过程,从启动到结束会依次执行以下流程
- 初始化参数: 从shell参数和配置文件合并参数,得出最终的参数
- 开始编译:从上一步获得的参数初始化compiler对象,加载所有的插件,通过run方法执行编译。
- 确定入口:根据配置文件的entry找出所有入口文件。
- 编译模块:从入口文件开始,调用所有配置的loader对模块进行翻译成compliation,然后递归所有依赖的模块,然后重复编译。得到每个模块翻译后的最终内容以及它们之间的依赖关系。
- 输出资源:根据入口和模块的依赖关系,组装成一个个包含多个模块的chunk,然后将chunk转换成一个单独的文件加入输出列表,这是可以修改输出内容的最后机会
- 输出完成: 在确定好输出内容后,根据配置确定输出的路径和文件名,将文件的内容写入文件系统上。
WebPack 编译流程图 原图出自:blog.didiyun.com/index.php/2…
在以上过程中,webpack会在特定的时间点广播特定的事件,插件通过监听到感兴趣的事件后执行特定的逻辑,并且改变webpack的运行结果。
这篇文章会从源码级别讲解流程,有兴趣或者有需要可以回头看。
理解事件流机制 Tabable
webpack
本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable。
Webpack
的 Tapable
事件流机制保证了插件的有序性,将各个插件串联起来, Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条webapck机制中,去改变webapck的运作,使得整个系统扩展性良好。
Tapable
也是一个小型的 library,是Webpack
的一个核心工具。类似于node
中的events
库,核心原理就是一个订阅发布模式。作用是提供类似的插件接口。
webpack中最核心的负责编译的Compiler
和负责创建bundles的Compilation
都是Tapable的实例,可以直接在 Compiler
和 Compilation
对象上广播和监听事件,方法如下:
1 | /** |
Tapable
类暴露了tap
、tapAsync
和tapPromise
方法,可以根据钩子的同步/异步方式来选择一个函数注入逻辑。
tap
同步钩子
1 | compiler.hooks.compile.tap('MyPlugin', params => { |
tapAsync
异步钩子,通过callback
回调告诉Webpack
异步执行完毕 tapPromise
异步钩子,返回一个Promise
告诉Webpack
异步执行完毕
1 | compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => { |
Tapable用法
1 | const { |
简单实现一个 SyncHook
1 | class Hook{ |
tapable是如何将webapck/webpack插件关联的?
Compiler.js
1 | const { AsyncSeriesHook ,SyncHook } = require("tapable"); |
MyPlugin.js
1 | const Compiler = require('./Compiler') |
想要深入了解tapable
的文章可以看看这篇文章:
webpack4
核心模块tapable
源码解析: https://www.cnblogs.com/tugenhua0707/p/11317557.html
总结
总的来说,webpack的打包分为几个阶段,类似于生命周期,每个生命周期开始和结束都可以发出事件,loader是编译阶段用于编译我们指定类型文件的。
所以就像前面所说,webpack的构建流程是基于事件的,对于webpack而言,它的事件发出和订阅都是依赖于Tapable类,我们的Plugin其实就是 一个个的Tapable类,而我们用到的compiler或者compilation就是内置的Plugin。
我么可以利用Tapable类的apply和plugin方法发出和订阅事件,由于complier本身是一个Tapable,所以它自身可以apply和plugin事件,而我们在complier的hooks上通过类似tap的方法添加函数,就相当于监听了该hooks发出的事件。
参考文章: