From 93164c3530a7b4fc7bbedfb986d6afa9546cdef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0?= Date: Wed, 2 Sep 2026 12:03:54 +0900 Subject: [PATCH 1/3] feat: add top-level `tsconfig` option (#23310) --- docs/config/shared-options.md | 14 ++++++++ docs/guide/features.md | 2 +- .../vite/src/node/__tests__/build.spec.ts | 24 ++++++++++++- .../vite/src/node/__tests__/config.spec.ts | 25 ++++++++++++++ .../src/node/__tests__/plugins/oxc.spec.ts | 34 +++++++++++++++++++ packages/vite/src/node/build.ts | 7 ++++ packages/vite/src/node/config.ts | 8 +++++ packages/vite/src/node/plugins/esbuild.ts | 2 +- packages/vite/src/node/plugins/oxc.ts | 5 ++- packages/vite/src/node/plugins/resolve.ts | 1 + 10 files changed, 118 insertions(+), 4 deletions(-) diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md index 2ef7c400527a55..9050af96fff37e 100644 --- a/docs/config/shared-options.md +++ b/docs/config/shared-options.md @@ -629,6 +629,20 @@ Ensure that `@vitejs/devtools` is installed as a dependency. This feature is cur See [Vite DevTools](https://github.com/vitejs/devtools) for more details. +## tsconfig + +- **Type:** `string` + +Path to the TypeScript configuration file used by Vite. Relative paths are resolved from the project [`root`](#root). + +When this option is not set, Vite discovers the closest matching `tsconfig.json` for each file. See [TypeScript Compiler Options](/guide/features#typescript-compiler-options) for more details. + +::: warning Prefer automatic discovery +Setting this option is discouraged because it overrides Vite's per-file tsconfig discovery which is aligned with TypeScript language server. Prefer placing a `tsconfig.json` near the files it configures and using TypeScript [`references`](https://www.typescriptlang.org/tsconfig/#references) for multi-project setups. + +If the goal is to remap imports, prefer [`resolve.alias`](#resolve-alias) or the `imports` and `exports` fields in `package.json` instead of selecting a tsconfig solely for [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig/#paths). Use this option only when automatic discovery cannot identify the intended configuration. +::: + ## future - **Type:** `Record` diff --git a/docs/guide/features.md b/docs/guide/features.md index bbd9ad8b921eb6..ababdef81c8cab 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -53,7 +53,7 @@ export type { T } ### TypeScript Compiler Options -Vite respects some of the options in `tsconfig.json` and sets the corresponding Oxc Transformer options. For each file, Vite uses the closest parent `tsconfig.json` that matches the file, or a config referenced by its [`references`](https://www.typescriptlang.org/tsconfig/#references) field that matches the file. Vite treats a config as matching the file when the file satisfies the config's [`files`](https://www.typescriptlang.org/tsconfig/#files), [`include`](https://www.typescriptlang.org/tsconfig/#include), and [`exclude`](https://www.typescriptlang.org/tsconfig/#exclude) fields. +Vite respects some of the options in `tsconfig.json` and sets the corresponding Oxc Transformer options. By default, Vite uses the closest parent `tsconfig.json` that matches each file. A config referenced by that config's [`references`](https://www.typescriptlang.org/tsconfig/#references) field is used when it matches the file. Vite treats a config as matching the file when the file satisfies the config's [`files`](https://www.typescriptlang.org/tsconfig/#files), [`include`](https://www.typescriptlang.org/tsconfig/#include), and [`exclude`](https://www.typescriptlang.org/tsconfig/#exclude) fields. When the options are set in both the Vite config and the `tsconfig.json`, the value in the Vite config takes precedence. diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index f87a27b37e9c45..c194760f856866 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -12,7 +12,7 @@ import type { RollupLog, } from 'rolldown' import { afterEach, describe, expect, assert, test, vi } from 'vitest' -import { BuildEnvironment, resolveConfig } from '..' +import { BuildEnvironment, normalizePath, resolveConfig } from '..' import type { LibraryFormats, LibraryOptions } from '../build' import { ChunkMetadataMap, @@ -488,6 +488,28 @@ describe('resolveBuildOutputs', () => { expect(options.input).toBe('explicit-entry.js') }) + test('top-level tsconfig applies to Rolldown options', async () => { + const builder = await createBuilder({ + root: buildProjectRoot, + logLevel: 'silent', + tsconfig: './custom.tsconfig.json', + build: { + rolldownOptions: { + tsconfig: './other.tsconfig.json', + resolve: { tsconfigFilename: './legacy.tsconfig.json' }, + }, + }, + }) + const options = resolveRolldownOptions( + builder.environments.client, + new ChunkMetadataMap(), + ) + expect(options.tsconfig).toBe( + normalizePath(resolve(buildProjectRoot, 'custom.tsconfig.json')), + ) + expect(options.resolve?.tsconfigFilename).toBeUndefined() + }) + test('falls back to index.html when no input is set', async () => { const builder = await createBuilder({ root: buildProjectRoot, diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 0dcd4645745d85..3a68ba8853ab42 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -1154,6 +1154,31 @@ describe('resolveConfig', () => { expect(results2.clearScreen).toBe(false) }) + test('resolves the configured tsconfig path', async () => { + const root = path.resolve(import.meta.dirname, 'fixtures') + const resolved = await resolveConfig( + { root, tsconfig: './custom.tsconfig.json' }, + 'build', + ) + expect(resolved.tsconfig).toBe( + normalizePath(path.resolve(root, 'custom.tsconfig.json')), + ) + + const absoluteTsconfig = path.resolve(root, 'absolute.tsconfig.json') + expect( + ( + await resolveConfig( + { root, tsconfig: absoluteTsconfig, configFile: false }, + 'build', + ) + ).tsconfig, + ).toBe(normalizePath(absoluteTsconfig)) + + expect( + (await resolveConfig({ root, configFile: false }, 'build')).tsconfig, + ).toBeUndefined() + }) + test('resolveConfig with root path including "#" and "?" and "*" should warn ', async () => { expect.assertions(1) diff --git a/packages/vite/src/node/__tests__/plugins/oxc.spec.ts b/packages/vite/src/node/__tests__/plugins/oxc.spec.ts index 99093cdd95b61d..48c91c9f7b979f 100644 --- a/packages/vite/src/node/__tests__/plugins/oxc.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/oxc.spec.ts @@ -1,5 +1,6 @@ import path from 'node:path' import { describe, expect, test } from 'vitest' +import { resolveConfig } from '../../config' import { transformWithOxc } from '../../plugins/oxc' describe('transformWithOxc', () => { @@ -153,4 +154,37 @@ describe('transformWithOxc', () => { ) expect(result?.code).toContain('_decorateMetadata("design:type"') }) + + test('uses the configured tsconfig instead of automatic discovery', async () => { + const code = ` + class Foo { + bar = 'bar' + } + ` + const fixtures = path.resolve( + import.meta.dirname, + './fixtures/oxc-tsconfigs', + ) + const explicitTsconfig = path.resolve( + fixtures, + 'use-define-false/tsconfig.json', + ) + const config = await resolveConfig( + { root: fixtures, tsconfig: explicitTsconfig, configFile: false }, + 'serve', + ) + const expected = await transformWithOxc( + code, + path.resolve(fixtures, 'use-define-false/bar.ts'), + { target: 'esnext' }, + ) + const actual = await transformWithOxc( + code, + path.resolve(fixtures, 'use-define-true/bar.ts'), + { target: 'esnext' }, + undefined, + config, + ) + expect(actual.code).toBe(expected.code) + }) }) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 38ba6ba27f5492..b07cd8e3bf6370 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -662,6 +662,13 @@ export function resolveRolldownOptions( : false, // cache: options.watch ? undefined : false, ...options.rolldownOptions, + tsconfig: environment.config.tsconfig ?? options.rolldownOptions.tsconfig, + resolve: environment.config.tsconfig + ? { + ...options.rolldownOptions.resolve, + tsconfigFilename: undefined, + } + : options.rolldownOptions.resolve, output: options.rolldownOptions.output, input, plugins, diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e350b1c3e82d76..7df58af219ff10 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -374,6 +374,11 @@ export interface UserConfig extends DefaultEnvironmentOptions { * @default process.cwd() */ root?: string + /** + * Path to the TypeScript configuration file. Relative paths are resolved + * from the project root. + */ + tsconfig?: string /** * Base public path when served in development or production. * @default '/' @@ -2032,6 +2037,9 @@ export async function resolveConfig( ), inlineConfig, root: resolvedRoot, + tsconfig: config.tsconfig + ? normalizePath(path.resolve(resolvedRoot, config.tsconfig)) + : undefined, base, decodedBase: decodeBase(base), rawBase: resolvedBase, diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index d6b3dc39215f4e..b9a4b562fef3d3 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -468,7 +468,7 @@ export function getTSConfigResolutionCache( } let cache = tsconfigResolutionCacheMap.get(config) if (!cache) { - cache = new TsconfigCache() + cache = new TsconfigCache(config.tsconfig) tsconfigResolutionCacheMap.set(config, cache) } return cache diff --git a/packages/vite/src/node/plugins/oxc.ts b/packages/vite/src/node/plugins/oxc.ts index 6a8c424a06880f..7626a69cbaa164 100644 --- a/packages/vite/src/node/plugins/oxc.ts +++ b/packages/vite/src/node/plugins/oxc.ts @@ -153,7 +153,9 @@ export async function transformWithOxc( filename, code, resolvedOptions, - getTSConfigResolutionCache(config), + options?.tsconfig === undefined || options.tsconfig === config?.tsconfig + ? getTSConfigResolutionCache(config) + : undefined, ) if ( watcher && @@ -292,6 +294,7 @@ export function oxcPlugin(config: ResolvedConfig): Plugin { return nativeTransformPlugin({ root: environment.config.root, + tsconfig: environment.config.tsconfig, include, exclude, jsxRefreshInclude, diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index d4ce653a798112..7bd7282aed712b 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -258,6 +258,7 @@ export function oxcResolvePlugin( : [options.noExternal] const plugin = viteResolvePlugin({ + tsconfig: partialEnv.config.tsconfig, resolveOptions: { isBuild: options.isBuild, isProduction: options.isProduction, From db45bb113d4c9ad894f5238c0d1343a4277d957f Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Wed, 2 Sep 2026 11:32:25 +0800 Subject: [PATCH 2/3] chore: enable `minimumReleaseAgeExcludePrune` (#23411) --- pnpm-workspace.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 40eb988dbf9c74..1db6b74b66d990 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -46,6 +46,7 @@ allowBuilds: unrs-resolver: true workerd: true +minimumReleaseAgeExcludePrune: true minimumReleaseAgeExclude: - rolldown - '@rolldown/binding-*' From b120589f052fe8e0d5ea75bade1a0278c0bdfa7f Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Wed, 2 Sep 2026 11:35:38 +0800 Subject: [PATCH 3/3] chore: update debugging playwright tests section in contributing.md (#23412) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45aa095ce78006..c6f001bf1b4e3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,7 +100,7 @@ To use breakpoints and explore code execution, you can use the ["Run and Debug"] Some errors are masked and hidden away because of the layers of abstraction and sandboxed nature added by Vitest, Playwright, and Chromium. In order to see what's actually going wrong and the contents of the devtools console in those instances, follow this setup: -1. Add a `debugger` statement to the `playground/vitestSetup.ts` -> `afterAll` hook. This will pause execution before the tests quit and the Playwright browser instance exits. +1. Add an `afterAll` hook with a `debugger` statement to `playground/vitestSetup.ts`. This will pause execution before the tests quit and the Playwright browser instance exits. 2. Run the tests with the `debug-serve` script command, which will enable remote debugging: `pnpm run debug-serve resolve`.