likes
comments
collection
share

彻底搞懂Webpack插件前言 首先我们先回忆一下Webpack插件是如何使用的?下面是一份基础的Webpack配置文件

作者站长头像
站长
· 阅读数 38

前言

首先我们先回忆一下Webpack插件是如何使用的?下面是一份基础的Webpack配置文件:

let htmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
 mode: 'development',
 entry: {
  main: path.join(__dirname, 'src/index.js')
 },
 output: {
  path: path.join(__dirname, 'dist'),
  filename: '[name].js'
 },
 plugins: [
  new htmlWebpackPlugin({
   template: path.join(__dirname, 'src/index.html')
  })
 ]
}

可以看到,插件都在plugins中,并且这是一个数组,包含了每一个插件的实例化调用,可以给这个实例传一些参数,因此不难看出来,Webpack插件就是一个类。

流程控制

而熟悉Webpack,你一定知道,插件是在整个构建的生命周期中指定的时机来进行一些逻辑操作的,比如你希望在构建开始时控制台打印时间,在构建产物生成时再打印一次时间;或者是希望在构建过程中进行一些特定操作,这就需要了解Webpack背后实现流程控制的原理。而Webpack则是基于tapable这个第三方库来实现的。

tapable是啥?怎么用?看一下这段代码:

xxx.tap等这类写法很符合tapable风格,我们再来看看看下面的代码:

let { SyncHook } = require('tapable');

class MyPlugin{
    constructor(){
        this.hooks = new SyncHook();
    }
    // 注册事件
    registryEvent(eventName, eventFn){
        this.hooks.tap(eventName, () => {
            eventFn();
        });
    }
    // 执行事件
    executeEvent(){
        this.hooks.call();
    }
}

let compiler = new MyHook();

compiler.registryEvent('事件1', () => {
    console.log('事件1执行了');
});

complier.registryEvent('事件2', () => {
    console.log('事件2执行了');
})

complier.executeEvent();

上述代码就是注册事件 -> 触发事件的一个流程,而回到Webpack中,Webpack也具有很多的流程阶段。

Webpack 的构建流程可以大致分为以下几个阶段:

  1. 初始化阶段:Webpack 准备编译环境。

  2. 编译阶段:Webpack 开始编译,读取配置和模块。

  3. 构建阶段:Webpack 处理模块依赖,生成依赖图谱。

  4. 输出阶段:Webpack 根据依赖图谱生成最终的输出文件。

  5. 完成阶段:Webpack 完成构建,输出结果。

在这些阶段中,Webpack 会触发多种钩子(Tapable 提供的 Hooks),插件可以通过这些钩子介入到构建流程中。 例如产出阶段,则是emit代表了构建产物产物前,afterEmit代表了构建产物产出后。Webpack基于Tapable实现了许多的钩子函数:

彻底搞懂Webpack插件前言 首先我们先回忆一下Webpack插件是如何使用的?下面是一份基础的Webpack配置文件

因此我们写插件,需要知道这段逻辑在Webpack哪个阶段来执行,因此插件就是套在Tapable hook里的一层逻辑。

就比如这是一个时间记录打印日志的插件,我在两个生命周期阶段打印了当前时间:

const { Compiler } = require('webpack');

class TimePlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    // 监听编译开始的钩子
    compiler.hooks.run.tapAsync('TimePlugin', (compilation, callback) => {
      console.log('编译开始时间:', new Date().toLocaleTimeString());
      callback();
    });

    // 监听编译完成的钩子
    compiler.hooks.done.tap('TimePlugin', (stats) => {
      console.log('编译结束时间:', new Date().toLocaleTimeString());
    });
  }
}

module.exports = TimePlugin;

在这个示例中,TimePlugin 类监听了 run 和 done 两个钩子。run 钩子在每次编译开始时触发,而 done 钩子在编译结束时触发。tapAsync 方法用于异步钩子,允许在回调中处理异步逻辑。

怎么使用呢?也很简单,在webpack.config.js中实例化下就好:

const TimePlugin = require('./TimePlugin');

module.exports = {
  // Webpack 配置...
  plugins: [
    new TimePlugin({ /* 插件选项 */ })
  ]
};

那为什么逻辑需要编写在apply方法中呢?看一下Webpack源码解析plugins的部分:

彻底搞懂Webpack插件前言 首先我们先回忆一下Webpack插件是如何使用的?下面是一份基础的Webpack配置文件

相当于做了一层约定,在编译阶段Webpack会去遍历所有的插件,并且调用apply方法,同时把Tapable实例传进去,这样我们就可以在插件中调用所有的生命周期钩子函数了。

看到这里,你已经很熟悉了吧。再看一个Webpack5的官方demo:

class FileListPlugin {
    constructor(fileName1){
        this.fileName = fileName1;
    }
    apply(compiler){
        let self = this;
        const { webpack } = compiler;

        // Compilation 对象提供了对一些有用常量的访问。
        const { Compilation } = webpack;

        // RawSource 是其中一种 “源码”("sources") 类型,
        // 用来在 compilation 中表示资源的源码
        const { RawSource } = webpack.sources;

        compiler.hooks.thisCompilation.tap('fileListDone', (compilation) => {
            compilation.hooks.processAssets.tap(
                {
                    name: self.fileName,
          
                    // 用某个靠后的资源处理阶段,
                    // 确保所有资源已被插件添加到 compilation
                    stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
                },
                (assets) => {
                    // "assets" 是一个包含 compilation 中所有资源(assets)的对象。
                    // 该对象的键是资源的路径,
                    // 值是文件的源码

                    // 生成 Markdown 文件的内容
                    const content = '# 这是一级标题';
            
                    // 向 compilation 添加新的资源,
                    // 这样 webpack 就会自动生成并输出到 output 目录
                    compilation.emitAsset(
                        self.fileName,
                        new RawSource(content)
                    );
                }
            )
        });

    }
}

module.exports = FileListPlugin;

代码中涉及到了2个生命周期,thisCompilation代表了Webpack开始编译的阶段;processAssets代表了所有的静态资源已经生成完毕。

这个demo的主要作用是在Webpack编译的靠后阶段,生成了一个markdown文件,同时输出到了output目录中。

结尾

看完希望你对于Wbepack插件机制的了解能更上一层楼,评论区欢迎一起讨论。

转载自:https://juejin.cn/post/7386571173608030227
评论
请登录