Repository: vercel/next.js
Pull request: Don't start worker for Typescript when only doing config validation

Only start a Worker  for typescript when type checking is actually enabled. When only validating the config (which doesn't load the `typescript` package itself, just invoke it in-process)

testing with `pnpm next build bench/app-router-server` and `module.exports = {typescript: {ignoreBuildErrors: true}}`

`startTypeChecking: Xms` is a `console.time` and `console.timeEnd` around `startTypeChecking`

before:
```
✓ Finished TypeScript config validation in 82ms    
startTypeChecking: 85.19ms

✓ Finished TypeScript config validation in 46ms    
startTypeChecking: 50.084ms

✓ Finished TypeScript config validation in 47ms    
startTypeChecking: 50.882ms
```


after:
```
✓ Finished TypeScript config validation in 1ms    
startTypeChecking: 5.281ms

✓ Finished TypeScript config validation in 1ms    
startTypeChecking: 4.797ms

✓ Finished TypeScript config validation in 1ms    
startTypeChecking: 4.697ms
```


Changed files:
filename: packages/next/src/build/type-check.ts
patch: @@ -17,11 +17,11 @@ import { hrtimeDurationToString } from './duration-to-string'
  * instead of running "next/lib/typescript/runTypeCheck" in a worker,
  * we will run entire "next/lib/verify-typescript-setup" in a worker instead.
  */
-function verifyTypeScriptSetup(
+function verifyAndRunTypeScript(
   dir: string,
   distDir: string,
   strictRouteTypes: boolean,
-  typeCheckPreflight: boolean,
+  shouldRunTypeCheck: boolean,
   tsconfigPath: string | undefined,
   typedRoutes: boolean,
   disableStaticImages: boolean,
@@ -33,38 +33,50 @@ function verifyTypeScriptSetup(
   pagesDir: string | undefined,
   debugBuildPaths: { app: string[]; pages: string[] } | undefined
 ) {
-  const typeCheckWorker = new Worker(
-    require.resolve('../lib/verify-typescript-setup'),
-    {
-      exposedMethods: ['verifyTypeScriptSetup'],
-      debuggerPortOffset: -1,
-      isolatedMemory: false,
-      numWorkers: 1,
-      enableWorkerThreads,
-      maxRetries: 0,
-    }
-  ) as Worker & {
-    verifyTypeScriptSetup: typeof import('../lib/verify-typescript-setup').verifyTypeScriptSetup
+  let impl: typeof import('../lib/verify-typescript-setup').verifyAndRunTypeScript
+  let typeCheckWorker:
+    | (Worker & {
+        verifyAndRunTypeScript: typeof impl
+      })
+    | undefined
+  if (shouldRunTypeCheck) {
+    typeCheckWorker = new Worker(
+      require.resolve('../lib/verify-typescript-setup'),
+      {
+        exposedMethods: ['verifyAndRunTypeScript'],
+        debuggerPortOffset: -1,
+        isolatedMemory: false,
+        numWorkers: 1,
+        enableWorkerThreads,
+        maxRetries: 0,
+      }
+    ) as typeof typeCheckWorker
+    impl = typeCheckWorker!.verifyAndRunTypeScript
+  } else {
+    // When not running typecheck, just run the implementation in-process without spawning a worker,
+    // to avoid the overhead of the worker.
+    impl = (
+      require('../lib/verify-typescript-setup') as typeof import('../lib/verify-typescript-setup')
+    ).verifyAndRunTypeScript
   }
 
-  return typeCheckWorker
-    .verifyTypeScriptSetup({
-      dir,
-      distDir,
-      strictRouteTypes,
-      typeCheckPreflight,
-      tsconfigPath,
-      typedRoutes,
-      disableStaticImages,
-      cacheDir,
-      hasAppDir,
-      hasPagesDir,
-      appDir,
-      pagesDir,
-      debugBuildPaths,
-    })
+  return impl({
+    dir,
+    distDir,
+    strictRouteTypes,
+    shouldRunTypeCheck,
+    tsconfigPath,
+    typedRoutes,
+    disableStaticImages,
+    cacheDir,
+    hasAppDir,
+    hasPagesDir,
+    appDir,
+    pagesDir,
+    debugBuildPaths,
+  })
     .then((result) => {
-      typeCheckWorker.end()
+      typeCheckWorker?.end()
       return result
     })
     .catch(() => {
@@ -116,7 +128,7 @@ export async function startTypeChecking({
     const [verifyResult, typeCheckEnd] = await nextBuildSpan
       .traceChild('run-typescript')
       .traceAsyncFn(() =>
-        verifyTypeScriptSetup(
+        verifyAndRunTypeScript(
           dir,
           config.distDir,
           Boolean(config.experimental.strictRouteTypes),

filename: packages/next/src/cli/next-test.ts
patch: @@ -11,7 +11,7 @@ import { installDependencies } from '../lib/install-dependencies'
 import type { NextConfigComplete } from '../server/config-shared'
 import findUp from 'next/dist/compiled/find-up'
 import { findPagesDir } from '../lib/find-pages-dir'
-import { verifyTypeScriptSetup } from '../lib/verify-typescript-setup'
+import { verifyAndRunTypeScript } from '../lib/verify-typescript-setup'
 import path from 'path'
 import spawn from 'next/dist/compiled/cross-spawn'
 
@@ -137,11 +137,11 @@ async function runPlaywright(
   if (!playwrightConfigFile) {
     const { pagesDir, appDir } = findPagesDir(baseDir)
 
-    const { version: typeScriptVersion } = await verifyTypeScriptSetup({
+    const { version: typeScriptVersion } = await verifyAndRunTypeScript({
       dir: baseDir,
       distDir: nextConfig.distDir,
       strictRouteTypes: Boolean(nextConfig.experimental.strictRouteTypes),
-      typeCheckPreflight: false,
+      shouldRunTypeCheck: false,
       tsconfigPath: nextConfig.typescript.tsconfigPath,
       typedRoutes: Boolean(nextConfig.typedRoutes),
       disableStaticImages: nextConfig.images.disableStaticImages,

filename: packages/next/src/cli/next-typegen.ts
patch: @@ -9,7 +9,7 @@ import { printAndExit } from '../server/lib/utils'
 import { PHASE_PRODUCTION_BUILD } from '../shared/lib/constants'
 import { getProjectDir } from '../lib/get-project-dir'
 import { findPagesDir } from '../lib/find-pages-dir'
-import { verifyTypeScriptSetup } from '../lib/verify-typescript-setup'
+import { verifyAndRunTypeScript } from '../lib/verify-typescript-setup'
 import { discoverRoutes } from '../build/route-discovery'
 
 import {
@@ -42,11 +42,11 @@ const nextTypegen = async (
 
   const strictRouteTypes = Boolean(nextConfig.experimental.strictRouteTypes)
 
-  await verifyTypeScriptSetup({
+  await verifyAndRunTypeScript({
     dir: baseDir,
     distDir: nextConfig.distDir,
     strictRouteTypes,
-    typeCheckPreflight: false,
+    shouldRunTypeCheck: false,
     tsconfigPath: nextConfig.typescript.tsconfigPath,
     typedRoutes: Boolean(nextConfig.typedRoutes),
     disableStaticImages: nextConfig.images.disableStaticImages,

filename: packages/next/src/lib/verify-typescript-setup.ts
patch: @@ -53,13 +53,13 @@ function hasNativeTypeScriptPreview(dir: string): boolean {
   }
 }
 
-export async function verifyTypeScriptSetup({
+export async function verifyAndRunTypeScript({
   dir,
   distDir,
   cacheDir,
   strictRouteTypes,
   tsconfigPath,
-  typeCheckPreflight,
+  shouldRunTypeCheck,
   typedRoutes,
   disableStaticImages,
   hasAppDir,
@@ -73,7 +73,7 @@ export async function verifyTypeScriptSetup({
   cacheDir?: string
   strictRouteTypes: boolean
   tsconfigPath: string | undefined
-  typeCheckPreflight: boolean
+  shouldRunTypeCheck: boolean
   typedRoutes: boolean
   disableStaticImages: boolean
   hasAppDir: boolean
@@ -217,7 +217,7 @@ export async function verifyTypeScriptSetup({
     })
 
     let result
-    if (typeCheckPreflight) {
+    if (shouldRunTypeCheck) {
       const { runTypeCheck } =
         require('./typescript/runTypeCheck') as typeof import('./typescript/runTypeCheck')
 
@@ -248,7 +248,7 @@ export async function verifyTypeScriptSetup({
     }
 
     /**
-     * verifyTypeScriptSetup can be either invoked directly in the main thread (during next dev / next lint)
+     * verifyAndRunTypeScript can be either invoked directly in the main thread (during next dev / next lint)
      * or run in a worker (during next build). In the latter case, we need to print the error message, as the
      * parent process will only receive an `Jest worker encountered 1 child process exceptions, exceeding retry limit`.
      */

filename: packages/next/src/server/lib/router-utils/setup-dev-bundler.ts
patch: @@ -26,7 +26,7 @@ import {
 } from '../../../telemetry/events'
 import { getSortedRoutes } from '../../../shared/lib/router/utils'
 import { sortByPageExts } from '../../../build/sort-by-page-exts'
-import { verifyTypeScriptSetup } from '../../../lib/verify-typescript-setup'
+import { verifyAndRunTypeScript } from '../../../lib/verify-typescript-setup'
 import { verifyPartytownSetup } from '../../../lib/verify-partytown-setup'
 import { getNamedRouteRegex } from '../../../shared/lib/router/utils/route-regex'
 import { buildDataRoute } from './build-data-route'
@@ -149,11 +149,11 @@ export type ServerFields = {
 }
 
 async function verifyTypeScript(opts: SetupOpts) {
-  const verifyResult = await verifyTypeScriptSetup({
+  const verifyResult = await verifyAndRunTypeScript({
     dir: opts.dir,
     distDir: opts.nextConfig.distDir,
     strictRouteTypes: Boolean(opts.nextConfig.experimental.strictRouteTypes),
-    typeCheckPreflight: false,
+    shouldRunTypeCheck: false,
     tsconfigPath: opts.nextConfig.typescript.tsconfigPath,
     typedRoutes: Boolean(opts.nextConfig.typedRoutes),
     disableStaticImages: opts.nextConfig.images.disableStaticImages,