Skip to content

Commit

Permalink
perf: run type check and eslint in parallel (#37105)
Browse files Browse the repository at this point in the history
  • Loading branch information
SukkaW committed May 23, 2022
1 parent 4e6b6a5 commit d1122f8
Showing 1 changed file with 104 additions and 37 deletions.
141 changes: 104 additions & 37 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chalk from 'next/dist/compiled/chalk'
import crypto from 'crypto'
import { isMatch } from 'next/dist/compiled/micromatch'
import { promises, writeFileSync } from 'fs'
import { Worker as JestWorker } from 'next/dist/compiled/jest-worker'
import { Worker } from '../lib/worker'
import devalue from 'next/dist/compiled/devalue'
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
Expand Down Expand Up @@ -34,7 +35,6 @@ import { nonNullable } from '../lib/non-nullable'
import { recursiveDelete } from '../lib/recursive-delete'
import { verifyAndLint } from '../lib/verifyAndLint'
import { verifyPartytownSetup } from '../lib/verify-partytown-setup'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
import {
BUILD_ID_FILE,
BUILD_MANIFEST,
Expand Down Expand Up @@ -229,30 +229,74 @@ export default async function build(
const ignoreTypeScriptErrors = Boolean(
config.typescript.ignoreBuildErrors
)

const ignoreESLint = Boolean(config.eslint.ignoreDuringBuilds)
const eslintCacheDir = path.join(cacheDir, 'eslint/')
const shouldLint = !ignoreESLint && runLint

if (ignoreTypeScriptErrors) {
Log.info('Skipping validation of types')
}
if (runLint && ignoreESLint) {
// only print log when build requre lint while ignoreESLint is enabled
Log.info('Skipping linting')
}

let typeCheckingAndLintingSpinnerPrefixText: string | undefined
let typeCheckingAndLintingSpinner:
| ReturnType<typeof createSpinner>
| undefined

if (!ignoreTypeScriptErrors && shouldLint) {
typeCheckingAndLintingSpinnerPrefixText =
'Linting and checking validity of types'
} else if (!ignoreTypeScriptErrors) {
typeCheckingAndLintingSpinnerPrefixText = 'Checking validity of types'
} else if (shouldLint) {
typeCheckingAndLintingSpinnerPrefixText = 'Linting'
}

// we will not create a spinner if both ignoreTypeScriptErrors and ignoreESLint are
// enabled, but we will still verifying project's tsconfig and dependencies.
if (typeCheckingAndLintingSpinnerPrefixText) {
typeCheckingAndLintingSpinner = createSpinner({
prefixText: `${Log.prefixes.info} ${typeCheckingAndLintingSpinnerPrefixText}`,
})
}

const typeCheckStart = process.hrtime()
const typeCheckingSpinner = createSpinner({
prefixText: `${Log.prefixes.info} ${
ignoreTypeScriptErrors
? 'Skipping validation of types'
: 'Checking validity of types'
}`,
})

const verifyResult = await nextBuildSpan
.traceChild('verify-typescript-setup')
.traceAsyncFn(() =>
const [[verifyResult, typeCheckEnd]] = await Promise.all([
nextBuildSpan.traceChild('verify-typescript-setup').traceAsyncFn(() =>
verifyTypeScriptSetup(
dir,
[pagesDir, viewsDir].filter(Boolean) as string[],
!ignoreTypeScriptErrors,
config,
cacheDir
)
)
cacheDir,
config.experimental.cpus,
config.experimental.workerThreads
).then((resolved) => {
const checkEnd = process.hrtime(typeCheckStart)
return [resolved, checkEnd] as const
})
),
shouldLint &&
nextBuildSpan.traceChild('verify-and-lint').traceAsyncFn(async () => {
await verifyAndLint(
dir,
eslintCacheDir,
config.eslint?.dirs,
config.experimental.cpus,
config.experimental.workerThreads,
telemetry
)
}),
])

const typeCheckEnd = process.hrtime(typeCheckStart)
typeCheckingAndLintingSpinner?.stopAndPersist()

if (!ignoreTypeScriptErrors) {
if (!ignoreTypeScriptErrors && verifyResult) {
telemetry.record(
eventTypeCheckCompleted({
durationInSeconds: typeCheckEnd[0],
Expand All @@ -264,27 +308,6 @@ export default async function build(
)
}

if (typeCheckingSpinner) {
typeCheckingSpinner.stopAndPersist()
}

const ignoreESLint = Boolean(config.eslint.ignoreDuringBuilds)
const eslintCacheDir = path.join(cacheDir, 'eslint/')
const shouldLint = !ignoreESLint && runLint
if (shouldLint) {
await nextBuildSpan
.traceChild('verify-and-lint')
.traceAsyncFn(async () => {
await verifyAndLint(
dir,
eslintCacheDir,
config.eslint?.dirs,
config.experimental.cpus,
config.experimental.workerThreads,
telemetry
)
})
}
const buildLintEvent: EventBuildFeatureUsage = {
featureName: 'build-lint',
invocationCount: shouldLint ? 1 : 0,
Expand Down Expand Up @@ -2318,6 +2341,50 @@ export default async function build(
}
}

/**
* typescript will be loaded in "next/lib/verifyTypeScriptSetup" and
* then passed to "next/lib/typescript/runTypeCheck" as a parameter.
*
* Since it is impossible to pass a function from main thread to a worker,
* instead of running "next/lib/typescript/runTypeCheck" in a worker,
* we will run entire "next/lib/verifyTypeScriptSetup" in a worker instead.
*/
function verifyTypeScriptSetup(
dir: string,
intentDirs: string[],
typeCheckPreflight: boolean,
config: NextConfigComplete,
cacheDir: string | undefined,
numWorkers: number | undefined,
enableWorkerThreads: boolean | undefined
) {
const typeCheckWorker = new JestWorker(
require.resolve('../lib/verifyTypeScriptSetup'),
{
numWorkers,
enableWorkerThreads,
}
) as JestWorker & {
verifyTypeScriptSetup: typeof import('../lib/verifyTypeScriptSetup').verifyTypeScriptSetup
}

typeCheckWorker.getStdout().pipe(process.stdout)
typeCheckWorker.getStderr().pipe(process.stderr)

return typeCheckWorker
.verifyTypeScriptSetup(
dir,
intentDirs,
typeCheckPreflight,
config,
cacheDir
)
.then((result) => {
typeCheckWorker.end()
return result
})
}

function generateClientSsgManifest(
prerenderManifest: PrerenderManifest,
{
Expand Down

0 comments on commit d1122f8

Please sign in to comment.