Implementation of a simple Webpack Plugin

Record how to implement a simple Webpack Plugin

Webpack 内部执行流程

在实现一个Plugin之前,我们需要了解一下webpack 内部的大致执行流程


  1. ● 将命令行参数与 webpack 配置文件 合并、解析得到参数对象。
    ● 参数对象传给 webpack 执行得到 Compiler 对象。
    ● 执行 Compiler 的 run 方法开始编译。每次执行 run 编译都会生成一个 Compilation 对象。
    ● 触发 Compiler 的 make 方法分析入口文件,调用 compilation 的 buildModule 方法创建主模块对象。
    ● 生成入口文件 AST(抽象语法树),通过 AST 分析和递归加载依赖模块。
    ● 所有模块分析完成后,执行 compilation 的 seal 方法对每个 chunk 进行整理、优化、封装。
    ● 最后执行 Compiler 的 emitAssets 方法把生成的文件输出到 output 的目录中。

Plugin 作用

官方介绍: Plugins are the backbone of webpack. Webpack itself is built on the same plugin system that you use in your webpack configuration! (webpack plugin
按我的理解,Webpack 插件的作用就是在 webpack 运行到某个时刻的时候,帮我们做一些事情。
在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

实现一个Plugin

Plugin 组成

  • ● 一个 JS 命名函数或一个类(可以想下我们平时使用插件就是 new XXXPlugin()的方式)
    ● 在插件类/函数的 (prototype) 上定义一个 apply 方法。
    ● 通过 apply 函数中传入 compiler 并插入指定的事件钩子,在钩子回调中取到 compilation 对象
    ● 通过 compilation 处理 webpack 内部特定的实例数据
    ● 如果是插件是异步的,在插件的逻辑编写完后调用 webpack 提供的 callback

基本代码结构

以下两种写法均可:

javascript 复制代码
class CopyrightWebpackPlugin {
    constructor(options) {
        this.options = options || {};
    }
    apply(compiler) {}
}
module.exports = CopyrightWebpackPlugin;
javascript 复制代码
function CopyrightWebpackPlugin() {}

CopyrightWebpackPlugin.prototype.apply = function (compiler) {}

module.exports = CopyrightWebpackPlugin

webpack 在启动之后,在读取配置的过程中会先执行new CopyrightWebpackPlugin(options)操作,初始化一个CopyrightWebpackPlugin实例对象。在初始化 compiler 对象之后,会调用上述实例对象的apply方法并将compiler对象传入。
在apply方法中,通过compiler对象来监听 webpack 生命周期中广播出来的事件,我们也可以通过 compiler 对象来操作 webpack 的输出。

Compiler 和 Compilation

在插件开发中最重要的两个对象是 compilercompilation 对象。
compiler 对象代表了完整的 webpack 环境配置,在初始化 compiler 对象之后,通过调用插件实例的 apply 方法,作为其参数传入。这个对象在启动 webpack 时被一次性建立,并包含了 webpack 环境的所有的配置信息,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象会作为 plugin 内置事件回调函数的参数,一个 compilation 对象包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 compilation 将被创建。compilation 对象也提供了很多事件回调供插件做扩展。通过 compilation 也能读取到 compiler 对象

实现Copyright Plugin

新建文件src/plugins/copyright-webpack-plugin.js,下面代码为生成一个版权 txt 文件:

javascript 复制代码
class CopyrightWebpackPlugin {
    constructor(options) {
        this.options = options || {};
    }

    apply(compiler) {
        // emit 钩子是生成资源到 output 目录之前执行,emit 是一个异步串行钩子,需要用 tapAsync 来注册
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
            const copyright = this.options.copyright || 'Default Copyright';
            // compilation存放了这次打包的所有内容, 所有待生成的文件都在它的 assets 属性上
            // 添加copyright.txt
            compilation.assets['copyright.txt'] = {
                source: () => copyright,
                size: () => copyright.length,
            };
            callback();
        });
    }
}

module.exports = CopyrightWebpackPlugin;

webpack 中许多对象扩展自 Tapable 类。这个类暴露 tap, tapAsync 和 tapPromise 方法,可以使用这些方法,注入自定义的构建步骤,这些步骤将在整个编译过程中不同时机触发。
使用 tapAsync 方法来访问插件时,需要调用作为最后一个参数提供的回调函数。

使用Plugin

javascript 复制代码
const path = require('path');
const CopyrightWebpackPlugin = require('./src/plugins/copyright-webpack-plugin');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',
    },
    plugins: [new CopyrightWebpackPlugin({
        copyright: 'Copyright © slim'
    })]
}

配置完webpack config后执行 npx webpack就会发现dist文件夹下生成了copyright.txt文件。说明我们的plugin实现了它的功能。与loader参数传递类似。在plugin函数/类内部通过this.options可以获取到配置参数。