Skip to content

Commit 051f2ea

Browse files
committed
fix(unplugin-dts): resolve tsconfig paths without baseUrl relative to config dir
When baseUrl is absent in tsconfig.json, TypeScript 5.4+ resolves paths relative to the tsconfig.json directory. The plugin previously defaulted to process.cwd(), causing incorrect alias resolution. Fixes #458
1 parent 42c57a4 commit 051f2ea

2 files changed

Lines changed: 94 additions & 10 deletions

File tree

packages/unplugin-dts/src/core/runtime.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,19 @@ export class Runtime {
182182

183183
const outDirs = normalizeOutDirs(options.outDirs, root, defaultOutDir)
184184

185-
const {
186-
// Here we are using the default value to set the `baseUrl` to the current directory if no value exists. This is
187-
// the same behavior as the TS Compiler. See TS source:
188-
// https://github.com/microsoft/TypeScript/blob/3386e943215613c40f68ba0b108cda1ddb7faee1/src/compiler/utilities.ts#L6493-L6501
189-
baseUrl = compilerOptions.paths ? process.cwd() : undefined,
190-
paths,
191-
} = compilerOptions
192-
193-
if (pathsToAliases && baseUrl && paths) {
185+
const { baseUrl, paths } = compilerOptions
186+
187+
// When `paths` is used without `baseUrl` in tsconfig.json, TypeScript 5.4+
188+
// resolves paths relative to the containing directory of tsconfig.json.
189+
const resolvedBaseUrl = baseUrl ?? (paths && configPath ? dirname(configPath) : root)
190+
191+
if (pathsToAliases && resolvedBaseUrl && paths) {
194192
aliases.push(
195193
...parseTsAliases(
196-
ensureAbsolute(resolveConfigDir(baseUrl, root), configPath ? dirname(configPath) : root),
194+
ensureAbsolute(
195+
resolveConfigDir(resolvedBaseUrl, root),
196+
configPath ? dirname(configPath) : root,
197+
),
197198
paths,
198199
),
199200
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2+
import { tmpdir } from 'node:os'
3+
import { dirname, resolve } from 'node:path'
4+
import { afterEach, describe, expect, it } from 'vitest'
5+
6+
import { Runtime } from '../src/core/runtime'
7+
import { normalizePath } from '../src/core/utils'
8+
9+
describe('runtime tests', () => {
10+
let tempDir: string
11+
12+
afterEach(() => {
13+
if (tempDir) {
14+
rmSync(tempDir, { recursive: true, force: true })
15+
}
16+
})
17+
18+
it('should resolve paths relative to tsconfig dir when baseUrl is absent', async () => {
19+
tempDir = mkdtempSync(resolve(tmpdir(), 'unplugin-dts-'))
20+
21+
writeFileSync(
22+
resolve(tempDir, 'tsconfig.json'),
23+
JSON.stringify({
24+
compilerOptions: {
25+
paths: {
26+
'@/*': ['./src/*'],
27+
},
28+
},
29+
include: ['src/**/*'],
30+
}),
31+
)
32+
33+
mkdirSync(resolve(tempDir, 'src'))
34+
writeFileSync(resolve(tempDir, 'src', 'index.ts'), 'export const foo = 1\n')
35+
writeFileSync(resolve(tempDir, 'src', 'helper.ts'), 'export const bar = 2\n')
36+
37+
const runtime = await Runtime.toInstance({
38+
root: tempDir,
39+
tsconfigPath: 'tsconfig.json',
40+
pathsToAliases: true,
41+
})
42+
43+
const alias = runtime.aliases.find((a: any) =>
44+
typeof a.find === 'string' ? a.find === '@/' : a.find.test('@/helper'),
45+
)
46+
47+
expect(alias).toBeDefined()
48+
expect(normalizePath(alias!.replacement)).toBe(normalizePath(resolve(tempDir, 'src/$1')))
49+
})
50+
51+
it('should use baseUrl when explicitly set', async () => {
52+
tempDir = mkdtempSync(resolve(tmpdir(), 'unplugin-dts-'))
53+
54+
writeFileSync(
55+
resolve(tempDir, 'tsconfig.json'),
56+
JSON.stringify({
57+
compilerOptions: {
58+
baseUrl: './lib',
59+
paths: {
60+
'@/*': ['./src/*'],
61+
},
62+
},
63+
include: ['lib/src/**/*'],
64+
}),
65+
)
66+
67+
mkdirSync(resolve(tempDir, 'lib', 'src'), { recursive: true })
68+
writeFileSync(resolve(tempDir, 'lib', 'src', 'index.ts'), 'export const foo = 1\n')
69+
70+
const runtime = await Runtime.toInstance({
71+
root: tempDir,
72+
tsconfigPath: 'tsconfig.json',
73+
pathsToAliases: true,
74+
})
75+
76+
const alias = runtime.aliases.find((a: any) =>
77+
typeof a.find === 'string' ? a.find === '@/' : a.find.test('@/helper'),
78+
)
79+
80+
expect(alias).toBeDefined()
81+
expect(normalizePath(alias!.replacement)).toBe(normalizePath(resolve(tempDir, 'lib/src/$1')))
82+
})
83+
})

0 commit comments

Comments
 (0)