webpack系统学习(十五)如何编写一个plugin

Loader帮助我们去处理一类文件,Plugin又是什么呢?如果对webpack的基础知识不太熟悉,可以查看笔者的另一篇博客
plugin: 在我们打包的过程中,到了某一个特定节点,该插件生效。
比如说,我们使用HtmlWebpackPlugin,这个插件会在打包完成之后,简单的创建html文件,可用于服务器的访问。

webpack的plugin 采用的是发布订阅模式,在这个模式下,代码的执行是通过事件来驱动的。

构建一个简单的plugin

我们想在打包完成之后再向dist目录增加一个文件,里面包含了版权信息和一些打包信息。

前面我们学了loader,loader实际是就是一个导出为函数的JavaScript模块。plugin和loadedr不一样,它实际是一个类,就也是为什么我们在webpack.config.js中使用一个插件时需要new的原因。

./plugins目录下创建 info-webpack-plugin.js:

class InfoWebpackPlugin{
  constructor() {
  }

  apply(compiler){

  }
}

module.exports = InfoWebpackPlugin;

apply()就是该模块发挥作用是使用的方法。
然后在
webpack.config.js
中使用这个插件:

const path = require('path')
const InfoWebpackPlugin = require('./plugins/info-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  plugins:[
    new InfoWebpackPlugin()
  ],
  output: {
    path: path.resolve(__dirname,'dist'),
    filename: '[name].js'
  }
}

当然,这时候**InfoWebpackPlugin 没有任何作用。
我们修改一下
InfoWebpackPlugin **:

class InfoWebpackPlugin{
  constructor() {
    console.log('InfoWebpackPlugin is created!')
  }

  apply(compiler){

  }
}

module.exports = InfoWebpackPlugin;

然后打包,我们发现控制台出现了这样的内容:

InfoWebpackPlugin is created!
Hash: d23f266366dabf54e803
Version: webpack 4.46.0
Time: 74ms
...

现在,我们逐步来实现我们的plugin。
首先,我们需要知道,apply()方法在安装插件时,会被webpack compiler调用一次。apply接收一个webpack compiler对象的引用,所以我们可以使用complier对象。
那么什么是compiler

Tapable:
Tapable事实上就是一个事件管理器,类似于 NodeJS 的 EventEmitter 类,专注于自定义事件的触发和处理。
Compier:
Compiler 也是我们所说的 Tapable 实例,他就是webpack的整体环境。通过这种实现机制,我们可以理解为,它混合(mix)了 Tapable类,来使实例也具备注册和调用插件功能。
插件机制事实上就是通过注册在Complier上,在运行时Compier会根据各种事件钩子,从而触发插件的注册函数。
Compilation:
Compilation 实例继承于 compiler。例如,compiler.compilation 是对所有 require 图(graph)中对象的字面上的编译。这个对象可以访问所有的模块和它们的依赖(大部分是循环依赖)。在编译阶段,模块被加载,封闭,优化,分块,哈希和重建等等。这将是任何编译操作中,重要的生命周期。
官方文档看起来比较难理解,compilation 对象代表了一次单一的版本构建和生成资源。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,一次新的编译将被创建,从而生成一组新的编译资源。一个编译对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。编译对象也提供了很多关键点回调供插件做自定义处理时选择使用。

compilercompilation之间的区别?

  • compiler对象代表的是不变的webpack环境。
  • compilation 对象检测的是随时可变的项目文件,只要文件有改动,compilation就会重新创建。

根据所使用的 钩子(hook) 和 tap 方法,插件可以以多种不同的方式运行。这个工作方式与 Tapable 提供的 hooks 密切相关。compiler hooks 分别记录了 Tapable 内在的钩子,指出哪些 tap 方法可用。

因此,根据你触发到 tap 事件,插件可能会以不同的方式运行。例如,当钩入 compile 阶段时,只能使用同步的 tap 方法:

compiler.hooks.compile.tap('MyPlugin', params => {
  console.log('以同步方式触及 compile 钩子。');
})

然而,对于能够使用了 AsyncHook(异步钩子) 的 run,我们可以使用 tapAsync 或 tapPromise(以及 tap):

compiler.hooks.run.tapAsync('MyPlugin', (source, target, routesList, callback) => {
  console.log('以异步方式触及 run 钩子。');
  callback();
});

compiler.hooks有哪些钩子可以选择,以及该钩子是异步还是同步,我们都可以查看webpack的plugin api,里面有相关说明。
回到我们现在编写的plugin,我们需要在打包时生成一个文件到dist目录下,通过查找钩子,我们发现emit符合我们的期望。emit会在生成资源到 output 目录之前被驱动,又因为emit是异步钩子,所以,修改info-webpack-plugin.js:

class InfoWebpackPlugin{
  constructor() {
    console.log('InfoWebpackPlugin is created!')
  }

  apply(compiler){
    compiler.hooks.emit.tabAsync('InfoWebpackPlugin',(comilation,cb)=>{
      
    })
  }
}

module.exports = InfoWebpackPlugin;

这里compiler.hooks.emit.tabAsync(plugin,func)表示,当打包执行到emit阶段的时候,安装名为plugin的插件,此插件的效果是func的内容。

class InfoWebpackPlugin{
  apply(compiler){
    compiler.hooks.emit.tapAsync('InfoWebpackPlugin',(compilation,cb)=>{
      compilation.assets['info.txt'] = {
        source: function (){
          return 'copyright by Tang at ' + new Date().toLocaleString()
        },
        size: function() {
          return 40;
        }
      }
      cb()
    })
  }
}

module.exports = InfoWebpackPlugin;

这里comilation.assets['info.txt'] = {}向本次编译生成的资源中添加一个info.txt{}中的内容就是生成info.txt需要的一些配置。
最后调用cb()告诉compilation此插件已经安装完毕。
如果是同步钩子,就简单很多:

		compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
			console.log('compiler');
		})

现在我们重新打包,最后生成的文件:
在这里插入图片描述
info.txt的内容:

copyright by Tang at 2022-2-23 3:35:28F10: PM

注意,如果需要向plugin传入参数,可以直接使用构造函数constructor接收即可。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值