dx: avoid next-env.d.ts change in dev (#88103)

This commit is contained in:
Jiachi Liu
2026-01-05 14:35:33 +01:00
committed by GitHub
parent c5b19eb7f5
commit 42493fb3a4
10 changed files with 85 additions and 5 deletions
+3
View File
@@ -20,6 +20,7 @@ import { hrtimeDurationToString } from './duration-to-string'
function verifyTypeScriptSetup(
dir: string,
distDir: string,
distDirRoot: string | undefined,
typeCheckPreflight: boolean,
tsconfigPath: string | undefined,
disableStaticImages: boolean,
@@ -50,6 +51,7 @@ function verifyTypeScriptSetup(
.verifyTypeScriptSetup({
dir,
distDir,
distDirRoot,
typeCheckPreflight,
tsconfigPath,
disableStaticImages,
@@ -117,6 +119,7 @@ export async function startTypeChecking({
verifyTypeScriptSetup(
dir,
config.distDir,
config.distDirRoot,
!ignoreTypeScriptErrors,
config.typescript.tsconfigPath,
config.images.disableStaticImages,
+1
View File
@@ -140,6 +140,7 @@ async function runPlaywright(
const { version: typeScriptVersion } = await verifyTypeScriptSetup({
dir: baseDir,
distDir: nextConfig.distDir,
distDirRoot: nextConfig.distDirRoot,
typeCheckPreflight: false,
tsconfigPath: nextConfig.typescript.tsconfigPath,
disableStaticImages: nextConfig.images.disableStaticImages,
+1
View File
@@ -57,6 +57,7 @@ const nextTypegen = async (
await verifyTypeScriptSetup({
dir: baseDir,
distDir: nextConfig.distDir,
distDirRoot: nextConfig.distDirRoot,
typeCheckPreflight: false,
tsconfigPath: nextConfig.typescript.tsconfigPath,
disableStaticImages: nextConfig.images.disableStaticImages,
@@ -5,12 +5,15 @@ import { promises as fs } from 'fs'
export async function writeAppTypeDeclarations({
baseDir,
distDir,
distDirRoot,
imageImportsEnabled,
hasPagesDir,
hasAppDir,
}: {
baseDir: string
distDir: string
/** The original distDir before any modifications (e.g., for isolatedDevBuild). Used for stable type imports. */
distDirRoot?: string
imageImportsEnabled: boolean
hasPagesDir: boolean
hasAppDir: boolean
@@ -57,10 +60,12 @@ export async function writeAppTypeDeclarations({
)
}
const routeTypesPath = path.posix.join(
distDir.replaceAll(path.win32.sep, path.posix.sep),
'types/routes.d.ts'
// Use distDirRoot for stable path that doesn't change between dev/build modes
const stableDistDir = (distDirRoot ?? distDir).replaceAll(
path.win32.sep,
path.posix.sep
)
const routeTypesPath = path.posix.join(stableDistDir, 'types/routes.d.ts')
// Use ESM import instead of triple-slash reference for better ESLint compatibility
directives.push(`import "./${routeTypesPath}";`)
@@ -36,6 +36,7 @@ const requiredPackages = [
export async function verifyTypeScriptSetup({
dir,
distDir,
distDirRoot,
cacheDir,
tsconfigPath,
typeCheckPreflight,
@@ -49,6 +50,8 @@ export async function verifyTypeScriptSetup({
}: {
dir: string
distDir: string
/** The original distDir before any modifications (e.g., for isolatedDevBuild). */
distDirRoot?: string
cacheDir?: string
tsconfigPath: string | undefined
typeCheckPreflight: boolean
@@ -143,6 +146,7 @@ export async function verifyTypeScriptSetup({
await writeAppTypeDeclarations({
baseDir: dir,
distDir,
distDirRoot,
imageImportsEnabled: !disableStaticImages,
hasPagesDir,
hasAppDir,
@@ -395,3 +395,28 @@ export async function writeValidatorFile(
: generateValidatorFile(manifest)
)
}
/**
* Writes a proxy routes.d.ts file at the stable path that re-exports from
* the actual dev types location. This allows next-env.d.ts to always reference
* the same stable path regardless of dev/build mode.
*
* @param stableTypesDir - The stable types directory (e.g., .next/types)
* @param devTypesRelativePath - Relative path from stable dir to dev types (e.g., ../dev/types/routes.d.ts)
*/
export async function writeRouteTypesProxy(
stableTypesDir: string,
devTypesRelativePath: string
) {
if (!fs.existsSync(stableTypesDir)) {
await fs.promises.mkdir(stableTypesDir, { recursive: true })
}
const proxyFilePath = path.join(stableTypesDir, 'routes.d.ts')
const proxyContent = `// This file re-exports route types from the dev types location.
// This provides a stable import path for next-env.d.ts across dev/build modes.
export * from '${devTypesRelativePath}';
`
await fs.promises.writeFile(proxyFilePath, proxyContent)
}
@@ -85,6 +85,7 @@ import {
createRouteTypesManifest,
writeRouteTypesManifest,
writeValidatorFile,
writeRouteTypesProxy,
} from './route-types-utils'
import { writeCacheLifeTypes } from './cache-life-type-utils'
import { isParallelRouteSegment } from '../../../shared/lib/segment'
@@ -143,6 +144,7 @@ async function verifyTypeScript(opts: SetupOpts) {
const verifyResult = await verifyTypeScriptSetup({
dir: opts.dir,
distDir: opts.nextConfig.distDir,
distDirRoot: opts.nextConfig.distDirRoot,
typeCheckPreflight: false,
tsconfigPath: opts.nextConfig.typescript.tsconfigPath,
disableStaticImages: opts.nextConfig.images.disableStaticImages,
@@ -266,6 +268,17 @@ async function startWatcher(
opts.nextConfig
)
// When isolatedDevBuild is enabled, write a proxy file at the stable path
// that re-exports from the dev types location
if (opts.nextConfig.experimental.isolatedDevBuild) {
const stableTypesDir = path.join(
opts.dir,
opts.nextConfig.distDirRoot,
'types'
)
await writeRouteTypesProxy(stableTypesDir, '../dev/types/routes.d.ts')
}
const routesManifestPath = path.join(distDir, ROUTES_MANIFEST)
const routesManifest: DevRoutesManifest = {
version: 3,
@@ -23,4 +23,32 @@ describe('isolated-dev-build', () => {
expect(await browser.elementByCss('p').text()).toBe('hello updated world')
})
})
it('should use stable path in next-env.d.ts that does not change between dev/build', async () => {
const nextEnvContent = await next.readFile('next-env.d.ts')
// Snapshot ensures next-env.d.ts content stays stable
expect(nextEnvContent).toMatchInlineSnapshot(`
"/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
"
`)
})
it('should create proxy routes.d.ts at stable path that re-exports from dev types', async () => {
// The proxy file should re-export from the dev types location
const proxyContent = await next.readFile('.next/types/routes.d.ts')
expect(proxyContent).toMatchInlineSnapshot(`
"// This file re-exports route types from the dev types location.
// This provides a stable import path for next-env.d.ts across dev/build modes.
export * from '../dev/types/routes.d.ts';
"
`)
// The actual dev types should exist
expect(await next.hasFile('.next/dev/types/routes.d.ts')).toBe(true)
})
})
+1 -1
View File
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import './.next/dev/types/routes.d.ts'
import './.next/types/routes.d.ts'
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.