开发webpack
插件须要晓得的几个必要条件:
- 获取编译器 compiler 对象,通过这个对象能过获取包含config配置,资源文件,编译信息,钩子函数等信息
- 编译阶段的生命周期函数,找到适宜的钩子函数解决对应逻辑
- 返回后果反对同步和异步两种形式
获取compiler
实例
第一步获取 compiler
实例对象:
// helloPlugin.jsmodule.exports = class RemoveLogs { constructor(options){ this.options = options } apply(compiler) { console.log(`Hello ${this.options.name}`) }};
引入这个脚本,在控制台执行就能看到编译后果了:
// webpack.config.jsvar 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.jsconst 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.jsconst { 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) }}