Skip to content

Conversation

@hamlim
Copy link

@hamlim hamlim commented Jan 21, 2026

Summary

This PR implements the beforeModuleIds hook for rspack, providing webpack API compatibility for plugins that need to assign custom module IDs before rspack's built-in module ID assignment strategy runs.

This hook is called during the module IDs phase, after modules are collected but before any ID assignment occurs. Plugins can use it to assign custom IDs to specific modules (e.g., for stable chunk hashing, debugging, or custom naming schemes).

Note: This PR was created with assistance using Claude Code

Motivation

Webpack provides compilation.hooks.beforeModuleIds which allows plugins to intercept the module ID assignment process. Some webpack plugins rely on this hook to:

  • Assign deterministic/stable module IDs for long-term caching
  • Provide human-readable module IDs for debugging
  • Implement custom module ID schemes for specific use cases

Without this hook, these plugins cannot be ported to rspack.

Related links

Closes: #10764

Implementation

Key Design Decisions

Proxy-based Module Exposure: To provide full webpack API compatibility, the JS implementation uses a Proxy pattern:

  1. Rust collects all module identifiers needing IDs and sends them to JS in a single call
  2. JS looks up the real Module objects from compilation.modules by identifier
  3. Each module is wrapped in a Proxy that:
    • Exposes all real module properties (libIdent(), userRequest, type, rootModule, etc.)
    • Intercepts id setter to track assignments in a Map
  4. Plugins iterate and can access full module API while setting module.id = "custom-id"
  5. All assignments are returned to Rust in a single response
  6. Rust applies all ID assignments to the artifact

This approach ensures:

  • Only one round-trip across the Rust/JS boundary regardless of how many modules are processed
  • Full access to module properties needed for sophisticated ID generation (e.g., libIdent() for context-relative paths, userRequest for the original import path, rootModule for concatenated modules)

Artifact Passing: The moduleIds plugins (deterministic, natural) need to see IDs assigned by beforeModuleIds. This required adding a new helper function get_used_module_ids_and_modules_with_artifact that accepts the artifact as a parameter, since the artifact is temporarily moved out of compilation during hook invocation.

Test

Added tests/rspack-test/configCases/hooks/before-module-ids/ with:

  • rspack.config.js - Plugin that assigns custom ID to index.js module
  • index.js - Test entry point
  • a.js - Helper module

The test verifies:

  1. The hook is called with modules
  2. Custom IDs can be assigned via module.id = "..."
  3. The custom ID appears in the final build stats
  4. Real module properties are accessible (libIdent(), userRequest, type)

Usage

compiler.hooks.compilation.tap("MyPlugin", compilation => {
  compilation.hooks.beforeModuleIds.tap("MyPlugin", modules => {
    for (const module of modules) {
      if (module.identifier.includes("my-special-module")) {
        module.id = "my-custom-id";
      }
    }
  });
});

API

Hook Signature

beforeModuleIds: SyncHook<[Iterable<Module>]>

Module Object

Each module in the iterable is a proxied Module object with full access to:

Core Properties:

  • identifier - The module's unique identifier (string)
  • id - The module's ID (read/write, set this to assign a custom ID)
  • type - The module type (e.g., "javascript/auto")
  • context - The module's context directory
  • layer - The module's layer (if any)

Methods:

  • libIdent(options: { context: string }) - Returns a context-relative identifier suitable for stable IDs

NormalModule Properties:

  • userRequest - The user's original request string
  • rawRequest - The raw request before resolution
  • resource - The resolved resource path
  • resourceResolveData - Full resolution data
  • loaders - Array of loaders applied to this module
  • matchResource - The match resource (if using !=! syntax)

ConcatenatedModule Properties:

  • rootModule - The root module of the concatenated module
  • modules - Array of all modules in the concatenation

Testing

All existing tests pass (563 tests in Config.part2.test.js which includes hooks tests).

✓ config > configCases-configCases/hooks/before-module-ids > should pass

Checklist

  • Hook definition follows existing patterns (define_hook! macro)
  • Hook is registered in CompilationHooks struct
  • JS bindings use NAPI object pattern
  • TypeScript types are properly defined
  • Test case added with property access verification
  • cargo fmt applied
  • Biome linting passes

@netlify
Copy link

netlify bot commented Jan 21, 2026

Deploy Preview for rspack canceled.

Built without sensitive environment variables

Name Link
🔨 Latest commit ad55a72
🔍 Latest deploy log https://app.netlify.com/projects/rspack/deploys/6970e8ed0318b10009060daa

