开发webpack
插件需要知道的几个必要条件:
- 获取编译器 compiler 对象,通过这个对象能过获取包括config配置,资源文件,编译信息,钩子函数等信息
- 编译阶段的生命周期函数,找到适合的钩子函数处理对应逻辑
- 返回结果支持同步和异步两种方式
获取compiler
实例
第一步获取 compiler
实例对象:
// helloPlugin.js
module.exports = class RemoveLogs {
constructor(options){
this.options = options
}
apply(compiler) {
console.log(`Hello ${this.options.name}`)
}
};
引入这个脚本,在控制台执行就能看到编译结果了:
// webpack.config.js
var HelloWorldPlugin = require('./helloPlugin.js');
module.exports = {
// 配置插件
plugins: [new HelloWorldPlugin({ name:"chenwl" })]
};
生命周期钩子函数
通过官方文档 compiler-hooks 可以查看到compiler
提供的钩子函数,也可以直接到 /node_modules/webpack/lib/Compiler.js
查看
同步和异步方式
钩子函数可以同步也可以异步的方式处理:
module.exports = class SyncPlugin {
apply(compiler){
// tap 同步
compiler.hooks.emit.tap("tap", (compilation) => {
console.log("***** tap *****")
})
// tapAsync 参数cb未调用之前进程会暂停
compiler.hooks.emit.tapAsync("tapAsync", (compilation,cb) => {
start(0);
function start(index){
console.log(index);
if(index<=3){
setTimeout(() => {
start(++index);
}, 1000);
}else{
cb()
}
}
})
// tapPromise 通过promise的方式调用
compiler.hooks.emit.tapPromise("tapPromise", (compilation)=>{
return new Promise((resolve,reject)=>{
console.log("start tap-promise");
setTimeout(()=>{
resolve()
},2000)
})
})
}
}
logRemoverPlugin
文件编译完成后,去掉console
:
// logRemoverPlugin.js
const fs = require("fs");
module.exports = class RemoveLogs {
apply(compiler) {
compiler.hooks.done.tap("RemoveLogs", stats => {
const { path, filename } = stats.compilation.options.output;
try {
// 这里可以做匹配到 filename 才做处理
let filePath = path + "/" + filename;
fs.readFile(filePath, "utf8", (err, data) => {
const rgx = /console.log\(['|"](.*?)['|"]\)/;
const newdata = data.replace(rgx, "");
if (err) console.log(err);
fs.writeFile(filePath, newdata, function(err) {
if (err) {
return console.log(err)
}
console.log("Logs Removed");
});
});
} catch (error) {
console.log(error)
}
});
}
};
AnalyzePlugin
分析打包后的资源文件信息,并生成文件:
文件名 | 文件大小 |
---|---|
index.html | 1266 |
文件总数 1 个
// AnalyzePlugin.js
const { compilation } = require("webpack")
module.exports = class Analyze {
constructor(config){
// 获取打包文件名
this.filename = config.filename;
}
apply(compiler){
compiler.hooks.emit.tap("analyze-plugin",(compilation)=>{
const assets = compilation.assets;
const entries = Object.entries(assets);
const content = `| 文件名 | 文件大小 |
| ------------ | ------------ |
`
entries.forEach(([filename,fileObj])=>{
content+=`|${filename}|${fileObj.size()}|
`
});
content += `
> 文件总数 ${entries.length} 个`
// console.log(this.filename)
compilation.assets[this.filename] = {
source(){
return content
},
size(){
return content.length
}
}
})
}
}
inlinePlugin
将资源文件插入到html中
- 获取
head
标签组和body
标签组 link
标签转成style
标签,获取link属性链接的样式内容,插入到style
标签内部script
标签获取src属性链接的脚本内容,插入到script
标签内部
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = class InlinePlugin {
constructor(config) {
this.match = config.match; // 匹配需要转换的文件
this.compilation = null; // 保存 compilation
}
processTag(tag) {
if (!this.compilation) return;
// 获取文件链接
const url = tag.attributes.href || tag.attributes.src;
// 获取文件内容
const source = this.compilation.assets[url].source()
if (!this.match || !this.match.test(url)) return tag;
if (tag.tagName === "link") {
tag = {
tagName: "style",
innerHTML: source
}
}
if (tag.tagName === "script") {
tag = {
tagName: "script",
innerHTML: source
}
}
delete this.compilation.assets[url];
return tag
}
processTags(data) {
let headTags = data.headTags
let bodyTags = data.bodyTags
headTags = headTags.map((tag) => {
return this.processTag(tag)
})
bodyTags = bodyTags.map((tag) => {
return this.processTag(tag)
});
return {
headTags,
bodyTags,
}
}
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
"MyPlugin",
(data, cb) => {
// 保存 compilation
this.compilation = compilation;
cb(null, this.processTags(data))
}
)
})
}
}
编写将文件上传到七牛云的plugin
UploadQiNiuPlugin
首先要安装qiniu
云的依赖包
npm install qiniu
const path = require("path")
const qiniu = require("qiniu")
module.exports = class UploadQiNiuPlugin {
constructor(options) {
let { bucket = "", accessKey = "", secretKey = "" } = options
let mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
let putPolicy = new qiniu.rs.PutPolicy({ scope: bucket })
this.outputPath = ""
this.uploadToken = putPolicy.uploadToken(mac)
let config = new qiniu.conf.Config()
this.formUploader = new qiniu.form_up.FormUploader(config)
this.putExtra = new qiniu.form_up.PutExtra()
}
upload(filename) {
return new Promise((resolve, reject) => {
let realPath = path.join(this.outputPath, filename)
// 上传文件
this.formUploader.putFile(
this.uploadToken,
filename,
realPath,
this.putExtra,
(err, body) => {
err ? reject(err) : resolve(body)
}
)
})
}
apply(compiler) {
compiler.hooks.afterEmit.tapPromise("upload-plugin", (compilation) => {
this.outputPath = compiler.outputPath
let assets = compilation.assets
let promises = []
Object.keys(assets).forEach((filename) => {
promises.push(this.upload(filename))
})
return Promise.all(promises)
})
}
}
QiniuManager
上传之前,可能要先删掉七牛云旧的资源文件,这里也写个工具:
class QiniuManager {
constructor({ bucket, accessKey, secretKey }) {
let mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
let config = new qiniu.conf.Config()
this.bucketManager = new qiniu.rs.BucketManager(mac, config)
}
deleteFiles(filenames) {
let deleteFile = (filename) => {
return new Promise((resolve, reject) => {
this.bucketManager.delete(bucket, filename, (err) =>
err ? reject(err) : resolve(filename)
)
})
}
let deletePromises = filenames.map((f) => deleteFile(f))
return Promise.all(deletePromises)
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。