写点什么

Webpack 中的 plugin 插件机制

作者:Geek_02d948
  • 2022-11-07
    浙江
  • 本文字数:3101 字

    阅读完需:约 10 分钟

大家有没有遇到过这些问题:


  • webpack 打包之后的文件没有压缩

  • 静态文件要手动拷贝到输出目录

  • 代码中写了很多环境判断的多余代码


上一篇 「webpack 核心特性」loader 说到 webpack 的 loader 机制,本文主要聊一聊另外一个核心特性:插件(plugin)。


插件机制就是为了完成项目中除了资源模块打包以外的其他自动化工作,解决上述的问题。


plugin 是用来扩展 webpack 功能的,通过在构建流程里注入钩子实现,它为 webpack 带来了很大的灵活性。

plugin 相对于 loader 有哪些区别?

loader 是转换器,将一种文件编译转换为另一个文件,操作的是文件。例如:将 .less 文件转换成 .css 文件。


plugin 是扩展器,它是针对 loader 结束之后,打包的整个过程。它并不直接操作文件,而是基于事件机制工作。在 webpack 构建流程中的特定时机会广播对应的事件,插件可以监听这些事件的发生,在特定的时机做对应的事情。包括:打包优化,资源管理,注入环境变量。

plugin 该怎么配置呢?

例如 HtmlWebpackPlugin 可以为我们生成一个 HTML 文件,其中包括使用 script 标签的 body 中的所有模块。看下如何配置:


const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpackConfig = { ... plugins: [new HtmlWebpackPlugin()]};
复制代码


温馨提示:loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个对象,内部包含了 test(类型文件)、loader、options(参数)等属性。plugin 则单独配置,类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。

有哪些常见的 plugin?

下面整理的插件列表来自 webpack 中文官网,大家看见不熟悉的 plugin 可以点击名称跳转,看一看,了解一下具体玩法。


怎么写一个 plugin?

在说怎么写插件之前,先简单介绍几个好玩的东西:tapablecompilercompilation。##### 参考 webpack 视频讲解:进入学习

tapable

tapable 是一个类似于 nodejsEventEmitter 的库, 主要是控制钩子函数的发布与订阅。当然,tapable 提供的 hook 机制比较全面,分为同步和异步两个大类(异步中又区分异步并行和异步串行),而根据事件执行的终止条件的不同,由衍生出 Bail / Waterfall / Loop 类型。


基本使用:


const { SyncHook } = require('tapable');const hook = new SyncHook(['name']);hook.tap('hello', (name) => {  console.log(`hello ${name}`);});hook.tap('hi', (name) => {  console.log(`hi ${name}`);});
hook.call('july');
// hello july// hi july
复制代码


小结:


tapable 基本逻辑是,先通过类实例的 tap 方法注册对应 hook 的处理函数,然后通过 call 方法触发回调函数。

compiler

compiler 对象包含 webpack 环境所有配置信息,包含 options、loaders 和 plugins 等信息。可以简单理解为 webpack 实例。代表整个 webpack 从启动到关闭的生命周期


compile 的内部实现:


class Compiler extends Tapable {  constructor(context) {    super();    this.hooks = {      /** @type {SyncBailHook<Compilation>} */      shouldEmit: new SyncBailHook(["compilation"]),      /** @type {AsyncSeriesHook<Stats>} */      done: new AsyncSeriesHook(["stats"]),      /** @type {AsyncSeriesHook<>} */      additionalPass: new AsyncSeriesHook([]),      /** @type {AsyncSeriesHook<Compiler>} */      ...    };    ...}
复制代码


小结:


compile 继承了 tapable ,然后在实例上绑定了一个 hook 对象。

compilation

compilation 对象包含了当前的模块资源、编译生成资源和变化的文件等。仅代表一次新的编译


compilation 的实现:


class Compilation extends Tapable {  /**   * Creates an instance of Compilation.   * @param {Compiler} compiler the compiler which created the compilation   */  constructor(compiler) {    super();    this.hooks = {      /** @type {SyncHook<Module>} */      buildModule: new SyncHook(["module"]),      /** @type {SyncHook<Module>} */      rebuildModule: new SyncHook(["module"]),      /** @type {SyncHook<Module, Error>} */      failedModule: new SyncHook(["module", "error"]),      /** @type {SyncHook<Module>} */      succeedModule: new SyncHook(["module"]),      /** @type {SyncHook<Dependency, string>} */      addEntry: new SyncHook(["entry", "name"]),      /** @type {SyncHook<Dependency, string, Error>} */    };  }}
复制代码


当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

热身

写一个最基础的 plugin


// 一个 JavaScript 命名函数。function SimplePlugin() {}
// 在插件函数的 prototype 上定义一个 `apply` 方法。SimplePlugin.prototype.apply = function (compiler) { // 指定一个挂载到 webpack 自身的事件钩子。 compiler.plugin("webpacksEventHook", function ( compilation /* 处理 webpack 内部实例的特定数据。*/, callback ) { console.log("This is an simple plugin!!!");
// 功能完成后调用 webpack 提供的回调。 callback(); });};
复制代码


webpack 启动后,做了下面几件事情:


  • 在读取配置的过程中先执行 new SimplePlugin(),初始化一个 SimplePlugin 并获得其实例。

  • 在初始化 compiler 对象后,再调用 SimplePlugin.apply(compiler) 为插件实例传入 compiler 对象。

  • 插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin("webpacksEventHook", function(compilation, callback){}) 监听到 webpack 广播的事件,并且通过 compiler 对象去操作 webpack。

实战

下面写一个实用的插件。


作用是在 webpack 马上关闭时做一些事情。例如告知用打包完成,是否执行成功。或者执行一些 script 脚本。我们将其命名为 AfterWebpackPlugin 。用法如下:


module.exports = {  plugins: [    // 分别传入成功和失败时的回调函数    new AfterWebpackPlugin(      () => {        // 可以通知用户构建成功,执行发布脚本      },      (err) => {        // 构建失败时,抛出异常        console.error(err);      }    ),  ],};
复制代码


这里实现过程需要借助以下两个钩子:


  • done:在成功构建并且输出文件后,webpack 马上退出时发生。

  • failed:在构建异常时导致构建失败,webpack 马上退出时发生。


实现代码如下:


class AfterWebpackPlugin {  constructor(doneCb, failedCb) {    // 传入回调函数    this.doneCb = doneCb;    this.failedCb = failedCb;  }
apply(compiler) { compiler.plugin("done", (res) => { // webpack 生命周期 `done` 中的回调 this.doneCb(res); }); compiler.plugin("failed", (err) => { // webpack 生命周期 `failed` 中的回调 this.failedCb(err); }); }}
module.exports = AfterWebpackPlugin;
复制代码


开发插件小结:


  • 注意在 webpack 生命周期中找到合适的钩子去完成功能。

  • 注意理解 webpack 生命周期中各个钩子的细微区别。

拓展

webpack 输出阶段会发生的事件及解释如下:


系列文章

  • 「webpack 核心特性」loader

  • 「webpack 核心特性」插件(plugin)

  • 「webpack 核心特性」模块热替换(HMR)

感谢

  • 如果本文对你有帮助,就点个赞支持下吧!感谢阅读。


用户头像

Geek_02d948

关注

还未添加个人签名 2022-09-08 加入

还未添加个人简介

评论

发布
暂无评论
Webpack中的plugin插件机制_webpack_Geek_02d948_InfoQ写作社区