知海

插件模式

webpackjsorg-main贡献指南

title: 插件模式
sort: 5
contributors:

  • nveenjain
  • EugeneHlushko
  • benglynn
  • raj-sapalya

插件模式

插件为在 webpack 构建系统中执行自定义提供了无限的机会。这允许你创建自定义资源类型、执行独特的构建修改,甚至在使用中间件时增强 webpack 运行时。以下是 webpack 在编写插件时一些有用的特性。

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

编译被封存(sealed)后,编译中的所有结构都可以被遍历。

js 复制代码
class MyPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => {
      // Explore each chunk (build output):
      for (const chunk of compilation.chunks) {
        // Explore each module within the chunk (built inputs):
        for (const module of chunk.getModules()) {
          // Explore each source file path that was included into the module:
          if (module.buildInfo && module.buildInfo.fileDependencies) {
            for (const fileDependency of module.buildInfo.fileDependencies) {
              // we've learned a lot about the source structure now...
              console.log(fileDependency);
            }
          }
        }

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

      callback();
    });
  }
}

export default MyPlugin;
  • compilation.modules:编译中的模块(构建输入)集合。每个模块管理来自你源代码库的原始文件的构建。

弃用警告:数组函数仍然可以正常工作。

  • module.fileDependencies:包含在模块中的源文件路径数组。这包括源 JavaScript 文件本身(例如:index.js),以及它所需的所有依赖资源文件(样式表、图片等)。审查依赖关系有助于查看哪些源文件属于一个模块。
  • compilation.chunks:编译中的 chunk(构建输出)集合。每个 chunk 管理最终渲染资源的组成。

弃用警告:数组函数仍然可以正常工作。

  • chunk.getModules():包含在 chunk 中的模块数组。以此类推,你可以查看每个模块的依赖关系,以了解哪些原始源文件输入到一个 chunk。
  • chunk.files:由 chunk 生成的输出文件名的 Set。你可以从 compilation.assets 表中访问这些资源的源码。

监听监视图(watch graph)

在运行 webpack 中间件时,每次编译都包含一个 fileDependencies Set(哪些文件正在被监听)和一个 fileTimestamps Map,用于将监听的文件路径映射到时间戳。这些对于检测编译中哪些文件发生了变化非常有用:

js 复制代码
class MyPlugin {
  constructor() {
    this.startTime = Date.now();
    this.prevTimestamps = new Map();
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => {
      const changedFiles = [...compilation.fileTimestamps.keys()].filter(
        (watchfile) =>
          (this.prevTimestamps.get(watchfile) || this.startTime) <
          (compilation.fileTimestamps.get(watchfile) || Infinity),
      );

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

export default MyPlugin;

你还可以向监视图中添加新的文件路径,以便在这些文件发生变化时收到编译触发。将有效的文件路径添加到 compilation.fileDependencies Set 中,即可将它们加入监听文件。

提示:fileDependencies Set 在每次编译中都会重建,因此你的插件必须将自己的监听依赖添加到每次编译中,才能保持它们处于监听状态。

警告:自 webpack 5 起,compilation.fileDependencies、compilation.contextDependencies 和 compilation.missingDependencies 现在是 Set,而不是 Sortable Set,因此不再排序。

已更改的 chunk

与监视图类似,你可以通过跟踪 chunk 的内容哈希来监控编译中已更改的 chunk。

警告:compilation.chunks 是一个 Set,而不是数组。直接对其调用 .filter() 或 .map() 会抛出 TypeError。
请使用 for...of 或先将其展开为数组。

警告:chunk.hash 未定义在 chunk 对象上。请改用 chunk.contentHash.javascript。
访问 chunk.hash 会返回 undefined,导致每次重新构建时每个 chunk 都显示为已更改。

js 复制代码
class MyPlugin {
  constructor() {
    this.chunkVersions = {};
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => {
      const changedChunks = [];

      for (const chunk of compilation.chunks) {
        const key = chunk.id ?? chunk.name;
        const currentHash = chunk.contentHash.javascript;

        if (currentHash === undefined) continue;

        const oldHash = this.chunkVersions[key];
        this.chunkVersions[key] = currentHash;

        if (currentHash !== oldHash) {
          changedChunks.push(chunk);
        }
      }

      if (changedChunks.length > 0) {
        console.log(
          "Changed chunks:",
          changedChunks.map((chunk) => chunk.id),
        );
      }

      callback();
    });
  }
}

export default MyPlugin;

提示:使用 chunk.id ?? chunk.name 作为跟踪键。某些 chunk 有 id 但没有 name,因此仅依赖 chunk.name 可能会遗漏变更。
CSS 或其他非 JS chunk 的 contentHash 可能没有 javascript 键——continue 守卫可以安全地跳过它们。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。