Skip to content

插件钩子过滤器

¥Plugin hook filters

有关为什么需要插件钩子过滤器的更多详细信息,请参阅 [why-plugin-hook-filter](/in-depth/why-plugin-hook-filter)

¥[!note] For more details about why you need plugin hook filter please refer why-plugin-hook-filter

关于 Rolldown 中的 JavaScript 插件,需要注意的一点是,每次插件钩子调用都会在 Rust 和 JavaScript 运行时(例如 Node.js)之间产生少量通信开销。

¥One important thing to note for JavaScript plugins in Rolldown is that every plugin hook call incurs a small communication overhead between Rust and the JavaScript runtime (i.e. Node.js).

考虑以下插件:

¥Consider the following plugin:

js
export default function myPlugin() {
  return {
    name: 'example',
    transform(code, id) {
      if (!id.endsWith('.data')) {
        // early return
        return
      }
      // perform actual transform
      return transformedCode
    },
  }
}

第 5 行是 Rollup 插件中非常常见的模式:检查插件钩子内部模块的文件扩展名,以确定是否需要处理。

¥Line 5 is a very common pattern in Rollup plugins: check the file extension of a module inside the plugin hook to determine whether it needs to be processed.

然而,这在像 Rolldown 这样的 Rust 打包器中并非最佳选择。假设这个插件用于一个包含数千个模块的大型应用 - Rolldown 必须为每个模块调用一次 Rust-to-JS 调用,并且在许多情况下,插件会发现它实际上不需要执行任何操作。由于 JavaScript 的单线程特性,不必要的 Rust 到 JS 调用也会降低并行化的优化效果。

¥However, this would be sub-optimal in a Rust bundler like Rolldown. Imagine this plugin is used in a large app with thousands of modules - Rolldown would have to invoke a Rust-to-JS call for every module, and in many cases just for the plugin to find out it doesn't actually need to do anything. Due to the single-threaded nature of JavaScript, unnecessary Rust-to-JS calls can also de-optimize parallelization.

理想情况下,我们希望能够在不离开 Rust 的情况下确定是否需要调用插件钩子。这就是 Rolldown 通过附加 filter 属性增强 Rollup 插件对象钩子格式的原因。上述插件可以更新为:

¥Ideally, we want to be able to determine whether a plugin hook needs to be invoked at all without leaving Rust. This is why Rolldown augments Rollup plugin's object hook format with the additional filter property. The above plugin can be updated to:

js
export default function myPlugin() {
  return {
    name: 'example',
    transform: {
      filter: {
        id: /\.data$/
      },
      handler (code) {
        // perform actual transform
        return transformedCode
      },
    }
  }
}

Rolldown 现在可以在 Rust 端编译并执行正则表达式,并且如果过滤器不匹配,则可以避免调用 JS。

¥Rolldown can now compile and execute the regular expression on the Rust side, and can avoid invoking JS if the filter does not match.

除了 id 之外,你还可以基于 moduleType 和模块的源代码进行过滤。filter 属性的工作方式与 @rollup/pluginutils 中的 createFilter 类似。以下是一些重要细节:

¥In addition to id, you can also filter based on moduleType and the module's source code. The filter property works similarly to createFilter from @rollup/pluginutils. Here are some important details:

  • 如果将多个值传递给 include,则只要其中任何一个值匹配,过滤器就会匹配。

    ¥If multiple values are passed to include, the filter matches if any of them match.

  • 如果过滤器同时包含 includeexclude,则 exclude 优先。

    ¥If a filter has both include and exclude, exclude takes precedence.

  • 如果指定了多个过滤器属性,则当所有指定的属性都匹配时,过滤器才会匹配。换句话说,即使只有一个属性不匹配,它也会被视为排除,而不管其他属性如何。例如,以下过滤器仅当模块的文件名以 .js 结尾、其源代码包含 foo 且不包含 bar 时才匹配该模块:

    ¥If multiple filter properties are specified, the filter matches when all of the specified properties match. In other words, if even one property fails to match, it is excluded, regardless of the other properties. For example, the following filter matches a module only if its file names ends with .js, its source code contains foo, and does not contain bar:

    js
    {
      id: {
        include: /\.js$/,
        exclude: /\.ts$/
      },
      code: {
        include: 'foo',
        exclude: 'bar'
      }
    }

filter 属性的完整 HookFilter 接口:

¥Full HookFilter interface for the filter property:

ts
interface HookFilter {
  /**

   * This filter is used to do a pre-test to determine whether the hook should be called.

   *    * @example

   * Include all `id`s that contain `node_modules` in the path.

   * ```js

   * { id: '**'+'/node_modules/**' }

   * ```

   * @example

   * Include all `id`s that contain `node_modules` or `src` in the path.

   * ```js

   * { id: ['**'+'/node_modules/**', '**'+'/src/**'] }

   * ```

   * @example

   * Include all `id`s that start with `http`

   * ```js

   * { id: /^http/ }

   * ```

   * @example

   * Exclude all `id`s that contain `node_modules` in the path.

   * ```js

   * { id: { exclude: '**'+'/node_modules/**' } }

   * ```

   * @example

   * Formal pattern to define includes and excludes.

   * ```

   * { id : {

   *   include: ['**'+'/foo/**', /bar/],

   *   exclude: ['**'+'/baz/**', /qux/]

   * }}

   * ```
   */
  id?: StringFilter;
  moduleType?: ModuleTypeFilter;
  code?: StringFilter;
}

每个钩子都支持以下属性:

¥The following properties are supported by each hook:

  • resolveId 钩子:id

    ¥resolveId hook: id

  • load 钩子:id

    ¥load hook: id

  • transform 钩子:id, moduleType, code

    ¥transform hook: id, moduleType, code

当你传递 `string` 时,`id` 会被视为 glob 模式;当你传递 `RegExp` 时,`id` 会被视为正则表达式。在 `resolve` 钩子中,`id` 必须是 `RegExp`。不允许使用 `string`。这是因为 `resolveId` 中的 `id` 值是导入语句中写入的精确文本,通常不是绝对路径,而 glob 模式旨在匹配绝对路径。

¥[!NOTE] id is treated as a glob pattern when you pass a string, and treated as a regular expression when you pass a RegExp. In the resolve hook, id must be a RegExp. strings are not allowed. This is because the id value in resolveId is the exact text written in the import statement and usually not an absolute path, while glob patterns are designed to match absolute paths.