weekly
weekly copied to clipboard
2018/11/20 - webpack原理与实践(二):实现一个webpack插件
[TOC]
webpack原理与实践(二):实现一个webpack插件
关于 loader
和 plugin
-
webpack
本身只能处理js
文件。那如何处理如css
、内联图像
、html
等这些文件了呢。这就需要用loader
来进行转化。 - 通常
loader
功能比较单一,只专注于语言的转化。但是我们会有像压缩,分离文件这样的需求,这就需要通过插件来实现
实现一个插件
插件本身为一个构造函数,除了自己定义的方法外,会有一个 apply
方法 , apply
方法中传入全局唯一的 compiler
对象。
基本结构
class FileFilterPlugin {
constructor(options){
this.options = options;
}
apply(compiler) {
}
}
- 插件
options
是你在使用插件的时候new 一个插件时传入的配置。通常做法是你可以有一些默认的配置,通过Object.assign
来合并传入的配置和默认的配置,来得到最终的配置项。 - 第二个重点就是
apply
方法,该方法会传入一个compile
对象。compile
对象和compilation
对象是webpack
打包过程中最重要的两个对象。他们都继承自Tapable
关于,通过Tapable
注入钩子进行流程管理。compile
对象每一次编译全局唯一,并且包含了compilation
对象。一个compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。compilation
对象也提供了很多事件回调供插件做扩展。在开发模式下,每次热更新都会生成一个新的compilation
对象。
准备工作
前一篇文章中已经讲过 webpack
执行的一个流程了。我们需要知道插件应该挂载在执行过程中的哪个阶段,各个阶段都做了什么。这里就给大家贴几张总结的比较到位的图(来自《深入浅出webpack》):
然后你需要了解:
- 你需要做的操作对应挂载在
compile
的哪个钩子以; - 这个钩子的触发方式有哪些需要选择哪一种, 注册在这个钩子下的插件时如何执行的
- 下面这段代码截取
compile
对象的hooks
,挂载在这些钩子上的插件如何执行取决于 Tapable, 关于Tapable
的各种行为和原理, 仍然推荐这篇文章: 传送门
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
......
}
}
}
在插件apply
方法的回调中我们可以传入 compilation
对象. 如果是异步, 通常还会传入一个回调函数,对资源进行处理.
例子
下面会根据实际开发中的问题举一个完整的例子.
- 问题: 在开发小程序的
webpack
脚手架过程中我们遇到一个问题: 我们使用了scss
来编写我们的样式, 但是在输出的文件中我们需要的是wxss
文件. 首先我们通过file-loader
已经完成了css
文件到wxss
文件的转化,但是还有一个问题: 我们输出的文件中包含了scss
文件, 需要在输出的时候去掉这个文件.
首先是第一中解决方案.我们找到了一个暴露 webpack
钩子的插件: event-hooks-webpack-plugin
该插件通过传入调用的钩子名称和相应的回调函数,在回调函数中执行钩子对应阶段可以执行的操作.插件本身很强大,并且代码很简单,但是需要你了解webpack的原理才能使用,也就是有一定的门槛.我先把我们的用法粘贴出来:
new EventHooksPlugin({
emit: (compilation) => {
// compilation.chunks 存放所有代码块,是一个数组
compilation.chunks.forEach(function(chunk) {
// chunk 代表一个代码块
chunk.files.forEach(function(filename) {
// compilation.assets 存放当前所有即将输出的资源,是一个对象
let regex = /\.scss$/;
if (regex.test(filename)) {
delete compilation.assets[filename];
}
});
});
}
})
这里我们调用的钩子是 emit
从上文粘贴的对该钩子的介绍我们知道这是最后一个可以改变输出文件的钩子.传入的回调中我们对 scss
文件做了删除处理.到这里我们的目标实现了.但是同时也在思考: 本身这个插件使用时有门槛的,我们能不能自己写一个简单的插件来替代他,
让配置变得简单,不用了解webpack
内部的实现就能使用.于是我们自己写了一个插件:
module.exports = class FileFilerPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tap(' FileFilerPlugin', compilation => {
// compilation.chunks 存放所有代码块,是一个数组
compilation.chunks.forEach((chunk) => {
// chunk 代表一个代码块
chunk.files.forEach(function(filename) {
// compilation.assets 存放当前所有即将输出的资源,是一个对象
// let regex = /\.scss$/;
let regex = this.options.deleteFileReg
if (regex.test(filename)) {
delete compilation.assets[filename];
}
});
});
})
}
};
如上,我们只需要在配置中传入需要删除的文件的正则表达式就能实现目标:
new FileFilterPlugin({
deleteFileReg: /\.scss$/
}),
这个插件的实际上就是把我们第一个插件的配置转移到了插件源码中进行实现.但是针对这个场景的使用就变得简单了很多.
参考
- <<深入浅出webpack>>
-
官方文档(注意文档目前采用还是老版本插件的写法, 新版钩子都放到了
hooks
里面,大多数插件也对两种写法做了兼容)
广而告之
本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。
欢迎讨论,点个赞再走吧 。◕‿◕。 ~
有点简单了,继续