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 { getNodeOptionsArgs } from '../../server/lib/utils'; function resolveSWCOptions(cwd, compilerOptions) { var _process_versions, _process; return { jsc: { parser: { syntax: 'typescript' }, ...compilerOptions.paths ? { paths: compilerOptions.paths } : {}, ...compilerOptions.baseUrl ? { baseUrl: path.resolve(cwd, compilerOptions.baseUrl) } : compilerOptions.paths ? { baseUrl: cwd } : {} }, module: { type: 'commonjs' }, isModule: 'unknown', env: { targets: { // Setting the Node.js version can reduce unnecessary code generation. node: ((_process = process) == null ? void 0 : (_process_versions = _process.versions) == null ? void 0 : _process_versions.node) ?? '20.19.0' } } }; } function resolveExtends(extendsPath, currentConfigDir) { // Relative paths are resolved relative to the current config's directory if (extendsPath.startsWith('./') || extendsPath.startsWith('../') || path.isAbsolute(extendsPath)) { const resolved = path.resolve(currentConfigDir, extendsPath); // TypeScript allows omitting .json extension if (existsSync(resolved)) { return resolved; } if (!resolved.endsWith('.json') && existsSync(resolved + '.json')) { return resolved + '.json'; } return resolved; } // Package paths - use require.resolve to find the package try { // Try resolving as a direct path within the package return require.resolve(extendsPath, { paths: [ currentConfigDir ] }); } catch { // If that fails, try appending tsconfig.json for package names like "@tsconfig/node18" try { return require.resolve(extendsPath + '/tsconfig.json', { paths: [ currentConfigDir ] }); } catch { // Return the original path and let it fail later with a clear error return path.resolve(currentConfigDir, extendsPath); } } } function loadTsConfigFile(configPath, visited) { const resolvedPath = path.resolve(configPath); if (visited.has(resolvedPath)) { return {}; } visited.add(resolvedPath); if (!existsSync(resolvedPath)) { return {}; } const configContent = readFileSync(resolvedPath, 'utf8'); const config = CommentJson.parse(configContent); const configDir = path.dirname(resolvedPath); let mergedOptions = {}; // Note that config options from `extends` should get overwritten, not merged if (config.extends) { const extendsList = Array.isArray(config.extends) ? config.extends : [ config.extends ]; for (const extendsPath of extendsList){ const parentConfigPath = resolveExtends(extendsPath, configDir); const parentOptions = loadTsConfigFile(parentConfigPath, visited); mergedOptions = { ...mergedOptions, ...parentOptions }; } } const currentOptions = config.compilerOptions ?? {}; mergedOptions = { ...mergedOptions, paths: currentOptions.paths ?? mergedOptions.paths, baseUrl: currentOptions.baseUrl ?? mergedOptions.baseUrl }; return mergedOptions; } async function loadTsConfig(dir) { // NOTE: This doesn't fully cover the edge case for setting // "typescript.tsconfigPath" in next config which is currently // a restriction. // 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 (!existsSync(resolvedTsConfigPath)) { return {}; } return loadTsConfigFile(resolvedTsConfigPath, new Set()); } export async function transpileConfig({ nextConfigPath, dir }) { try { // envs are passed to the workers and preserve the flag if (process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED === 'true') { try { // Node.js v22.10.0+ // Value is 'strip' or 'transform' based on how the feature is enabled. // https://nodejs.org/api/process.html#processfeaturestypescript // TODO: Remove `as any` once we bump @types/node to v22.10.0+ if (process.features.typescript) { // Run import() here to catch errors and fallback to legacy resolution. return (await import(pathToFileURL(nextConfigPath).href)).default; } if (getNodeOptionsArgs().includes('--no-experimental-strip-types') || process.execArgv.includes('--no-experimental-strip-types')) { warnOnce(`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'); } // Feature is not enabled, fallback to legacy resolution for current session. process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'false'; } catch (cause) { warnOnce(`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 }); // Once failed, fallback to legacy resolution for current session. process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'false'; } } const compilerOptions = await loadTsConfig(dir); return handleCJS({ dir, nextConfigPath, compilerOptions }); } catch (cause) { throw Object.defineProperty(new Error(`Failed to transpile "${path.basename(nextConfigPath)}".`, { cause }), "__NEXT_ERROR_CODE", { value: "E797", enumerable: false, configurable: true }); } } async function handleCJS({ dir, nextConfigPath, compilerOptions }) { const swcOptions = resolveSWCOptions(dir, compilerOptions); let hasRequire = false; try { var _config_experimental; 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'); const bindings = await loadBindings(); const { code } = await bindings.transform(nextConfigString, swcOptions); // register require hook only if require exists if (code.includes('require(')) { registerHook(swcOptions); hasRequire = true; } // filename & extension don't matter here const config = requireFromString(code, 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 // that the configuration was ignored. if ((config == null ? void 0 : (_config_experimental = config.experimental) == null ? void 0 : _config_experimental.useWasmBinary) && !bindings.isWasm) { warn('Using a next.config.ts file is incompatible with `experimental.useWasmBinary` unless ' + '`--experimental-next-config-strip-types` is also passed.\nSetting `useWasmBinary` to `false'); config.experimental.useWasmBinary = false; } return config; } catch (error) { throw error; } finally{ if (hasRequire) { deregisterHook(); } } } //# sourceMappingURL=transpile-config.js.map