Skip to content

Commit 680cf93

Browse files
authored
Validate tsconfig when using TypeScript (facebook#5524)
* Sanity check TypeScript config * Check more options * Set all defaults and suggestions * Update docs * Update doc notes * Automatically copy react app declared types to project on start * Remove note about loaders.d.ts
1 parent 7ba343c commit 680cf93

File tree

4 files changed

+114
-24
lines changed

4 files changed

+114
-24
lines changed

docusaurus/docs/adding-typescript.md

+2-24
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,8 @@ To add TypeScript to a Create React App project, follow these steps:
1111

1212
1. Run `npm install --save typescript @types/react @types/react-dom @types/jest` (or `yarn add typescript @types/react @types/react-dom @types/jest`).
1313
2. Rename the `.js` files you want to convert: use `.tsx` if they use JSX or `.ts` if not (e.g. `git mv src/index.js src/index.tsx`).
14-
15-
3. Create a [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) at the root directory with the following content:
16-
17-
```json
18-
{
19-
"compilerOptions": {
20-
"target": "es5",
21-
"module": "esnext",
22-
"moduleResolution": "node",
23-
"lib": ["esnext", "dom", "dom.iterable"],
24-
"allowJs": true,
25-
"allowSyntheticDefaultImports": true,
26-
"esModuleInterop": true,
27-
"isolatedModules": true,
28-
"jsx": "preserve",
29-
"noEmit": true,
30-
"skipLibCheck": true,
31-
"strict": true
32-
},
33-
"include": ["src"]
34-
}
35-
```
36-
37-
4. Copy [loaders.d.ts](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/src/loaders.d.ts) from the template to your `src` directory.
14+
3. Create a [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) at the root directory with `{}` in it.
15+
4. Restart your development server (if applicable). This will set sensible defaults and the required values in your [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
3816

3917
Type errors will show up in the same console as the build one.
4018

packages/react-scripts/template/src/loaders.d.ts packages/react-scripts/config/react-app.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// @remove-file-on-eject
2+
// Do not edit this file. It's replaced every time you launch a toolbox action.
3+
// If you need to add additional declarations, please do so in a new file.
4+
15
declare module '*.json' {
26
const value: any;
37
export default value;

packages/react-scripts/scripts/test.js

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const verifyPackageTree = require('./utils/verifyPackageTree');
2828
if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') {
2929
verifyPackageTree();
3030
}
31+
const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup');
32+
verifyTypeScriptSetup();
3133
// @remove-on-eject-end
3234

3335
const jest = require('jest');

packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js

+106
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
const chalk = require('chalk');
1212
const fs = require('fs');
1313
const resolve = require('resolve');
14+
const path = require('path');
1415
const paths = require('../../config/paths');
16+
const os = require('os');
17+
18+
function writeJson(fileName, object) {
19+
fs.writeFileSync(fileName, JSON.stringify(object, null, 2) + os.EOL);
20+
}
1521

1622
function verifyTypeScriptSetup() {
1723
if (!fs.existsSync(paths.appTsConfig)) {
@@ -53,6 +59,106 @@ function verifyTypeScriptSetup() {
5359
console.error();
5460
process.exit(1);
5561
}
62+
63+
const messages = [];
64+
let tsconfig;
65+
try {
66+
tsconfig = require(paths.appTsConfig);
67+
} catch (_) {
68+
console.error(
69+
chalk.red.bold(
70+
'Could not parse',
71+
chalk.cyan('tsconfig.json') + '.',
72+
'Please make sure it contains syntactically correct JSON.'
73+
)
74+
);
75+
process.exit(1);
76+
}
77+
78+
if (tsconfig.compilerOptions == null) {
79+
tsconfig.compilerOptions = {};
80+
}
81+
82+
const compilerOptions = {
83+
target: { suggested: 'es5' },
84+
allowJs: { suggested: true },
85+
skipLibCheck: { suggested: true },
86+
module: { value: 'esnext', reason: 'for import() and import/export' },
87+
moduleResolution: { value: 'node', reason: 'to match webpack resolution' },
88+
isolatedModules: { value: true, reason: 'implementation limitation' },
89+
noEmit: { value: true },
90+
jsx: { value: 'preserve', reason: 'JSX is compiled by Babel' },
91+
esModuleInterop: { value: true, reason: 'Babel compatibility' },
92+
allowSyntheticDefaultImports: {
93+
value: true,
94+
reason: 'Babel compatibility',
95+
},
96+
strict: { suggested: true },
97+
};
98+
99+
for (const option of Object.keys(compilerOptions)) {
100+
const { value, suggested, reason } = compilerOptions[option];
101+
if (suggested != null) {
102+
if (tsconfig.compilerOptions[option] === undefined) {
103+
tsconfig.compilerOptions[option] = suggested;
104+
messages.push(
105+
`${chalk.cyan('compilerOptions.' + option)} to be ${chalk.bold(
106+
'suggested'
107+
)} value: ${chalk.cyan.bold(suggested)} (this can be changed)`
108+
);
109+
}
110+
} else if (tsconfig.compilerOptions[option] !== value) {
111+
tsconfig.compilerOptions[option] = value;
112+
messages.push(
113+
`${chalk.cyan('compilerOptions.' + option)} ${chalk.bold(
114+
'must'
115+
)} be ${chalk.cyan.bold(value)}` +
116+
(reason != null ? ` (${reason})` : '')
117+
);
118+
}
119+
}
120+
121+
if (tsconfig.include == null) {
122+
tsconfig.include = ['src'];
123+
messages.push(
124+
`${chalk.cyan('include')} should be ${chalk.cyan.bold('src')}`
125+
);
126+
}
127+
if (tsconfig.exclude == null) {
128+
tsconfig.exclude = ['**/__tests__/**', '**/?*(spec|test).*'];
129+
messages.push(`${chalk.cyan('exclude')} should exclude test files`);
130+
}
131+
132+
if (messages.length > 0) {
133+
console.warn(
134+
chalk.bold(
135+
'The following changes are being made to your',
136+
chalk.cyan('tsconfig.json'),
137+
'file:'
138+
)
139+
);
140+
messages.forEach(message => {
141+
console.warn(' - ' + message);
142+
});
143+
console.warn();
144+
writeJson(paths.appTsConfig, tsconfig);
145+
}
146+
147+
// Copy type declarations associated with this version of `react-scripts`
148+
const declaredTypes = path.resolve(
149+
__dirname,
150+
'..',
151+
'..',
152+
'config',
153+
'react-app.d.ts'
154+
);
155+
const declaredTypesContent = fs
156+
.readFileSync(declaredTypes, 'utf8')
157+
.replace(/\/\/ @remove-file-on-eject\r?\n/, '');
158+
fs.writeFileSync(
159+
path.resolve(paths.appSrc, 'react-app.d.ts'),
160+
declaredTypesContent
161+
);
56162
}
57163

58164
module.exports = verifyTypeScriptSetup;

0 commit comments

Comments
 (0)