# 一、什么是插件(介绍)
- 插件介绍
- 扩展器,丰富 webpack 功能,针对 loader 结束后的打包过程
- 不是直接操作文件,基于事件机制。监听打包过程的某些节点。
- 与 loader 的区别 loader 是加载器,编译时转换文件。webpack 自身只支持 js、json 解析。其他文件需要借助 loader 将其转换为 cms 规范文件后,webpack 才能解析。
- 插件的特征
- 是独立的模块
- 对外暴露 js 文件
- 函数原型(prototype)定义并注入
complier对象的apply方法。
# tapable介绍
是一个小型的 lib,有点类似与 node 中的 event 库。基于事件机制。核心原理就是订阅发布模式,作用是提供插件接口
//广播
compiler.apply("eventName", params);
compilation.apply("eventName", params);
//监听
compiler.plugin("eventName", (params) => {});
compilation.plugin("eventName", (params) => {});
# 二、事件机制
# Tapable
function Tapable() {
this._plugins = {};
}
//发布
Tapable.prototype.ApplyPlugin = function ApplyPlugin(name) {};
//订阅
Tapable.prototype.plugin = function plugin(name, fn) {
if (this._plugin[name]) {
this._plugins[name] = [fn];
} else {
this._plugins[name].push(fn);
}
};
//给定一个插件数组,调用自身apply的注册插件
Tapable.prototype.apply = function () {
for (let i = -0; i < arguments.length; i++) {
arguments[i].apply.call(this);
}
};
Tapable 为 webpack 提供 10 多种(钩子)类型的定义。
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
还保留 3 个方法给插件,用于注册不同类型的自定义构建行为
//同步
tap 注册同步和异步钩子
//异步回调
tapAsync
//异步promise
tapPromise
webpack中几个重要对象compiler、compilation、JavascriptParser都继承tapable.身上挂满钩子。
# 编写一个插件
class MyPlugin {
constructor() {}
apply(compiler) {
compiler.hook.run.tap("run", () => {
console.log("run");
});
}
}
//使用
plugins:[new MyPlugin()]
# compiler对象(负责编译)
compiler包括当前webpack运行的配置信息。entry output loaders等。在webpack启动被实例化。
常用的钩子
| 钩子 | 类型 | 什么时候调用 |
|---|---|---|
| run | AsyncSeriesHook | 编译器开始读取记录 |
| compile | SyncHook | 新的compilaton创建之前执行 |
| compilation | SyncHook | 在一次compilation创建后执行 |
| make | AsyncParalleHook | 完成一次编译前执行 |
| emit | AsyncSeriesHook | 生成文件到output前,compilation |
| afferEmit | AsyncSeriesHook | 生成文件到output后 |
| assetEmitted | AsyncSeriesHook | 生成文件的时候执行 回调 file info |
| done | AsyncSeriesHook | 一次编译完成后,回调参数stats |
# compilation (表示一次构建)
compilation对象代表一次资源版本构建。简单来说就是将本次打包的内容存在内存里。
compilation对象也提供插件需要的自定义功能的回调。
简单来说,compilation的职责就是构建模块和chunk,并利用插件优化过程。
一个compilation表示当前的模块资源、编译生成资源、变化的文件、已经被跟踪依赖的状态信息。
compiler和compilation的区别
compiler:代表整个webpack从启动到关闭的生命周期
compilation:只是代表一次新的编译,只要有文件改动,compilation就会重新构建。
# 三、手写插件
输出时记录输出文件
class FileListPlugin {
constructor(options) {
// 获取插件配置项
this.filename = options && options.filename ? options.filename : 'FILELIST.md'
}
apply(compiler) {
// 注册 compiler 上的 emit 钩子
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 = FileListPlugin
去除注释
- 获取assets 拿到内容
- 重新修改assets (正则replace删除注释)
class RemoveCommentPlugin
{
constructor(options){this.options=options}
apply(compiler) {
const reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)|(\/\*\*\*\*\*\*\/)/g
compiler.hooks.emit.tap('removeComment',function(compilation,cb){
let assets = compilations.assets
for(let key in assets) {
let source = assets[key].source
source = source.replace(reg, function (word) {
// 去除注释后的文本
return /^\/{2,}/.test(word) || /^\/\*!/.test(word) || /^\/\*{3,}\//.test(word) ? '' : word
})
//重新修改内容
assets[key].source = source
}
})
}
}
module.exports = RemoveCommentPlugin