[路飞] 简单实现一个 clean-webpack-plugin

292 阅读1分钟

如何编写 webpack5 plugin

首先需要有一个带有 apply 方法的对象。

// 可以用实例,这样的好处是可以暴露出插件的配置对象。
module.exports = class myplugin {
  constructor(option) {}
  apply() {}
}

plugins: [
  new myplugin()
]

// 也可以直接返回对象
module.exports = {
  apply() {}
}

plugins: [
  myplugin
]

apply 方法会接收一个参数 compiler。来自于 webpack 的 Compiler。里面有解析的钩子 hooks,webpack 配置对象,fileSystem等。可以通过 hooks 在各个生命周期注册事件,修改对应的 compilation。如果是异步的话,除了 compilation 会接受第二个参数 callback。用来结束异步的等待,类似于 resolve。compilation 也有自己的 hooks,用同样的方法使用。

更多 hooks:compiler 钩子 | webpack 中文文档 (docschina.org)

结合 tapable 注册对应的事件的方法。主要有 3 种。

// 同步调用
hooks.xxx.tap("MyPlugin", (arg: Compiler) => {});
// 异步调用,类似于通过 callback 进行 resolve()
hooks.xxx.tapAsync("MyPlugin", (arg: Compiler, callback) => {});
// 返回 promise 的异步调用,自己 resolve() 外面应该是套了一个 await 的,或者 then
hooks.xxx.tapPromise("MyPlugin", (arg: Compiler):Promise<any> => {});

简单的尝试

写一个简单的 clean-webpack-plugin,功能在打包输出前清除 ouput 的目录。

const path = require('path')
const fs = require('fs')

module.exports = class myplugin {
  apply(compiler) {
    this.compiler = compiler
    const basePath = compiler.options.output.path
    if (this.isDirectory(basePath)) {
      const dirList = compiler.inputFileSystem.readdirSync(basePath)
      compiler.hooks.emit.tap('my-plugin', () => {
        // emit 是在输出到 output 之前执行的生命周期
        this.del(basePath, dirList)
      })
    } else {
      throw Error(`${basePath} 路径不存在`)
    }
  }

  del(basePath, dirList) {
    // 进行递归删除文件夹下面的内容
    dirList.forEach(name => {
      const p = path.join(basePath, name)
      if (this.isDirectory(p)) {
        const list = this.compiler.inputFileSystem.readdirSync(p)
        this.del(p, list)
        fs.rmdirSync(p)
      } else {
        fs.unlinkSync(p)
      }
    })
  }

  isDirectory(path) {
    const fileSystem = this.compiler.inputFileSystem
    try {
      return fileSystem.lstatSync(path).isDirectory()
    } catch {
      return false
    }
  }
}