首页 > 其他分享 >Webpack插件核心原理

Webpack插件核心原理

时间:2022-11-08 14:33:08浏览次数:64  
标签:插件 Plugin 对象 compilation Hook Webpack 原理 compiler

引言

围绕 Webpack 打包流程中最核心的机制就是所谓的 Plugin 机制。

所谓插件即是 webpack 生态中最关键的部分, 它为社区用户提供了一种强有力的方式来直接触及 webpack 的编译过程(compilation process)。

今天,我们来聊聊 Webpack 中必不可少的核心 Plugin 机制 ~

Plugin

本质上在 Webpack 编译阶段会为各个编译对象初始化不同的 Hook ,开发者可以在自己编写的 Plugin 中监听到这些 Hook ,在打包的某个特定时间段触发对应 Hook 注入特定的逻辑从而实现自己的行为。

关于 Plugin 中的 Hook 内部完全是基于 tapable 来实现

Plugin 中的常用对象

首先让我们先来看看 Webpack 中哪些对象可以注册 Hook :

  • compiler Hook

  • compilation Hook

  • ContextModuleFactory Hook

  • JavascriptParser Hooks

  • NormalModuleFactory Hooks

别担心,也许对于这 5 个对象现在你会感觉到非常陌生,之后我会逐步带你攻克它们。

插件的基本构成

我们先来看这样一个最简单的插件,它会在 compilation(编译)完成时执行输出 done :

class DonePlugin {
  apply(compiler) {
    // 调用 Compiler Hook 注册额外逻辑
    compiler.hooks.done.tap('Plugin Done', () => {
      console.log('compilation done');
    });
  }
}

module.exports = DonePlugin;

此时,在 compilation 完成时打包终端会打印出来一行 compilation done

我们可以看到一个 Webpack Plugin 主要由以下几个方面组成:

  • 首先一个 Plugin 应该是一个 class,当然也可以是一个函数。

  • 其次 Plugin 的原型对象上应该存在一个 apply 方法,当 webpack 创建 compiler 对象时会调用各个插件实例上的 apply 方法并且传入 compiler 对象作为参数。

  • 同时需要指定一个绑定在 compiler 对象上的 Hook , 比如 compiler.hooks.done.tap 在传入的 compiler 对象上监听 done 事件。

  • 在 Hook 的回调中处理插件自身的逻辑,这里我们简单的做了 console.log。

  • 根据 Hook 的种类,在完成逻辑后通知 webpack 继续进行。

插件的构建对象

上边我们有提到过 Webpack Plugin 中哪些对应可以进行 Hook 注册,接下来我会带你深入这 5 个对象。

理解它们是理解并应用 Webpack Plugin 的重中之重。

compiler 对象

class DonePlugin {
  apply(compiler) {
    // 调用 Compiler Hook 注册额外逻辑
    compiler.hooks.done.tapAsync('Plugin Done', (stats, callback) => {
      console.log(compiler, 'compiler 对象');
    });
  }
}

module.exports = DonePlugin;

在 compiler 对象中保存着完整的 Webpack 环境配置,它通过 CLI 或 者 Node API传递的所有选项创建出一个 compilation 实例。

这个对象会在首次启动 Webpack 时创建,我们可以通过 compiler 对象上访问到 Webapck 的主环境配置,比如 loader 、 plugin 等等配置信息。

compiler 你可以认为它是一个单例,每次启动 webpack 构建时它都是一个独一无二,仅仅会创建一次的对象。

关于 compiler 对象存在以下几个主要属性:

  • 通过 compiler.options , 我们可以访问编译过程中 webpack 的完整配置信息。

在 compiler.options 对象中存储着本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。

  • 通过 compiler.inputFileSystem(获取文件相关 API 对象)、outputFileSystem(输出文件相关 API 对象) 可以帮助我们实现文件操作,你可以将它简单的理解为 Node Api 中的 fs 模块的拓展。

如果我们希望自定义插件的一些输入输出行为能够跟 webpack 尽量同步,那么最好使用 compiler 提供的这两个变量。

需要额外注意的是当 compiler 对象运行在 watch 模式通常是 devServer 下,outputFileSystem 会被重写成内存输出对象,换句话来说也就是在 watch 模式下 webpack 构建并非生成真正的文件而是保存在了内存中。

如果你的插件对于文件操作存在对应的逻辑,那么接下里请使用 compiler.inputFileSystem/outputFileSystem 更换掉代码中的 fs 吧。

  • 同时 compiler.hooks 中也保存了扩展了来自 tapable 的不同种类 Hook ,监听这些 Hook 从而可以在 compiler 生命周期中植入不同的逻辑。

