背景
接到一个关于 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;
使用的时候:
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()
})
参考
www.webpackjs.com/api/compile…
www.webpackjs.com/api/compila…
webpack.wuhaolin.cn/5%E5%8E%9F%…
webpack.wuhaolin.cn/5%E5%8E%9F%…
