写一个webpack插件

webpack的工作机制是基于事件流,将各个插件串联起来,而核心就是tapable对象

核心概念:
  • tapable 对象,是webpackplugin创建hook的包,中文文档
  • 开发webpack插件最重要两个资源是对象:compiler和compilation,这两货都是扩展了tapable对象
    • Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
    • compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。

Compiler 和 Compilation 的区别在于:Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。

webpack 插件由以下组成:
  • 一个 JavaScript 命名函数。
  • 在插件函数的 prototype 上定义一个 apply 方法。
  • 指定一个绑定到 webpack 自身的事件钩子。
  • 处理 webpack 内部实例的特定数据。
  • 功能完成后调用 webpack 提供的回调。
    下面是一个简单的插件结构:

这里呢emitcomplier上的钩子,tap是为这个钩子注册监听函数,第一个参数MyExampleWebpackPlugin是监听器名称(自己随便定),第二个参数是回调会在钩子执行的时候被调用,回调传入的参数是compilation对象

// 定义一个class
class MyExampleWebpackPlugin {
  // 定义 apply方法,参数是 compiler
  apply(compiler) {
    // 附加一个指定的钩子
    compiler.hooks.emit.tap(
      'MyExampleWebpackPlugin',
      (compilation) => {
        // Manipulate the build using the plugin API provided by webpack
        compilation.addModule(/* ... */);
        compilation.hooks.buildModule.tap('MyExampleWebpackPlugin', () => {
        //do something...
      });
      }
    );
  }
}

在同步钩子中, tap是唯一的绑定方法,异步钩子通常支持异步插件

// promise: 绑定promise钩子的API
myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
    // return a promise
    return google.maps.findRoute(source, target).then(route => {
        routesList.add(route);
    });
});
// tapAsync:绑定异步钩子的API
compilation.hooks.additionalAssets.tapAsync('MyPlugin', callback => {
  download('https://img.shields.io/npm/v/webpack.svg', function(resp) {
    if(resp.status === 200) {
      compilation.assets['webpack-version.svg'] = toAsset(resp);
      callback();
    } else {
      callback(new Error('[webpack-example-plugin] Unable to download the image'));
    }
  });
});

tapable 的钩子类型:

每一个钩子都可以tap 一个或者多个函数, 他们如何运行,取决于他们的钩子类型

  • 基本的钩子, (钩子类名没有waterfall, Bail, 或者 Loop 的 ), 这个钩子只会简单的调用每个tap进去的函数
  • Waterfall, 一个waterfall 钩子,也会调用每个tap进去的函数,不同的是,他会从每一个函数传一个返回的值到
    下一个函数
  • Bail, Bail 钩子允许更早的退出,当任何一个tap进去的函数,返回任何值, bail类会停止执行其他的函数执行.(类似 Promise.race())
  • Loop, TODO(我.... 这里也没描述,应该是写文档得时候 还没想好这个要怎么写,我尝试看他代码去补全,不
    过可能需要点时间.)

此外,钩子可以是同步的,也可以是异步的,Sync, AsyncSeries 和 AsyncParallel ,从名字就可以看出,哪些是可以绑定异步函数的

  • Sync, 一个同步钩子只能tap同步函数(myHook.tap()), 不然会报错.
  • AsyncSeries, 一个 async-series 钩子 可以tap 同步钩子, 基于回调的钩子(我估计是类似chunk的东西)和一个基于promise的钩子(使用myHook.tap(), myHook.tapAsync() 和 myHook.tapPromise()).他会按顺序的调用每个方法.
  • AsyncParallel, 一个 async-parallel 钩子跟上面的 async-series 一样 不同的是他会把异步钩子并行执行(并行执行就是把异步钩子全部一起开启,不按顺序执行).
钩子总结:

tapable导出的钩子,根据名字分为同步和异步钩子
钩子注册方式:同步钩子只能使用 tap(),异步钩子使用 tapAsync() tapPromise()
钩子调用方式:同步钩子只能使用call(),异步钩子使用promise() callAsync()
测试效果请看这里

const {
    SyncHook, //同步执行多个函数;
    SyncBailHook,//同步执行多个函数,有return就退出;
    SyncWaterfallHook,//同步执行多个函数,下一个的函数的参数就是上一个的返回值;
    SyncLoopHook,//
    AsyncParallelHook,//并行执行多个异步函数;
    AsyncParallelBailHook,//无意义;都并行执行了,没有什么提前结束,webpack源码也没有用到
    AsyncSeriesHook,//串行执行多个异步函数;
    AsyncSeriesBailHook,//串行执行多个异步函数,callback有返回值就退出;
    AsyncSeriesWaterfallHook//串行执行多个异步函数,下一个的函数的参数就是上一个函数的callback返回值;
 } = require("tapable");
实战,编写一个webpack插件

class FileListPlugin{
  apply (compiler) {
    compiler.hooks.emit.tap('FileListPlugin', function(compilation) {
      // 在生成文件中,创建一个头部字符串:
      var filelist = 'In this build:\n\n';
  
      // 遍历所有编译过的资源文件,
      // 对于每个文件名称,都添加一行内容。
      for (var filename in compilation.assets) {
        filelist += ('- '+ filename +'\n');
      }
  
      // 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
      compilation.assets['filelist.md'] = {
        // 返回文件内容
        source: function() {
          //  既可以是代表文本文件的字符串,也可以是代表二进制文件的 Buffer
          return filelist;
        },
        // 返回文件大小
        size: function() {
          return filelist.length;
        }
      };
    });
  }
}
自定义钩子:
const SyncHook = require('tapable').SyncHook;

// 具有 `apply` 方法……
if (compiler.hooks.myCustomHook) throw new Error('Already in use');
compiler.hooks.myCustomHook = new SyncHook(['a', 'b', 'c'])
//为这个钩子注册监听函数
compiler.hooks.myCustomHook .tap("plugin-name",()=>{
//钩子 myCustomHook 被执行时会调用这个回调函数
}),
// 在你想要触发钩子的位置/时机下调用执行钩子,并传入3个参数
compiler.hooks.myCustomHook.call(a, b, c);

webpack插件调用顺序:

是按照plugins数组的顺序执行,而webpack chain插件的before、after方法是改变这个plugins数组的顺序,那么插件是可以指定再某个插件之前或之后执行,但是如果是异步钩子插件,无法指定在该插件执行完毕之后执行

你可能感兴趣的:(写一个webpack插件)