Skip to content

Commit

Permalink
Improve initial setup with new App Router TypeScript project (#64826)
Browse files Browse the repository at this point in the history
  • Loading branch information
unstubbable authored Apr 26, 2024
1 parent 270a9db commit 3438b39
Show file tree
Hide file tree
Showing 35 changed files with 360 additions and 142 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/build_reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ jobs:
# clean up any previous artifacts to avoid hitting disk space limits
- run: git clean -xdf && rm -rf /tmp/next-repo-*; rm -rf /tmp/next-install-* /tmp/yarn-* /tmp/ncc-cache target

# Configure a git user so that Create Next App can initialize git repos during integration tests.
- name: Set CI git user
run: |
git config --global user.name "vercel-ci-bot"
git config --global user.email "infra+ci@vercel.com"
- run: cargo clean
if: ${{ inputs.skipNativeBuild != 'yes' || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}

Expand Down
1 change: 1 addition & 0 deletions packages/create-next-app/templates/app-tw/ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
Expand Down
1 change: 1 addition & 0 deletions packages/create-next-app/templates/app/ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
Expand Down
257 changes: 257 additions & 0 deletions packages/next/src/lib/typescript/writeConfigurationDefaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import { mkdtemp, writeFile, readFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
// eslint-disable-next-line import/no-extraneous-dependencies
import ts from 'typescript'
import { writeConfigurationDefaults } from './writeConfigurationDefaults'

describe('writeConfigurationDefaults()', () => {
let consoleLogSpy: jest.SpyInstance
let distDir: string
let hasAppDir: boolean
let tmpDir: string
let tsConfigPath: string
let isFirstTimeSetup: boolean
let hasPagesDir: boolean

beforeEach(async () => {
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
distDir = '.next'
tmpDir = await mkdtemp(join(tmpdir(), 'nextjs-test-'))
tsConfigPath = join(tmpDir, 'tsconfig.json')
isFirstTimeSetup = false
})

afterEach(() => {
consoleLogSpy.mockRestore()
})

describe('appDir', () => {
beforeEach(() => {
hasAppDir = true
hasPagesDir = false
})

it('applies suggested and mandatory defaults to existing tsconfig.json and logs them', async () => {
await writeFile(tsConfigPath, JSON.stringify({ compilerOptions: {} }), {
encoding: 'utf8',
})

await writeConfigurationDefaults(
ts,
tsConfigPath,
isFirstTimeSetup,
hasAppDir,
distDir,
hasPagesDir
)

const tsConfig = await readFile(tsConfigPath, { encoding: 'utf8' })

expect(JSON.parse(tsConfig)).toMatchInlineSnapshot(`
{
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"incremental": true,
"isolatedModules": true,
"jsx": "preserve",
"lib": [
"dom",
"dom.iterable",
"esnext",
],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"plugins": [
{
"name": "next",
},
],
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": false,
"target": "ES2017",
},
"exclude": [
"node_modules",
],
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx",
],
}
`)

expect(
consoleLogSpy.mock.calls
.flat()
.join('\n')
// eslint-disable-next-line no-control-regex
.replace(/\x1B\[\d+m/g, '') // remove color control characters
).toMatchInlineSnapshot(`
"
We detected TypeScript in your project and reconfigured your tsconfig.json file for you. Strict-mode is set to false by default.
The following suggested values were added to your tsconfig.json. These values can be changed to fit your project's needs:
- target was set to ES2017 (For top-level \`await\`. Note: Next.js only polyfills for the esmodules target.)
- lib was set to dom,dom.iterable,esnext
- allowJs was set to true
- skipLibCheck was set to true
- strict was set to false
- noEmit was set to true
- incremental was set to true
- include was set to ['next-env.d.ts', '.next/types/**/*.ts', '**/*.ts', '**/*.tsx']
- plugins was updated to add { name: 'next' }
- exclude was set to ['node_modules']
The following mandatory changes were made to your tsconfig.json:
- module was set to esnext (for dynamic import() support)
- esModuleInterop was set to true (requirement for SWC / babel)
- moduleResolution was set to node (to match webpack resolution)
- resolveJsonModule was set to true (to match webpack resolution)
- isolatedModules was set to true (requirement for SWC / Babel)
- jsx was set to preserve (next.js implements its own optimized jsx transform)
"
`)
})

it('does not warn about disabled strict mode if strict mode was already enabled', async () => {
await writeFile(
tsConfigPath,
JSON.stringify({ compilerOptions: { strict: true } }),
{ encoding: 'utf8' }
)

await writeConfigurationDefaults(
ts,
tsConfigPath,
isFirstTimeSetup,
hasAppDir,
distDir,
hasPagesDir
)

expect(
consoleLogSpy.mock.calls
.flat()
.join('\n')
// eslint-disable-next-line no-control-regex
.replace(/\x1B\[\d+m/g, '') // remove color control characters
).not.toMatch('Strict-mode is set to false by default.')
})

describe('with tsconfig extends', () => {
let tsConfigBasePath: string
let nextAppTypes: string

beforeEach(() => {
tsConfigBasePath = join(tmpDir, 'tsconfig.base.json')
nextAppTypes = `${distDir}/types/**/*.ts`
})

it('should support empty includes when base provides it', async () => {
const include = ['**/*.ts', '**/*.tsx', nextAppTypes]
const content = { extends: './tsconfig.base.json' }
const baseContent = { include }

await writeFile(tsConfigPath, JSON.stringify(content, null, 2))
await writeFile(tsConfigBasePath, JSON.stringify(baseContent, null, 2))

await expect(
writeConfigurationDefaults(
ts,
tsConfigPath,
isFirstTimeSetup,
hasAppDir,
distDir,
hasPagesDir
)
).resolves.not.toThrow()

const output = await readFile(tsConfigPath, 'utf-8')
const parsed = JSON.parse(output)

expect(parsed.include).toBeUndefined()
})

it('should replace includes when base is missing appTypes', async () => {
const include = ['**/*.ts', '**/*.tsx']
const content = { extends: './tsconfig.base.json' }
const baseContent = { include }

await writeFile(tsConfigPath, JSON.stringify(content, null, 2))
await writeFile(tsConfigBasePath, JSON.stringify(baseContent, null, 2))

await expect(
writeConfigurationDefaults(
ts,
tsConfigPath,
isFirstTimeSetup,
hasAppDir,
distDir,
hasPagesDir
)
).resolves.not.toThrow()

const output = await readFile(tsConfigPath, 'utf8')
const parsed = JSON.parse(output)

expect(parsed.include.sort()).toMatchInlineSnapshot(`
[
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
]
`)
})

it('should not add strictNullChecks if base provides it', async () => {
const content = { extends: './tsconfig.base.json' }

const baseContent = {
compilerOptions: { strictNullChecks: true, strict: true },
}

await writeFile(tsConfigPath, JSON.stringify(content, null, 2))
await writeFile(tsConfigBasePath, JSON.stringify(baseContent, null, 2))

await writeConfigurationDefaults(
ts,
tsConfigPath,
isFirstTimeSetup,
hasAppDir,
distDir,
hasPagesDir
)
const output = await readFile(tsConfigPath, 'utf8')
const parsed = JSON.parse(output)

expect(parsed.compilerOptions.strictNullChecks).toBeUndefined()
})
})
})
})
11 changes: 7 additions & 4 deletions packages/next/src/lib/typescript/writeConfigurationDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,7 @@ export async function writeConfigurationDefaults(
cyan(optionKey) +
' was set to ' +
bold(check.suggested) +
check.reason
? ` (${check.reason})`
: ''
(check.reason ? ` (${check.reason})` : '')
)
}
} else if ('value' in check) {
Expand Down Expand Up @@ -337,8 +335,13 @@ export async function writeConfigurationDefaults(
Log.info(
`We detected TypeScript in your project and reconfigured your ${cyan(
'tsconfig.json'
)} file for you. Strict-mode is set to ${cyan('false')} by default.`
)} file for you.${
userTsConfig.compilerOptions?.strict
? ''
: ` Strict-mode is set to ${cyan('false')} by default.`
}`
)

if (suggestedActions.length) {
Log.info(
`The following suggested values were added to your ${cyan(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"baseUrl": "."
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
]
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
"include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
{
"name": "next"
}
]
],
"target": "ES2017"
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"name": "next"
}
],
"strictNullChecks": true
"strictNullChecks": true,
"target": "ES2017"
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
{
"name": "next"
}
]
],
"target": "ES2017"
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/app-dir/metadata-suspense/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
{
"name": "next"
}
]
],
"target": "ES2017"
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/app-dir/modularizeimports/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"name": "next"
}
],
"strictNullChecks": true
"strictNullChecks": true,
"target": "ES2017"
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
Loading

0 comments on commit 3438b39

Please sign in to comment.