Skip to content
This repository was archived by the owner on Mar 22, 2019. It is now read-only.

how to write a plugin.cn

e-cloud edited this page Jul 12, 2016 · 3 revisions

Plugins expose the full potential of the Webpack engine to third-party developers. Using staged build callbacks, developers can introduce their own behaviors into the Webpack build process. Building plugins is a bit more advanced than building loaders, because you'll need to understand some of the Webpack low-level internals to hook into them. Be prepared to read some source code!

webpack 引擎的所有潜力由插件体系提供给第三方开发者。使用分阶段构建的回调,开发者可以向 webpack 构建流程注入自定义行为。建立插件比建立 loader 更为高级一点,因为你需要了解部分 webpack 底层的内部对象以便挂接进去。做好心理准备阅读更多的源代码吧!

Compiler and Compilation

编译器和编译流程

Among the two most important resources while developing plugins are the compiler and compilation objects. Understanding their roles is an important first step in extending the Webpack engine.

开发插件时最重要的两个资源是 compilercompilation对象。

  • The compiler object represents the fully configured Webpack environment. This object is built once upon starting Webpack, and is configured with all operational settings including options, loaders, and plugins. When applying a plugin to the Webpack environment, the plugin will receive a reference to this compiler. Use the compiler to access the main Webpack environment.

  • compiler对象表示配置完整的 webpack 环境。这个对象在 webpack 启动时构建一次,配置有所有操作设定,包括选项、loader 和插件。当一个插件应用到 webpack 环境中,该插件将获得这个 compiler 对象的引用。可使用这个 compiler 来访问 webpack 主环境。

  • A compilation object represents a single build of versioned assets. While running Webpack development middleware, a new compilation will be created each time a file change is detected, thus generating a new set of compiled assets. A compilation surfaces information about the present state of module resources, compiled assets, changed files, and watched dependencies. The compilation also provides many callback points at which a plugin may choose to perform custom actions.

  • 一个compilation对象代表对版本化资源的单次构建。在运行 webpack 开发中间件事时,每当侦测到文件变更,会创建一个新的 compilation,从而生成一套新的编译过的资源。一个 compilation 透露了很多数据的信息,包括模块资源的当前状态,编译过的资源,变更的文件,以及监视的依赖。而且,它也提供了很多回调点,插件可以选用来进行自定义操作。

These two components are an integral part of any Webpack plugin (especially a compilation), so developers will benefit by familiarizing themselves with these source files:

这两个组件都被集成到每个 webpack 插件(特别是compilation),所以开发者熟悉它们的源码的话会受益不少。

Basic plugin architecture

插件的基本架构

Plugins are instanceable objects with an apply method on their prototype. This apply method is called once by the Webpack compiler while installing the plugin. The apply method is given a reference to the underlying Webpack compiler, which grants access to compiler callbacks. A simple plugin is structured as follows:

插件是可实例化的对象,其原型对象有一个apply方法。这个apply方法由 webpack 编译器在安装该插件时被调用一次。这个apply方法被提供一个 webpack 底层编译器的一个引用,以获得编译器回调点的访问权限。一个简单的插件结构如下:

function HelloWorldPlugin(options) {
  // Setup the plugin instance with options...
  // 使用 options 创建插件实例
}

HelloWorldPlugin.prototype.apply = function(compiler) {
  compiler.plugin('done', function() {
    console.log('Hello World!');
  });
};

module.exports = HelloWorldPlugin;

Then to install the plugin, just include an instance in your Webpack config plugins array:

然后,为了安装插件,只要在 webpack 配置中的plugins数组加入插件的实例即可。

var HelloWorldPlugin = require('hello-world');

var webpackConfig = {
  // ... config settings here ...
  plugins: [
    new HelloWorldPlugin({options: true})
  ]
};

Accessing the compilation

访问编译过程

Using the compiler object, you may bind callbacks that provide a reference to each new compilation. These compilations provide callbacks for hooking into numerous steps within the build process.

使用编译器对象,你可以绑定回调以提供每一个新编译过程的引用。这些编译过程为挂接到构建过程中的许多步骤提供回调。

function HelloCompilationPlugin(options) {}

HelloCompilationPlugin.prototype.apply = function(compiler) {

  // Setup callback for accessing a compilation:
  // 设置回调来访问编译过程
  compiler.plugin("compilation", function(compilation) {

    // Now setup callbacks for accessing compilation steps:
    // 设置回调来访问编译步骤
    compilation.plugin("optimize", function() {
      console.log("Assets are being optimized.");
    });
  });
});

module.exports = HelloCompilationPlugin;

For more information on what callbacks are available on the compiler, compilation, and other important objects, see the plugins API doc.

有关更多哪些回调可用在compilercompilation,以及其他重要对象的详细信息,请参阅plugins API文档。

Async compilation plugins

异步编译插件

Some compilation plugin steps are asynchronous, and pass a callback function that must be invoked when your plugin is finished running.

一些编译插件的步骤是异步的,所以当你的插件完成运行时传入一个必须执行的回调函数。

function HelloAsyncPlugin(options) {}

HelloAsyncPlugin.prototype.apply = function(compiler) {
  compiler.plugin("emit", function(compilation, callback) {

    // Do something async...
    // 做些异步的工作
    setTimeout(function() {
      console.log("Done with async work...");
      callback();
    }, 1000);

  });
});

module.exports = HelloAsyncPlugin;

A simple example

一个简单的例子

Once we can latch onto the Webpack compiler and each individual compilations, the possibilities become endless for what we can do with the engine itself. We can reformat existing files, create derivative files, or fabricate entirely new assets.

