refactor(rolldown_common): model ModuleId as a classified Path/Virtual/Bare enum#9927
Conversation
✅ Deploy Preview for rolldown-rs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 54a11609e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Merging this PR will improve performance by 3.78%
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
|
One behavioral subtlety worth confirming around the new
!importer_id.is_in_node_modules() && pre_rendered_chunk.module_ids.contains(importer_id)
pub fn is_in_node_modules(&self) -> bool {
self.as_path().is_some_and(rolldown_std_utils::PathExt::is_in_node_modules)
}For a Since |
|
@IWANABETHATGUY confirmed intentional — and good catch that the branch was untested.
On the coverage gap: the suppression branch has had no test since it was introduced in #8284 — the existing fixtures only exercise the positive-warning path with root-level importers, so nothing guarded the short-circuit either way. I've added |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff0253ea81
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Addressed both Codex review threads in 1. Stale module-id internal doc ( 2. |
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Merge activity
|
…l/Bare enum (#9927) ## Summary `ModuleId` was a `struct { inner: ArcStr }` that every call site re-interpreted as a filesystem path through `SugarPath::as_path()` — treating virtual (`\0…`), bare (`react`), and URL ids as paths, and round-tripping bytes we already know are UTF-8 through `to_string_lossy`. This remodels it as an enum classified **once** at construction: ```rust pub struct ModuleId { repr: Repr } // private enum Repr { Path | Virtual | Bare } ``` - **Gated path ops** — `as_path() -> Option<&Path>` (`Some` only for `Path`), plus `is_path()`, `is_virtual()`, `kind()`, `is_in_node_modules()`, `representative_name()`. Non-path ids are no longer silently path-parsed. - **`ArcStr` kept** as the backing store — cheap clone, durable cross-build identity, `literal!` const for the runtime module. `ModuleIdx` (the `u32` per-build handle) stays the hot graph key, so `ModuleId` optimizes for being a faithful string/path identity rather than for size/hash speed. - **Lossless** — every `to_string_lossy` on a `ModuleId` / `StableModuleId`-derived path becomes `to_str()` (module ids are always valid UTF-8). This is done in the shared `path_ext` helpers, so it also covers `ast_scanner` and preserve-modules naming. - **`Hash` / `Eq` / `Ord` / `Borrow<str>`** are hand-implemented over `as_str()` (byte-exact, ignoring the discriminant) so `&str` lookups into `module_id_to_idx` / `user_defined_entry` keep working. - **`StableModuleId::new`** branches on the cached `kind()` instead of re-running `is_absolute()` / `\0` detection. ## Why A module id is sometimes a path and sometimes not. Treating it uniformly as a path was wasteful (redundant UTF-8 scans, `to_string_lossy` on known-UTF-8 data) and a latent correctness hazard. Classifying once makes the path / not-path distinction explicit and confines path semantics to where they belong. ## Validation - `cargo test` — 1756 integration/snapshot tests pass with **zero snapshot/fixture churn**; behavior is identical. - `clippy` (pedantic) and `fmt` clean. - **Perf-neutral.** Benchmarked main vs branch (criterion, `bundle` group); a main-vs-main control showed the apparent ~3% delta was a first-run warm-up artifact correlated with case order, not the code — no regression, no real speedup, as expected for a modeling change whose per-module path logic is cost-equivalent.
1b7cbe4 to
8ed0b76
Compare
## [1.1.3] - 2026-06-24 ### 🐛 Bug Fixes - `defer_drop` crashes the browser main thread (#9942) by @shulaoda - camel-case: correct camel case for nested values (#9933) by @kb019 - cli: display --help options in camelCase (#9941) by @IWANABETHATGUY - preserve used re-exports under preserveModules (#9122) (#9934) by @IWANABETHATGUY - watch: make close reentrant in event callbacks (#9904) by @hyf0 - git for windows treats symlink files as regular files (#9915) by @AliceLanniste - dev: cancel pending full reload on build error (#9903) by @h-a-n-a - chunking: pass plugin meta to codeSplitting groups name function (#9267) by @Kyujenius - dev: serve assets emitted during HMR/lazy compile (vite#22596) (#9815) by @h-a-n-a - release: dry-run step no longer publishes binding packages (#9866) by @Boshen ### 🚜 Refactor - rolldown_common: model ModuleId as a classified Path/Virtual/Bare enum (#9927) by @Boshen - remove unused LegacyModuleIdx (#9872) by @shulaoda - remove unused StmtInfos::get_namespace_stmt_info (#9870) by @shulaoda - remove unused Module::as_external_mut (#9871) by @shulaoda - remove unused EcmaAst::is_body_empty (#9869) by @shulaoda - drop dead is_css_module handling in resolve_dependencies (#9867) by @shulaoda - drop redundant with_commonjs on cjs source type (#9868) by @shulaoda ### 📚 Documentation - clarify on drafting PRs (#9952) by @h-a-n-a - update contribution guidelines (#9944) by @fubhy - note Rust crates don't follow semver in AGENTS.md (#9905) by @IWANABETHATGUY - add feedback form (#9159) by @TheAlexLichter ### ⚡ Performance - utils: avoid allocation in default_sanitize_file_name for clean names (#9928) by @Boshen - binding: box once-per-build futures before spawn_future (#9864) by @Boshen - utils: avoid wasted allocation in legitimize_identifier_name (#9926) by @Boshen - rolldown: fuse the canonical-name dedup and insert in the renamer (#9900) by @Boshen - rolldown: probe the name map once in ConflictResolver::resolve (#9899) by @Boshen - cut two heap allocations from wrapped ESM init finalize (#9901) by @Boshen - rolldown_plugin_vite_reporter: hoist invariant out_dir prefix out of reporter loop (#9873) by @shulaoda - drop throwaway Vec in wrapped esm init stmt (#9878) by @shulaoda - borrow owner_filename in build-import-analysis AddDeps (#9874) by @shulaoda ### 🧪 Testing - cover preserveModules named export via namespace re-export (#6010) (#9937) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - deps: update napi to v3.9.4 (#9954) by @shulaoda - reduce noise from CODEOWNERS for trival changes (#9953) by @h-a-n-a - deps: update mimalloc-safe to 0.1.64 (#9950) by @shulaoda - deps: update rollup submodule for tests to v4.62.2 (#9931) by @rolldown-guard[bot] - deps: test mimalloc-safe upstream-mimalloc switch in CI (#9930) by @shulaoda - rolldown_plugin_vite_build_import_analysis: remove unused v2 code path (#9917) by @shulaoda - rolldown_plugin_vite_manifest: remove unused is_enable_v2 code path (#9916) by @shulaoda - rolldown_plugin_vite_asset_import_meta_url: remove unexposed native vite plugin (#9896) by @shulaoda - rolldown_plugin_vite_asset: remove unexposed native vite plugin (#9895) by @shulaoda - rolldown_plugin_vite_css_post: remove unexposed native vite plugin (#9894) by @shulaoda - rolldown_plugin_vite_css: remove unexposed native vite plugin (#9893) by @shulaoda - rolldown_plugin_vite_html_inline_proxy: remove unexposed native vite plugin (#9892) by @shulaoda - rolldown_plugin_vite_html: remove unexposed native vite plugin (#9891) by @shulaoda - deps: update github actions (#9909) by @renovate[bot] - deps: update rust crate oxc_sourcemap to v8.0.2 (#9910) by @renovate[bot] - deps: update npm packages (#9912) by @renovate[bot] - deps: update github actions to v7 (#9913) by @renovate[bot] - deps: update rolldown-plugin-dts to ^0.26.0 (#9897) by @renovate[bot] - remove rolldown_filter_analyzer crate (#9865) by @Boshen ### ❤️ New Contributors * @fubhy made their first contribution in [#9944](#9944) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Summary
ModuleIdwas astruct { inner: ArcStr }that every call site re-interpreted as a filesystem path throughSugarPath::as_path()— treating virtual (\0…), bare (react), and URL ids as paths, and round-tripping bytes we already know are UTF-8 throughto_string_lossy.This remodels it as an enum classified once at construction:
as_path() -> Option<&Path>(Someonly forPath), plusis_path(),is_virtual(),kind(),is_in_node_modules(),representative_name(). Non-path ids are no longer silently path-parsed.ArcStrkept as the backing store — cheap clone, durable cross-build identity,literal!const for the runtime module.ModuleIdx(theu32per-build handle) stays the hot graph key, soModuleIdoptimizes for being a faithful string/path identity rather than for size/hash speed.to_string_lossyon aModuleId/StableModuleId-derived path becomesto_str()(module ids are always valid UTF-8). This is done in the sharedpath_exthelpers, so it also coversast_scannerand preserve-modules naming.Hash/Eq/Ord/Borrow<str>are hand-implemented overas_str()(byte-exact, ignoring the discriminant) so&strlookups intomodule_id_to_idx/user_defined_entrykeep working.StableModuleId::newbranches on the cachedkind()instead of re-runningis_absolute()/\0detection.Why
A module id is sometimes a path and sometimes not. Treating it uniformly as a path was wasteful (redundant UTF-8 scans,
to_string_lossyon known-UTF-8 data) and a latent correctness hazard. Classifying once makes the path / not-path distinction explicit and confines path semantics to where they belong.Validation
cargo test— 1756 integration/snapshot tests pass with zero snapshot/fixture churn; behavior is identical.clippy(pedantic) andfmtclean.bundlegroup); a main-vs-main control showed the apparent ~3% delta was a first-run warm-up artifact correlated with case order, not the code — no regression, no real speedup, as expected for a modeling change whose per-module path logic is cost-equivalent.