-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(utils): resolve bundle-inlined modules in importResolve #5971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ import path from 'node:path'; | |
| import coffee from 'coffee'; | ||
| import { afterEach, describe, it } from 'vitest'; | ||
|
|
||
| import { importModule, setBundleModuleLoader } from '../src/import.ts'; | ||
| import { importModule, importResolve, setBundleModuleLoader } from '../src/import.ts'; | ||
| import { getFilepath } from './helper.ts'; | ||
|
|
||
| describe('test/bundle-import.test.ts', () => { | ||
|
|
@@ -135,4 +135,40 @@ describe('test/bundle-import.test.ts', () => { | |
| const result = await importModule(filepath); | ||
| assert.deepEqual(result, fakeModule); | ||
| }); | ||
|
|
||
| it('importResolve returns the path as canonical key for bundle-only modules', () => { | ||
| const seen: string[] = []; | ||
| setBundleModuleLoader((p) => { | ||
| seen.push(p); | ||
| return p === 'virtual/not-on-disk' ? { virtual: true } : undefined; | ||
| }); | ||
|
|
||
| // The module is inlined into the bundle and has no source on disk, but the | ||
| // loader recognizes it, so importResolve hands it back unchanged. | ||
| assert.equal(importResolve('virtual/not-on-disk'), 'virtual/not-on-disk'); | ||
| assert.ok(seen.includes('virtual/not-on-disk')); | ||
| }); | ||
|
|
||
| it('importResolve normalizes Windows-style paths before the bundle lookup', () => { | ||
| setBundleModuleLoader((p) => (p === 'virtual/win/mod' ? { windows: true } : undefined)); | ||
|
|
||
| const filepath = 'virtual/win/mod'.split(path.posix.sep).join(path.win32.sep); | ||
| assert.equal(importResolve(filepath), filepath); | ||
| }); | ||
|
|
||
| it('importResolve does not consult the loader once on-disk resolution succeeds', () => { | ||
| let called = false; | ||
| setBundleModuleLoader(() => { | ||
| called = true; | ||
| return { hit: true }; | ||
| }); | ||
|
|
||
| const resolved = importResolve(getFilepath('esm')); | ||
| assert.match(resolved, /[\\/]fixtures[\\/]esm[\\/]index\.js$/); | ||
| assert.equal(called, false); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }); | ||
|
|
||
| it('importResolve still throws for missing modules when no loader is registered', () => { | ||
| assert.throws(() => importResolve('virtual/not-on-disk')); | ||
| }); | ||
|
Comment on lines
+139
to
+173
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test case to verify that it('importResolve returns the path as canonical key for bundle-only modules', () => {
const seen: string[] = [];
setBundleModuleLoader((p) => {
seen.push(p);
return p === 'virtual/not-on-disk' ? { virtual: true } : undefined;
});
// The module is inlined into the bundle and has no source on disk, but the
// loader recognizes it, so importResolve hands it back unchanged.
assert.equal(importResolve('virtual/not-on-disk'), 'virtual/not-on-disk');
assert.ok(seen.includes('virtual/not-on-disk'));
});
it('importResolve resolves relative paths for bundle-only modules against paths option', () => {
const absolutePath = path.resolve(process.cwd(), 'virtual/relative/mod.js');
const normalizedAbsolute = absolutePath.split(path.win32.sep).join(path.posix.sep);
setBundleModuleLoader((p) => (p === normalizedAbsolute ? { relative: true } : undefined));
assert.equal(importResolve('./relative/mod', { paths: [path.resolve(process.cwd(), 'virtual')] }), absolutePath);
});
it('importResolve normalizes Windows-style paths before the bundle lookup', () => {
setBundleModuleLoader((p) => (p === 'virtual/win/mod' ? { windows: true } : undefined));
const filepath = 'virtual/win/mod'.split(path.posix.sep).join(path.win32.sep);
assert.equal(importResolve(filepath), filepath);
});
it('importResolve does not consult the loader once on-disk resolution succeeds', () => {
let called = false;
setBundleModuleLoader(() => {
called = true;
return { hit: true };
});
const resolved = importResolve(getFilepath('esm'));
assert.ok(resolved.endsWith('/fixtures/esm/index.js'));
assert.equal(called, false);
});
it('importResolve still throws for missing modules when no loader is registered', () => {
assert.throws(() => importResolve('virtual/not-on-disk'));
});
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Skipping the relative-path test for the reason explained on the import.ts thread: the fallback is intentionally symmetric with |
||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When resolving a relative path (e.g.,
./foo) to a bundle-only module, the current implementation only checks the unresolvedfilepathagainst the bundle loader. Since the bundle loader typically indexes modules by their absolute canonical paths, this check will fail. We should resolve the relative path against the providedpathsoption and check those resolved absolute paths (along with common extensions) against the bundle loader to ensure correct resolution.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, but I am intentionally not resolving relative paths here. This fallback is deliberately symmetric with
importModule, which keys the loader onnormalizeBundleModulePath(filepath)using the raw (unresolved)filepath.importResolvereturns that samefilepathas the canonical key precisely so a subsequentimportModule(filepath)looks the loader up with an identical key.Resolving
./footo an absolute path before the lookup would makeimportResolvereturn a different key than the oneimportModulekeys on, breaking that contract. The bundle loader itself owns the path namespace and decides what it recognizes; both functions hand it the same path-as-passed. So this is by design rather than an oversight, and I am leaving the relative-path test out for the same reason.