webpack plugin原理及实现一个简单插件

173 阅读3分钟

image.png

一、简介:

插件向第三方开发者提供了webpack引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到webpack构建流程中。创建插件比创建loader更加高级,因为你将需要理解一些webpack底层的内部特性来做相应的钩子

1.1 为什么需要一个插件

  • webpack基础配置无法满足需求
  • 插件几乎能够任意更改webpack编译结果
  • webpack内部也是通过大量内部插件实现的

1.2 可以加载插件的常用对象

对象钩子
Compilerrun, compile, compilation, make, emit, done
CompilationbuildModule, normalModuleLoader, succeedModule, finishModules, seal, optimize, after-seal

二、创建插件

webpack插件由以下组成:

  • 一个JavaScript命名函数
  • 在插件函数的prototype上定义一个apply方法
  • 指定一个绑定到webpack自身的事件钩子
  • 处理webpack内部实例的特定数据
  • 功能完成后调用webpack提供的回调

三、Compiler和Compilation

在插件开发中最重要的两个资源就是compiler和compilation对象。理解它们的角色是扩展webpack引擎重要的第一步。

  • compiler对象代表了完整的webpack环境配置。这个对象在启动webpack时被一次性建立,并配置好所有可操作的设置,包括options, loader和plugin。当在webpack环境中应用一个插件时,插件将收到此compiler对象的引用。可以使用它来访问webpack的主环境
  • compilation对象代表了一次资源版本构建。当运行webpack开发环境中间件时,每当检测到一个文件变化,就会创建一个新的compilation,从而生成一组新的编译资源。一个compilation对象表现了当前的模块资源,编译生成资源,变化的文件,以及被跟踪依赖的状态信息。compilation对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用

四、Tapable

webpack通过tapable实现自定义事件的触发和处理

4.1 同步钩子

const { SyncHook } = require('tapable');
const hook = new SyncHook(['name']);
hook.tap('hello', (name) => {
    console.log(`hello ${name}`);
});
hook.tap('hello again', (name) => {
    console.log(`hello ${name}, again`);
});

hook.call('jack');
// hello jack
// hello jack, again

每个事件独立执行 互不影响 按照注册顺序依次执行

4.2 异步钩子

const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);

hook.tapAsync('hello', (name, cb) => {
    setTimeout(() => {
        console.log(`hello ${name}`)
        cb()
    }, 2000)
})

hook.tapPromise('hello again', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`hello ${name}, again`);
      resolve();
    }, 1000);
  });
})

hook.callAsync('qqq', (name) => {
    console.log(name)
})
// hello qqq, again
// hello qqq
// done

callAsync 当所有异步事件执行完后在执行此回调

五、基本插件架构

  • 插件是由【具有apply方法的prototype对象】所实例化出来的
  • 这个apply方法在安装插件时,会被webpack compiler调用一次
  • apply方法可以接收一个webpack compiler对象的引用,从而可以在回到函数中访问到compiler对象

5.1 使用插件的代码

if(options.plugin && Array.isArray(options.plugins)) {
  for(const plugin of options.plugins) {
    plugin.apply(compiler)
  }
}

六、实现一个简单的插件

监测打包时间插件

①新建文件夹

npm init -y 生成package.json

npm init -y

安装webpack webpack-cli

npm i webpack webpack-cli

②新建webpack.config.js文件并初始化

const {resolve} = require('path')

module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js', // 入口文件
  output: {
    filename: 'built.js', // 打包文件
    path: resolve(__dirname,'build')
  },
  module: {
    rules: [
     
    ]
  },
  plugins: [

  ],
}

③新建src文件夹, 并新建index.js作为入口文件

image.png

④新建plugins文件夹,并新建donePlugin.js

class DonePlugin {
  constructor(options) {
    this.options = options
  }
  // compiler创建后, 会挂载所有的钩子 new DonePlugin().apply(compiler)
  apply(compiler) {
   // 蝙译开始时间
    console.time('time')
    // done代表打包完成
    compiler.hooks.done.tapAsync('DonePlugin', (stats, callback) => {
        // 蝙译结束时间
        console.timeEnd('time')
        callback()
    })
  }
}

module.exports = DonePlugin

⑤在webpack.config.js中使用插件

  ...
  plugins: [
    new DonePlugin()
  ],

⑥配置package.json打包命令

{
  "name": "webpack-lianxi",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

⑦执行npm run build

image.png

time就是本次打包的时间 这样一个监测打包时间的插件就做好了