@github-actions github-actions bot added the release: feature release: feature related release(mr only) label Jan 21, 2026
@hamlim hamlim marked this pull request as ready for review January 21, 2026 15:41
@hamlim hamlim requested a review from hardfist as a code owner January 21, 2026 15:41
@hardfist
Copy link
Contributor

@hamlim have you checked the performance cost when using this hooks for your case, I'm not sure whether this will slow down build too much?

@hamlim
Copy link
Author

hamlim commented Jan 22, 2026

Sorry for the noise on the PR, just re-synced my fork and rebased on top of latest main to clean up some of the misc commits that had briefly ended up in this PR.

@hamlim hamlim force-pushed the hamlim/before-module-ids-hook branch from 721ccde to 40b7ea9 Compare January 23, 2026 11:40
@hamlim
Copy link
Author

hamlim commented Jan 23, 2026

@hardfist I created a minimal test repo here to compare perf of an rspack app with and without using the new hook: https://github.com/hamlim/rspack-before-module-ids-perf-test

It is a bit slower on my machine, see bench-results.json or the readme for more details

@github-actions
Copy link
Contributor

github-actions bot commented Jan 24, 2026

📝 Benchmark detail: Open

task failure

@hamlim hamlim force-pushed the hamlim/before-module-ids-hook branch 2 times, most recently from 6b65752 to daa512b Compare January 26, 2026 16:21
@hardfist hardfist force-pushed the hamlim/before-module-ids-hook branch from daa512b to 217d89a Compare January 27, 2026 06:24
@github-actions
Copy link
Contributor

github-actions bot commented Jan 27, 2026

📝 Benchmark detail: Open

task failure

@hamlim hamlim force-pushed the hamlim/before-module-ids-hook branch from 217d89a to dc4de6e Compare January 27, 2026 12:40
@github-actions
Copy link
Contributor

github-actions bot commented Jan 27, 2026

📝 Benchmark detail: Open

task failure

@hamlim hamlim force-pushed the hamlim/before-module-ids-hook branch from dc4de6e to c47745e Compare January 27, 2026 13:25
@codspeed-hq
Copy link

codspeed-hq bot commented Jan 27, 2026

Merging this PR will improve performance by 12.67%

⚡ 1 improved benchmark
✅ 15 untouched benchmarks
⏩ 1 skipped benchmark1

Performance Changes

Benchmark BASE HEAD Efficiency
bundle@threejs-development 831.1 ms 737.7 ms +12.67%

Comparing hamlim:hamlim/before-module-ids-hook (f60d97f) with main (855d938)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@hamlim hamlim force-pushed the hamlim/before-module-ids-hook branch from c47745e to f60d97f Compare January 28, 2026 13:36
@hamlim
Copy link
Author

hamlim commented Jan 28, 2026

The wasm test seems to report as a timeout, but it seems like the underlying tests actually complete pretty quickly?

@CPunisher
Copy link
Contributor

CPunisher commented Jan 29, 2026

Let me check the CI problem. Sadly, it seems like a flaky failure 😅

@github-actions
Copy link
Contributor

github-actions bot commented Jan 29, 2026

📝 Benchmark detail: Open

