Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { getModulePathCache } from "../cache/index.ts";
import { hashString } from "../utils/hash.ts";
import { resolveModuleFile } from "../resolution/file-finder.ts";
import { getTransformCacheKey, getVersionedPathCacheKey } from "./cache-keys.ts";
import { resolveNestedModuleImports } from "./nested-imports.ts";
import { resolveNestedImportBase, resolveNestedModuleImports } from "./nested-imports.ts";
import { readDistributedCache } from "./distributed-cache.ts";
import { resolveUnresolvedModuleViaHttpFallback } from "./http-fallback.ts";
import { normalizePath } from "./module-cache.ts";
Expand Down Expand Up @@ -285,6 +285,7 @@ async function doFetchAndCacheModule(
moduleCode,
esmCacheDir,
normalizedPath,
parentBasePath: resolveNestedImportBase(normalizedPath, actualFilePath),
strictMissingModules: context.strictMissingModules ?? true,
projectSlug,
fetchAndCacheModule: fetchAndCacheModuleFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { describe, it } from "#veryfront/testing/bdd.ts";
import {
findNestedImports,
hasUnresolvedImports,
resolveNestedImportBase,
resolveNestedModuleImports,
} from "./nested-imports.ts";

Expand Down Expand Up @@ -159,3 +160,56 @@ import { bar } from "./local.js";
});
});
});

describe("resolveNestedImportBase", () => {
// A barrel lives at lib/index.ts but is addressed as _vf_modules/lib.
// Resolving its children against "_vf_modules/lib.js" drops the "lib"
// segment, so ./constants.js resolved to _vf_modules/constants.js — one
// directory too high. The file was then stubbed and the barrel silently
// stopped re-exporting: "does not provide an export named 'COLORS'".
it("keeps the directory segment for an index module", () => {
assertEquals(
resolveNestedImportBase("_vf_modules/lib.js", "/project/lib/index.ts"),
"_vf_modules/lib/index.js",
);
assertEquals(
resolveNestedImportBase("_vf_modules/components.js", "/project/components/index.tsx"),
"_vf_modules/components/index.js",
);
});

it("leaves a plain module untouched", () => {
assertEquals(
resolveNestedImportBase("_vf_modules/lib/constants.js", "/project/lib/constants.ts"),
"_vf_modules/lib/constants.js",
);
});

it("does not double up when the path already names index", () => {
assertEquals(
resolveNestedImportBase("_vf_modules/lib/index.js", "/project/lib/index.ts"),
"_vf_modules/lib/index.js",
);
});

it("is a no-op without a resolved file path", () => {
assertEquals(resolveNestedImportBase("_vf_modules/lib.js"), "_vf_modules/lib.js");
});

it("does not treat a file merely named index-something as an index module", () => {
assertEquals(
resolveNestedImportBase("_vf_modules/lib.js", "/project/lib/indexer.ts"),
"_vf_modules/lib.js",
);
});

it("recognises every index extension the resolver accepts", () => {
for (const ext of ["ts", "tsx", "js", "jsx", "mdx", "md"]) {
assertEquals(
resolveNestedImportBase("_vf_modules/lib.js", `/project/lib/index.${ext}`),
"_vf_modules/lib/index.js",
ext,
);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,38 @@ export interface ResolveNestedModuleImportsInput {
strictMissingModules: boolean;
fetchAndCacheModule: (path: string, parent?: string) => Promise<string | null>;
log?: Logger;
/**
* Path this module's relative imports resolve against. Defaults to
* `normalizedPath`; see {@link resolveNestedImportBase}.
*/
parentBasePath?: string;
}

/**
* The path a module's own relative imports should resolve against.
*
* A directory barrel lives at `lib/index.ts` but is addressed as
* `_vf_modules/lib`. Resolving its children against `_vf_modules/lib.js` drops
* the trailing segment as if it were a filename, so `./constants.js` becomes
* `_vf_modules/constants.js` — one directory too high. The file is then not
* found and gets replaced by a stub, and the barrel silently stops re-exporting
* anything: `does not provide an export named 'COLORS'`.
*
* When the module actually resolved to an index file, keep the directory
* segment by addressing it as `<dir>/index.js`.
*/
export function resolveNestedImportBase(
normalizedPath: string,
actualFilePath?: string,
): string {
if (!actualFilePath || !/(?:^|\/)index\.(?:tsx?|jsx?|mdx|md)$/.test(actualFilePath)) {
return normalizedPath;
}

const withoutExt = normalizedPath.replace(/\.(?:js|mjs)$/, "");
if (withoutExt.endsWith("/index")) return normalizedPath;

return `${withoutExt}/index.js`;
}

/**
Expand Down Expand Up @@ -167,7 +199,10 @@ export async function resolveNestedModuleImports(
original,
start,
end,
nestedFilePath: await input.fetchAndCacheModule(path, input.normalizedPath),
nestedFilePath: await input.fetchAndCacheModule(
path,
input.parentBasePath ?? input.normalizedPath,
),
[key]: path,
})),
);
Expand Down