Files
cloudflare__vinext/tests/tsconfig-paths-config-loader.test.ts
T
James Anderson eccee1843e fix(deploy-suite): apply tsconfig compilerOptions.paths when loading next.config.ts (#1215)
Next.js's own loader (packages/next/src/build/next-config-ts/transpile-config.ts)
reads compilerOptions.paths + baseUrl from tsconfig.json and passes them to SWC
so next.config.ts can import via tsconfig aliases like:

    import { foo } from '@/foo'

vinext's loadNextConfig() calls Vite's runnerImport(), but did not configure any
resolve.alias entries, so the same imports fail with:

    [vinext] Failed to load next.config.ts:
      Cannot find module '@/foo' imported from '<dir>/next.config.ts'

This is a parity gap surfaced by the Next.js deploy test suite.

Fix: extract a small tsconfig-paths loader (config/tsconfig-paths.ts) that
reads compilerOptions.paths (following extends, honoring baseUrl) and returns
absolute Vite resolve.alias entries. loadNextConfig now passes those aliases
into runnerImport's inlineConfig.

Affected suites (from run 25870737355):

  - e2e/app-dir/next-config-ts/import-alias-paths-only/
  - e2e/app-dir/next-config-ts/import-alias-paths-with-baseurl/
  - e2e/app-dir/next-config-ts/import-js-extensions-cjs/
  - e2e/app-dir/next-config-ts/import-js-extensions-esm/
  - e2e/app-dir/next-config-ts-native-ts/dynamic-import-esm/
  - e2e/app-dir/next-config-ts-native-mts/import-from-node-modules/

Verification:
  - 9 new unit tests for loadTsconfigPathAliasesForRoot
  - 4 new unit tests covering loadNextConfig + tsconfig paths (ported from
    Next.js's import-alias-paths-only / import-alias-paths-with-baseurl
    fixtures), including extends + jsconfig fallback
  - Reproduced against .nextjs-ref/test/e2e/app-dir/next-config-ts/
    import-alias-paths-only/ — config now loads with `foo === "foo"`
  - vp check (fmt + lint + types) clean
  - bash -n scripts/*.sh clean

Refs: cloudflare/vinext#actions/runs/25870737355
2026-05-14 19:36:21 +01:00

149 lines
4.0 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vite-plus/test";
import { loadTsconfigPathAliasesForRoot } from "../packages/vinext/src/config/tsconfig-paths.js";
function makeTempDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), "vinext-tsconfig-paths-test-"));
}
describe("loadTsconfigPathAliasesForRoot", () => {
let tmpDir: string;
afterEach(() => {
if (tmpDir) {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it("returns absolute alias paths for wildcard mapping without baseUrl", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "tsconfig.json"),
JSON.stringify({
compilerOptions: {
paths: {
"@/*": ["./src/*"],
},
},
}),
);
const aliases = loadTsconfigPathAliasesForRoot(tmpDir);
expect(aliases["@"]).toBe(path.join(tmpDir, "src"));
});
it("supports baseUrl with relative path values", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "tsconfig.json"),
JSON.stringify({
compilerOptions: {
baseUrl: ".",
paths: {
"@/*": ["src/*"],
},
},
}),
);
const aliases = loadTsconfigPathAliasesForRoot(tmpDir);
expect(aliases["@"]).toBe(path.join(tmpDir, "src"));
});
it("follows 'extends' for inherited paths", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "tsconfig.base.json"),
JSON.stringify({
compilerOptions: { paths: { "@/*": ["./lib/*"] } },
}),
);
fs.writeFileSync(
path.join(tmpDir, "tsconfig.json"),
JSON.stringify({ extends: "./tsconfig.base.json" }),
);
const aliases = loadTsconfigPathAliasesForRoot(tmpDir);
expect(aliases["@"]).toBe(path.join(tmpDir, "lib"));
});
it("child paths override extended paths", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "tsconfig.base.json"),
JSON.stringify({
compilerOptions: { paths: { "@/*": ["./lib/*"] } },
}),
);
fs.writeFileSync(
path.join(tmpDir, "tsconfig.json"),
JSON.stringify({
extends: "./tsconfig.base.json",
compilerOptions: { paths: { "@/*": ["./src/*"] } },
}),
);
const aliases = loadTsconfigPathAliasesForRoot(tmpDir);
expect(aliases["@"]).toBe(path.join(tmpDir, "src"));
});
it("supports non-wildcard exact alias", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "tsconfig.json"),
JSON.stringify({
compilerOptions: {
paths: {
"@app": ["./src/app.ts"],
},
},
}),
);
const aliases = loadTsconfigPathAliasesForRoot(tmpDir);
expect(aliases["@app"]).toBe(path.join(tmpDir, "src", "app.ts"));
});
it("returns empty object when tsconfig.json is missing", () => {
tmpDir = makeTempDir();
expect(loadTsconfigPathAliasesForRoot(tmpDir)).toEqual({});
});
it("falls back to jsconfig.json when tsconfig.json is absent", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "jsconfig.json"),
JSON.stringify({
compilerOptions: {
paths: { "@/*": ["./src/*"] },
},
}),
);
const aliases = loadTsconfigPathAliasesForRoot(tmpDir);
expect(aliases["@"]).toBe(path.join(tmpDir, "src"));
});
it("ignores malformed wildcard patterns", () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "tsconfig.json"),
JSON.stringify({
compilerOptions: {
// Invalid: wildcard not at end
paths: { "@*x/*": ["./src/*"] },
},
}),
);
expect(loadTsconfigPathAliasesForRoot(tmpDir)).toEqual({});
});
it("returns empty object on unparseable tsconfig.json", () => {
tmpDir = makeTempDir();
fs.writeFileSync(path.join(tmpDir, "tsconfig.json"), "not valid json{{{");
expect(loadTsconfigPathAliasesForRoot(tmpDir)).toEqual({});
});
});