Repository: rolldown/rolldown
Pull request #4192: perf: skip empty js hook for js plugin
Author: login: IWANABETHATGUY
Created: 2025-04-18T09:32:21Z | Merged: 2025-04-19T04:39:18Z

<!-- Thank you for contributing! -->


## With 1000 empty plugin(only have name), use `10000` case, 
#### Main 
```bash
Benchmark 1: node --run build:rolldown
  Time (mean ± σ):     953.0 ms ±   6.0 ms    [User: 15170.3 ms, System: 875.5 ms]
  Range (min … max):   947.8 ms … 959.5 ms    3 runs
```

chrome-trace json size: 
![image](https://github.com/user-attachments/assets/459f8b51-beec-4d6e-991e-511a9fe859cd)
This is only partial trace json file, I need to terminate it because it stuck my computer.

#### This branch
```bash
Benchmark 1: node --run build:rolldown
  Time (mean ± σ):     584.0 ms ±   6.7 ms    [User: 2775.2 ms, System: 1057.3 ms]
  Range (min … max):   577.2 ms … 590.5 ms    3 runs
```
Tracing chrome-json: 161MB


## With zero plugin
#### Main
```bash
Benchmark 1: node --run build:rolldown
  Time (mean ± σ):     548.0 ms ±   6.9 ms    [User: 2439.8 ms, System: 926.7 ms]
  Range (min … max):   542.4 ms … 555.7 ms    3 runs
```

#### This branch
```bash
Benchmark 1: node --run build:rolldown
  Time (mean ± σ):     537.3 ms ±  12.8 ms    [User: 2450.7 ms, System: 1034.6 ms]
  Range (min … max):   527.7 ms … 551.9 ms    3 runs
```




<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->



Diff:
diff --git a/crates/rolldown_binding/src/options/plugin/binding_plugin_options.rs b/crates/rolldown_binding/src/options/plugin/binding_plugin_options.rs
index 449fcf58d615..303c477d8c7e 100644
--- a/crates/rolldown_binding/src/options/plugin/binding_plugin_options.rs
+++ b/crates/rolldown_binding/src/options/plugin/binding_plugin_options.rs
@@ -34,6 +34,7 @@ pub type BindingPluginOrParallelJsPluginPlaceholder =
 #[derive(Default)]
 pub struct BindingPluginOptions {
   pub name: String,
+  pub hook_usage: u32,
   #[napi(
     ts_type = "(ctx: BindingPluginContext, opts: BindingNormalizedOptions) => MaybePromise<VoidNullable>"
   )]
diff --git a/crates/rolldown_binding/src/options/plugin/js_plugin.rs b/crates/rolldown_binding/src/options/plugin/js_plugin.rs
index 83a5913f2058..e769056b1826 100644
--- a/crates/rolldown_binding/src/options/plugin/js_plugin.rs
+++ b/crates/rolldown_binding/src/options/plugin/js_plugin.rs
@@ -540,7 +540,7 @@ impl Plugin for JsPlugin {
   }
 
   fn register_hook_usage(&self) -> HookUsage {
-    HookUsage::all()
+    HookUsage::from_bits(self.inner.hook_usage).expect("Failed to register hook usage")
   }
 }
 