Name Base (2026-01-29 855d938) Current Change
10000_big_production-mode_disable-minimize + exec 23.1 s ± 326 ms 23.1 s ± 363 ms -0.14 %
10000_development-mode + exec 1.3 s ± 16 ms 1.3 s ± 31 ms -0.29 %
10000_development-mode_hmr + stats 237 ms ± 5.7 ms 242 ms ± 3.7 ms +2.09 %
10000_development-mode_noop-loader + exec 2.25 s ± 61 ms 2.24 s ± 39 ms -0.62 %
10000_production-mode + exec 1.35 s ± 20 ms 1.38 s ± 25 ms +2.35 %
10000_production-mode_persistent-cold + exec 1.52 s ± 20 ms 1.54 s ± 47 ms +0.96 %
10000_production-mode_persistent-hot + exec 1.05 s ± 16 ms 1.08 s ± 28 ms +2.86 %
arco-pro_development-mode + exec 1.5 s ± 78 ms 1.51 s ± 72 ms +0.61 %
arco-pro_development-mode_hmr + stats 40 ms ± 1.3 ms 40 ms ± 1.1 ms +1.09 %
arco-pro_production-mode + exec 2.94 s ± 77 ms 2.93 s ± 45 ms -0.41 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.01 s ± 170 ms 2.97 s ± 94 ms -1.62 %
arco-pro_production-mode_persistent-cold + exec 3.13 s ± 314 ms 3 s ± 85 ms -4.08 %
arco-pro_production-mode_persistent-hot + exec 1.76 s ± 66 ms 1.72 s ± 52 ms -2.60 %
arco-pro_production-mode_traverse-chunk-modules + exec 2.98 s ± 74 ms 2.93 s ± 76 ms -1.85 %
large-dyn-imports_development-mode + exec 1.59 s ± 51 ms 1.57 s ± 86 ms -1.28 %
large-dyn-imports_production-mode + exec 1.72 s ± 25 ms 1.73 s ± 42 ms +0.12 %
threejs_development-mode_10x + exec 1.28 s ± 18 ms 1.28 s ± 14 ms -0.30 %
threejs_development-mode_10x_hmr + stats 209 ms ± 4.7 ms 203 ms ± 4.5 ms -2.76 %
threejs_production-mode_10x + exec 4.06 s ± 28 ms 3.96 s ± 50 ms -2.42 %
threejs_production-mode_10x_persistent-cold + exec 4.2 s ± 29 ms 4.11 s ± 63 ms -2.26 %
threejs_production-mode_10x_persistent-hot + exec 3.59 s ± 20 ms 3.6 s ± 42 ms +0.20 %
10000_big_production-mode_disable-minimize + rss memory 5743 MiB ± 300 MiB 5750 MiB ± 137 MiB +0.12 %
10000_development-mode + rss memory 619 MiB ± 23.7 MiB 621 MiB ± 25.8 MiB +0.38 %
10000_development-mode_hmr + rss memory 789 MiB ± 38.3 MiB 799 MiB ± 16.7 MiB +1.34 %
10000_development-mode_noop-loader + rss memory 910 MiB ± 33.1 MiB 928 MiB ± 13.3 MiB +1.99 %
10000_production-mode + rss memory 648 MiB ± 52.4 MiB 635 MiB ± 22.4 MiB -1.97 %
10000_production-mode_persistent-cold + rss memory 779 MiB ± 42.4 MiB 755 MiB ± 15.4 MiB -3.10 %
10000_production-mode_persistent-hot + rss memory 766 MiB ± 27.2 MiB 729 MiB ± 54.5 MiB -4.95 %
arco-pro_development-mode + rss memory 592 MiB ± 61.9 MiB 598 MiB ± 41.1 MiB +1.01 %
arco-pro_development-mode_hmr + rss memory 498 MiB ± 14.6 MiB 490 MiB ± 26.2 MiB -1.62 %
arco-pro_production-mode + rss memory 742 MiB ± 26.1 MiB 747 MiB ± 69.3 MiB +0.75 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 745 MiB ± 35.6 MiB 710 MiB ± 60.6 MiB -4.66 %
arco-pro_production-mode_persistent-cold + rss memory 786 MiB ± 60.6 MiB 778 MiB ± 67.2 MiB -0.97 %
arco-pro_production-mode_persistent-hot + rss memory 585 MiB ± 36.8 MiB 561 MiB ± 101 MiB -4.10 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 710 MiB ± 59.7 MiB 712 MiB ± 62.1 MiB +0.35 %
large-dyn-imports_development-mode + rss memory 652 MiB ± 4.98 MiB 646 MiB ± 6.07 MiB -0.87 %
large-dyn-imports_production-mode + rss memory 562 MiB ± 14.2 MiB 558 MiB ± 8.54 MiB -0.76 %
threejs_development-mode_10x + rss memory 585 MiB ± 14.4 MiB 585 MiB ± 31 MiB -0.06 %
threejs_development-mode_10x_hmr + rss memory 887 MiB ± 20.5 MiB 879 MiB ± 16.4 MiB -0.92 %
threejs_production-mode_10x + rss memory 736 MiB ± 91.7 MiB 695 MiB ± 105 MiB -5.56 %
threejs_production-mode_10x_persistent-cold + rss memory 816 MiB ± 47.3 MiB 810 MiB ± 65.4 MiB -0.66 %
threejs_production-mode_10x_persistent-hot + rss memory 704 MiB ± 24.7 MiB 665 MiB ± 54.2 MiB -5.54 %

@hamlim hamlim force-pushed the hamlim/before-module-ids-hook branch from f60d97f to ce3d716 Compare February 1, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release: feature release: feature related release(mr only)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support custom module identifiers (ids)

3 participants