Webpack的Plugin看这一篇就够了

Plugin的概念

Webpack 插件Plugin是一个 JavaScript 对象,它可以通过 Webpack 的插件系统与编译过程进行交互。插件通过订阅特定的钩子(hooks)来执行自定义的逻辑,从而影响构建流程、修改资源、添加额外的功能等。

每个插件都需要实现一个 apply 方法,该方法接收一个 compiler 参数,代表当前的编译器对象。通过 compiler 对象,插件可以访问和操作编译过程中的各种数据和配置。

开发一个简单的Plugin

让我们从一个简单的示例开始,开发一个 Webpack 插件,它会在构建结束时输出一条提示信息。

1. 首先,创建一个名为 CustomPlugin 的文件,并编写如下代码:

js 复制代码
class CustomPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('CustomPlugin', () => {
      console.log('Build process completed!');
    });
  }
}

module.exports = CustomPlugin;

在这个示例中,我们创建了一个名为 CustomPlugin 的插件类,并实现了 apply 方法。在 apply 方法中,我们通过 compiler.hooks.done.tap 方法订阅了 done 钩子,并在构建完成时输出一条提示信息。

2. 接下来,在项目的 Webpack 配置文件中使用该插件。

假设 Webpack 配置文件为 webpack.config.js,我们可以进行如下配置:

js 复制代码
const CustomPlugin = require('./CustomPlugin');

module.exports = {
  // ...其他配置项
  plugins: [
    new CustomPlugin()
  ]
};

通过以上配置,我们将 CustomPlugin 实例添加到了 Webpack 配置的 plugins 数组中,使其成为一个生效的插件。

常见应用场景

Webpack 插件的应用场景非常广泛,可以根据具体需求编写各种功能丰富的插件。以下是一些常见的插件应用场景:

  1. 修改资源:通过订阅相应的钩子,插件可以访问和修改编译过程中的资源,例如替换资源内容、添加额外的打包文件等。
js 复制代码
class ModifyAssetPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('ModifyAssetPlugin', (compilation, callback) => {
      // 获取生成的资源列表
      const assets = compilation.assets;

      // 遍历资源列表并修改内容
      for (const assetName in assets) {
        if (assetName.endsWith('.js')) {
          const asset = assets[assetName];
          const modifiedSource = modifySource(asset.source());
          asset.source = () => modifiedSource;
        }
      }

      callback();
    });
  }
}

在这个示例中,ModifyAssetPlugin 插件订阅了 emit 钩子,并在构建完成时获取生成的资源列表。然后,它遍历资源列表并对 JavaScript 文件进行修改,通过调用 asset.source() 获取原始资源内容,然后对其进行修改,最后将修改后的内容赋值回 asset.source。

  1. 优化:插件可以实现各种优化策略,例如代码压缩、文件合并、图片优化等,以提升项目的性能和加载速度。
js 复制代码
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // ...其他配置项
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin()
    ]
  }
};

在这个示例中,我们使用了 TerserPlugin 插件来进行代码的压缩和混淆。通过将 TerserPlugin 实例添加到 Webpack 配置的 optimization.minimizer 数组中,可以启用代码压缩功能。

  1. 注入全局变量:插件可以向打包后的代码中注入全局变量,以便在应用程序中直接访问这些变量,例如将环境配置注入到代码中。
js 复制代码
const webpack = require('webpack');

module.exports = {
  // ...其他配置项
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
};

在这个示例中,我们使用了 webpack.DefinePlugin 插件来注入全局变量 process.env.NODE_ENV,并将其值设置为 'production'。通过这种方式,可以在打包后的代码中直接访问该全局变量。

  1. 资源管理:通过插件,可以将构建生成的文件进行复制、移动、删除等操作,以便更好地管理构建产物。
js 复制代码
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  // ...其他配置项
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        { from: 'src/assets', to: 'assets' }
      ]
    })
  ]
};

在这个示例中,我们使用了 CopyWebpackPlugin 插件来复制静态资源文件。通过配置 CopyWebpackPlugin 实例的 patterns 属性,我们可以指定要复制的文件来源和目标路径。

  1. 自定义模板:插件可以根据自定义的模板生成特定类型的文件,例如生成 HTML 文件、生成样式文件等。
js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // ...其他配置项
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html'
    })
  ]
};

在这个示例中,我们使用了 HtmlWebpackPlugin 插件来生成 HTML 文件。通过配置 HtmlWebpackPlugin 实例的 template 属性,我们可以指定 HTML 模板文件的路径,然后通过 filename 属性指定生成的 HTML 文件的名称。

这些示例代码只是展示了常见应用场景的一部分,使用的插件也只是其中的一些示例。还有其它应用场景,像生成Source Map文件,多语言国际化,提取CSS文件,清理输出目录等。

Plugin的触发时机

在 Webpack 的源码中,当调用 webpack 命令或使用 Node.js API 运行 Webpack 编译时,Webpack 会创建一个 Compiler 实例。Compiler 是一个编译器对象,它负责管理整个编译过程,并在适当的时机调用插件的 apply 方法。