diff --git a/crates/rolldown_plugin/src/plugin_driver/build_hooks.rs b/crates/rolldown_plugin/src/plugin_driver/build_hooks.rs
index d40e3c2aede9..a14a0e782877 100644
--- a/crates/rolldown_plugin/src/plugin_driver/build_hooks.rs
+++ b/crates/rolldown_plugin/src/plugin_driver/build_hooks.rs
@@ -366,7 +366,7 @@ impl PluginDriver {
     for (plugin_idx, plugin, ctx) in
       self.iter_plugin_with_context_by_order(&self.order_by_build_end_meta)
     {
-      if !self.plugin_usage_vec[plugin_idx].contains(HookUsage::Transform) {
+      if !self.plugin_usage_vec[plugin_idx].contains(HookUsage::BuildEnd) {
         continue;
       }
       plugin
diff --git a/packages/rolldown/src/binding.d.ts b/packages/rolldown/src/binding.d.ts
index 9b0136c9d7b9..85c0579c3e7c 100644
--- a/packages/rolldown/src/binding.d.ts
+++ b/packages/rolldown/src/binding.d.ts
@@ -587,6 +587,7 @@ export interface BindingPluginHookMeta {
 
 export interface BindingPluginOptions {
   name: string
+  hookUsage: number
   buildStart?: (ctx: BindingPluginContext, opts: BindingNormalizedOptions) => MaybePromise<VoidNullable>
   buildStartMeta?: BindingPluginHookMeta
   resolveId?: (ctx: BindingPluginContext, specifier: string, importer: Nullable<string>, options: BindingHookResolveIdExtraArgs) => MaybePromise<VoidNullable<BindingHookResolveIdOutput>>
diff --git a/packages/rolldown/src/plugin/bindingify-plugin.ts b/packages/rolldown/src/plugin/bindingify-plugin.ts
index b6c3aebe211b..f37b0d05f04e 100644
--- a/packages/rolldown/src/plugin/bindingify-plugin.ts
+++ b/packages/rolldown/src/plugin/bindingify-plugin.ts
@@ -31,6 +31,7 @@ import {
   bindingifyCloseWatcher,
   bindingifyWatchChange,
 } from './bindingify-watch-hooks';
+import { extractHookUsage } from './generated/hook-usage';
 import type { Plugin, RolldownPlugin } from './index';
 import { PluginContextData } from './plugin-context-data';
 
@@ -137,6 +138,7 @@ export function bindingifyPlugin(
 
   const { plugin: closeWatcher, meta: closeWatcherMeta } =
     bindingifyCloseWatcher(args);
+  let hookUsage = extractHookUsage(plugin).inner();
   const result: BindingPluginOptions = {
     // The plugin name already normalized at `normalizePlugins`, see `packages/rolldown/src/utils/normalize-plugin-option.ts`
     name: plugin.name!,
@@ -184,6 +186,7 @@ export function bindingifyPlugin(
     watchChangeMeta,
     closeWatcher,
     closeWatcherMeta,
+    hookUsage,
   };
   return wrapHandlers(result);
 }
diff --git a/packages/rolldown/src/plugin/generated/hook-usage.ts b/packages/rolldown/src/plugin/generated/hook-usage.ts
index 9c065cb6515f..a504bee6a2ce 100644
--- a/packages/rolldown/src/plugin/generated/hook-usage.ts
+++ b/packages/rolldown/src/plugin/generated/hook-usage.ts
@@ -26,7 +26,8 @@ export enum HookUsageKind {
 }
 
 export class HookUsage {
-  constructor(public bitflag: bigint) {}
+  private bitflag: bigint = BigInt(0);
+  constructor() {}
 
   union(kind: HookUsageKind): void {
     this.bitflag |= BigInt(kind);
@@ -39,3 +40,90 @@ export class HookUsage {
     return Number(this.bitflag);
   }
 }
+
+import { Plugin } from '../..';
+export function extractHookUsage(plugin: Plugin): HookUsage {
+  let hookUsage = new HookUsage();
+
+  if (plugin.buildStart) {
+    hookUsage.union(HookUsageKind.buildStart);
+  }
+
+  if (plugin.resolveId) {
+    hookUsage.union(HookUsageKind.resolveId);
+  }
+
+  if (plugin.resolveDynamicImport) {
+    hookUsage.union(HookUsageKind.resolveDynamicImport);
+  }
+
+  if (plugin.load) {
+    hookUsage.union(HookUsageKind.load);
+  }
+
+  if (plugin.transform) {
+    hookUsage.union(HookUsageKind.transform);
+  }
+
+  if (plugin.moduleParsed) {
+    hookUsage.union(HookUsageKind.moduleParsed);
+  }
+
+  if (plugin.buildEnd) {
+    hookUsage.union(HookUsageKind.buildEnd);
+  }
+
+  if (plugin.renderStart) {
+    hookUsage.union(HookUsageKind.renderStart);
+  }
+
+  if (plugin.renderError) {
+    hookUsage.union(HookUsageKind.renderError);
+  }
+
+  if (plugin.renderChunk) {
+    hookUsage.union(HookUsageKind.renderChunk);
+  }
+
+  if (plugin.augmentChunkHash) {
+    hookUsage.union(HookUsageKind.augmentChunkHash);
+  }
+
+  if (plugin.generateBundle) {
+    hookUsage.union(HookUsageKind.generateBundle);
+  }
+
+  if (plugin.writeBundle) {
+    hookUsage.union(HookUsageKind.writeBundle);
+  }
+
+  if (plugin.closeBundle) {
+    hookUsage.union(HookUsageKind.closeBundle);
+  }
+
+  if (plugin.watchChange) {
+    hookUsage.union(HookUsageKind.watchChange);
+  }
+
+  if (plugin.closeWatcher) {
+    hookUsage.union(HookUsageKind.closeWatcher);
+  }
+
+  if (plugin.banner) {
+    hookUsage.union(HookUsageKind.banner);
+  }
+
+  if (plugin.footer) {
+    hookUsage.union(HookUsageKind.footer);
+  }
+
+  if (plugin.intro) {
+    hookUsage.union(HookUsageKind.intro);
+  }
+
+  if (plugin.outro) {
+    hookUsage.union(HookUsageKind.outro);
+  }
+
+  return hookUsage;
+}
diff --git a/tasks/generator/src/generators/hook_usage.rs b/tasks/generator/src/generators/hook_usage.rs
index 31de3ffd993c..557a61beb615 100644
--- a/tasks/generator/src/generators/hook_usage.rs
+++ b/tasks/generator/src/generators/hook_usage.rs
@@ -35,6 +35,8 @@ const HOOK_KIND: [&str; 21] = [
   "outro",
 ];
 
+const DISABLE_JS_HOOK: [&str; 1] = ["transform_ast"];
+
 impl Generator for HookUsageGenerator {
   fn generate_many(&self, _ctx: &Context) -> anyhow::Result<Vec<crate::output::Output>> {
     Ok(vec![
@@ -57,6 +59,26 @@ fn generate_hook_usage_ts() -> String {
     .map(|(i, &kind)| format!("  {} = 1 << {},", kind.to_lower_camel_case(), i))
     .collect::<Vec<_>>()
     .join("\n");
+
+  let union_hook_usage_list = HOOK_KIND
+    .iter()
+    .filter_map(|kind| {
+      if DISABLE_JS_HOOK.contains(kind) {
+        return None;
+      }
+      Some(format!(
+        r"
+      if (plugin.{}) {{
+        hookUsage.union(HookUsageKind.{});
+      
+      }}
+      ",
+        kind.to_lower_camel_case(),
+        kind.to_lower_camel_case()
+      ))
+    })
+    .collect::<Vec<_>>()
+    .join("\n");
   format!(
     r"
    export enum HookUsageKind {{
@@ -64,7 +86,8 @@ fn generate_hook_usage_ts() -> String {
    }};
 
   export class HookUsage {{
-  	constructor(public bitflag: bigint) {{}}
+    private bitflag: bigint = BigInt(0);
+  	constructor() {{}}
 
     union(kind: HookUsageKind): void {{
       this.bitflag |= BigInt(kind);
@@ -77,6 +100,13 @@ fn generate_hook_usage_ts() -> String {
       return Number(this.bitflag)
     }}
   }}
+
+import {{ Plugin }} from '../..';
+export function extractHookUsage(plugin: Plugin): HookUsage {{
+  let hookUsage = new HookUsage();
+  {union_hook_usage_list}
+  return hookUsage;
+}}
   ",
   )
 }


Reviews:
user: login: hyf0
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-18T10:48:07Z
user: login: hyf0
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T10:50:27Z
user: login: IWANABETHATGUY
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T10:59:49Z

Review comments:
diff_hunk: @@ -34,6 +34,7 @@ pub type BindingPluginOrParallelJsPluginPlaceholder =
 #[derive(Default)]
 pub struct BindingPluginOptions {
   pub name: String,
+  pub hook_usage: u32,
path: crates/rolldown_binding/src/options/plugin/binding_plugin_options.rs
user: login: hyf0
body: Could this be infered by in rust side

```rust
let usage = ...l

if opts.build_start.is_some() {
   usage |= HookUsage::BuildStart
}
```
created_at: 2025-04-18T10:50:26Z
author_association: MEMBER
diff_hunk: @@ -34,6 +34,7 @@ pub type BindingPluginOrParallelJsPluginPlaceholder =
 #[derive(Default)]
 pub struct BindingPluginOptions {
   pub name: String,
+  pub hook_usage: u32,
path: crates/rolldown_binding/src/options/plugin/binding_plugin_options.rs
user: login: IWANABETHATGUY
body: I chose the number because it is auto-generated and not exposed to an outside user; this method should have better performance.
created_at: 2025-04-18T10:59:49Z
author_association: MEMBER

PR comments:
user: login: IWANABETHATGUY
created_at: 2025-04-18T09:32:40Z
body: * **#4192** <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4192?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4192?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#4187** <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4187?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#4181** <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4181?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-18T09:57:21Z
body: ### Benchmarks Rust
- target: `main`(37e71eb010648a8828c10e70c7300b27c979a2f8)
- pr: `04-18-perf_skip_empty_js_hook_for_js_plugin`(dc81ecb0bad6a1f3f36ce51005aa094d38b1d370)
```
group                                                               pr                                     target
-----                                                               --                                     ------
bundle/bundle@multi-duplicated-top-level-symbol                     1.01     74.2±2.59ms        ? ?/sec    1.00     73.4±1.64ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify              1.04    101.6±3.59ms        ? ?/sec    1.00     97.9±3.24ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify-sourcemap    1.06    116.6±2.52ms        ? ?/sec    1.00    110.0±1.52ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-sourcemap           1.07     92.7±1.53ms        ? ?/sec    1.00     86.9±4.56ms        ? ?/sec
bundle/bundle@rome-ts                                               1.02    127.3±1.52ms        ? ?/sec    1.00    124.4±1.74ms        ? ?/sec
bundle/bundle@rome-ts-minify                                        1.01    203.2±3.08ms        ? ?/sec    1.00    201.7±4.55ms        ? ?/sec
bundle/bundle@rome-ts-minify-sourcemap                              1.06   257.5±11.80ms        ? ?/sec    1.00    242.6±2.32ms        ? ?/sec
bundle/bundle@rome-ts-sourcemap                                     1.01    139.7±1.98ms        ? ?/sec    1.00    138.0±2.57ms        ? ?/sec
bundle/bundle@threejs                                               1.00     41.9±1.38ms        ? ?/sec    1.01     42.4±1.65ms        ? ?/sec
bundle/bundle@threejs-minify                                        1.01     86.4±1.42ms        ? ?/sec    1.00     85.4±0.64ms        ? ?/sec
bundle/bundle@threejs-minify-sourcemap                              1.01    102.0±1.30ms        ? ?/sec    1.00    100.6±0.58ms        ? ?/sec
bundle/bundle@threejs-sourcemap                                     1.00     48.7±0.81ms        ? ?/sec    1.00     48.6±0.98ms        ? ?/sec
bundle/bundle@threejs10x                                            1.01    437.2±3.31ms        ? ?/sec    1.00    434.6±3.34ms        ? ?/sec
bundle/bundle@threejs10x-minify                                     1.00   1086.4±8.23ms        ? ?/sec    1.01  1095.8±11.75ms        ? ?/sec
bundle/bundle@threejs10x-minify-sourcemap                           1.00  1275.8±15.75ms        ? ?/sec    1.00   1273.2±8.88ms        ? ?/sec
bundle/bundle@threejs10x-sourcemap                                  1.00    512.4±6.04ms        ? ?/sec    1.01    516.8±7.02ms        ? ?/sec
remapping/remapping                                                 1.00     26.6±1.14ms        ? ?/sec    1.04     27.6±0.36ms        ? ?/sec
remapping/render-chunk-remapping                                    1.07     69.4±3.62ms        ? ?/sec    1.00     65.2±0.75ms        ? ?/sec
scan/scan@rome-ts                                                   1.02     98.7±3.74ms        ? ?/sec    1.00     96.4±1.90ms        ? ?/sec
scan/scan@threejs                                                   1.05     33.5±1.24ms        ? ?/sec    1.00     32.1±1.17ms        ? ?/sec
scan/scan@threejs10x                                                1.02    329.4±4.30ms        ? ?/sec    1.00    323.9±4.18ms        ? ?/sec
```
author_association: CONTRIBUTOR
user: login: underfin
created_at: 2025-04-18T10:07:19Z
body: Awesome!
author_association: CONTRIBUTOR
user: login: graphite-app[bot]
created_at: 2025-04-19T03:21:14Z
body: ### Merge activity

* **Apr 18, 11:21 PM EDT**: [Graphite](https://app.graphite.dev/github/pr/rolldown/rolldown/4192) rebased this pull request after merging its parent, because this pull request is set to merge when ready.

author_association: CONTRIBUTOR
user: login: netlify[bot]
created_at: 2025-04-19T03:22:05Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *rolldown-rs* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 80a825ec7bd42a2898cdb1c889dd533d85502dfc |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/rolldown-rs/deploys/6803168c20a9270008f63ae5 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-4192--rolldown-rs.netlify.app](https://deploy-preview-4192--rolldown-rs.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTQxOTItLXJvbGxkb3duLXJzLm5ldGxpZnkuYXBwIn0.Ct4pYAvoFQhtIpWIA9_YWohzkTFT97MDSRpSTThVL0Y)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [rolldown-rs Preview](https://deploy-preview-4192--rolldown-rs.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/rolldown-rs/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-19T03:22:57Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *rolldown-rs* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | dc81ecb0bad6a1f3f36ce51005aa094d38b1d370 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/rolldown-rs/deploys/680316966e9d2e00080b30da |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-4192--rolldown-rs.netlify.app](https://deploy-preview-4192--rolldown-rs.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTQxOTItLXJvbGxkb3duLXJzLm5ldGxpZnkuYXBwIn0.Ct4pYAvoFQhtIpWIA9_YWohzkTFT97MDSRpSTThVL0Y)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [rolldown-rs Preview](https://deploy-preview-4192--rolldown-rs.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/rolldown-rs/configuration/notifications#deploy-webhooks)._
author_association: NONE