关于 compiler 对象的属性你可以在 webpack/lib/Compiler.js中进行查看所有属性。

参考webpack视频讲解:进入学习

compilation 对象

class DonePlugin {
  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync(
      'Plugin Done',
      (compilation, callback) => {
        console.log(compilation, 'compilation 对象');
      }
    );
  }
}

module.exports = DonePlugin;

所谓 compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。

一个 compilation 对象会对构建依赖图中所有模块,进行编译。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

在 compilation 对象中我们可以获取/操作本次编译当前模块资源、编译生成资源、变化的文件以及被跟踪的状态信息,同样 compilation 也基于 tapable 拓展了不同时机的 Hook 回调。

简单来说比如在 devServer 下每次修改代码都会进行重新编译,此时你可以理解为每次构建都会创建一个新的 compilation 对象。

关于 compilation 对象存在以下几个主要属性:

  • modules

它的值是一个 Set 类型,关于 modules 。简单来说你可以认为一个文件就是一个模块,无论你使用 ESM 还是 Commonjs 编写你的文件。每一个文件都可以被理解成为一个独立的 module。

  • chunks

所谓 chunk 即是多个 modules 组成而来的一个代码块,当 Webapck 进行打包时会首先根据项目入口文件分析对应的依赖关系,将入口依赖的多个 modules 组合成为一个大的对象,这个对象即可被称为 chunk 。

所谓 chunks 当然是多个 chunk 组成的一个 Set 对象。

  • assets

assets 对象上记录了本次打包生成所有文件的结果。

  • hooks

同样在 compilation 对象中基于 tapable 提供给一系列的 Hook ,用于在 compilation 编译模块阶段进行逻辑添加以及修改。

在 Webpack 5 之后提供了一系列 compilation API 替代直接操作 moduels/chunks/assets 等属性,从而提供给开发者来操作对应 API 影响打包结果。

比如一些常见的输出文件工作,现在使用 compilation.emitAsset API 来替代直接操作 compilation.assets 对象。

ContextModuleFactory Hook

class DonePlugin {
  apply(compiler) {
    compiler.hooks.contextModuleFactory.tap(
      'Plugin',
      (contextModuleFactory) => {
        // 在 require.context 解析请求的目录之前调用该 Hook
        // 参数为需要解析的 Context 目录对象
        contextModuleFactory.hooks.beforeResolve.tapAsync(
          'Plugin',
          (data, callback) => {
            console.log(data, 'data');
            callback();
          }
        );
      }
    );
  }
}

module.exports = DonePlugin;

compiler.hooks 对象上同样存在一个 contextModuleFactory ,它同样是基于 tapable 进行衍生了一些列的 hook 。

contextModuleFactory 提供了一些列的 hook ,正如其名称那样它主要用来使用 Webpack 独有 API require.context 解析文件目录时候进行处理。

关于 ContextModuleFactory 系列的 Hook 不是特别常用

NormalModuleFactory Hook

class DonePlugin {
  apply(compiler) {
    compiler.hooks.normalModuleFactory.tap(
      'MyPlugin',
      (NormalModuleFactory) => {
        NormalModuleFactory.hooks.beforeResolve.tap(
          'MyPlugin',
          (resolveData) => {
            console.log(resolveData, 'resolveData');
            // 仅仅解析目录为./src/index.js 忽略其他引入的模块
            return resolveData.request === './src/index.js';
          }
        );
      }
    );
  }
}

module.exports = DonePlugin;

Webpack compiler 对象中通过 NormalModuleFactory 模块生成各类模块。

换句话来说,从入口文件开始,NormalModuleFactory 会分解每个模块请求,解析文件内容以查找进一步的请求,然后通过分解所有请求以及解析新的文件来爬取全部文件。在最后阶段,每个依赖项都会成为一个模块实例。

我们可以通过 NormalModuleFactory Hook 来注入 Plugin 逻辑从而控制 Webpack 中对于默认模块引用时的处理,比如 ESM、CJS 等模块引入前后时注入对应逻辑。

关于 NormalModuleFactory Hook 可以用于在 Plugin 中处理 Webpack 解析模块时注入特定的逻辑从而影影响打包时的模块引入内容

JavascriptParser Hook

const t = require('@babel/types');
const g = require('@babel/generator').default;
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');