一旦我们能接触 Webpack 编译器和每个编译过程,我们可以利用发动机本身做事的可能性变得无穷无尽。我们可以重新格式化现有文件,创建衍生文件,或制造全新的资源。

Let's write a simple example plugin that generates a new build file called filelist.md; the contents of which will list all of the asset files in our build. This plugin might look something like this:

我们来写一个简单的示例:生成一个新的名为filelist.md的文件;其内容将列出构建中所有的资源文件。这个插件可能是这个样子:

function FileListPlugin(options) {}

FileListPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(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;

Useful Plugin Patterns

常见的插件使用模式

Plugins grant unlimited opportunity to perform customizations within the Webpack build system. This allows you to create custom asset types, perform unique build modifications, or even enhance the Webpack runtime while using middleware. The following are some features of Webpack that become very useful while writing plugins. 在 webpack 构建体系中,插件被赋予了无限可能去进行定制化。这样允许你去创建自定义的资源类型,进行独一无二的构建修改,甚至使用中间件增强 webpack 运行时。下面是,当你编写插件时,部分相当有用的 webpack 特性。

Exploring assets, chunks, modules, and dependencies

探索资源、chunk、模块与依赖

After a compilation is sealed, all structures within the compilation may be traversed. 当一次 compilation 被冻结后,其中所有数据都可以进行遍历。

function MyPlugin() {}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {

    // Explore each chunk (build output):
    compilation.chunks.forEach(function(chunk) {
      // Explore each module within the chunk (built inputs):
      chunk.modules.forEach(function(module) {
        // Explore each source file path that was included into the module:
        module.fileDependencies.forEach(function(filepath) {
          // we've learned a lot about the source structure now...
        });
      });

      // Explore each asset filename generated by the chunk:
      chunk.files.forEach(function(filename) {
        // Get the asset source for each file generated by the chunk:
        var source = compilation.assets[filename].source();
      });
    });

    callback();
  });
};

return MyPlugin;
  • compilation.modules: An array of modules (built inputs) in the compilation. Each module manages the build of a raw file from your source library.

  • compilation.modules: 一组模块(已构建好的)。每个模块管理源目录中一个原始文件的构建

  • module.fileDependencies: An array of source file paths included into a module. This includes the source JavaScript file itself (ex: index.js), and all dependency asset files (stylesheets, images, etc) that it has required. Reviewing dependencies is useful for seeing what source files belong to a module.

  • module.fileDependencies: 被包含进一个模块的一组源文件路径。其中包括 JavaScript 源文件(如index.js),及所有依赖的资源文件(样式、图片等)。审阅依赖可用于查看哪些源文件归属于一个模块。

  • compilation.chunks: An array of chunks (build outputs) in the compilation. Each chunk manages the composition of a final rendered assets.

  • compilation.chunks: 一组 chunk(已构建好的)。每个 chunk 管理一个最终输出资源的集合。

  • chunk.modules: An array of modules that are included into a chunk. By extension, you may look through each module's dependencies to see what raw source files fed into a chunk.

  • chunk.modules: 包含进一个 chunk 的一组模块。此外,你可以浏览每个模块的依赖,去查看哪些原始文件被放进了一个 chunk。

  • chunk.files: An array of output filenames generated by the chunk. You may access these asset sources from the compilation.assets table.

  • chunk.files: 被 chunk 生成的一组输出文件名。你可以从compilation.assets访问这些资源的内容。

Monitoring the watch graph

监视“监听图谱”

While running Webpack middleware, each compilation includes a fileDependencies array (what files are being watched) and a fileTimestamps hash that maps watched file paths to a timestamp. These are extremely useful for detecting what files have changed within the compilation: 当运行 webpack 中间件时,每个 compilation 包含了一个 fileDependencies 数组(正在被监听的文件)和一个fileTimestamps哈希对象进行被监听文件的路径到一个时间戳的映射。这些对于监测什么文件在一次 compilation 中变更了是非常有用的。

function MyPlugin() {
  this.startTime = Date.now();
  this.prevTimestamps = {};
}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {

    var changedFiles = Object.keys(compilation.fileTimestamps).filter(function(watchfile) {
      return (this.prevTimestamps[watchfile] || this.startTime) < (compilation.fileTimestamps[watchfile] || Infinity);
    }.bind(this));

    this.prevTimestamps = compilation.fileTimestamps;
    callback();
  }.bind(this));
};

return MyPlugin;

You may also feed new file paths into the watch graph to receive compilation triggers when those files change. Simply push valid filepaths into the compilation.fileDependencies array to add them to the watch. Note: the fileDependencies array is rebuilt in each compilation, so your plugin must push its own watched dependencies into each compilation to keep them under watch. 你也可以向监视图谱加入新的文件路径,一旦文件变更则触发 compilation。只需要将有效文件路径加入compilation.fileDependencies进行监控。注意:每次 compilation fileDependencies数组都会被重新构建,所以插件必须将其自身监听的依赖加入到每次 compilation 中,以保证它们处于被监听状态。

Changed chunks

Similar to the watch graph, it's fairly simple to monitor changed chunks (or modules, for that matter) within a compilation by tracking their hashes. 类似于监视图谱,监视 chunk 的变更相当简单——在 compilation 中追踪其 hash 值的变更。

function MyPlugin() {
  this.chunkVersions = {};
}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {

    var changedChunks = compilation.chunks.filter(function(chunk) {
      var oldVersion = this.chunkVersions[chunk.name];
      this.chunkVersions[chunk.name] = chunk.hash;
      return chunk.hash !== oldVersion;
    }.bind(this));

    callback();
  }.bind(this));
};

return MyPlugin;
Clone this wiki locally