开发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.html1266
文件总数 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)  }}