相比于 Loader
,Plugin
的能力范围要更宽一些。Loader
是在加载模块的环节去工作,而 Plugin
几乎触及到 webpack
工作的每一个环节。那么 Plugin
是怎么实现的呢?其实是利用了我们开发工程中常用的钩子机制去实现的。
webpack
为了能够更好的扩展,在整个打包过程的各个节点留下了很多预先定义好的钩子,这样插件 Plugin
就可以往这些节点上挂载不同的任务,可以轻松地去扩展 webpack
的能力。
webpack
要求 Plugin
是一个函数或者是一个包含 apply
方法的对象。一般会把这个 Plugin
定义为一个类型,然后在这个类型中定义一个 apply
方法。在使用中就是利用这个类型去构建一个实例,从而调用实例的 apply
方法去使用。
下面自定义一个类,类当中 apply
方法会在 webpack
启动加载执行插件时自动调用。apply
方法接收一个 compiler
对象参数,这个 compiler
对象是 webpack
在工作中最核心的对象。这个对象负责把控整个 webpack
打包的构建流程,我们也是通过这个对象去注册钩子函数。
接下来是要实现一个清除打包后 bundle
文件没用的注释的功能。
接下来就是要明确钩子执行的时机,以便于挂载我们自定义的任务。查看官网可以知道,webpack
的 emit
钩子会在即将往目录输出文件的时候执行。
通过 compiler
的 hooks
属性访问到 emit
钩子,通过 tap
方法去注册一个钩子函数。tap
方法接收两个参数,第一个参数是插件的名称,这里是 MyPlugin
;第二个参数是挂载到钩子上的函数,函数接收一个参数 compilation
,可以理解为此次打包的上下文,此次打包的结果都放在这个对象中。通过 compilation.assets
属性可以获取到此次打包要输出的所有资源文件信息,这里 compilation.assets
属性值是一个对象,对象的键就是每个文件的名称。
class MyPlugin {
apply (compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation -> 可以理解为此次打包的上下文
for (const name in compilation.assets) {
// console.log(name) // 文件名称
// console.log(compilation.assets[name].source()) // 文件对应的内容
// 匹配 js 文件,对文件中的无用注释进行替换
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source()
// 对文件内容进行正则替换
const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
// 替换后的文件内容覆盖原结果文件
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
通过判断处理 js
文件,获取文件的内容,以正则匹配的方式去替换掉代码中的注释。其中正则是以全局模式去替换,然后将替换完的结果覆盖到原有的结果当中。需要注意的是文件对象要求包含 source
方法以及 size
方法。
// webpack.config.js
class MyPlugin {
...
}
module.exports = {
...
...
plugins: [
new MyPlugin()
]
}
将 MyPlugin
实例化对象配置到 plugins
属性,然后运行打包命令 yarn build
,即可看到打包后文件的无用代码头注释被清除了。
webpack
插件是通过在 webpack
生命周期的钩子上挂载任务函数从而实现扩展的。