Skip to content

Commit d1122f8

Browse files
authored
perf: run type check and eslint in parallel (#37105)
1 parent 4e6b6a5 commit d1122f8

File tree

1 file changed

+104
-37
lines changed

1 file changed

+104
-37
lines changed

packages/next/build/index.ts

Lines changed: 104 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import chalk from 'next/dist/compiled/chalk'
44
import crypto from 'crypto'
55
import { isMatch } from 'next/dist/compiled/micromatch'
66
import { promises, writeFileSync } from 'fs'
7+
import { Worker as JestWorker } from 'next/dist/compiled/jest-worker'
78
import { Worker } from '../lib/worker'
89
import devalue from 'next/dist/compiled/devalue'
910
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
@@ -34,7 +35,6 @@ import { nonNullable } from '../lib/non-nullable'
3435
import { recursiveDelete } from '../lib/recursive-delete'
3536
import { verifyAndLint } from '../lib/verifyAndLint'
3637
import { verifyPartytownSetup } from '../lib/verify-partytown-setup'
37-
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
3838
import {
3939
BUILD_ID_FILE,
4040
BUILD_MANIFEST,
@@ -229,30 +229,74 @@ export default async function build(
229229
const ignoreTypeScriptErrors = Boolean(
230230
config.typescript.ignoreBuildErrors
231231
)
232+
233+
const ignoreESLint = Boolean(config.eslint.ignoreDuringBuilds)
234+
const eslintCacheDir = path.join(cacheDir, 'eslint/')
235+
const shouldLint = !ignoreESLint && runLint
236+
237+
if (ignoreTypeScriptErrors) {
238+
Log.info('Skipping validation of types')
239+
}
240+
if (runLint && ignoreESLint) {
241+
// only print log when build requre lint while ignoreESLint is enabled
242+
Log.info('Skipping linting')
243+
}
244+
245+
let typeCheckingAndLintingSpinnerPrefixText: string | undefined
246+
let typeCheckingAndLintingSpinner:
247+
| ReturnType<typeof createSpinner>
248+
| undefined
249+
250+
if (!ignoreTypeScriptErrors && shouldLint) {
251+
typeCheckingAndLintingSpinnerPrefixText =
252+
'Linting and checking validity of types'
253+
} else if (!ignoreTypeScriptErrors) {
254+
typeCheckingAndLintingSpinnerPrefixText = 'Checking validity of types'
255+
} else if (shouldLint) {
256+
typeCheckingAndLintingSpinnerPrefixText = 'Linting'
257+
}
258+
259+
// we will not create a spinner if both ignoreTypeScriptErrors and ignoreESLint are
260+
// enabled, but we will still verifying project's tsconfig and dependencies.
261+
if (typeCheckingAndLintingSpinnerPrefixText) {
262+
typeCheckingAndLintingSpinner = createSpinner({
263+
prefixText: `${Log.prefixes.info} ${typeCheckingAndLintingSpinnerPrefixText}`,
264+
})
265+
}
266+
232267
const typeCheckStart = process.hrtime()
233-
const typeCheckingSpinner = createSpinner({
234-
prefixText: `${Log.prefixes.info} ${
235-
ignoreTypeScriptErrors
236-
? 'Skipping validation of types'
237-
: 'Checking validity of types'
238-
}`,
239-
})
240268

241-
const verifyResult = await nextBuildSpan
242-
.traceChild('verify-typescript-setup')
243-
.traceAsyncFn(() =>
269+
const [[verifyResult, typeCheckEnd]] = await Promise.all([
270+
nextBuildSpan.traceChild('verify-typescript-setup').traceAsyncFn(() =>
244271
verifyTypeScriptSetup(
245272
dir,
246273
[pagesDir, viewsDir].filter(Boolean) as string[],
247274
!ignoreTypeScriptErrors,
248275
config,
249-
cacheDir
250-
)
251-
)
276+
cacheDir,
277+
config.experimental.cpus,
278+
config.experimental.workerThreads
279+
).then((resolved) => {
280+
const checkEnd = process.hrtime(typeCheckStart)
281+
return [resolved, checkEnd] as const
282+
})
283+
),
284+
shouldLint &&
285+
nextBuildSpan.traceChild('verify-and-lint').traceAsyncFn(async () => {
286+
await verifyAndLint(
287+
dir,
288+
eslintCacheDir,
289+
config.eslint?.dirs,
290+
config.experimental.cpus,
291+
config.experimental.workerThreads,
292+
telemetry
293+
)
294+
}),
295+
])
252296

253-
const typeCheckEnd = process.hrtime(typeCheckStart)
297+
typeCheckingAndLintingSpinner?.stopAndPersist()
254298

255-
if (!ignoreTypeScriptErrors) {
299+
if (!ignoreTypeScriptErrors && verifyResult) {
256300
telemetry.record(
257301
eventTypeCheckCompleted({
258302
durationInSeconds: typeCheckEnd[0],
@@ -264,27 +308,6 @@ export default async function build(
264308
)
265309
}
266310

267-
if (typeCheckingSpinner) {
268-
typeCheckingSpinner.stopAndPersist()
269-
}
270-
271-
const ignoreESLint = Boolean(config.eslint.ignoreDuringBuilds)
272-
const eslintCacheDir = path.join(cacheDir, 'eslint/')
273-
const shouldLint = !ignoreESLint && runLint
274-
if (shouldLint) {
275-
await nextBuildSpan
276-
.traceChild('verify-and-lint')
277-
.traceAsyncFn(async () => {
278-
await verifyAndLint(
279-
dir,
280-
eslintCacheDir,
281-
config.eslint?.dirs,
282-
config.experimental.cpus,
283-
config.experimental.workerThreads,
284-
telemetry
285-
)
286-
})
287-
}
288311
const buildLintEvent: EventBuildFeatureUsage = {
289312
featureName: 'build-lint',
290313
invocationCount: shouldLint ? 1 : 0,
@@ -2318,6 +2341,50 @@ export default async function build(
23182341
}
23192342
}
23202343

2344+
/**
2345+
* typescript will be loaded in "next/lib/verifyTypeScriptSetup" and
2346+
* then passed to "next/lib/typescript/runTypeCheck" as a parameter.
2347+
*
2348+
* Since it is impossible to pass a function from main thread to a worker,
2349+
* instead of running "next/lib/typescript/runTypeCheck" in a worker,
2350+
* we will run entire "next/lib/verifyTypeScriptSetup" in a worker instead.
2351+
*/
2352+
function verifyTypeScriptSetup(
2353+
dir: string,
2354+
intentDirs: string[],
2355+
typeCheckPreflight: boolean,
2356+
config: NextConfigComplete,
2357+
cacheDir: string | undefined,
2358+
numWorkers: number | undefined,
2359+
enableWorkerThreads: boolean | undefined
2360+
) {
2361+
const typeCheckWorker = new JestWorker(
2362+
require.resolve('../lib/verifyTypeScriptSetup'),
2363+
{
2364+
numWorkers,
2365+
enableWorkerThreads,
2366+
}
2367+
) as JestWorker & {
2368+
verifyTypeScriptSetup: typeof import('../lib/verifyTypeScriptSetup').verifyTypeScriptSetup
2369+
}
2370+
2371+
typeCheckWorker.getStdout().pipe(process.stdout)
2372+
typeCheckWorker.getStderr().pipe(process.stderr)
2373+
2374+
return typeCheckWorker
2375+
.verifyTypeScriptSetup(
2376+
dir,
2377+
intentDirs,
2378+
typeCheckPreflight,
2379+
config,
2380+
cacheDir
2381+
)
2382+
.then((result) => {
2383+
typeCheckWorker.end()
2384+
return result
2385+
})
2386+
}
2387+
23212388
function generateClientSsgManifest(
23222389
prerenderManifest: PrerenderManifest,
23232390
{

0 commit comments

Comments
 (0)