Skip to content

Commit

Permalink
Merge pull request #1 from facebook/master
Browse files Browse the repository at this point in the history
Update from upstream repo facebook/create-react-app@master
  • Loading branch information
chanand authored Oct 23, 2018
2 parents 5d23f84 + 3c11efe commit 75a1d39
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 33 deletions.
27 changes: 2 additions & 25 deletions docusaurus/docs/adding-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,8 @@ Recent versions of [TypeScript](https://www.typescriptlang.org/) work with Creat
To add TypeScript to a Create React App project, follow these steps:

1. Run `npm install --save typescript @types/react @types/react-dom @types/jest` (or `yarn add typescript @types/react @types/react-dom @types/jest`).
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`).

3. Create a [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) at the root directory with the following content:

```json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "node",
"lib": ["esnext", "dom", "dom.iterable"],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"jsx": "preserve",
"noEmit": true,
"skipLibCheck": true,
"strict": true
},
"include": ["src"]
}
```

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.
2. Rename `src/index.js` to `src/index.tsx` or create an empty [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) at the root project directory.
3. 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).

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

Expand Down
1 change: 1 addition & 0 deletions packages/react-dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"openChrome.applescript",
"printBuildError.js",
"printHostingInstructions.js",
"typescriptFormatter.js",
"WatchMissingNodeModulesPlugin.js",
"WebpackDevServerUtils.js",
"webpackHotDevClient.js"
Expand Down
46 changes: 46 additions & 0 deletions packages/react-dev-utils/typescriptFormatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const os = require('os');
const codeFrame = require('@babel/code-frame').codeFrameColumns;
const chalk = require('chalk');
const fs = require('fs');

function formatter(message, useColors) {
const colors = new chalk.constructor({ enabled: useColors });
const messageColor = message.isWarningSeverity() ? colors.yellow : colors.red;

const source =
message.getFile() &&
fs.existsSync(message.getFile()) &&
fs.readFileSync(message.getFile(), 'utf-8');
let frame = '';

if (source) {
frame = codeFrame(
source,
{ start: { line: message.line, column: message.character } },
{ highlightCode: useColors }
)
.split('\n')
.map(str => ' ' + str)
.join(os.EOL);
}

return [
messageColor.bold(`Type ${message.getSeverity().toLowerCase()}: `) +
message.getContent() +
' ' +
messageColor.underline(`TS${message.code}`),
'',
frame,
].join(os.EOL);
}

module.exports = formatter;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
declare module '*.json' {
const value: any;
export default value;
}
// @remove-file-on-eject
// Do not edit this file. It's replaced every time you launch a toolbox action.
// If you need to add additional declarations, please do so in a new file.

declare module '*.bmp' {
const src: string;
Expand Down
3 changes: 3 additions & 0 deletions packages/react-scripts/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const paths = require('./paths');
const ManifestPlugin = require('webpack-manifest-plugin');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
// @remove-on-eject-begin
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
// @remove-on-eject-end
Expand Down Expand Up @@ -420,6 +421,8 @@ module.exports = {
checkSyntacticErrors: true,
tsconfig: paths.appTsConfig,
watch: paths.appSrc,
silent: true,
formatter: typescriptFormatter,
}),
].filter(Boolean),

Expand Down
3 changes: 3 additions & 0 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const paths = require('./paths');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
// @remove-on-eject-begin
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
// @remove-on-eject-end
Expand Down Expand Up @@ -539,6 +540,8 @@ module.exports = {
checkSyntacticErrors: true,
tsconfig: paths.appTsConfig,
watch: paths.appSrc,
silent: true,
formatter: typescriptFormatter,
}),
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
Expand Down
2 changes: 1 addition & 1 deletion packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"webpack": "4.19.1",
"webpack-dev-server": "3.1.9",
"webpack-manifest-plugin": "2.0.4",
"workbox-webpack-plugin": "3.6.2"
"workbox-webpack-plugin": "3.6.3"
},
"devDependencies": {
"react": "^16.3.2",
Expand Down
2 changes: 2 additions & 0 deletions packages/react-scripts/scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const verifyPackageTree = require('./utils/verifyPackageTree');
if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') {
verifyPackageTree();
}
const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup');
verifyTypeScriptSetup();
// @remove-on-eject-end

const jest = require('jest');
Expand Down
142 changes: 139 additions & 3 deletions packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,52 @@
const chalk = require('chalk');
const fs = require('fs');
const resolve = require('resolve');
const path = require('path');
const paths = require('../../config/paths');
const os = require('os');

function writeJson(fileName, object) {
fs.writeFileSync(fileName, JSON.stringify(object, null, 2) + os.EOL);
}

const compilerOptions = {
// These are suggested values and will be set when not present in the
// tsconfig.json
target: { suggested: 'es5' },
allowJs: { suggested: true },
skipLibCheck: { suggested: true },
esModuleInterop: { suggested: true },
allowSyntheticDefaultImports: { suggested: true },
strict: { suggested: true },

// These values are required and cannot be changed by the user
module: { value: 'esnext', reason: 'for import() and import/export' },
moduleResolution: { value: 'node', reason: 'to match webpack resolution' },
resolveJsonModule: { value: true, reason: 'to match webpack loader' },
isolatedModules: { value: true, reason: 'implementation limitation' },
noEmit: { value: true },
jsx: { value: 'preserve', reason: 'JSX is compiled by Babel' },
};

function verifyTypeScriptSetup() {
let firstTimeSetup = false;

if (!fs.existsSync(paths.appTsConfig)) {
return;
if (!paths.appIndexJs.match(/\.tsx?$/)) {
return;
}
writeJson(paths.appTsConfig, {});
firstTimeSetup = true;
}

const isYarn = fs.existsSync(paths.yarnLockFile);

// Ensure typescript is installed
let ts;
try {
resolve.sync('typescript', {
ts = require(resolve.sync('typescript', {
basedir: paths.appNodeModules,
});
}));
} catch (_) {
console.error(
chalk.red(
Expand Down Expand Up @@ -53,6 +85,110 @@ function verifyTypeScriptSetup() {
console.error();
process.exit(1);
}

const messages = [];
let tsconfig;
try {
const { config, error } = ts.readConfigFile(
paths.appTsConfig,
ts.sys.readFile
);

if (error) {
throw error;
}

tsconfig = config;
} catch (_) {
console.error(
chalk.red.bold(
'Could not parse',
chalk.cyan('tsconfig.json') + '.',
'Please make sure it contains syntactically correct JSON.'
)
);
process.exit(1);
}

if (tsconfig.compilerOptions == null) {
tsconfig.compilerOptions = {};
firstTimeSetup = true;
}

for (const option of Object.keys(compilerOptions)) {
const { value, suggested, reason } = compilerOptions[option];
if (suggested != null) {
if (tsconfig.compilerOptions[option] === undefined) {
tsconfig.compilerOptions[option] = suggested;
messages.push(
`${chalk.cyan('compilerOptions.' + option)} to be ${chalk.bold(
'suggested'
)} value: ${chalk.cyan.bold(suggested)} (this can be changed)`
);
}
} else if (tsconfig.compilerOptions[option] !== value) {
tsconfig.compilerOptions[option] = value;
messages.push(
`${chalk.cyan('compilerOptions.' + option)} ${chalk.bold(
'must'
)} be ${chalk.cyan.bold(value)}` +
(reason != null ? ` (${reason})` : '')
);
}
}

if (tsconfig.include == null) {
tsconfig.include = ['src'];
messages.push(
`${chalk.cyan('include')} should be ${chalk.cyan.bold('src')}`
);
}
if (tsconfig.exclude == null) {
tsconfig.exclude = ['**/__tests__/**', '**/?*test.*', '**/?*spec.*'];
messages.push(`${chalk.cyan('exclude')} should exclude test files`);
}

if (messages.length > 0) {
if (firstTimeSetup) {
console.log(
chalk.bold(
'Your',
chalk.cyan('tsconfig.json'),
'has been populated with default values.'
)
);
console.log();
} else {
console.warn(
chalk.bold(
'The following changes are being made to your',
chalk.cyan('tsconfig.json'),
'file:'
)
);
messages.forEach(message => {
console.warn(' - ' + message);
});
console.warn();
}
writeJson(paths.appTsConfig, tsconfig);
}

// Copy type declarations associated with this version of `react-scripts`
const declaredTypes = path.resolve(
__dirname,
'..',
'..',
'config',
'react-app.d.ts'
);
const declaredTypesContent = fs
.readFileSync(declaredTypes, 'utf8')
.replace(/\/\/ @remove-file-on-eject\r?\n/, '');
fs.writeFileSync(
path.resolve(paths.appSrc, 'react-app.d.ts'),
declaredTypesContent
);
}

module.exports = verifyTypeScriptSetup;
Empty file.
16 changes: 16 additions & 0 deletions test/fixtures/typescript-advanced/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const testSetup = require('../__shared__/test-setup');

test('builds in development', async () => {
const { fulfilled, ...rest } = await testSetup.scripts.start({ smoke: true });
expect(fulfilled).toBe(true);
});
test('builds in production', async () => {
const { fulfilled } = await testSetup.scripts.build();
expect(fulfilled).toBe(true);
});
test('passes tests', async () => {
const { fulfilled } = await testSetup.scripts.test({
jestEnvironment: 'node',
});
expect(fulfilled).toBe(true);
});
9 changes: 9 additions & 0 deletions test/fixtures/typescript-advanced/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "*",
"react-dom": "*",
"typescript": "3.1.3"
}
}
8 changes: 8 additions & 0 deletions test/fixtures/typescript-advanced/src/App.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import App from './App';

it('reads a typescript file with no syntax error', () => {
const app = new App();
expect(App.foo.bar).toBe(true);
expect(App.foo.baz!.n).toBe(123);
expect(app.n).toBe(123);
});
20 changes: 20 additions & 0 deletions test/fixtures/typescript-advanced/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';

interface MyType {
foo: number;
bar: boolean;
baz?: { n: number };
}

type MyObject = Pick<MyType, 'bar' | 'baz'>;

class App extends React.Component {
static foo: MyObject = { bar: true, baz: { n: 123 } };
n = App.foo.baz!.n;

render() {
return <div />;
}
}

export default App;
5 changes: 5 additions & 0 deletions test/fixtures/typescript-advanced/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

0 comments on commit 75a1d39

Please sign in to comment.