fix: normalize "." and ".." in resolved module paths to dedupe modules#1977
fix: normalize "." and ".." in resolved module paths to dedupe modules#1977adrian-niculescu wants to merge 1 commit into
Conversation
ResolveModuleCallback builds the on-disk path for a relative specifier without collapsing "." and ".." segments, so the same file imported as "./x" from /a/b and as "../x" from /a/b/c lands under two different registry keys. stat() resolves the segments so both locate the file, but the differing keys make V8 compile and evaluate the module twice: two instances with separate state, and import.meta.dirname becomes "/a/b/c/.." for the ".." spelling. Normalize the resolved path before it becomes the registry key, the same way the HTTP branch canonicalizes through CanonicalizeHttpUrlKey. The pass is lexical and runs for every on-disk candidate kind, and is a no-op for already-canonical paths. Adds a test-app spec that imports one module as "./counter.mjs" and "../counter.mjs" and asserts a single shared instance.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds ChangesESM Deduplication Fix and Tests
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@NathanWalker, (and @coderabbitai), I checked the iOS runtime and it doesn't need an equivalent of this change. Vanilla It's all in
So canonicalization on iOS happens earlier and in more places than the single pass here, and both sides end up keying the registry by the same canonical path. No parity PR needed on iOS. |
|
Tip For best results, initiate chat on the files or code changes.
Both approaches converge on the same canonical path as the registry key, so ✏️ Learnings added
|
ResolveModuleCallbackbuilds the on-disk path for a relative specifier without collapsing.and..segments, so the same file imported as./xfrom/a/band as../xfrom/a/b/clands under two different registry keys.stat()resolves the segments so both locate the file, but the differing keys make V8 compile and evaluate the module twice: two instances with separate module state, broken cyclic resolution, and animport.meta.dirnameof/a/b/c/..for the..spelling.This normalizes the resolved path before it becomes the registry key, the same way the HTTP branch already canonicalizes through
CanonicalizeHttpUrlKey. The pass is lexical (matching how specifiers are joined, not arealpath) and applies to every on-disk candidate kind; it is a no-op for already-canonical paths.Test: a test-app spec imports one module as
./counter.mjsand../counter.mjsand asserts they share one instance (===, and a counter incremented to 2). It fails before the change and passes after.Summary by CodeRabbit