Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration flags to disable integrated type checker #9138

Merged
merged 10 commits into from
Oct 28, 2019
16 changes: 16 additions & 0 deletions packages/next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2316,6 +2316,22 @@ To learn more about TypeScript checkout its [documentation](https://www.typescri
> **Note**: Next.js does not enable TypeScript's `strict` mode by default.
> When you feel comfortable with TypeScript, you may turn this option on in your `tsconfig.json`.

> **Note**: By default, Next.js reports TypeScript errors during development for pages you are actively working on.
> TypeScript errors for inactive pages do not block the development process.
> Trying to run `next build` for an app that has TypeScript errors on any page will fail.
>
> If you don't want to leverage this behavior and prefer to do type checks manually, set the following options in your `next.config.js`:
>
> ```js
> // next.config.js
> module.exports = {
> typescript: {
> ignoreDevErrors: true,
> ignoreBuildErrors: true,
> },
> }
> ```

### Exported types

Next.js provides `NextPage` type that can be used for pages in the `pages` directory. `NextPage` adds definitions for [`getInitialProps`](#fetching-data-and-component-lifecycle) so that it can be used without any extra typing needed.
Expand Down
2 changes: 1 addition & 1 deletion packages/next/build/output/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ store.subscribe(state => {
}

if (state.typeChecking) {
Log.info('bundled successfully, waiting for typecheck results ...')
Log.info('bundled successfully, waiting for typecheck results...')
return
}

Expand Down
4 changes: 4 additions & 0 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ export default async function getBaseWebpackConfig(
const useTypeScript = Boolean(
typeScriptPath && (await fileExists(tsConfigPath))
)
const ignoreTypeScriptErrors = dev
? config.typescript && config.typescript.ignoreDevErrors
: config.typescript && config.typescript.ignoreBuildErrors

const resolveConfig = {
// Disable .mjs for node_modules bundling
Expand Down Expand Up @@ -845,6 +848,7 @@ export default async function getBaseWebpackConfig(
}),
!isServer &&
useTypeScript &&
!ignoreTypeScriptErrors &&
new ForkTsCheckerWebpackPlugin(
PnpWebpackPlugin.forkTsCheckerOptions({
typescript: typeScriptPath,
Expand Down
4 changes: 3 additions & 1 deletion packages/next/server/hot-reloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,13 @@ export default class HotReloader {
async prepareBuildTools(multiCompiler: webpack.MultiCompiler) {
const tsConfigPath = join(this.dir, 'tsconfig.json')
const useTypeScript = await fileExists(tsConfigPath)
const ignoreTypeScriptErrors =
this.config.typescript && this.config.typescript.ignoreDevErrors

watchCompilers(
multiCompiler.compilers[0],
multiCompiler.compilers[1],
useTypeScript,
useTypeScript && !ignoreTypeScriptErrors,
({ errors, warnings }) => this.send('typeChecked', { errors, warnings })
)

Expand Down
1 change: 1 addition & 0 deletions test/integration/typescript-ignore-errors/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
2 changes: 2 additions & 0 deletions test/integration/typescript-ignore-errors/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Below type error is intentional, it helps check typescript → ignoreDevErrors / ignoreBuildErrors flags in next.config.js
export default (): boolean => 'Index page'
84 changes: 84 additions & 0 deletions test/integration/typescript-ignore-errors/test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* eslint-env jest */
/* global jasmine */
import { join } from 'path'
import {
renderViaHTTP,
nextBuild,
findPort,
launchApp,
killApp,
File
} from 'next-test-utils'

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2

const appDir = join(__dirname, '..')
const nextConfigFile = new File(join(appDir, 'next.config.js'))

describe('TypeScript with error handling options', () => {
for (const ignoreDevErrors of [false, true]) {
for (const ignoreBuildErrors of [false, true]) {
describe(`ignoreDevErrors: ${ignoreDevErrors}, ignoreBuildErrors: ${ignoreBuildErrors}`, () => {
beforeAll(() => {
const nextConfig = {
typescript: { ignoreDevErrors, ignoreBuildErrors }
}
nextConfigFile.replace('{}', JSON.stringify(nextConfig))
})
afterAll(() => {
nextConfigFile.restore()
})

it(
ignoreDevErrors
? 'Next renders the page in dev despite type errors'
: 'Next dev does not render the page in dev because of type errors',
async () => {
let app
let output = ''
try {
const appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStdout: msg => (output += msg),
onStderr: msg => (output += msg)
})
await renderViaHTTP(appPort, '')

if (ignoreDevErrors) {
expect(output).not.toContain('waiting for typecheck results...')
expect(output).not.toContain("not assignable to type 'boolean'")
} else {
expect(output).toContain('waiting for typecheck results...')
expect(output).toContain("not assignable to type 'boolean'")
}
} finally {
await killApp(app)
}
}
)

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
})

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("not assignable to type 'boolean'")
}
}
)
})
}
}
})
19 changes: 19 additions & 0 deletions test/integration/typescript-ignore-errors/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "esnext",
"jsx": "preserve",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "pages"]
}
15 changes: 14 additions & 1 deletion test/integration/typescript/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
const appDir = join(__dirname, '..')
let appPort
let app
let output

const handleOutput = msg => {
output += msg
}

async function get$ (path, query) {
const html = await renderViaHTTP(appPort, path, query)
Expand All @@ -26,8 +31,12 @@ async function get$ (path, query) {
describe('TypeScript Features', () => {
describe('default behavior', () => {
beforeAll(async () => {
output = ''
appPort = await findPort()
app = await launchApp(appDir, appPort)
app = await launchApp(appDir, appPort, {
onStdout: handleOutput,
onStderr: handleOutput
})
})
afterAll(() => killApp(app))

Expand All @@ -36,6 +45,10 @@ describe('TypeScript Features', () => {
expect($('body').text()).toMatch(/Hello World/)
})

it('should report type checking to stdout', async () => {
expect(output).toContain('waiting for typecheck results...')
})

it('should not fail to render when an inactive page has an error', async () => {
await killApp(app)
let evilFile = join(appDir, 'pages', 'evil.tsx')
Expand Down