fix: anchor relative dynamic imports at the file:// referrer's directory#1976
Conversation
A dynamic import() with a relative specifier from a module loaded over file:// resolved against the application root instead of the importing module's own directory, so a module outside the app root could not resolve its relative dependencies. ImportModuleDynamicallyCallback only used the referrer URL for http(s) referrers; for file:// it passed an empty referrer and the resolver fell back to the app root. Anchor the specifier at the referrer's directory from resource_name and pass the resolver an absolute file:// URL. Static imports are unaffected. Adds test-app specs for ./ and ../ dynamic imports from subdirectories plus an app-root control.
📝 WalkthroughWalkthroughAdds relative dynamic ChangesRelative Dynamic Import Resolution
Sequence Diagram(s)sequenceDiagram
participant V8 as V8 Engine
participant IMDC as ImportModuleDynamicallyCallback
participant RFR as ResolveFileRelative
participant NDS as NormalizeDotSegments
participant RMC as ResolveModuleCallback
V8->>IMDC: dynamic import("./sibling.mjs", referrer=file:///app/esm-subdir/parent.mjs)
IMDC->>RFR: ResolveFileRelative(referrer, "./sibling.mjs")
RFR->>RFR: strip fragment/query → base dir = /app/esm-subdir/
RFR->>NDS: NormalizeDotSegments("/app/esm-subdir/sibling.mjs")
NDS-->>RFR: "/app/esm-subdir/sibling.mjs"
RFR-->>IMDC: "file:///app/esm-subdir/sibling.mjs"
IMDC->>RMC: ResolveModuleCallback("file:///app/esm-subdir/sibling.mjs", empty referrer)
RMC-->>V8: resolved sibling module
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test-app/app/src/main/assets/app/tests/testESModules.mjs (1)
38-44: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPrefer terminal
done.failto avoid timeout-style failures in async specs.A thrown assertion in these callbacks can skip
done()and turn a direct assertion failure into a spec timeout. Using a terminal rejection handler gives cleaner failures.Proposed pattern
it("resolves a relative dynamic import from a subdirectory module", (done) => { import("~/esm-subdir/parent.mjs") .then((parent) => parent.loadSibling()) - .then( - (value) => { expect(value).toBe("sibling-loaded"); done(); }, - (err) => { expect(err).toBeUndefined(); done(); } - ); + .then((value) => { + expect(value).toBe("sibling-loaded"); + }) + .then(() => done(), done.fail); });Apply the same shape to the other two specs in this block.
Also applies to: 47-53, 56-62
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test-app/app/src/main/assets/app/tests/testESModules.mjs` around lines 38 - 44, The test "resolves a relative dynamic import from a subdirectory module" and the two similar specs in this block use explicit error callbacks that can cause timeout-style failures when assertions fail. Replace the second argument (the rejection handler) in each of the three promise chain `.then()` calls with `done.fail`, which properly handles assertion failures and provides cleaner test failure messages. This pattern change should be applied to all three specs: the one starting with "resolves a relative dynamic import from a subdirectory module" and the two other specs mentioned in the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@test-app/app/src/main/assets/app/tests/testESModules.mjs`:
- Around line 38-44: The test "resolves a relative dynamic import from a
subdirectory module" and the two similar specs in this block use explicit error
callbacks that can cause timeout-style failures when assertions fail. Replace
the second argument (the rejection handler) in each of the three promise chain
`.then()` calls with `done.fail`, which properly handles assertion failures and
provides cleaner test failure messages. This pattern change should be applied to
all three specs: the one starting with "resolves a relative dynamic import from
a subdirectory module" and the two other specs mentioned in the comment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 6819f21a-2871-43cd-8d2b-94e764298eeb
📒 Files selected for processing (6)
test-app/app/src/main/assets/app/esm-subdir/nested/child.mjstest-app/app/src/main/assets/app/esm-subdir/parent.mjstest-app/app/src/main/assets/app/esm-subdir/sibling.mjstest-app/app/src/main/assets/app/testRelativeDynamicImport.mjstest-app/app/src/main/assets/app/tests/testESModules.mjstest-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp
|
@coderabbitai, the test-app pins Jasmine 2.0.1, which has no This version's expectations also do not throw on failure ( |
|
Tip For best results, initiate chat on the files or code changes.
✏️ Learnings added
|
|
Tip For best results, initiate chat on the files or code changes.
|
|
Nice fix, ios may need similar @adrian-niculescu ? |
|
@NathanWalker, thanks and thank you for the quick review! iOS already handles this. In then passes the resulting absolute path to This Android change brings the file:// path to parity. Static imports were already fine on both platforms. |
A dynamic
import()with a relative specifier (./x,../x) from a module loaded overfile://resolves against the application root instead of the importing module's own directory. A module that lives in a subdirectory then fails to resolve its relative dependencies.ImportModuleDynamicallyCallbackreads the referrer URL but only uses it when the referrer ishttp(s). Forfile://referrers it passes an empty referrer toResolveModuleCallback, which falls back to anchoring relative specifiers at the app root. Static imports are unaffected because V8 passes the real parent module.This anchors the relative specifier at the referrer's directory, taken from
resource_name, and passes the resolver an absolutefile://URL. It is a no-op when the importing module already sits at the app root. The iOS runtime already resolves this case the same way.Test: added test-app ES module specs for a relative
./import from a subdirectory, a../import from a nested directory, and an app-root control. The first two fail before the change and pass after.Summary by CodeRabbit
Release Notes
New Features
Tests