1

创建一个 plugin

webpack 的插件组成有以下这几部分:

  1. 一个 javascript function 或者一个 class
  2. prototype 上定义 apply 方法。
  3. 指定要插入的事件挂钩。
  4. 使用webpack提供的插件API操作构建。
  5. 在功能完成后调用webpack提供的回调函数。
// A JavaScript class.
class MyExampleWebpackPlugin {
  // Define `apply` as its prototype method which is supplied with compiler as its argument
  apply(compiler) {
    // Specify the event hook to attach to
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        console.log('This is an example plugin!');
        console.log('Here’s the `compilation` object which represents a single build of assets:', compilation);

        // Manipulate the build using the plugin API provided by webpack
        compilation.addModule(/* ... */);

        callback();
      }
    );
  }
}

基本的插件架构

plugin 是在其prototype 上使用 apply 方法实例化的对象。这个 apply 方法在安装插件时由 webpack 编译器调用一次。apply 方法被赋予对底层 webpack 编译器的引用,该引用授予对编译器回调的访问权。一个简单的插件结构如下:

class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('Hello World Plugin', (
      stats /* stats is passed as argument when done hook is tapped.  */
    ) => {
      console.log('Hello World!');
    });
  }
}

module.exports = HelloWorldPlugin;

Compiler and Compilation

开发插件时最重要的两种资源是Compiler 和 Compilation。

class HelloCompilationPlugin {
  apply(compiler) {
    // Tap into compilation hook which gives compilation as argument to the callback function
    compiler.hooks.compilation.tap('HelloCompilationPlugin', compilation => {
      // Now we can tap into various hooks available through compilation
      compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
        console.log('Assets are being optimized.');
      });
    });
  }
}

module.exports = HelloCompilationPlugin;

Async event hooks 异步事件钩子

有些插件挂钩是异步的。为了利用它们,我们可以使用 tap 方法,它将以同步的方式运行,或者使用tapAsync方法或tapprogre方法之一,这是异步方法。

tapAsync

当我们使用tapAsync方法访问插件时,我们需要调用回调函数,它是函数的最后一个参数。

class HelloAsyncPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('HelloAsyncPlugin', (compilation, callback) => {
      // Do something async...
      setTimeout(function() {
        console.log('Done with async work...');
        callback();
      }, 1000);
    });
  }
}

module.exports = HelloAsyncPlugin;

tapPromise

当我们使用 tapPromise 方法定义插件时,我们需要返回一个 promise,这个 promise 将在异步任务完成时 resolve。

class HelloAsyncPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapPromise('HelloAsyncPlugin', compilation => {
      // return a Promise that resolves when we are done...
      return new Promise((resolve, reject) => {
        setTimeout(function() {
          console.log('Done with async work...');
          resolve();
        }, 1000);
      });
    });
  }
}

module.exports = HelloAsyncPlugin;

Example

一旦我们能够使用 webpack 编译器和每个单独的编译器,我们就可以对引擎本身进行无穷无尽的操作。我们可以重新格式化现有文件,创建派生文件,或者创建全新的资源。

让我们编写一个简单的示例插件,它生成一个名为 fillist .md 的新构建文件;其中的内容将列出构建中的所有资源文件。这个插件可能看起来像这样:

class FileListPlugin {
  apply(compiler) {
    // emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
      // Create a header string for the generated file:
      var filelist = 'In this build:\n\n';

      // Loop through all compiled assets,
      // adding a new line item for each filename.
      for (var filename in compilation.assets) {
        filelist += '- ' + filename + '\n';
      }

      // Insert this list into the webpack build as a new file asset:
      compilation.assets['filelist.md'] = {
        source: function() {
          return filelist;
        },
        size: function() {
          return filelist.length;
        }
      };

      callback();
    });
  }
}

module.exports = FileListPlugin;

Different Plugin Shapes

插件可以根据它所访问的事件挂钩被划分为不同的类型。每个事件钩子都预先定义为同步(synchronous)或异步(asynchronous)、瀑布式或并行钩子,并且钩子在内部使用call/callAsync 方法调用。受支持的或可以使用的钩子列表通常指定在 this.hooks 上。


RickyLong
501 声望27 粉丝

所有事情都有一套底层的方法论,主要找到关键点,然后刻意练习,没有刻意练习,做事情只是低效率的重复