开发 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)
}
}