Skip to content

Commit 3438b39

Browse files
authored
Improve initial setup with new App Router TypeScript project (#64826)
1 parent 270a9db commit 3438b39

File tree

35 files changed

+360
-142
lines changed

35 files changed

+360
-142
lines changed

.github/workflows/build_reusable.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ jobs:
133133
# clean up any previous artifacts to avoid hitting disk space limits
134134
- run: git clean -xdf && rm -rf /tmp/next-repo-*; rm -rf /tmp/next-install-* /tmp/yarn-* /tmp/ncc-cache target
135135

136+
# Configure a git user so that Create Next App can initialize git repos during integration tests.
137+
- name: Set CI git user
138+
run: |
139+
git config --global user.name "vercel-ci-bot"
140+
git config --global user.email "[email protected]"
141+
136142
- run: cargo clean
137143
if: ${{ inputs.skipNativeBuild != 'yes' || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}
138144

packages/create-next-app/templates/app-tw/ts/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"target": "ES2017",
34
"lib": ["dom", "dom.iterable", "esnext"],
45
"allowJs": true,
56
"skipLibCheck": true,

packages/create-next-app/templates/app/ts/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"target": "ES2017",
34
"lib": ["dom", "dom.iterable", "esnext"],
45
"allowJs": true,
56
"skipLibCheck": true,

packages/create-next-app/templates/default-tw/ts/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"target": "ES2017",
34
"lib": ["dom", "dom.iterable", "esnext"],
45
"allowJs": true,
56
"skipLibCheck": true,

packages/create-next-app/templates/default/ts/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"target": "ES2017",
34
"lib": ["dom", "dom.iterable", "esnext"],
45
"allowJs": true,
56
"skipLibCheck": true,
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import { mkdtemp, writeFile, readFile } from 'node:fs/promises'
2+
import { tmpdir } from 'node:os'
3+
import { join } from 'node:path'
4+
// eslint-disable-next-line import/no-extraneous-dependencies
5+
import ts from 'typescript'
6+
import { writeConfigurationDefaults } from './writeConfigurationDefaults'
7+
8+
describe('writeConfigurationDefaults()', () => {
9+
let consoleLogSpy: jest.SpyInstance
10+
let distDir: string
11+
let hasAppDir: boolean
12+
let tmpDir: string
13+
let tsConfigPath: string
14+
let isFirstTimeSetup: boolean
15+
let hasPagesDir: boolean
16+
17+
beforeEach(async () => {
18+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
19+
distDir = '.next'
20+
tmpDir = await mkdtemp(join(tmpdir(), 'nextjs-test-'))
21+
tsConfigPath = join(tmpDir, 'tsconfig.json')
22+
isFirstTimeSetup = false
23+
})
24+
25+
afterEach(() => {
26+
consoleLogSpy.mockRestore()
27+
})
28+
29+
describe('appDir', () => {
30+
beforeEach(() => {
31+
hasAppDir = true
32+
hasPagesDir = false
33+
})
34+
35+
it('applies suggested and mandatory defaults to existing tsconfig.json and logs them', async () => {
36+
await writeFile(tsConfigPath, JSON.stringify({ compilerOptions: {} }), {
37+
encoding: 'utf8',
38+
})
39+
40+
await writeConfigurationDefaults(
41+
ts,
42+
tsConfigPath,
43+
isFirstTimeSetup,
44+
hasAppDir,
45+
distDir,
46+
hasPagesDir
47+
)
48+
49+
const tsConfig = await readFile(tsConfigPath, { encoding: 'utf8' })
50+
51+
expect(JSON.parse(tsConfig)).toMatchInlineSnapshot(`
52+
{
53+
"compilerOptions": {
54+
"allowJs": true,
55+
"esModuleInterop": true,
56+
"incremental": true,
57+
"isolatedModules": true,
58+
"jsx": "preserve",
59+
"lib": [
60+
"dom",
61+
"dom.iterable",
62+
"esnext",
63+
],
64+
"module": "esnext",
65+
"moduleResolution": "node",
66+
"noEmit": true,
67+
"plugins": [
68+
{
69+
"name": "next",
70+
},
71+
],
72+
"resolveJsonModule": true,
73+
"skipLibCheck": true,
74+
"strict": false,
75+
"target": "ES2017",
76+
},
77+
"exclude": [
78+
"node_modules",
79+
],
80+
"include": [
81+
"next-env.d.ts",
82+
".next/types/**/*.ts",
83+
"**/*.ts",
84+
"**/*.tsx",
85+
],
86+
}
87+
`)
88+
89+
expect(
90+
consoleLogSpy.mock.calls
91+
.flat()
92+
.join('\n')
93+
// eslint-disable-next-line no-control-regex
94+
.replace(/\x1B\[\d+m/g, '') // remove color control characters
95+
).toMatchInlineSnapshot(`
96+
"
97+
98+
We detected TypeScript in your project and reconfigured your tsconfig.json file for you. Strict-mode is set to false by default.
99+
100+
The following suggested values were added to your tsconfig.json. These values can be changed to fit your project's needs:
101+
102+
103+
- target was set to ES2017 (For top-level \`await\`. Note: Next.js only polyfills for the esmodules target.)
104+
105+
- lib was set to dom,dom.iterable,esnext
106+
107+
- allowJs was set to true
108+
109+
- skipLibCheck was set to true
110+
111+
- strict was set to false
112+
113+
- noEmit was set to true
114+
115+
- incremental was set to true
116+
117+
- include was set to ['next-env.d.ts', '.next/types/**/*.ts', '**/*.ts', '**/*.tsx']
118+
119+
- plugins was updated to add { name: 'next' }
120+
121+
- exclude was set to ['node_modules']
122+
123+
124+
The following mandatory changes were made to your tsconfig.json:
125+
126+
127+
- module was set to esnext (for dynamic import() support)
128+
129+
- esModuleInterop was set to true (requirement for SWC / babel)
130+
131+
- moduleResolution was set to node (to match webpack resolution)
132+
133+
- resolveJsonModule was set to true (to match webpack resolution)
134+
135+
- isolatedModules was set to true (requirement for SWC / Babel)
136+
137+
- jsx was set to preserve (next.js implements its own optimized jsx transform)
138+
"
139+
`)
140+
})
141+
142+
it('does not warn about disabled strict mode if strict mode was already enabled', async () => {
143+
await writeFile(
144+
tsConfigPath,
145+
JSON.stringify({ compilerOptions: { strict: true } }),
146+
{ encoding: 'utf8' }
147+
)
148+
149+
await writeConfigurationDefaults(
150+
ts,
151+
tsConfigPath,
152+
isFirstTimeSetup,
153+
hasAppDir,
154+
distDir,
155+
hasPagesDir
156+
)
157+
158+
expect(
159+
consoleLogSpy.mock.calls
160+
.flat()
161+
.join('\n')
162+
// eslint-disable-next-line no-control-regex
163+
.replace(/\x1B\[\d+m/g, '') // remove color control characters
164+
).not.toMatch('Strict-mode is set to false by default.')
165+
})
166+
167+
describe('with tsconfig extends', () => {
168+
let tsConfigBasePath: string
169+
let nextAppTypes: string
170+
171+
beforeEach(() => {
172+
tsConfigBasePath = join(tmpDir, 'tsconfig.base.json')
173+
nextAppTypes = `${distDir}/types/**/*.ts`
174+
})
175+
176+
it('should support empty includes when base provides it', async () => {
177+
const include = ['**/*.ts', '**/*.tsx', nextAppTypes]
178+
const content = { extends: './tsconfig.base.json' }
179+
const baseContent = { include }
180+
181+
await writeFile(tsConfigPath, JSON.stringify(content, null, 2))
182+
await writeFile(tsConfigBasePath, JSON.stringify(baseContent, null, 2))
183+
184+
await expect(
185+
writeConfigurationDefaults(
186+
ts,
187+
tsConfigPath,
188+
isFirstTimeSetup,
189+
hasAppDir,
190+
distDir,
191+
hasPagesDir
192+
)
193+
).resolves.not.toThrow()
194+
195+
const output = await readFile(tsConfigPath, 'utf-8')
196+
const parsed = JSON.parse(output)
197+
198+
expect(parsed.include).toBeUndefined()
199+
})
200+
201+
it('should replace includes when base is missing appTypes', async () => {
202+
const include = ['**/*.ts', '**/*.tsx']
203+
const content = { extends: './tsconfig.base.json' }
204+
const baseContent = { include }
205+
206+
await writeFile(tsConfigPath, JSON.stringify(content, null, 2))
207+
await writeFile(tsConfigBasePath, JSON.stringify(baseContent, null, 2))
208+
209+
await expect(
210+
writeConfigurationDefaults(
211+
ts,
212+
tsConfigPath,
213+
isFirstTimeSetup,
214+
hasAppDir,
215+
distDir,
216+
hasPagesDir
217+
)
218+
).resolves.not.toThrow()
219+
220+
const output = await readFile(tsConfigPath, 'utf8')
221+
const parsed = JSON.parse(output)
222+
223+
expect(parsed.include.sort()).toMatchInlineSnapshot(`
224+
[
225+
"**/*.ts",
226+
"**/*.tsx",
227+
".next/types/**/*.ts",
228+
]
229+
`)
230+
})
231+
232+
it('should not add strictNullChecks if base provides it', async () => {
233+
const content = { extends: './tsconfig.base.json' }
234+
235+
const baseContent = {
236+
compilerOptions: { strictNullChecks: true, strict: true },
237+
}
238+
239+
await writeFile(tsConfigPath, JSON.stringify(content, null, 2))
240+
await writeFile(tsConfigBasePath, JSON.stringify(baseContent, null, 2))
241+
242+
await writeConfigurationDefaults(
243+
ts,
244+
tsConfigPath,
245+
isFirstTimeSetup,
246+
hasAppDir,
247+
distDir,
248+
hasPagesDir
249+
)
250+
const output = await readFile(tsConfigPath, 'utf8')
251+
const parsed = JSON.parse(output)
252+
253+
expect(parsed.compilerOptions.strictNullChecks).toBeUndefined()
254+
})
255+
})
256+
})
257+
})

packages/next/src/lib/typescript/writeConfigurationDefaults.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,7 @@ export async function writeConfigurationDefaults(
176176
cyan(optionKey) +
177177
' was set to ' +
178178
bold(check.suggested) +
179-
check.reason
180-
? ` (${check.reason})`
181-
: ''
179+
(check.reason ? ` (${check.reason})` : '')
182180
)
183181
}
184182
} else if ('value' in check) {
@@ -337,8 +335,13 @@ export async function writeConfigurationDefaults(
337335
Log.info(
338336
`We detected TypeScript in your project and reconfigured your ${cyan(
339337
'tsconfig.json'
340-
)} file for you. Strict-mode is set to ${cyan('false')} by default.`
338+
)} file for you.${
339+
userTsConfig.compilerOptions?.strict
340+
? ''
341+
: ` Strict-mode is set to ${cyan('false')} by default.`
342+
}`
341343
)
344+
342345
if (suggestedActions.length) {
343346
Log.info(
344347
`The following suggested values were added to your ${cyan(

test/development/acceptance-app/fixtures/app-hmr-changes/tsconfig.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414
"resolveJsonModule": true,
1515
"isolatedModules": true,
1616
"incremental": true,
17-
"baseUrl": "."
17+
"baseUrl": ".",
18+
"plugins": [
19+
{
20+
"name": "next"
21+
}
22+
]
1823
},
1924
"exclude": ["node_modules"],
20-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
25+
"include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"]
2126
}

test/development/app-dir/experimental-lightningcss/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
{
1717
"name": "next"
1818
}
19-
]
19+
],
20+
"target": "ES2017"
2021
},
2122
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
2223
"exclude": ["node_modules"]

