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
7 changes: 0 additions & 7 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ import {
createRouteTypesManifest,
writeRouteTypesManifest,
writeValidatorFile,
writeDynamicTypesFile,
} from '../server/lib/router-utils/route-types-utils'
import { Lockfile } from './lockfile'
import {
Expand Down Expand Up @@ -1485,12 +1484,6 @@ export default async function build(
config
)
await writeValidatorFile(routeTypesManifest, validatorFilePath)
await writeDynamicTypesFile({
distDir,
imageImportsEnabled: !config.images.disableStaticImages,
hasPagesDir: !!pagesDir,
hasAppDir: !!appDir,
})
})

// Turbopack already handles conflicting app and page routes.
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/build/type-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function verifyTypeScriptSetup(
distDir: string,
typeCheckPreflight: boolean,
tsconfigPath: string | undefined,
disableStaticImages: boolean,
cacheDir: string | undefined,
enableWorkerThreads: boolean | undefined,
hasAppDir: boolean,
Expand Down Expand Up @@ -51,6 +52,7 @@ function verifyTypeScriptSetup(
distDir,
typeCheckPreflight,
tsconfigPath,
disableStaticImages,
cacheDir,
hasAppDir,
hasPagesDir,
Expand Down Expand Up @@ -117,6 +119,7 @@ export async function startTypeChecking({
config.distDir,
!ignoreTypeScriptErrors,
config.typescript.tsconfigPath,
config.images.disableStaticImages,
cacheDir,
config.experimental.workerThreads,
!!appDir,
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/cli/next-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ async function runPlaywright(
distDir: nextConfig.distDir,
typeCheckPreflight: false,
tsconfigPath: nextConfig.typescript.tsconfigPath,
disableStaticImages: nextConfig.images.disableStaticImages,
hasAppDir: !!appDir,
hasPagesDir: !!pagesDir,
isolatedDevBuild: nextConfig.experimental.isolatedDevBuild,
Expand Down
9 changes: 1 addition & 8 deletions packages/next/src/cli/next-typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
createRouteTypesManifest,
writeRouteTypesManifest,
writeValidatorFile,
writeDynamicTypesFile,
} from '../server/lib/router-utils/route-types-utils'
import { writeCacheLifeTypes } from '../server/lib/router-utils/cache-life-type-utils'
import { createValidFileMatcher } from '../server/lib/find-page-file'
Expand Down Expand Up @@ -60,6 +59,7 @@ const nextTypegen = async (
distDir: nextConfig.distDir,
typeCheckPreflight: false,
tsconfigPath: nextConfig.typescript.tsconfigPath,
disableStaticImages: nextConfig.images.disableStaticImages,
hasAppDir: !!appDir,
hasPagesDir: !!pagesDir,
isolatedDevBuild: nextConfig.experimental.isolatedDevBuild,
Expand Down Expand Up @@ -171,13 +171,6 @@ const nextTypegen = async (

await writeValidatorFile(routeTypesManifest, validatorFilePath)

await writeDynamicTypesFile({
distDir,
imageImportsEnabled: !nextConfig.images.disableStaticImages,
hasPagesDir: !!pagesDir,
hasAppDir: !!appDir,
})

// Generate cache-life types if cacheLife config exists
const cacheLifeFilePath = join(distDir, 'types', 'cache-life.d.ts')
writeCacheLifeTypes(nextConfig.cacheLife, cacheLifeFilePath)
Expand Down
82 changes: 82 additions & 0 deletions packages/next/src/lib/typescript/writeAppTypeDeclarations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import os from 'os'
import path from 'path'
import { promises as fs } from 'fs'

export async function writeAppTypeDeclarations({
baseDir,
distDir,
imageImportsEnabled,
hasPagesDir,
hasAppDir,
}: {
baseDir: string
distDir: string
imageImportsEnabled: boolean
hasPagesDir: boolean
hasAppDir: boolean
}): Promise<void> {
// Reference `next` types
const appTypeDeclarations = path.join(baseDir, 'next-env.d.ts')

// Defaults EOL to system default
let eol = os.EOL
let currentContent: string | undefined

try {
currentContent = await fs.readFile(appTypeDeclarations, 'utf8')
// If file already exists then preserve its line ending
const lf = currentContent.indexOf('\n', /* skip first so we can lf - 1 */ 1)

if (lf !== -1) {
if (currentContent[lf - 1] === '\r') {
eol = '\r\n'
} else {
eol = '\n'
}
}
} catch {}

/**
* "Triple-slash directives" used to create typings files for Next.js projects
* using Typescript .
*
* @see https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html
*/
const directives: string[] = [
// Include the core Next.js typings.
'/// <reference types="next" />',
]

if (imageImportsEnabled) {
directives.push('/// <reference types="next/image-types/global" />')
}

if (hasAppDir && hasPagesDir) {
directives.push(
'/// <reference types="next/navigation-types/compat/navigation" />'
)
}

const routeTypesPath = path.posix.join(
distDir.replaceAll(path.win32.sep, path.posix.sep),
'types/routes.d.ts'
)

// Use ESM import instead of triple-slash reference for better ESLint compatibility
directives.push(`import "./${routeTypesPath}";`)

// Push the notice in.
directives.push(
'',
'// NOTE: This file should not be edited',
`// see https://nextjs.org/docs/${hasAppDir ? 'app' : 'pages'}/api-reference/config/typescript for more information.`
)

const content = directives.join(eol) + eol

// Avoids an un-necessary write on read-only fs
if (currentContent === content) {
return
}
await fs.writeFile(appTypeDeclarations, content)
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ describe('writeConfigurationDefaults()', () => {
"node_modules",
],
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts",
Expand All @@ -106,7 +107,7 @@ describe('writeConfigurationDefaults()', () => {
- strict was set to false
- noEmit was set to true
- incremental was set to true
- include was set to ['.next/types/**/*.ts', '.next/dev/types/**/*.ts', '**/*.mts', '**/*.ts', '**/*.tsx']
- include was set to ['next-env.d.ts', '.next/types/**/*.ts', '.next/dev/types/**/*.ts', '**/*.mts', '**/*.ts', '**/*.tsx']
- plugins was updated to add { name: 'next' }
- exclude was set to ['node_modules']

Expand Down
11 changes: 7 additions & 4 deletions packages/next/src/lib/typescript/writeConfigurationDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,16 +282,19 @@ export async function writeConfigurationDefaults(
)

if (!('include' in userTsConfig)) {
// Always include .next/types for dynamic types (image imports, route types, etc.)
userTsConfig.include = [...nextAppTypes, '**/*.mts', '**/*.ts', '**/*.tsx']
userTsConfig.include = hasAppDir
? ['next-env.d.ts', ...nextAppTypes, '**/*.mts', '**/*.ts', '**/*.tsx']
: ['next-env.d.ts', '**/*.mts', '**/*.ts', '**/*.tsx']
suggestedActions.push(
cyan('include') +
' was set to ' +
bold(
`[${nextAppTypes.map((type) => `'${type}'`).join(', ')}, '**/*.mts', '**/*.ts', '**/*.tsx']`
hasAppDir
? `['next-env.d.ts', ${nextAppTypes.map((type) => `'${type}'`).join(', ')}, '**/*.mts', '**/*.ts', '**/*.tsx']`
: `['next-env.d.ts', '**/*.mts', '**/*.ts', '**/*.tsx']`
)
)
} else {
} else if (hasAppDir) {
const missingFromResolved = []
for (const type of nextAppTypes) {
if (!userTsConfig.include.includes(type)) {
Expand Down
13 changes: 13 additions & 0 deletions packages/next/src/lib/verify-typescript-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as log from '../build/output/log'

import { getTypeScriptIntent } from './typescript/getTypeScriptIntent'
import type { TypeCheckResult } from './typescript/runTypeCheck'
import { writeAppTypeDeclarations } from './typescript/writeAppTypeDeclarations'
import { writeConfigurationDefaults } from './typescript/writeConfigurationDefaults'
import { installDependencies } from './install-dependencies'
import { isCI } from '../server/ci-info'
Expand Down Expand Up @@ -38,6 +39,7 @@ export async function verifyTypeScriptSetup({
cacheDir,
tsconfigPath,
typeCheckPreflight,
disableStaticImages,
hasAppDir,
hasPagesDir,
isolatedDevBuild,
Expand All @@ -50,6 +52,7 @@ export async function verifyTypeScriptSetup({
cacheDir?: string
tsconfigPath: string | undefined
typeCheckPreflight: boolean
disableStaticImages: boolean
hasAppDir: boolean
hasPagesDir: boolean
isolatedDevBuild: boolean | undefined
Expand Down Expand Up @@ -135,6 +138,16 @@ export async function verifyTypeScriptSetup({
hasPagesDir,
isolatedDevBuild
)
// Write out the necessary `next-env.d.ts` file to correctly register
// Next.js' types:
await writeAppTypeDeclarations({
baseDir: dir,
distDir,
imageImportsEnabled: !disableStaticImages,
hasPagesDir,
hasAppDir,
})

let result
if (typeCheckPreflight) {
const { runTypeCheck } =
Expand Down
33 changes: 0 additions & 33 deletions packages/next/src/server/lib/router-utils/route-types-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
generateRouteTypesFile,
generateLinkTypesFile,
generateValidatorFile,
generateDynamicTypesFile,
} from './typegen'
import { tryToParsePath } from '../../../lib/try-to-parse-path'
import {
Expand Down Expand Up @@ -383,35 +382,3 @@ export async function writeValidatorFile(

await fs.promises.writeFile(filePath, generateValidatorFile(manifest))
}

/**
* Writes the dynamic types file (.next/types/next-env.d.ts) that contains
* config-dependent type references (image imports, navigation compat, etc.)
*/
export async function writeDynamicTypesFile({
distDir,
imageImportsEnabled,
hasPagesDir,
hasAppDir,
}: {
distDir: string
imageImportsEnabled: boolean
hasPagesDir: boolean
hasAppDir: boolean
}) {
const typesDir = path.join(distDir, 'types')
const filePath = path.join(typesDir, 'next-env.d.ts')

// Directory should already be created by writeRouteTypesManifest, but ensure it exists
if (!fs.existsSync(typesDir)) {
await fs.promises.mkdir(typesDir, { recursive: true })
}

const content = generateDynamicTypesFile({
imageImportsEnabled,
hasPagesDir,
hasAppDir,
})

await fs.promises.writeFile(filePath, content)
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ import {
createRouteTypesManifest,
writeRouteTypesManifest,
writeValidatorFile,
writeDynamicTypesFile,
} from './route-types-utils'
import { writeCacheLifeTypes } from './cache-life-type-utils'
import { isParallelRouteSegment } from '../../../shared/lib/segment'
Expand Down Expand Up @@ -146,6 +145,7 @@ async function verifyTypeScript(opts: SetupOpts) {
distDir: opts.nextConfig.distDir,
typeCheckPreflight: false,
tsconfigPath: opts.nextConfig.typescript.tsconfigPath,
disableStaticImages: opts.nextConfig.images.disableStaticImages,
hasAppDir: !!opts.appDir,
hasPagesDir: !!opts.pagesDir,
isolatedDevBuild: opts.nextConfig.experimental.isolatedDevBuild,
Expand Down Expand Up @@ -265,12 +265,6 @@ async function startWatcher(
path.join(distTypesDir, 'routes.d.ts'),
opts.nextConfig
)
await writeDynamicTypesFile({
distDir,
imageImportsEnabled: !opts.nextConfig.images.disableStaticImages,
hasPagesDir: !!pagesDir,
hasAppDir: !!appDir,
})

const routesManifestPath = path.join(distDir, ROUTES_MANIFEST)
const routesManifest: DevRoutesManifest = {
Expand Down
39 changes: 1 addition & 38 deletions packages/next/src/server/lib/router-utils/typegen.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,6 @@
import type { RouteTypesManifest } from './route-types-utils'
import { isDynamicRoute } from '../../../shared/lib/router/utils/is-dynamic'

/**
* Generates the content for .next/types/next-env.d.ts
* This file contains dynamic type references that depend on project configuration.
*/
export function generateDynamicTypesFile({
imageImportsEnabled,
hasPagesDir,
hasAppDir,
}: {
imageImportsEnabled: boolean
hasPagesDir: boolean
hasAppDir: boolean
}): string {
const directives: string[] = [
// Include the core Next.js typings.
'/// <reference types="next" />',
'// This file is generated automatically by Next.js',
'// Do not edit this file manually',
'',
]

if (imageImportsEnabled) {
directives.push('/// <reference types="next/image-types/global" />')
}

if (hasAppDir && hasPagesDir) {
directives.push(
'/// <reference types="next/navigation-types/compat/navigation" />'
)
}

// Import routes types from the same directory
directives.push('import "./routes.d.ts"')

return directives.join('\n') + '\n'
}

function generateRouteTypes(routesManifest: RouteTypesManifest): string {
const appRoutes = Object.keys(routesManifest.appRoutes).sort()
const pageRoutes = Object.keys(routesManifest.pageRoutes).sort()
Expand Down Expand Up @@ -611,7 +574,7 @@ export function generateValidatorFile(
default: (req: any, res: any) => ReturnType<NextApiHandler>
config?: {
api?: {
bodyParser?: boolean | { sizeLimit?: string | number }
bodyParser?: boolean | { sizeLimit?: string }
responseLimit?: string | number | boolean
externalResolver?: boolean
}
Expand Down
Loading
Loading