# 编写一个plugin

# 插件结构

插件是伴随webpack构建的初始化到最后文件生成的整个生命周期,插件的目的是在于解决loader无法实现的其他事情。
另外,插件没有像loader那样的独立运行环境,所以插件的开发只能在webpack里面运行。

插件的基本结构:

// 提供一个class类
class Plugin{
    // 接受插件传递的参数
    constructor(options){
        ...
    }
    // 插件上提供apply方法,webpack提供compiler对象
    apply(compiler){
        // 指定一个挂载到 webpack 自身的事件钩子
        // compiler提供hooks钩子方法,作用于构建流程中
        compiler.hooks.done.tap('Plugin',
        // 功能完成后调用 webpack 提供的回调
        // 插件处理逻辑
        ()=>{
            ...
        })
    }
}
module.exports = Plugin

至此,了解到webpack插件的组成部分:

  • 提供一个class类函数
  • 插件上提供apply方法,接受compiler参数。compiler 对象代表了完整的 webpack 环境配置。 这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。
  • 指定一个挂载到 webpack 自身的事件钩子,使用 compiler 对象时,可以绑定提供了编译 compilation 引用的回调函数, 一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。
  • 功能完成后调用 webpack 提供的回调

# 插件事件处理

# 插件中获取传递的参数

通过插件的构造函数进行获取。

class Plugin{
    // 接受插件传递的参数
    constructor(options){
        this.options = options
    }
}

# 异步插件

有一些编译插件中的步骤是异步的,这样就需要额外传入一个 callback 回调函数,并且在插件运行结束时,必须调用这个回调函数。

# 插件的错误处理

  • 参数校验阶段可以直接throw的方式将错误抛出
throw new Error('error options')
  • 如果已经进入到hooks钩子阶段,可以通过compilation对象的warnings和errors接收
compilation.warnings.push('warnings')
compilation.errors.push('error')

# 对资源文件处理:通过Compilation进行文件写入

对于一些插件,本身需要产生一些静态资源。比如生成zip包,之前源码浏览时候有了解到,文件生成阶段 是在emit阶段,则我们可以监听compiler的emit钩子阶段,获取到compilation对象,并将最终的内容 设置到compilation.assets对象上输出到磁盘目录。
另外,文件的写入,一般需要借助webpack-sources

const { RawSource } = require('webpack-sources')

module.exports = class Plugin {
    constructor(options){
        this.options = options
    }
    apply(compiler){
        compiler.plugin("emit", function(compilation, callback) {
            compilation.assets['name'] = new RawSource('demo')
            callback()
        });
    }
}

# 插件扩展:编写插件的插件

插件自身也可以通过暴露hooks的方式进行自身扩展,例如html-webpack-plugin就暴露了 html-webpack-plugin-after-chunk(Sync)、html-webpack-plugin-before-html-generation(Async)等钩子。

# 插件实战

编写一个压缩构建资源为zip包的插件。其中,可实现根据参数生成zip包的文件名称。

实现思路:

  • 使用jszip实现zip包生成。
  • 使用compiler对象的emit钩子,compiler.hooks.emit对tapAsync方法实现监听,在emit 生成文件阶段,将zip包设置到compilation.assets对象上。
//zip-webpack-plugin
const path = require('path')
const JSZip = require('jszip')
const { RawSource } = require('webpack-sources')
const zip = new JSZip()

class MinZipWebpackPlugin{
    constructor(options){
        this.options = options
    }
    apply(compiler){
        compiler.hooks.emit.tapAsync('MinZipWebpackPlugin', (compilation,callback)=>{
            // zip.folder,创建目录名称
            const folder = zip.folder(this.options.filename);
            // 遍历compilation.assets对象
            for(let filename in compilation.assets){
                // 获取source
                const source = compilation.assets([filename]).source()
                // 将source添加到folder中
                folder.file(filename, source)
            }
            // 将内容生成zip
            zip.generateAsync({
                type: 'nodebuffer'
            }).then((content)=>{
                // 获取output(绝对路径)
                const outputPath = path.join(
                    compilation.options.output.path,
                    this.options.filename + '.zip'
                )
                const outputRelativePath = path.relative(
                    compilation.options.output.path,
                    outputPath
                )
                // 将内容挂载到compilation.assets上,并将buffer转换为source
                compilation.assets[outputRelativePath] = new RawSource(content)
                callback()
            })
        })
    }
}

module.exports = MinZipWebpackPlugin

线上代码:https://github.com/PCAaron/min-zip-webpack-plugin,对你有帮助的话就给个star吧~

# 推荐阅读

编写一个插件:https://www.webpackjs.com/contribute/writing-a-plugin/