什么是webpack plugin
-
官方解释:
插件是 webpack 生态的关键部分, 它为社区用户提供了一种强有力的方式来直接触及 webpack 的编译过程 (compilation process)。插件能够到每一个编译(compilation)发出的关键事件中。 在编译的每个阶段中,插件都拥, 有对 `compiler` 对象的完全访问能力, 并且在合适的时机,还可以访问当前的 `compilation` 对象 -
理解: webpack 在整个打包的流程上,暴露了很多的事件,我们可以通过监听这些事件,实现一些我们自定义的功能,比如 打包输出前清空dist目录
CleanWebpackPlugin。使用html模板,并引用打包后的js 文件HtmlWebpackPlugin。 -
所以我们要想实现一个webpack plugin, 需要了解一些概念
- webpack的构建流程, 以便于我们在编写plugin时,在合适的时间,监听事件并做相关的处理
- webpack 提供了哪儿些钩子函数让我们便于监听和操作?
# compiler.hooks和compilation.hooks - compiler 和 compilation 是什么
# compiler 和 compilation 是什么?
-
compiler: 构建之初就已经创建,并且贯穿webpack整个生命周期
[ before - run - beforeCompiler - complie - make - finishMake - afterComplie - done ],即每次运行webpack(或运行npm run serve 和 npm run build等) 构建时实例,且只有一个。 -
compilation:
compilation是到准备编译模块时,才会创建compilation对象 是compile - make阶段主要使用的对象。
# compiler.hooks
-
实现继承自
tapablecompile 的 hooks 执行顺序是按照webpack 官网所列举,右上到下开始执行的。具体每个钩子的含义及使用,需自己测试和阅读理解
-
它有以下主要属性:
-
compiler.options 可以访问本次启动webpack 时候所有的配置文件,包括但不限于 loaders、entry、output、plugin 等等
-
compiler. inputF1lesystem 和 compiler.outputFilesystea 可以进行文件操作,相当于 Nodejs 中的 fs
-
compiler.hooks 可以注册 tapable 的不同种类 Hook,从而可以在compiler 生命周期中植入不同的逻辑。
compilation.hooks
-
实现继承自
tapablecompilation 的 hooks 的执行顺序是按照webpack 官网所列,右上到下开始执行的。
compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。
一个compilation 对家会对构建依赖图中所有模块进行编译, 在编译阶段,横块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
-
它有以下主耍属性:
-
compilation.modules 可以访问所有横块,打包的每一个文件都是一个模块。
-
compilation.chunks chunk 即是多个modules 组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。
-
compilation.assets 可以访问本次打包生成所有文件的结果。
-
compilation.hooks 可以注册 tapable 的不同种类 Hook,用于在compilation 编译模块阶段进行逻辑添加以及修改。
tapable 中的hooks
tapable 是 webpack 的一个核心工具,但也可用于其他地方, 以提供类似的插件接口。 在 webpack 中的许多对象都扩展自 Tapable 类。 它对外暴露了 tap,tapAsync 和 tapPromise 等方法, 插件可以使用这些方法向 webpack 中注入自定义构建的步骤,这些步骤将在构建过程中触发。
compile中对tapable 的引用如下
const {
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
compilation中对tapable 的引用如下
const {
HookMap,
SyncHook,
SyncBailHook,
SyncWaterfallHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncParallelHook
} = require("tapable");
当使用 syncHook 声明的钩子,则监听时使用 tap, 如: initialize compile 等
源码中的声明:
this.hooks = {
compile: new SyncHook(["params"]), // params 是回调接收的参数
run: new AsyncSeriesHook(["compiler"]), // compiler 是回调接收的参数
...
}
同步监听使用`tap`:
compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('以同步方式触及 compile 钩子。');
});
异步监听则可使用 tapAsync、 tapPromise 或 tap
- tapAsync: 多一个参数 callback
- tapPromise: return 一个promise
compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
console.log('以异步方式触及运行钩子。');
setTimeout(() => {
callback()
}, 2000);
}
);
compiler.hooks.run.tapPromise('MyPlugin', (compiler) => {
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('异步任务完成...');
resolve();
}, 1000);
});
});
所以当我们编写插件进行监听时,要记得对应使用 tap tapAsync tapPromise。
注意:当我们注册compilation 的钩子函数时,至少要在 make 阶段。如果是在 afterCompile之后注册,是不会执行的
关于各个钩子函数的意义,可百度自行搜索下。如:tapable1
webpack 的构建流程
参考链接:
编写插件的方法
我们在webpack中使用方式如下
const myPlugin = require('./pliugins/myPlugin')
module.exports = {
plugins: [
new myPlugin({
filename: 'myPlugin.md', // 传递的对象为constructor 中的 option
})
]
};
自定义编写一个插件 myPlugin.js
- 通过module.export 导出一个类
- 整个类必须有个函数 即
apply, 参数是compile - apply 内部就可以监听相关的参数是compile.hooks事件去做自定义的处理
class myPlugin {
constructor (options) {
// 获取插件配置项
this.filename = options && options.filename ? options.filename : 'FILELIST.md';
}
apply(compiler) {
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
// 通过 compilation.assets 获取文件数量
let len = Object.keys(compilation.assets).length;
// 添加统计信息
let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpack\n\n`;
// 通过 compilation.assets 获取文件名列表
for (let filename in compilation.assets) {
content += `- ${filename}\n`;
}
// 往 compilation.assets 中添加清单文件
compilation.assets[this.filename] = {
// 写入新文件的内容
source: function () {
return content;
},
// 新文件大小(给 webapck 输出展示用)
size: function () {
return content.length;
}
}
// 执行回调,让 webpack 继续执行
cb();
})
}
}
module.exports = myPlugin
结果: 生成一个 filelist.md 的统计信息
注意:
- plugin 不建议更改 源文件的内容信息,因为loader 本身可以帮我们实现类似的功能。
- 如果使用plugin 更改源文件的内容,官方建议是在 complition.hooks.seal 之前。
使用node 进行调试【非常有用奥】
-
首先在我们的开发环境中添加一个npm 命令 debug
"scripts": { "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js" }, -
在我们开发的插件中添加一个debug
-
运行命令 npm run debug 进入debug 状态
-
打开浏览器随便的一个tab页面,并打开控制台
-
在点击【绿色六边形】后即可进入调试界面。
-
然后即可进入调试,查看compiler 和 compilation。操作其属性进行plugin的编写