知海

插件模式

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.fileDependenciescompilation.contextDependenciescompilation.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 守卫可以安全地跳过它们。

帮助我们改进文档

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