Webpack 在启动编译过程时,会创建 Compiler 实例,并通过调用 Compiler 构造函数来初始化。在 Compiler 的构造函数中,会调用 this.hooks 方法来创建各个钩子(hooks),每个钩子都是一个 Hook 类型的实例。

javascript 复制代码
class Compiler {
  constructor() {
    // ...

 this.hooks = Object.freeze({
   /** @type {SyncHook<[]>} */
   initialize: new SyncHook([]),

   /** @type {SyncBailHook<[Compilation], boolean | undefined>} */
   shouldEmit: new SyncBailHook(["compilation"]),
   /** @type {AsyncSeriesHook<[Stats]>} */
   done: new AsyncSeriesHook(["stats"]),
   /** @type {AsyncSeriesHook<[Compiler]>} */
   run: new AsyncSeriesHook(["compiler"]),
   /** @type {AsyncSeriesHook<[Compilation]>} */
   emit: new AsyncSeriesHook(["compilation"])
      // ... 其他钩子
    })

    // ...
  }
}

Compiler 实例创建后,Webpack 会遍历安装的插件列表,并针对每个插件调用其 apply 方法。

javascript 复制代码
const createCompiler = rawOptions => {
  //...
 const compiler = new Compiler(options);
  // ...
 if (Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
   if (typeof plugin === "function") {
    plugin.call(compiler, compiler);
   } else if (plugin) {
    plugin.apply(compiler);
   }
  }
 }
 // ...
 return compiler;
};

总结起来,Webpack 在启动编译过程时创建了一个 Compiler 实例,并在适当的时机调用每个插件的 apply 方法,将 Compiler 实例传递给插件。这样,插件就可以通过订阅各个钩子来介入编译过程并执行自定义的逻辑。

Plugin中的事件钩子

  • 每个钩子都是一个 Hook 类型的实例,Hook 内部完全是基于 tapable 来实现
  • 钩子的定义通常接收两个参数:事件名和回调函数。事件名用于标识特定的钩子,而回调函数则是在触发该钩子时执行的代码。
  • 在 Webpack 中,大多数钩子的回调函数接收一个参数,通常被命名为 compilation,它表示当前的编译实例。compilation 对象包含了与当前编译相关的信息、资源和依赖关系等。

compiler 对象提供了各种钩子(hooks)来让插件在构建过程的不同阶段执行自定义逻辑。以下是一些常见的使用场景和对应的钩子:

  1. 初始化阶段(Initialization Phase):

    • entryOption:在解析入口模块之前调用,可以修改 Webpack 配置的入口选项。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
          // 修改入口配置
          // ...
        });
      }
    }
    ```
  2. 环境准备阶段(Environment Setup Phase):

    • afterEnvironment:在 Webpack 环境准备好之后调用,可以扩展环境或添加全局变量。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.afterEnvironment.tap('MyPlugin', () => {
          // 扩展环境或添加全局变量
          // ...
        });
      }
    }
    ```
  3. 编译阶段(Compilation Phase):

    • make:在创建新的编译实例之前调用,可以创建自定义的编译实例。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.make.tap('MyPlugin', (compilation) => {
          // 创建自定义的编译实例
          // ...
        });
      }
    }
    ```
  4. 编译过程阶段(Compilation Process Phase):

    • compilation:在每次创建新的编译实例时调用,可以访问和修改编译过程中的资源和依赖关系。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
          // 访问和修改编译过程中的资源和依赖关系
          // ...
        });
      }
    }
    ```
  5. 构建完成阶段(Build Completion Phase):

    • done:在构建完成后调用,可以获取和处理构建结果的统计信息。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.done.tap('MyPlugin', (stats) => {
          // 获取和处理构建结果的统计信息
          // ...
        });
      }
    }
    ```
  6. 解析阶段(Resolve Phase):

    • beforeResolve:在解析模块路径之前调用,可以修改模块的解析规则或路径。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.beforeResolve.tap('MyPlugin', (resolveData) => {
          // 修改模块的解析规则或路径
          // ...
        });
      }
    }
    ```
  7. 优化阶段(Optimization Phase):

    • optimize:在优化阶段开始之前调用,可以自定义优化逻辑。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.optimize.tap('MyPlugin', (compilation) => {
          // 自定义优化逻辑
          // ...
        });
      }
    }
    ```
  8. 生成资源阶段(Asset Generation Phase):

    • emit:在生成最终资源之前调用,可以访问和修改最终生成的资源。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.emit.tap('MyPlugin', (compilation) => {
          // 访问和修改最终生成的资源
          // ...
        });
      }
    }
    ```
  9. 清理阶段(Cleanup Phase):

    • afterEmit:在生成最终资源之后调用,可以执行一些清理操作或触发其他任务。
    javascript 复制代码
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.afterEmit.tap('MyPlugin', (compilation) => {
          // 执行清理操作或触发其他任务
          // ...
        });
      }
    }
    ```

上面演示了一些场景下钩子对构建过程的不同阶段更精细的控制和处理能力的支持。根据具体的需求和业务逻辑,可以选择适合的钩子并在插件中编写相应的逻辑代码。

写在最后

通过开发自己的插件,可以根据项目需求添加自定义逻辑、优化构建过程,并实现更高效的前端开发流程。希望读到本文的都有所收获,共勉~

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui