背景
接到一个关于 webpack
的有意思需求——整理各个组件之间的依赖关系表
简单来讲,如下图,如果组件 B 或者 组件 C 更新了,那么我们就需要提示组件 A 去做更新
初识 webpack 原理
webpack 的核心概念
•entry
: webpack
的编译入口•module
: 模块,在 webpack
中,一切皆为模块,一个模块对应一个文件•Chunk
: 代码块,一个 chunk
由多个模块组合而成,用于代码的合并与分割•Loader
: 模块转换器,可以当做是一个翻译机器,可以将一些内容转换成新的内容,以便我们的浏览器能够识别•Plugin
: 扩展插件。在 webpack
运行的各个阶段,都会广播出去相对应的事件,插件可以监听到这些事件的发生,在特定的时机做相对应的事情
注意加粗的部分,我们之后就会用到
流程概括
如图所示,我们可以看到 webpack
的整一个编译流程。在这个过程中,我们上面提到的各个核心概念都发挥着重要的作用
•entry
帮忙确认入口•loader
帮忙将模块 module
加工•最后输出资源的时候,根据入口(entry
)和模块 (module
) 的关系,组装成包含多个模块的 chunk
,每个 chunk
实际上对应输出的一个文件
那么在大家都那么忙碌的时候, plugin
在做什么呢?
首先看开始编译的时候,webpack
会用上一步初始化得到的参数用来初始化 Compiler
对象(这是一个很重要的概念,我们稍后会讲),同时会加载所有配置的插件,并执行对象中的 run
方法开始执行编译
上面我们提到,webpack
会在特定的时间点广播事件。在开始编译之后,plugin
就像一个机动员工,哪里需要就往哪里跑。它可以监听特定的事件然后处理特定的逻辑,并且 plugin
可以调用 webpack
的 API
改变 webpack
的运行结果
对于我们平时使用 webpack
而言,webpack
就是一个黑盒子,我们不用关注于它内部的事情,但它也提供了 plugin
作为桥梁,开发者可以很好的运用 plugin
去修改 webpack
的编译结果,不得不感叹一下它的设计巧妙
各个阶段暴露的事件
这里不详细列出所有的事件,感兴趣的可以直接前往官网查看
注:官网文档称之为 Compiler Hooks
和 Compilation Hooks
,翻译过来就是 compilation
钩子和 Compilation
钩子
我们看看我们这次需求需要用到的事件,在上面的流程图中,在输出资源到输出完成是修改资源的绝佳时机,这个时候 webpack
会广播一个 emit
事件,代表确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容
编写一个 plugin
以上,我们已经找到合适的时机并可以获取到相关的资源文件,现在就是要怎么利用好信息,完成我们的组件关系依赖图了
自定义一个 plugin
实际上很简单,基础的如下所示:
class BasicPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options){
}
// Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler){
compiler.plugin('compilation',function(compilation) {
})
}
}
// 导出 Plugin
module.exports = BasicPlugin;
使用的时候:
代码语言:javascript复制const BasicPlugin = require('./BasicPlugin.js');
module.export = {
plugins:[
new BasicPlugin(options),
]
}
在 webpack
启动后,它会去执行我们配置的 new BasicPlugin(options)
初始化一个插件实例。在初始化 compiler
对象之后,会调用 basicPlugin .apply(compiler)
方法将 compiler
传入,插件获得 compiler
对象后,就可以通过 compiler.plugin('事件名', 回调函数)
的方式进行监听 webpack
广播出来的事件了
举个例子,目前的项目是 demo
项目是直接使用 vue
搭建,我现在定义了一个 HelloWorldPlugin
如下:
class HelloWorldPlugin {
constructor (options) {
console.log(options)
}
apply (compiler) {
console.log(`Hello World`)
// compilation('编译器'对'编译ing'这个事件的监听)
compiler.plugin('compile', function () {
console.log(`The compiler is starting to compile...-----`)
})
// compilation('编译器'对'编译ing'这个事件的监听)
compiler.plugin('compilation', function (compilation) {
console.log(`The compiler is starting a new compilation...-----`)
compilation.plugin('optimize', function () {
console.log('The compilation is starting to optimize files...')
})
})
compiler.plugin('done', function () {
console.log(`done......`)
})
}
}
module.exports = HelloWorldPlugin
在 webpack.prod.conf.js
中,我们引入插件
const HelloWorldPlugin = require('./plugin/helloPlugin')
并在 plugins
配置中加入配置
new HelloWorldPlugin({
name: 'helloPlugin',
des: '我是一段配置'
})
执行 npm run build
可以看到,我们可以监听到 webpack
的各个阶段了。
两个重要对象——compiler 和 compilation
•compiler
对象包含 webpack
所有的配置信息,包括 options
、plugins
和 loader
等等,这个对象在 webpack
启动的时候被初始化,是全局唯一的,我们可以理解成它是 webpack
实例•compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等等。当 webpack
以开发模式运行时,每一个文件变化,一个新的 compilation
就会被创建
两者的区别在于:Compiler
代表了整个 Webpack
从启动到关闭的生命周期,而 Compilation
只是代表了一次新的编译
回到最初的问题
其实我们这次是不需要修改到 webpack
的结果的,我们只需要获得最后每个 chunk
中包含哪些文件路径即可,获取之后,具体要怎么操作,跟具体的业务相关,这里就不方便细聊了,以下是 demo
示例:
compiler.plugin('emit', function (compilation, callback) {
// compilation.chunks 存放所有代码块,是一个数组
compilation.chunks.forEach(function (chunk) {
// chunk 代表一个代码块
// 代码块由多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块
chunk.forEachModule(function (module) {
// module 代表一个模块
// module.fileDependencies 存放当前模块的所有依赖的文件路径,是一个数组
module.fileDependencies.forEach(function (filepath) {
console.log(filepath)
})
})
})
// 这是一个异步事件,要记得调用 callback 通知 Webpack 本次事件监听处理结束。
// 如果忘记了调用 callback,Webpack 将一直卡在这里而不会往后执行。
callback()
})
参考
https://www.webpackjs.com/api/compiler-hooks/
https://www.webpackjs.com/api/compilation-hooks/
https://webpack.wuhaolin.cn/5原理/5-1工作原理概括.html
https://webpack.wuhaolin.cn/5原理/5-4编写Plugin.html