乐趣区

关于webpack:webpack篇插件plugin开发

开发 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)
  }
}
退出移动版