Skip to content

Commit 4e8fac9

Browse files
sokrakodiakhq[bot]
andauthored
cache typechecking with incremental compilation (#24559)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 42c4743 commit 4e8fac9

File tree

24 files changed

+113
-42
lines changed

24 files changed

+113
-42
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
"tailwindcss": "1.1.3",
125125
"taskr": "1.1.0",
126126
"tree-kill": "1.2.2",
127-
"typescript": "3.8.3",
127+
"typescript": "4.3.0-beta",
128128
"wait-port": "0.2.2",
129129
"web-streams-polyfill": "2.1.1",
130130
"webpack-bundle-analyzer": "4.3.0",

packages/next/build/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ export default async function build(
146146

147147
const { headers, rewrites, redirects } = customRoutes
148148

149+
const cacheDir = path.join(distDir, 'cache')
149150
if (ciEnvironment.isCI && !ciEnvironment.hasNextSupport) {
150-
const cacheDir = path.join(distDir, 'cache')
151151
const hasCache = await fileExists(cacheDir)
152152

153153
if (!hasCache) {
@@ -193,7 +193,7 @@ export default async function build(
193193
const verifyResult = await nextBuildSpan
194194
.traceChild('verify-typescript-setup')
195195
.traceAsyncFn(() =>
196-
verifyTypeScriptSetup(dir, pagesDir, !ignoreTypeScriptErrors)
196+
verifyTypeScriptSetup(dir, pagesDir, !ignoreTypeScriptErrors, cacheDir)
197197
)
198198

199199
const typeCheckEnd = process.hrtime(typeCheckStart)

packages/next/lib/typescript/runTypeCheck.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'path'
12
import {
23
DiagnosticCategory,
34
getFormattedDiagnostic,
@@ -18,7 +19,8 @@ export interface TypeCheckResult {
1819
export async function runTypeCheck(
1920
ts: typeof import('typescript'),
2021
baseDir: string,
21-
tsConfigPath: string
22+
tsConfigPath: string,
23+
cacheDir?: string
2224
): Promise<TypeCheckResult> {
2325
const effectiveConfiguration = await getTypeScriptConfiguration(
2426
ts,
@@ -35,11 +37,28 @@ export async function runTypeCheck(
3537
}
3638
const requiredConfig = getRequiredConfiguration(ts)
3739

38-
const program = ts.createProgram(effectiveConfiguration.fileNames, {
40+
const options = {
3941
...effectiveConfiguration.options,
4042
...requiredConfig,
4143
noEmit: true,
42-
})
44+
}
45+
46+
let program: import('typescript').Program
47+
let incremental = false
48+
if (options.incremental && cacheDir) {
49+
incremental = true
50+
const builderProgram = ts.createIncrementalProgram({
51+
rootNames: effectiveConfiguration.fileNames,
52+
options: {
53+
...options,
54+
incremental: true,
55+
tsBuildInfoFile: path.join(cacheDir, '.tsbuildinfo'),
56+
},
57+
})
58+
program = builderProgram.getProgram()
59+
} else {
60+
program = ts.createProgram(effectiveConfiguration.fileNames, options)
61+
}
4362
const result = program.emit()
4463

4564
// Intended to match:
@@ -79,6 +98,6 @@ export async function runTypeCheck(
7998
warnings,
8099
inputFilesCount: effectiveConfiguration.fileNames.length,
81100
totalFilesCount: program.getSourceFiles().length,
82-
incremental: false,
101+
incremental,
83102
}
84103
}

packages/next/lib/typescript/writeConfigurationDefaults.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { promises as fs } from 'fs'
22
import chalk from 'chalk'
33
import * as CommentJson from 'next/dist/compiled/comment-json'
4+
import semver from 'next/dist/compiled/semver'
45
import os from 'os'
56
import { getTypeScriptConfiguration } from './getTypeScriptConfiguration'
67

@@ -28,6 +29,9 @@ function getDesiredCompilerOptions(
2829
strict: { suggested: false },
2930
forceConsistentCasingInFileNames: { suggested: true },
3031
noEmit: { suggested: true },
32+
...(semver.gte(ts.version, '4.3.0-beta')
33+
? { incremental: { suggested: true } }
34+
: undefined),
3135

3236
// These values are required and cannot be changed by the user
3337
// Keep this in sync with the webpack config

packages/next/lib/verifyTypeScriptSetup.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { writeConfigurationDefaults } from './typescript/writeConfigurationDefau
1515
export async function verifyTypeScriptSetup(
1616
dir: string,
1717
pagesDir: string,
18-
typeCheckPreflight: boolean
18+
typeCheckPreflight: boolean,
19+
cacheDir?: string
1920
): Promise<{ result?: TypeCheckResult; version: string | null }> {
2021
const tsConfigPath = path.join(dir, 'tsconfig.json')
2122

@@ -48,7 +49,7 @@ export async function verifyTypeScriptSetup(
4849
const { runTypeCheck } = require('./typescript/runTypeCheck')
4950

5051
// Verify the project passes type-checking before we go to webpack phase:
51-
result = await runTypeCheck(ts, dir, tsConfigPath)
52+
result = await runTypeCheck(ts, dir, tsConfigPath, cacheDir)
5253
}
5354
return { result, version: ts.version }
5455
} catch (err) {

packages/react-dev-overlay/src/internal/components/ShadowPortal.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const ShadowPortal: React.FC<ShadowPortalProps> = function Portal({
1111
let mountNode = React.useRef<HTMLDivElement | null>(null)
1212
let portalNode = React.useRef<HTMLElement | null>(null)
1313
let shadowNode = React.useRef<ShadowRoot | null>(null)
14-
let [, forceUpdate] = React.useState()
14+
let [, forceUpdate] = React.useState<{} | undefined>()
1515

1616
React.useLayoutEffect(() => {
1717
const ownerDocument = mountNode.current!.ownerDocument!

test/integration/app-tree/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"strict": false,
88
"forceConsistentCasingInFileNames": true,
99
"noEmit": true,
10+
"incremental": true,
1011
"esModuleInterop": true,
1112
"module": "esnext",
1213
"moduleResolution": "node",

test/integration/custom-server-types/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": false,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/handle-non-page-in-pages/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"strict": false,
88
"forceConsistentCasingInFileNames": true,
99
"noEmit": true,
10+
"incremental": true,
1011
"esModuleInterop": true,
1112
"module": "esnext",
1213
"moduleResolution": "node",

test/integration/image-component/typescript-style/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/image-component/typescript/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/tsconfig-verifier/test/index.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('tsconfig.json verifier', () => {
3939
\\"strict\\": false,
4040
\\"forceConsistentCasingInFileNames\\": true,
4141
\\"noEmit\\": true,
42+
\\"incremental\\": true,
4243
\\"esModuleInterop\\": true,
4344
\\"module\\": \\"esnext\\",
4445
\\"moduleResolution\\": \\"node\\",
@@ -83,6 +84,7 @@ describe('tsconfig.json verifier', () => {
8384
\\"strict\\": false,
8485
\\"forceConsistentCasingInFileNames\\": true,
8586
\\"noEmit\\": true,
87+
\\"incremental\\": true,
8688
\\"esModuleInterop\\": true,
8789
\\"module\\": \\"esnext\\",
8890
\\"moduleResolution\\": \\"node\\",
@@ -150,6 +152,7 @@ describe('tsconfig.json verifier', () => {
150152
\\"strict\\": false,
151153
\\"forceConsistentCasingInFileNames\\": true,
152154
\\"noEmit\\": true,
155+
\\"incremental\\": true,
153156
\\"moduleResolution\\": \\"node\\",
154157
\\"resolveJsonModule\\": true,
155158
\\"isolatedModules\\": true,
@@ -198,6 +201,7 @@ describe('tsconfig.json verifier', () => {
198201
\\"strict\\": false,
199202
\\"forceConsistentCasingInFileNames\\": true,
200203
\\"noEmit\\": true,
204+
\\"incremental\\": true,
201205
\\"moduleResolution\\": \\"node\\",
202206
\\"resolveJsonModule\\": true,
203207
\\"isolatedModules\\": true,
@@ -243,6 +247,7 @@ describe('tsconfig.json verifier', () => {
243247
\\"strict\\": false,
244248
\\"forceConsistentCasingInFileNames\\": true,
245249
\\"noEmit\\": true,
250+
\\"incremental\\": true,
246251
\\"moduleResolution\\": \\"node\\",
247252
\\"resolveJsonModule\\": true,
248253
\\"isolatedModules\\": true,
@@ -281,6 +286,7 @@ describe('tsconfig.json verifier', () => {
281286
"strict": false,
282287
"forceConsistentCasingInFileNames": true,
283288
"noEmit": true,
289+
"incremental": true,
284290
"esModuleInterop": true,
285291
"module": "esnext",
286292
"moduleResolution": "node",

test/integration/typescript-baseurl/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"strict": true,
1212
"forceConsistentCasingInFileNames": true,
1313
"noEmit": true,
14+
"incremental": true,
1415
"moduleResolution": "node",
1516
"resolveJsonModule": true,
1617
"isolatedModules": true

test/integration/typescript-external-dir/project/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"strict": true,
1212
"forceConsistentCasingInFileNames": true,
1313
"noEmit": true,
14+
"incremental": true,
1415
"moduleResolution": "node",
1516
"resolveJsonModule": true,
1617
"isolatedModules": true

test/integration/typescript-external-dir/shared/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/typescript-filtered-files/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/typescript-hmr/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/typescript-ignore-errors/test/index.test.js

+53-32
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,65 @@ jest.setTimeout(1000 * 60 * 2)
77

88
const appDir = join(__dirname, '..')
99
const nextConfigFile = new File(join(appDir, 'next.config.js'))
10+
const tsConfigFile = new File(join(appDir, 'tsconfig.json'))
1011

1112
describe('TypeScript with error handling options', () => {
1213
// Dev can no longer show errors (for now), logbox will cover this in the
1314
// future.
14-
for (const ignoreBuildErrors of [false, true]) {
15-
describe(`ignoreBuildErrors: ${ignoreBuildErrors}`, () => {
16-
beforeAll(() => {
17-
const nextConfig = {
18-
typescript: { ignoreBuildErrors },
19-
}
20-
nextConfigFile.write('module.exports = ' + JSON.stringify(nextConfig))
21-
})
22-
afterAll(() => {
23-
nextConfigFile.restore()
24-
})
15+
for (const incremental of [false, true]) {
16+
for (const ignoreBuildErrors of [false, true]) {
17+
describe(`ignoreBuildErrors: ${ignoreBuildErrors}`, () => {
18+
beforeAll(() => {
19+
const nextConfig = {
20+
typescript: { ignoreBuildErrors },
21+
}
22+
nextConfigFile.write('module.exports = ' + JSON.stringify(nextConfig))
23+
const tsconfig = JSON.parse(tsConfigFile.originalContent)
24+
tsConfigFile.write(
25+
JSON.stringify(
26+
{
27+
...tsconfig,
28+
compilerOptions: {
29+
...tsconfig.compilerOptions,
30+
incremental,
31+
},
32+
},
33+
null,
34+
2
35+
)
36+
)
37+
})
38+
afterAll(() => {
39+
nextConfigFile.restore()
40+
tsConfigFile.restore()
41+
})
2542

26-
it(
27-
ignoreBuildErrors
28-
? 'Next builds the application despite type errors'
29-
: 'Next fails to build the application despite type errors',
30-
async () => {
31-
const { stdout, stderr } = await nextBuild(appDir, [], {
32-
stdout: true,
33-
stderr: true,
34-
})
43+
it(
44+
(ignoreBuildErrors
45+
? 'Next builds the application despite type errors'
46+
: 'Next fails to build the application despite type errors') +
47+
(incremental
48+
? ' in incremental mode'
49+
: ' without incremental mode'),
50+
async () => {
51+
const { stdout, stderr } = await nextBuild(appDir, [], {
52+
stdout: true,
53+
stderr: true,
54+
})
3555

36-
if (ignoreBuildErrors) {
37-
expect(stdout).toContain('Compiled successfully')
38-
expect(stderr).not.toContain('Failed to compile.')
39-
expect(stderr).not.toContain("not assignable to type 'boolean'")
40-
} else {
41-
expect(stdout).not.toContain('Compiled successfully')
42-
expect(stderr).toContain('Failed to compile.')
43-
expect(stderr).toContain('./pages/index.tsx:2:31')
44-
expect(stderr).toContain("not assignable to type 'boolean'")
56+
if (ignoreBuildErrors) {
57+
expect(stdout).toContain('Compiled successfully')
58+
expect(stderr).not.toContain('Failed to compile.')
59+
expect(stderr).not.toContain("not assignable to type 'boolean'")
60+
} else {
61+
expect(stdout).not.toContain('Compiled successfully')
62+
expect(stderr).toContain('Failed to compile.')
63+
expect(stderr).toContain('./pages/index.tsx:2:31')
64+
expect(stderr).toContain("not assignable to type 'boolean'")
65+
}
4566
}
46-
}
47-
)
48-
})
67+
)
68+
})
69+
}
4970
}
5071
})

test/integration/typescript-ignore-errors/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/typescript-only-remove-type-imports/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

test/integration/typescript-paths/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"strict": true,
2424
"forceConsistentCasingInFileNames": true,
2525
"noEmit": true,
26+
"incremental": true,
2627
"moduleResolution": "node",
2728
"resolveJsonModule": true,
2829
"isolatedModules": true

test/integration/typescript-workspaces-paths/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"strict": true,
2424
"forceConsistentCasingInFileNames": true,
2525
"noEmit": true,
26+
"incremental": true,
2627
"moduleResolution": "node",
2728
"resolveJsonModule": true,
2829
"isolatedModules": true

test/integration/typescript/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"strict": true,
1111
"forceConsistentCasingInFileNames": true,
1212
"noEmit": true,
13+
"incremental": true,
1314
"moduleResolution": "node",
1415
"resolveJsonModule": true,
1516
"isolatedModules": true

0 commit comments

Comments
 (0)