Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
108 changes: 30 additions & 78 deletions packages/next/src/build/next-config-ts/transpile-config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { Options as SWCOptions } from '@swc/core'
import type { CompilerOptions } from 'typescript'

import { resolve } from 'node:path'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { readFileSync, existsSync } from 'node:fs'
import { pathToFileURL } from 'node:url'
import * as CommentJson from 'next/dist/compiled/comment-json'
import { deregisterHook, registerHook, requireFromString } from './require-hook'
import { warn, warnOnce } from '../output/log'
import { installDependencies } from '../../lib/install-dependencies'
import { getNodeOptionsArgs } from '../../server/lib/utils'

type RelevantCompilerOptions = Pick<CompilerOptions, 'paths' | 'baseUrl'>

function resolveSWCOptions(
cwd: string,
compilerOptions: CompilerOptions
compilerOptions: RelevantCompilerOptions
): SWCOptions {
return {
jsc: {
Expand All @@ -21,7 +23,7 @@ function resolveSWCOptions(
...(compilerOptions.paths ? { paths: compilerOptions.paths } : {}),
...(compilerOptions.baseUrl
? // Needs to be an absolute path.
{ baseUrl: resolve(cwd, compilerOptions.baseUrl) }
{ baseUrl: path.resolve(cwd, compilerOptions.baseUrl) }
: compilerOptions.paths
? // If paths is given, baseUrl is required.
{ baseUrl: cwd }
Expand All @@ -40,79 +42,30 @@ function resolveSWCOptions(
} satisfies SWCOptions
}

// Ported from next/src/lib/verify-typescript-setup.ts
// Although this overlaps with the later `verifyTypeScriptSetup`,
// it is acceptable since the time difference in the worst case is trivial,
// as we are only preparing to install the dependencies once more.
async function verifyTypeScriptSetup(cwd: string, configFileName: string) {
try {
// Quick module check.
require.resolve('typescript', { paths: [cwd] })
} catch (error) {
if (
error &&
typeof error === 'object' &&
'code' in error &&
error.code === 'MODULE_NOT_FOUND'
) {
warn(
`Installing TypeScript as it was not found while loading "${configFileName}".`
)

await installDependencies(cwd, [{ pkg: 'typescript' }], true).catch(
(err) => {
if (err && typeof err === 'object' && 'command' in err) {
console.error(
`Failed to install TypeScript, please install it manually to continue:\n` +
(err as any).command +
'\n'
)
}
throw err
}
)
}
}
}

async function getTsConfig(cwd: string): Promise<CompilerOptions> {
const ts: typeof import('typescript') = require(
require.resolve('typescript', { paths: [cwd] })
)

async function getTsConfig(dir: string): Promise<RelevantCompilerOptions> {
// NOTE: This doesn't fully cover the edge case for setting
// "typescript.tsconfigPath" in next config which is currently
// a restriction.
const tsConfigPath = ts.findConfigFile(
cwd,
ts.sys.fileExists,
'tsconfig.json'
)
// It's a chicken-and-egg problem since we need to transpile
// the next config to get that value.
const resolvedTsConfigPath = path.join(dir, 'tsconfig.json')

if (!tsConfigPath) {
// It is ok to not return ts.getDefaultCompilerOptions() because
// we are only looking for paths and baseUrl from tsConfig.
if (!existsSync(resolvedTsConfigPath)) {
return {}
}

const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile)
const parsedCommandLine = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
cwd
)
const userTsConfigContent = readFileSync(resolvedTsConfigPath, 'utf8')
const userTsConfig = CommentJson.parse(userTsConfigContent)

return parsedCommandLine.options
return userTsConfig.compilerOptions ?? {}
}

export async function transpileConfig({
nextConfigPath,
configFileName,
cwd,
dir,
}: {
nextConfigPath: string
configFileName: string
cwd: string
dir: string
}) {
try {
// envs are passed to the workers and preserve the flag
Expand All @@ -132,7 +85,7 @@ export async function transpileConfig({
process.execArgv.includes('--no-experimental-strip-types')
) {
warnOnce(
`Skipped resolving "${configFileName}" using Node.js native TypeScript resolution because it was disabled by the "--no-experimental-strip-types" flag.` +
`Skipped resolving "${path.basename(nextConfigPath)}" using Node.js native TypeScript resolution because it was disabled by the "--no-experimental-strip-types" flag.` +
' Falling back to legacy resolution.' +
' Learn more: https://nextjs.org/docs/app/api-reference/config/typescript#using-nodejs-native-typescript-resolver-for-nextconfigts'
)
Expand All @@ -142,7 +95,7 @@ export async function transpileConfig({
process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'false'
} catch (cause) {
warnOnce(
`Failed to import "${configFileName}" using Node.js native TypeScript resolution.` +
`Failed to import "${path.basename(nextConfigPath)}" using Node.js native TypeScript resolution.` +
' Falling back to legacy resolution.' +
' Learn more: https://nextjs.org/docs/app/api-reference/config/typescript#using-nodejs-native-typescript-resolver-for-nextconfigts',
{ cause }
Expand All @@ -152,29 +105,28 @@ export async function transpileConfig({
}
}

// Ensure TypeScript is installed to use the API.
await verifyTypeScriptSetup(cwd, configFileName)
const compilerOptions = await getTsConfig(cwd)

return handleCJS({ cwd, nextConfigPath, compilerOptions })
const compilerOptions = await getTsConfig(dir)
return handleCJS({ dir, nextConfigPath, compilerOptions })
} catch (cause) {
throw new Error(`Failed to transpile "${configFileName}".`, { cause })
throw new Error(`Failed to transpile "${path.basename(nextConfigPath)}".`, {
cause,
})
}
}

async function handleCJS({
cwd,
dir,
nextConfigPath,
compilerOptions,
}: {
cwd: string
dir: string
nextConfigPath: string
compilerOptions: CompilerOptions
compilerOptions: RelevantCompilerOptions
}) {
const swcOptions = resolveSWCOptions(cwd, compilerOptions)
const swcOptions = resolveSWCOptions(dir, compilerOptions)
let hasRequire = false
try {
const nextConfigString = await readFile(nextConfigPath, 'utf8')
const nextConfigString = readFileSync(nextConfigPath, 'utf8')
// lazy require swc since it loads React before even setting NODE_ENV
// resulting loading Development React on Production
const { loadBindings } = require('../swc') as typeof import('../swc')
Expand All @@ -190,7 +142,7 @@ async function handleCJS({
// filename & extension don't matter here
const config = requireFromString(
code,
resolve(cwd, 'next.config.compiled.js')
path.resolve(dir, 'next.config.compiled.js')
)
// At this point we have already loaded the bindings without this configuration setting due to the `transform` call above.
// Possibly we fell back to wasm in which case, it all works out but if not we need to warn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,7 @@ export async function writeConfigurationDefaults(
writeFileSync(tsConfigPath, '{}' + os.EOL)
}

const userTsConfigContent = readFileSync(tsConfigPath, {
encoding: 'utf8',
})
const userTsConfigContent = readFileSync(tsConfigPath, 'utf8')
const userTsConfig = CommentJson.parse(userTsConfigContent)

// Bail automatic setup when the user has extended or referenced another config
Expand Down
3 changes: 1 addition & 2 deletions packages/next/src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1601,8 +1601,7 @@ export default async function loadConfig(
} else if (configFileName === 'next.config.ts') {
userConfigModule = await transpileConfig({
nextConfigPath: path,
configFileName,
cwd: dir,
dir,
})
} else {
userConfigModule = await import(pathToFileURL(path).href)
Expand Down
1 change: 0 additions & 1 deletion test/production/next-server-nft/next-server-nft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,6 @@ async function readNormalizedNFT(next, name) {
"/node_modules/semver/*",
"/node_modules/sharp/*",
"/node_modules/styled-jsx/*",
"/node_modules/typescript/*",
]
`)
})
Expand Down
Loading