编写一个自己的webpack插件plugin

要想写好插件就要知道Webpack中的几个比较核心的概念compiler、compilation、tapable

Webpack 通过 Plugin 机制让其更加灵活,以适应各种应用场景。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

1.实现一个webpack的基本包含以下几步: 1.一个JavaScript函数或者类 2.在函数原型(prototype)中定义一个注入compiler对象的apply方法。 3.apply函数中通过compiler插入指定的事件钩子,在钩子回调中拿到compilation对象,并对compilation对象进行操作。 4.使用compilation操纵修改webapack内部实例数据。 5.异步插件,数据处理完后使用callback回调

大白话: Webpack在启动时会实例化插件对象,在初始化compiler对象之后会调用插件实例的apply方法,传入compiler对象,插件实例在apply方法中会注册感兴趣的钩子,Webpack在执行过程中会根据构建阶段回调相应的钩子。

class WebpackCleanPlugin {
  // 构造函数
  constructor(options) {
    console.log("WebpackCleanPlugin", options);
  }
  // 应用函数
  apply(compiler) {
    console.log(compiler);
    // 绑定钩子事件
    compiler.hooks.done.tap(pluginName, compilation => {
      console.log(compilation);
    });
  }
}
  • webpack启动后,在读取配置的过程中会先执行new WebpackCleanupPlugin()初始化一个webpackCleanPlugin获得实例

  • 在初始化complier对象后,在调用webpackCleanPlugin.apply(compiler)给插件实例传入compiler对象

  • 插件实例获取到compiler对象后,就可以通过compiler.plugin(事件名称,回调函数)监听到webpack广播出来的事件

  • 并且可以通过compiler对象去操作webpack

Compiler、Compilation

  • Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,hook,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;Compiler中包含的东西如下所示:

  • Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。 ** Compiler 和 Compilation 的区别在于:

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

const recursiveReadSync = require("recursive-readdir-sync");
const minimatch = require("minimatch");
const path = require("path");
const fs = require("fs");
const union = require("lodash.union");

// 匹配文件
function getFiles(fromPath, exclude = []) {
  const files = recursiveReadSync(fromPath).filter((file) =>
    exclude.every(
      (excluded) =>
        !minimatch(path.relative(fromPath, file), path.join(excluded), {
          dot: true,
        })
    )
  );
  // console.log(files);
  return files;
}

class WebpackCleanPlugin {
  constructor(options = {}) {
    // 配置文件
    this.options = options;
  }
  apply(compiler) {
    // 获取output路径
    const outputPath = compiler.options.output.path;
    console.log("compiler: ", compiler);
    const pluginName = WebpackCleanPlugin.name;
    // 绑定钩子事件
    compiler.hooks.done.tap(pluginName, (stats) => {
      // 获取编译完成 文件名
      const assets = stats.toJson().assets.map((asset) => asset.name);
      console.log("assets", assets);
      // 多数组合并并且去重
      const exclude = union(this.options.exclude, assets);
      console.log("exclude", exclude);
      // 获取未匹配文件
      const files = getFiles(outputPath, exclude);
      console.log("files", files);
      // 删除未匹配文件
      files.forEach(fs.unlinkSync);
    });
  }
}
module.exports = WebpackCleanPlugin;
  • 获取output路径,也就是dist路径

  • 绑定钩子事件 compiler.hooks.done

  • 编译文件,与源文件做对比,判断是否需要删除匹配文件

Tapable

Tapable是webpack的一个核心工具,webpack 中许多对象扩展自Tapable类。Tapable类暴露了tap、tapAsync和tapPromise方法,可以根据钩子的同步、异步方式来选择一个函数注入逻辑

  • tap 同步钩子

  • tapAsync 异步钩子,通过callback回调告诉Webpack异步执行完毕

  • tapPromise 异步钩子,返回一个Promise告诉Webpack异步执行完毕

构建流程

1.校验配置文件 2.生成Compiler对象 3.初始化默认插件 4.run/watch:如果运行在watch模式则执行watch方法,否则执行run方法 5.compilation:创建Compilation对象回调compilation相关钩子 6.emit:文件内容准备完成,准备生成文件,这是最后一次修改最终文件的机会 7.afterEmit:文件已经写入磁盘完成 8.done:完成编译 常见钩子 Webpack会根据执行流程来回调对应的钩子,下面我们来看看都有哪些常见钩子,这些钩子支持的tap操作是什么。

钩子 说明 参数 类型

afterPlugins 启动一次新的编译 compiler 同步

compile 创建compilation对象之前 compilationParams 同步

compilation compilation对象创建完成 compilation 同步

emit 资源生成完成,输出之前 compilation 异步

afterEmit 资源输出到目录完成 compilation 异步

done 完成编译 stats 同步

最后更新于

这有帮助吗?