webpack插件没什么好说的,用过的都知道怎么配置,只是不知道内部怎么执行。今天学一学插件的一些机制,手写一个插件并不难。
之前介绍过了,webpack本质上是一种事件流机制,核心就是tapable,通过注册事件,触发回调,完成插件在不同生命周期的调用,内部也是通过大量的插件实现的。tapable内部暴露的方法挺多的,主要就是同步和异步,异步分为并行和串行。可以去GitHub上面看看:
https://github.com/webpack/tapable#tapable
这些方法都有用法示例,本来想写写使用方法,发现GitHub上面都有了,就不写了:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
在写插件之前,不得不提一下compiler和compilation:
compiler对象代表了完整的 webpack 环境配置,可以访问整个环境。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置(options、loader、plugin等)。使用插件时将收到此 compiler 对象的引用。
compilation 对象代表了一次资源版本构建。在运行过程中,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation提供了很多关键时机的回调供插件做自定义处理时使用。
使用插件就是new一个,所以插件其实就是一个类(构造函数或者class类),内部在prototype定义一个apply方法(会直接调用),并提供compiler,通过compiler提供的hooks注册事件和在相应的回调里面进行操作。而compiler提供的compilation的重要属性是assets,表示所有的静态资源。
关于提供的hook和参数,可以在webpack>lib>Compiler.js搜hooks,其实compiler和compilation都是继承tapable。
关于hooks分别表示什么阶段可以去官网查看:https://www.webpackjs.com/api/compiler-hooks/
还需要明确一下,tapable里面提供的几个hook,同步的(sync开头)注册是tap,异步的(async开头)有tap、tapAsync、tapPromise,后面两个提供了回调函数。
引入自己的插件:
const MyPlugin = require('./plugins/my-plugin.js')
plugins里面使用:
new MyPlugin({
name: 'wade plugin'
})
my-plugin.js里面:
class MyPlugin {
constructor(options) {
this.options = options;
}
apply(compiler){
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('MyPlugin ', this.options);
});
}
}
module.exports = MyPlugin;
apply提供了compiler,done是编译完成,同步的调用tap,第一个参数没什么意义,一般写自己插件名字,stats里面对象就多了,有 options、 outputOptions等,可以自己命令行那边看看。
异步的:
class MyPlugin {
constructor(options) {
this.options = options;
}
apply(compiler){
compiler.hooks.done.tapAsync('DonePlugin', (stats, callback) => {
console.log('Hello ', this.options.name);
setTimeout(() => {console.log(1);}, 1000);
setTimeout(() => {console.log(2);}, 2000);
setTimeout(() => {console.log(3);}, 3000);
setTimeout(() => {
callback();
}, 4000)
});
}
}
module.exports = MyPlugin;
可以看看控制台,看看效果。
一般自己写插件会在emit和afterEmit进行一些操作,这两个钩子的参数是compilation,里面有assets是静态资源,可以进行操作:
compiler.hooks.emit.tap('DonePlugin', (compilation) => {
let assets = compilation.assets;
console.log(assets);
});
比如我想给bundle.js添加一个字符串:
let content = assets['bundle.js'].source();
assets['bundle.js'] = {
source(){
return '"build by wade"\r\n' + content
},
size(){
return content.length;
}
}
打包结果:
比如创建一个文件:
let creatContent = '创建一个文件';
assets['creat.js'] = {
source(){
return creatContent
},
size(){
return creatContent.length
}
}
上面都是没什么意义的操作,只是想表达插件的一些方法,比如可以在文件生成之后进行压缩,或者自动化部署到服务器之类的插件。真正写一些有用的插件还是需要根据具体清空具体实现代码,可能还需要引入一些外部的插件,比如进行请求需要引入ajax或者axios,压缩要引入JSZip等。
(完)
Coding 个人笔记