test/development/basic/define-class-fields/fixture/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@
1515
"moduleResolution": "bundler",
1616
"resolveJsonModule": true,
1717
"isolatedModules": true
18-
}
18+
},
19+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
20+
"exclude": ["node_modules"]
1921
}

test/e2e/app-dir/edge-runtime-node-compatibility/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"name": "next"
1919
}
2020
],
21-
"strictNullChecks": true
21+
"strictNullChecks": true,
22+
"target": "ES2017"
2223
},
2324
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
2425
"exclude": ["node_modules"]

test/e2e/app-dir/interception-middleware-rewrite/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
{
1818
"name": "next"
1919
}
20-
]
20+
],
21+
"target": "ES2017"
2122
},
2223
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
2324
"exclude": ["node_modules"]

test/e2e/app-dir/metadata-suspense/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
{
1818
"name": "next"
1919
}
20-
]
20+
],
21+
"target": "ES2017"
2122
},
2223
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
2324
"exclude": ["node_modules"]

test/e2e/app-dir/modularizeimports/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"name": "next"
1919
}
2020
],
21-
"strictNullChecks": true
21+
"strictNullChecks": true,
22+
"target": "ES2017"
2223
},
2324
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
2425
"exclude": ["node_modules"]

0 commit comments

Comments
 (0)