class DonePlugin {
  apply(compiler) {
    // 解析模块时进入
    compiler.hooks.normalModuleFactory.tap('pluginA', (factory) => {
      // 当使用javascript/auto处理模块时会调用该hook
      const hook = factory.hooks.parser.for('javascript/auto');

      // 注册
      hook.tap('pluginA', (parser) => {
        parser.hooks.statementIf.tap('pluginA', (statementNode) => {
          const { code } = g(t.booleanLiteral(false));
          const dep = new ConstDependency(code, statementNode.test.range);
          dep.loc = statementNode.loc;
          parser.state.current.addDependency(dep);
          return statementNode;
        });
      });
    });
  }
}

module.exports = DonePlugin;

上边我们提到了 compiler.normalModuleFactory 钩子用于 Webpack 对于解析模块时候触发,而 JavascriptParser Hook 正是基于模块解析生成 AST 节点时注入的 Hook 。

webpack使用 Parser 对每个模块进行解析,我们可以在 Plugin 中注册 JavascriptParser Hook 在 Webpack 对于模块解析生成 AST 节点时添加额外的逻辑。

上述的 DonePlugin 会将模块中所有的 statementIf 节点的判断表达式修改称为 false 。

结尾

Webpack Plugin 的核心机制就是基于 tapable 产生的发布订阅者模式,在不同的周期触发不同的 Hook 从而影响最终的打包结果。

其实乍一看很多文章中很多概念,而且关于 Webpack 文档的确很多地方也没有进行完善的补充,但是回过头来仔细梳理一下。

你感觉到陌生的仅仅是文章中罗列出来的 API 而已,文章的目的并不是希望通过短短几千字你可以详细掌握 Webpack Plugin 的各种开发方式,而是在于让你对于 Plugin 机制和开发用法有一个简短的了解和概念。

之后我会在专栏中补充一些 Plugin 的实战开发,真正带大家领略开源插件项目中是如何在这些看似零碎的知识中化零为整,成为真正投身于业务之中的企业应用。

标签:插件,Plugin,对象,compilation,Hook,Webpack,原理,compiler
From: https://www.cnblogs.com/gogo2027/p/16869622.html

相关文章

  • 写过vue自定义指令吗,原理是什么?.m
    背景看了一些自定义指令的文章,但是探究其原理的文章却不多见,所以我决定水一篇。如何自定义指令?其实关于这个问题官方文档上已经有了很好的示例的,我们先来温故一下。除......
  • AI插件丨170+AI脚本插件合集,全新增强,功能更多
    AdobeIllustrator全新增强脚本插件合集,脚本更全,兼容性更强,更稳定!整合了AI脚本插件数量170+,包含了刀模线绘制、二维码生成、条码制作、角线绘制、置入多页面PDF、自动拼......
  • photoshop投影插件BBTools Shadow,轻松制作出各种物体的倒影
    BBToolsShadow(下载)是一款photoshop投影插件,轻松制作出各种物体的倒影,不管是文字、图层还是智能图层都可以直接添加倒影,非常真实。阴影太假?使用测试效果Photoshop......
  • vue源码分析-diff算法核心原理
    这一节,依然是深入剖析Vue源码系列,上几节内容介绍了VirtualDOM是Vue在渲染机制上做的优化,而渲染的核心在于数据变化时,如何高效的更新节点,这就是diff算法。由于源码中关于d......
  • React核心工作原理
    ##1.1、虚拟DOM常见问题:reactvirtualdom是什么?说一下diff算法?拿到一个问题,一般回答都是是什么?为什么?怎么办?那就按照这个思路来吧!what用JavaScript对象表示DOM......
  • 想会用synchronized锁,先掌握底层核心原理
    摘要:synchronized锁修饰方法和代码块时底层实现上是一样的,但是在修饰方法时,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时,是通过编译出来的字节码生成......
  • K8S-Calico 网络插件
    Calico是一个三层的虚拟网络解决方案,它把每个节点都当作虚拟路由器(vRouter),并把每个节点上的Pod都当作是“节点路由器”后的一个终端设备并为其分配一个IP地址。各节点......
  • 小程序开发vscode常用插件
    wechat-snippet微信小程序代码辅助,代码片段自动完成minapp微信小程序标签、属性的智能补全(同时支持原生小程序、mpvue和wepy框架,并提供snippets)wxapp-helper微信......
  • 网页翻译 iTranslator 浏览器插件
    iTranslator2.2.0浏览器插件版,免费的多功能网页翻译油猴脚本浏览器插件本文转自https://www.hezibuluo.com/159129.html,如有侵权,请联系删除。iTranslator是一款免......
  • webpack中配置CSS兼容性时报错 Failed to parse package.json data
      是因为在package.json中添加了注释正确webpack配置CSS兼容性的步骤:npmipostcss-loaderpostcss-preset-env-D/webpack.config.jsmodule:{    ru......