diff --git a/package.json b/package.json index 88c410c88017d..02d0e70449499 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "tailwindcss": "1.1.3", "taskr": "1.1.0", "tree-kill": "1.2.2", - "typescript": "3.8.3", + "typescript": "4.3.0-beta", "wait-port": "0.2.2", "web-streams-polyfill": "2.1.1", "webpack-bundle-analyzer": "4.3.0", diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 668a9b0098ecb..89617e2e609e9 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -146,8 +146,8 @@ export default async function build( const { headers, rewrites, redirects } = customRoutes + const cacheDir = path.join(distDir, 'cache') if (ciEnvironment.isCI && !ciEnvironment.hasNextSupport) { - const cacheDir = path.join(distDir, 'cache') const hasCache = await fileExists(cacheDir) if (!hasCache) { @@ -193,7 +193,7 @@ export default async function build( const verifyResult = await nextBuildSpan .traceChild('verify-typescript-setup') .traceAsyncFn(() => - verifyTypeScriptSetup(dir, pagesDir, !ignoreTypeScriptErrors) + verifyTypeScriptSetup(dir, pagesDir, !ignoreTypeScriptErrors, cacheDir) ) const typeCheckEnd = process.hrtime(typeCheckStart) diff --git a/packages/next/lib/typescript/runTypeCheck.ts b/packages/next/lib/typescript/runTypeCheck.ts index 510fad4a43c77..569c474b0c35a 100644 --- a/packages/next/lib/typescript/runTypeCheck.ts +++ b/packages/next/lib/typescript/runTypeCheck.ts @@ -1,3 +1,4 @@ +import path from 'path' import { DiagnosticCategory, getFormattedDiagnostic, @@ -18,7 +19,8 @@ export interface TypeCheckResult { export async function runTypeCheck( ts: typeof import('typescript'), baseDir: string, - tsConfigPath: string + tsConfigPath: string, + cacheDir?: string ): Promise { const effectiveConfiguration = await getTypeScriptConfiguration( ts, @@ -35,11 +37,28 @@ export async function runTypeCheck( } const requiredConfig = getRequiredConfiguration(ts) - const program = ts.createProgram(effectiveConfiguration.fileNames, { + const options = { ...effectiveConfiguration.options, ...requiredConfig, noEmit: true, - }) + } + + let program: import('typescript').Program + let incremental = false + if (options.incremental && cacheDir) { + incremental = true + const builderProgram = ts.createIncrementalProgram({ + rootNames: effectiveConfiguration.fileNames, + options: { + ...options, + incremental: true, + tsBuildInfoFile: path.join(cacheDir, '.tsbuildinfo'), + }, + }) + program = builderProgram.getProgram() + } else { + program = ts.createProgram(effectiveConfiguration.fileNames, options) + } const result = program.emit() // Intended to match: @@ -79,6 +98,6 @@ export async function runTypeCheck( warnings, inputFilesCount: effectiveConfiguration.fileNames.length, totalFilesCount: program.getSourceFiles().length, - incremental: false, + incremental, } } diff --git a/packages/next/lib/typescript/writeConfigurationDefaults.ts b/packages/next/lib/typescript/writeConfigurationDefaults.ts index 04333a8b33eae..0f8ee3ae4f049 100644 --- a/packages/next/lib/typescript/writeConfigurationDefaults.ts +++ b/packages/next/lib/typescript/writeConfigurationDefaults.ts @@ -1,6 +1,7 @@ import { promises as fs } from 'fs' import chalk from 'chalk' import * as CommentJson from 'next/dist/compiled/comment-json' +import semver from 'next/dist/compiled/semver' import os from 'os' import { getTypeScriptConfiguration } from './getTypeScriptConfiguration' @@ -28,6 +29,9 @@ function getDesiredCompilerOptions( strict: { suggested: false }, forceConsistentCasingInFileNames: { suggested: true }, noEmit: { suggested: true }, + ...(semver.gte(ts.version, '4.3.0-beta') + ? { incremental: { suggested: true } } + : undefined), // These values are required and cannot be changed by the user // Keep this in sync with the webpack config diff --git a/packages/next/lib/verifyTypeScriptSetup.ts b/packages/next/lib/verifyTypeScriptSetup.ts index 6f10375cb3743..b37b26e050880 100644 --- a/packages/next/lib/verifyTypeScriptSetup.ts +++ b/packages/next/lib/verifyTypeScriptSetup.ts @@ -15,7 +15,8 @@ import { writeConfigurationDefaults } from './typescript/writeConfigurationDefau export async function verifyTypeScriptSetup( dir: string, pagesDir: string, - typeCheckPreflight: boolean + typeCheckPreflight: boolean, + cacheDir?: string ): Promise<{ result?: TypeCheckResult; version: string | null }> { const tsConfigPath = path.join(dir, 'tsconfig.json') @@ -48,7 +49,7 @@ export async function verifyTypeScriptSetup( const { runTypeCheck } = require('./typescript/runTypeCheck') // Verify the project passes type-checking before we go to webpack phase: - result = await runTypeCheck(ts, dir, tsConfigPath) + result = await runTypeCheck(ts, dir, tsConfigPath, cacheDir) } return { result, version: ts.version } } catch (err) { diff --git a/packages/react-dev-overlay/src/internal/components/ShadowPortal.tsx b/packages/react-dev-overlay/src/internal/components/ShadowPortal.tsx index 228660da6dadb..61b7391c58ad3 100644 --- a/packages/react-dev-overlay/src/internal/components/ShadowPortal.tsx +++ b/packages/react-dev-overlay/src/internal/components/ShadowPortal.tsx @@ -11,7 +11,7 @@ export const ShadowPortal: React.FC = function Portal({ let mountNode = React.useRef(null) let portalNode = React.useRef(null) let shadowNode = React.useRef(null) - let [, forceUpdate] = React.useState() + let [, forceUpdate] = React.useState<{} | undefined>() React.useLayoutEffect(() => { const ownerDocument = mountNode.current!.ownerDocument! diff --git a/test/integration/app-tree/tsconfig.json b/test/integration/app-tree/tsconfig.json index c5d53d8983d19..fd92c3d69662e 100644 --- a/test/integration/app-tree/tsconfig.json +++ b/test/integration/app-tree/tsconfig.json @@ -7,6 +7,7 @@ "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", diff --git a/test/integration/custom-server-types/tsconfig.json b/test/integration/custom-server-types/tsconfig.json index 3731543adffe0..c84ece7ecaead 100644 --- a/test/integration/custom-server-types/tsconfig.json +++ b/test/integration/custom-server-types/tsconfig.json @@ -10,6 +10,7 @@ "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/handle-non-page-in-pages/tsconfig.json b/test/integration/handle-non-page-in-pages/tsconfig.json index c5d53d8983d19..fd92c3d69662e 100644 --- a/test/integration/handle-non-page-in-pages/tsconfig.json +++ b/test/integration/handle-non-page-in-pages/tsconfig.json @@ -7,6 +7,7 @@ "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", diff --git a/test/integration/image-component/typescript-style/tsconfig.json b/test/integration/image-component/typescript-style/tsconfig.json index 15cec79584e84..9ada0f9fde615 100644 --- a/test/integration/image-component/typescript-style/tsconfig.json +++ b/test/integration/image-component/typescript-style/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/image-component/typescript/tsconfig.json b/test/integration/image-component/typescript/tsconfig.json index 15cec79584e84..9ada0f9fde615 100644 --- a/test/integration/image-component/typescript/tsconfig.json +++ b/test/integration/image-component/typescript/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/tsconfig-verifier/test/index.test.js b/test/integration/tsconfig-verifier/test/index.test.js index d5762e6ecaf52..1011996308a4c 100644 --- a/test/integration/tsconfig-verifier/test/index.test.js +++ b/test/integration/tsconfig-verifier/test/index.test.js @@ -39,6 +39,7 @@ describe('tsconfig.json verifier', () => { \\"strict\\": false, \\"forceConsistentCasingInFileNames\\": true, \\"noEmit\\": true, + \\"incremental\\": true, \\"esModuleInterop\\": true, \\"module\\": \\"esnext\\", \\"moduleResolution\\": \\"node\\", @@ -83,6 +84,7 @@ describe('tsconfig.json verifier', () => { \\"strict\\": false, \\"forceConsistentCasingInFileNames\\": true, \\"noEmit\\": true, + \\"incremental\\": true, \\"esModuleInterop\\": true, \\"module\\": \\"esnext\\", \\"moduleResolution\\": \\"node\\", @@ -150,6 +152,7 @@ describe('tsconfig.json verifier', () => { \\"strict\\": false, \\"forceConsistentCasingInFileNames\\": true, \\"noEmit\\": true, + \\"incremental\\": true, \\"moduleResolution\\": \\"node\\", \\"resolveJsonModule\\": true, \\"isolatedModules\\": true, @@ -198,6 +201,7 @@ describe('tsconfig.json verifier', () => { \\"strict\\": false, \\"forceConsistentCasingInFileNames\\": true, \\"noEmit\\": true, + \\"incremental\\": true, \\"moduleResolution\\": \\"node\\", \\"resolveJsonModule\\": true, \\"isolatedModules\\": true, @@ -243,6 +247,7 @@ describe('tsconfig.json verifier', () => { \\"strict\\": false, \\"forceConsistentCasingInFileNames\\": true, \\"noEmit\\": true, + \\"incremental\\": true, \\"moduleResolution\\": \\"node\\", \\"resolveJsonModule\\": true, \\"isolatedModules\\": true, @@ -281,6 +286,7 @@ describe('tsconfig.json verifier', () => { "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", diff --git a/test/integration/typescript-baseurl/tsconfig.json b/test/integration/typescript-baseurl/tsconfig.json index dce2837cb1bcc..11bbfe8efba7a 100644 --- a/test/integration/typescript-baseurl/tsconfig.json +++ b/test/integration/typescript-baseurl/tsconfig.json @@ -11,6 +11,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-external-dir/project/tsconfig.json b/test/integration/typescript-external-dir/project/tsconfig.json index dce2837cb1bcc..11bbfe8efba7a 100644 --- a/test/integration/typescript-external-dir/project/tsconfig.json +++ b/test/integration/typescript-external-dir/project/tsconfig.json @@ -11,6 +11,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-external-dir/shared/tsconfig.json b/test/integration/typescript-external-dir/shared/tsconfig.json index de0655b2346cb..f28f31c99cba9 100644 --- a/test/integration/typescript-external-dir/shared/tsconfig.json +++ b/test/integration/typescript-external-dir/shared/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-filtered-files/tsconfig.json b/test/integration/typescript-filtered-files/tsconfig.json index 15cec79584e84..9ada0f9fde615 100644 --- a/test/integration/typescript-filtered-files/tsconfig.json +++ b/test/integration/typescript-filtered-files/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-hmr/tsconfig.json b/test/integration/typescript-hmr/tsconfig.json index 1442a27191070..f19745af2af6e 100644 --- a/test/integration/typescript-hmr/tsconfig.json +++ b/test/integration/typescript-hmr/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-ignore-errors/test/index.test.js b/test/integration/typescript-ignore-errors/test/index.test.js index 57f3a51f43582..230b1592fc546 100644 --- a/test/integration/typescript-ignore-errors/test/index.test.js +++ b/test/integration/typescript-ignore-errors/test/index.test.js @@ -7,44 +7,65 @@ jest.setTimeout(1000 * 60 * 2) const appDir = join(__dirname, '..') const nextConfigFile = new File(join(appDir, 'next.config.js')) +const tsConfigFile = new File(join(appDir, 'tsconfig.json')) describe('TypeScript with error handling options', () => { // Dev can no longer show errors (for now), logbox will cover this in the // future. - for (const ignoreBuildErrors of [false, true]) { - describe(`ignoreBuildErrors: ${ignoreBuildErrors}`, () => { - beforeAll(() => { - const nextConfig = { - typescript: { ignoreBuildErrors }, - } - nextConfigFile.write('module.exports = ' + JSON.stringify(nextConfig)) - }) - afterAll(() => { - nextConfigFile.restore() - }) + for (const incremental of [false, true]) { + for (const ignoreBuildErrors of [false, true]) { + describe(`ignoreBuildErrors: ${ignoreBuildErrors}`, () => { + beforeAll(() => { + const nextConfig = { + typescript: { ignoreBuildErrors }, + } + nextConfigFile.write('module.exports = ' + JSON.stringify(nextConfig)) + const tsconfig = JSON.parse(tsConfigFile.originalContent) + tsConfigFile.write( + JSON.stringify( + { + ...tsconfig, + compilerOptions: { + ...tsconfig.compilerOptions, + incremental, + }, + }, + null, + 2 + ) + ) + }) + afterAll(() => { + nextConfigFile.restore() + tsConfigFile.restore() + }) - it( - ignoreBuildErrors - ? 'Next builds the application despite type errors' - : 'Next fails to build the application despite type errors', - async () => { - const { stdout, stderr } = await nextBuild(appDir, [], { - stdout: true, - stderr: true, - }) + it( + (ignoreBuildErrors + ? 'Next builds the application despite type errors' + : 'Next fails to build the application despite type errors') + + (incremental + ? ' in incremental mode' + : ' without incremental mode'), + async () => { + const { stdout, stderr } = await nextBuild(appDir, [], { + stdout: true, + stderr: true, + }) - if (ignoreBuildErrors) { - expect(stdout).toContain('Compiled successfully') - expect(stderr).not.toContain('Failed to compile.') - expect(stderr).not.toContain("not assignable to type 'boolean'") - } else { - expect(stdout).not.toContain('Compiled successfully') - expect(stderr).toContain('Failed to compile.') - expect(stderr).toContain('./pages/index.tsx:2:31') - expect(stderr).toContain("not assignable to type 'boolean'") + if (ignoreBuildErrors) { + expect(stdout).toContain('Compiled successfully') + expect(stderr).not.toContain('Failed to compile.') + expect(stderr).not.toContain("not assignable to type 'boolean'") + } else { + expect(stdout).not.toContain('Compiled successfully') + expect(stderr).toContain('Failed to compile.') + expect(stderr).toContain('./pages/index.tsx:2:31') + expect(stderr).toContain("not assignable to type 'boolean'") + } } - } - ) - }) + ) + }) + } } }) diff --git a/test/integration/typescript-ignore-errors/tsconfig.json b/test/integration/typescript-ignore-errors/tsconfig.json index 1442a27191070..f19745af2af6e 100644 --- a/test/integration/typescript-ignore-errors/tsconfig.json +++ b/test/integration/typescript-ignore-errors/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-only-remove-type-imports/tsconfig.json b/test/integration/typescript-only-remove-type-imports/tsconfig.json index 15cec79584e84..9ada0f9fde615 100644 --- a/test/integration/typescript-only-remove-type-imports/tsconfig.json +++ b/test/integration/typescript-only-remove-type-imports/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-paths/tsconfig.json b/test/integration/typescript-paths/tsconfig.json index 5578956b9b4b3..dfd5380d77ca6 100644 --- a/test/integration/typescript-paths/tsconfig.json +++ b/test/integration/typescript-paths/tsconfig.json @@ -23,6 +23,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript-workspaces-paths/tsconfig.json b/test/integration/typescript-workspaces-paths/tsconfig.json index 2be4d1bba03e6..2c9953c90187d 100644 --- a/test/integration/typescript-workspaces-paths/tsconfig.json +++ b/test/integration/typescript-workspaces-paths/tsconfig.json @@ -23,6 +23,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/test/integration/typescript/tsconfig.json b/test/integration/typescript/tsconfig.json index 15cec79584e84..9ada0f9fde615 100644 --- a/test/integration/typescript/tsconfig.json +++ b/test/integration/typescript/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true diff --git a/yarn.lock b/yarn.lock index b756310393ea0..c163a6d979744 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16122,6 +16122,11 @@ typescript@3.8.3: version "3.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" +typescript@4.3.0-beta: + version "4.3.0-beta" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-beta.tgz#8098e7be29032f09827e94b8e3a6befc9ff70c77" + integrity sha512-bl3wxSVL6gWLQFa466Vm5Vk3z0BNx+QxWhb9wFiYEHm6H8oqFd8Wo3XjgCVxAa5yiSFFKgo/ngBpXdIwqo5o0A== + typescript@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"