webpack系列之plugin实现

337 阅读4分钟

webpack 是个很强大的构建工具,其丰富灵活的配置决定了使用也不简单。在面试中经常能遇到 webpack 相关的问题,如果平常只是使用脚手架如 vue-cli 而没有好好深入学习研究 webpack 的话,估计答不上什么。我相信,如果没有深入了解,部分面试官也问不出什么。可能也就变成了两个人侃侃如何配置出入口,常见的 loader,plugin 有哪些。

作为一名多年油条前端,一直没有正视 webpack 相关知识,面对 webpack 相关的面试题更是一问三不知。这次准备好好学习研究 webpack相关内容,并且将学习内容记录成 webpack 系列,希望可以让不了解 webpack 的小白能对其有所掌握。

编写一个插件

摘录自 webpack中文文档:编写一个插件

插件向第三方开发者提供了 webpack 引擎中完整的能力。专注处理 webpack 在编译过程中的某个特定任务的功能模块,可以称为插件。

创建插件

webpack 插件由以下组成:

  • 一个 Javascript 命名函数

  • 在插件函数的 prototype 上定义一个 apply 方法

  • 指定一个绑定到 webpack 自身的事件钩子

  • 处理 webpack 内部实例的特定数据

  • 功能完成之后调用 webpack 提供的回调

// 一个 JavaScript 命名函数。
function MyExampleWebpackPlugin() {

};

// 在插件函数的 prototype 上定义一个 `apply` 方法。
MyExampleWebpackPlugin.prototype.apply = function(compiler) {
  // 指定一个挂载到 webpack 自身的事件钩子。
  compiler.plugin('webpacksEventHook', function(compilation /* 处理 webpack 内部实例的特定数据。*/, callback) {
    console.log("This is an example plugin!!!");

    // 功能完成后调用 webpack 提供的回调。
    callback();
  });
};

Compiler 和 Compilation

在插件开发中最重要的两个资源就是 compiler 和 compilation 对象。插件的主要功能就是根据 compiler 和 compilation 得到当前配置项及编辑状态,再通过 compiler 及 compilation 给出的钩子参与编译过程。

  • compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。

  • compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

开发插件的核心就在于运用这个两个对象,其源码在

基本插件架构

插件是由「具有 apply 方法的 prototype 对象」所实例化出来的。这个 apply 方法在安装插件时,会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。一个简单的插件结构如下:

function HelloWorldPlugin(options) {
  // 使用 options 设置插件实例……
}

HelloWorldPlugin.prototype.apply = function(compiler) {
  // 相当于在Done钩子中注册函数
  compiler.plugin('done', function() {
    console.log('Hello World!');
  });
};

module.exports = HelloWorldPlugin;

然后,要安装这个插件,只需要在你的 webpack 配置的 plugin 数组中添加一个实例:

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

var webpackConfig = {
  // ... 这里是其他配置 ...
  plugins: [
    new HelloWorldPlugin({options: true})
  ]
};

访问 compilation 对象

使用 compiler 对象时,你可以绑定提供了编译 compilation 引用的回调函数,然后拿到每次新的 compilation 对象。这些 compilation 对象提供了一些钩子函数,来钩入到构建流程的很多步骤中。

function HelloCompilationPlugin(options) {}

HelloCompilationPlugin.prototype.apply = function(compiler) {

  // 设置回调来访问 compilation 对象:
  compiler.plugin("compilation", function(compilation) {

    // 现在,设置回调来访问 compilation 中的步骤:
    compilation.plugin("optimize", function() {
      console.log("Assets are being optimized.");
    });
  });
};

module.exports = HelloCompilationPlugin;

异步编译插件

一些异步钩子需要额外使用 callBack 回调函数

function HelloAsyncPlugin(options) {}

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

    // 做一些异步处理……
    setTimeout(function() {
      console.log("Done with async work...");
      callback();
    }, 1000);

  });
};

module.exports = HelloAsyncPlugin;

示例

function FileListPlugin(options) {}

FileListPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {
    // 在生成文件中,创建一个头部字符串:
    var filelist = 'In this build:\n\n';

    // 遍历所有编译过的资源文件,
    // 对于每个文件名称,都添加一行内容。
    for (var filename in compilation.assets) {
      filelist += ('- '+ filename +'\n');
    }

    // 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
    compilation.assets['filelist.md'] = {
      source: function() {
        return filelist;
      },
      size: function() {
        return filelist.length;
      }
    };

    callback();
  });
};

module.exports = FileListPlugin;

欢迎到前端菜鸟群一起学习交流~516913974