diff --git a/README.md b/README.md index ee9deb69693..2e02d5ba0f4 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ The [User Guide](https://github.com/facebook/create-react-app/blob/master/packag - [Using Global Variables](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#using-global-variables) - [Adding Bootstrap](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-bootstrap) - [Adding Flow](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-flow) +- [Adding TypeScript](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-typescript) - [Adding a Router](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-router) - [Adding Custom Environment Variables](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables) - [Can I Use Decorators?](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#can-i-use-decorators) @@ -216,7 +217,7 @@ Here’s a few common cases where you might want to try something else: * If your website is **mostly static** (for example, a portfolio or a blog), consider using [Gatsby](https://www.gatsbyjs.org/) instead. Unlike Create React App, it pre-renders the website into HTML at the build time. -* If you want to use **TypeScript**, consider using [create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript). +* If you want an alternative way to use **TypeScript**, consider using [create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript). * If you want to use **Parcel** instead of **Webpack** as your bundler, consider using [create-react-app-parcel](https://github.com/sw-yx/create-react-app-parcel). diff --git a/packages/babel-plugin-named-asset-import/package.json b/packages/babel-plugin-named-asset-import/package.json index eec4516f8ac..de1c3a0b8b8 100644 --- a/packages/babel-plugin-named-asset-import/package.json +++ b/packages/babel-plugin-named-asset-import/package.json @@ -12,6 +12,6 @@ "index.js" ], "peerDependencies": { - "@babel/core": "7.0.0-beta.46" + "@babel/core": "7.1.0" } } diff --git a/packages/babel-preset-react-app/README.md b/packages/babel-preset-react-app/README.md index dbf7c600515..04c67f10f3d 100644 --- a/packages/babel-preset-react-app/README.md +++ b/packages/babel-preset-react-app/README.md @@ -32,19 +32,25 @@ Then create a file named `.babelrc` with following contents in the root folder o This preset uses the `useBuiltIns` option with [transform-object-rest-spread](http://babeljs.io/docs/plugins/transform-object-rest-spread/) and [transform-react-jsx](http://babeljs.io/docs/plugins/transform-react-jsx/), which assumes that `Object.assign` is available or polyfilled. -## Usage with TypeScript +## Usage with Flow + +To use this package with [`@babel/preset-flow`]https://www.npmjs.com/package/@babel/preset-flow), you only need to have a `.flowconfig` file at the root directory and it will be detected automatically. Alternatively, you can edit `.babelrc` with the following: + +``` +{ + "presets": [["react-app", { "flow": true }]] +} +``` -To use this package with [`@babel/preset-typescript`](https://www.npmjs.com/package/@babel/preset-typescript), you need to disable `@babel/preset-flow` first. -You can achieve this by doing: +## Usage with TypeScript + +To use this package with [`@babel/preset-typescript`]https://www.npmjs.com/package/@babel/preset-typescript), you only need to have a `tsconfig.json` file at the root directory and it will be detected automatically. Alternatively, you can edit `.babelrc` with the following: ``` { - "presets": [ - ["react-app", { - "flow": false - }], - "@babel/typescript" - ] + "presets": [["react-app", { "typescript": true }]] } ``` + +TSLint is also automatically supported, you only need to create a `tslint.json` (and optionally a `tslint.prod.json`) file at the root directory. \ No newline at end of file diff --git a/packages/babel-preset-react-app/create.js b/packages/babel-preset-react-app/create.js index 2ead09570f6..7903a9e22ed 100644 --- a/packages/babel-preset-react-app/create.js +++ b/packages/babel-preset-react-app/create.js @@ -6,6 +6,15 @@ */ 'use strict'; +const path = require('path'); +const fs = require('fs'); + +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +const hasFlowConfig = fs.existsSync(resolveApp('.flowconfig')); +const hasTSConfig = fs.existsSync(resolveApp('tsconfig.json')); + const validateBoolOption = (name, value, defaultValue) => { if (typeof value === 'undefined') { value = defaultValue; @@ -26,7 +35,12 @@ module.exports = function(api, opts, env) { var isEnvDevelopment = env === 'development'; var isEnvProduction = env === 'production'; var isEnvTest = env === 'test'; - var isFlowEnabled = validateBoolOption('flow', opts.flow, true); + var isFlowEnabled = validateBoolOption('flow', opts.flow, hasFlowConfig); + var isTypeScriptEnabled = validateBoolOption( + 'typescript', + opts.typescript, + hasTSConfig + ); if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) { throw new Error( @@ -38,6 +52,14 @@ module.exports = function(api, opts, env) { ); } + if (isFlowEnabled && isTypeScriptEnabled) { + throw new Error( + 'Cannot enable both flow and typescript support in the same project. ' + + "Please make sure you don't have both .flowconfig and tsconfig.json files. '" + + 'If using .babelrc, set one option to false and the other one to true.' + ); + } + return { presets: [ isEnvTest && [ @@ -75,6 +97,7 @@ module.exports = function(api, opts, env) { }, ], isFlowEnabled && [require('@babel/preset-flow').default], + isTypeScriptEnabled && [require('@babel/preset-typescript').default], ].filter(Boolean), plugins: [ // Experimental macros support. Will be documented after it's had some time @@ -107,7 +130,6 @@ module.exports = function(api, opts, env) { require('@babel/plugin-transform-runtime').default, { helpers: false, - polyfill: false, regenerator: true, }, ], diff --git a/packages/babel-preset-react-app/package.json b/packages/babel-preset-react-app/package.json index 27b70195299..9665855caf7 100644 --- a/packages/babel-preset-react-app/package.json +++ b/packages/babel-preset-react-app/package.json @@ -16,21 +16,22 @@ "test.js" ], "dependencies": { - "@babel/core": "7.0.0-beta.46", - "@babel/plugin-proposal-class-properties": "7.0.0-beta.46", - "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.46", - "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.46", - "@babel/plugin-transform-classes": "7.0.0-beta.46", - "@babel/plugin-transform-destructuring": "7.0.0-beta.46", - "@babel/plugin-transform-react-constant-elements": "7.0.0-beta.46", - "@babel/plugin-transform-react-display-name": "7.0.0-beta.46", - "@babel/plugin-transform-regenerator": "7.0.0-beta.46", - "@babel/plugin-transform-runtime": "7.0.0-beta.46", - "@babel/preset-env": "7.0.0-beta.46", - "@babel/preset-flow": "7.0.0-beta.46", - "@babel/preset-react": "7.0.0-beta.46", - "babel-plugin-macros": "2.2.1", + "@babel/core": "7.1.0", + "@babel/plugin-proposal-class-properties": "7.1.0", + "@babel/plugin-proposal-object-rest-spread": "7.0.0", + "@babel/plugin-syntax-dynamic-import": "7.0.0", + "@babel/plugin-transform-classes": "7.1.0", + "@babel/plugin-transform-destructuring": "7.0.0", + "@babel/plugin-transform-react-constant-elements": "7.0.0", + "@babel/plugin-transform-react-display-name": "7.0.0", + "@babel/plugin-transform-regenerator": "7.0.0", + "@babel/plugin-transform-runtime": "7.1.0", + "@babel/preset-env": "7.1.0", + "@babel/preset-flow": "7.0.0", + "@babel/preset-react": "7.0.0", + "@babel/preset-typescript": "7.1.0", + "babel-plugin-macros": "2.4.1", "babel-plugin-transform-dynamic-import": "2.0.0", - "babel-plugin-transform-react-remove-prop-types": "0.4.13" + "babel-plugin-transform-react-remove-prop-types": "0.4.15" } } diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index bc0378459c5..f3d03df860c 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -177,7 +177,7 @@ function createCompiler(webpack, config, appName, urls, useYarn) { console.log(chalk.yellow('Compiled with warnings.\n')); console.log(messages.warnings.join('\n\n')); - // Teach some ESLint tricks. + // Teach some ESLint / TSLint tricks. console.log( '\nSearch for the ' + chalk.underline(chalk.yellow('keywords')) + @@ -186,6 +186,9 @@ function createCompiler(webpack, config, appName, urls, useYarn) { console.log( 'To ignore, add ' + chalk.cyan('// eslint-disable-next-line') + + ' (or ' + + chalk.cyan('// tslint:disable-next-line') + + ' if TypeScript)' + ' to the line before.\n' ); } diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 6e49955e453..3b646b6246c 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -38,7 +38,7 @@ "workspaceUtils.js" ], "dependencies": { - "@babel/code-frame": "7.0.0-beta.46", + "@babel/code-frame": "7.0.0", "address": "1.0.3", "browserslist": "3.2.6", "chalk": "2.4.1", diff --git a/packages/react-error-overlay/.babelrc b/packages/react-error-overlay/.babelrc index c14b2828d16..ea303b756a3 100644 --- a/packages/react-error-overlay/.babelrc +++ b/packages/react-error-overlay/.babelrc @@ -1,3 +1,3 @@ { - "presets": ["react-app"] + "presets": [["react-app", { "flow": true }]] } diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json index 6e7e1f3e885..efaa424461e 100644 --- a/packages/react-error-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -30,14 +30,14 @@ "lib/index.js" ], "devDependencies": { - "@babel/code-frame": "7.0.0-beta.46", - "@babel/core": "7.0.0-beta.46", - "@babel/runtime": "7.0.0-beta.46", + "@babel/code-frame": "7.0.0", + "@babel/core": "7.1.0", + "@babel/runtime": "7.0.0", "anser": "1.4.6", - "babel-core": "^7.0.0-bridge.0", + "babel-core": "7.0.0-bridge.0", "babel-eslint": "^8.2.2", - "babel-jest": "^22.4.3", - "babel-loader": "^8.0.0-beta.0", + "babel-jest": "23.6.0", + "babel-loader": "8.0.2", "babel-preset-react-app": "^3.1.1", "chalk": "^2.3.2", "chokidar": "^2.0.2", diff --git a/packages/react-error-overlay/webpack.config.iframe.js b/packages/react-error-overlay/webpack.config.iframe.js index 3a806f362db..4552c9e34d8 100644 --- a/packages/react-error-overlay/webpack.config.iframe.js +++ b/packages/react-error-overlay/webpack.config.iframe.js @@ -23,7 +23,7 @@ module.exports = { oneOf: [ // Source { - test: /\.js$/, + test: /\.(js)|(tsx?)$/, include: [path.resolve(__dirname, './src')], use: { loader: 'babel-loader', @@ -31,7 +31,7 @@ module.exports = { }, // Dependencies { - test: /\.js$/, + test: /\.(js)|(tsx?)$/, use: { loader: 'babel-loader', options: { diff --git a/packages/react-error-overlay/webpack.config.js b/packages/react-error-overlay/webpack.config.js index dce51e0f9e9..498207e6eaa 100644 --- a/packages/react-error-overlay/webpack.config.js +++ b/packages/react-error-overlay/webpack.config.js @@ -24,7 +24,7 @@ module.exports = { use: 'raw-loader', }, { - test: /\.js$/, + test: /\.(js)|(tsx?)$/, include: path.resolve(__dirname, './src'), use: 'babel-loader', }, diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 32665e9b76c..66cac63d60f 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -47,6 +47,14 @@ function getServedPath(appPackageJson) { return ensureSlash(servedUrl, true); } +const hasTSConfig = fs.existsSync(resolveApp('tsconfig.json')); +const hasTSConfigProd = fs.existsSync(resolveApp('tsconfig.prod.json')); + +const hasTSLint = fs.existsSync(resolveApp('tslint.json')); +const hasTSLintProd = fs.existsSync(resolveApp('tslint.prod.json')); + +const isTypeScript = hasTSConfig; + // config after eject: we're in ./config/ module.exports = { dotenv: resolveApp('.env'), @@ -54,11 +62,19 @@ module.exports = { appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), - appIndexJs: resolveApp('src/index.js'), + appIndexJs: isTypeScript + ? resolveApp('src/index.tsx') + : resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), - testsSetup: resolveApp('src/setupTests.js'), + testsSetup: isTypeScript + ? resolveApp('src/setupTests.ts') + : resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), + appTSConfig: resolveApp('tsconfig.json'), + appTSConfigProd: resolveApp('tsconfig.prod.json'), + appTSLint: resolveApp('tslint.json'), + appTSLintProd: resolveApp('tslint.prod.json'), publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')), }; @@ -75,11 +91,19 @@ module.exports = { appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), - appIndexJs: resolveApp('src/index.js'), + appIndexJs: isTypeScript + ? resolveApp('src/index.tsx') + : resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), - testsSetup: resolveApp('src/setupTests.js'), + testsSetup: isTypeScript + ? resolveApp('src/setupTests.ts') + : resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), + appTSConfig: resolveApp('tsconfig.json'), + appTSConfigProd: resolveApp('tsconfig.prod.json'), + appTSLint: resolveApp('tslint.json'), + appTSLintProd: resolveApp('tslint.prod.json'), publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')), // These properties only exist before ejecting: @@ -100,11 +124,19 @@ if (useTemplate) { appBuild: resolveOwn('../../build'), appPublic: resolveOwn('template/public'), appHtml: resolveOwn('template/public/index.html'), - appIndexJs: resolveOwn('template/src/index.js'), + appIndexJs: isTypeScript + ? resolveOwn('template/src/index.tsx') + : resolveOwn('template/src/index.js'), appPackageJson: resolveOwn('package.json'), appSrc: resolveOwn('template/src'), - testsSetup: resolveOwn('template/src/setupTests.js'), + testsSetup: isTypeScript + ? resolveOwn('template/src/setupTests.ts') + : resolveOwn('template/src/setupTests.js'), appNodeModules: resolveOwn('node_modules'), + appTSConfig: resolveOwn('template/tsconfig.json'), + appTSConfigProd: resolveOwn('template/tsconfig.prod.json'), + appTSLint: resolveOwn('template/tslint.json'), + appTSLintProd: resolveOwn('template/tslint.prod.json'), publicUrl: getPublicUrl(resolveOwn('package.json')), servedPath: getServedPath(resolveOwn('package.json')), // These properties only exist before ejecting: @@ -116,6 +148,12 @@ if (useTemplate) { module.exports.srcPaths = [module.exports.appSrc]; +module.exports.isTypeScript = isTypeScript; +module.exports.useTSConfigProd = isTypeScript && hasTSConfigProd; + +module.exports.useTSLint = isTypeScript && hasTSLint; +module.exports.useTSLintProd = isTypeScript && hasTSLintProd; + module.exports.useYarn = fs.existsSync( path.join(module.exports.appPath, 'yarn.lock') ); diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index db371dda6e8..1aebc927c6d 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -13,6 +13,7 @@ const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); @@ -76,7 +77,7 @@ const getStyleLoaders = (cssOptions, preProcessor) => { module.exports = { mode: 'development', // You may want 'eval' instead if you prefer to see the compiled output in DevTools. - // See the discussion in https://github.com/facebook/create-react-app/issues/343 + // See the discussion in https://github.com/facebook/create-react-app/issues/343. devtool: 'cheap-module-source-map', // These are the "entry points" to our application. // This means they will be the "root" imports that are included in JS bundle. @@ -143,7 +144,18 @@ module.exports = { // https://github.com/facebook/create-react-app/issues/290 // `web` extension prefixes have been added for better support // for React Native Web. - extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], + extensions: [ + '.web.js', + '.mjs', + '.js', + '.json', + '.web.jsx', + '.jsx', + '.web.ts', + '.web.tsx', + '.ts', + '.tsx', + ], alias: { // @remove-on-eject-begin // Resolve Babel runtime relative to react-scripts. @@ -175,7 +187,7 @@ module.exports = { // First, run the linter. // It's important to do this before Babel processes the JS. - { + !paths.isTypeScript && { test: /\.(js|jsx|mjs)$/, enforce: 'pre', use: [ @@ -216,7 +228,7 @@ module.exports = { // Process application JS with Babel. // The preset includes JSX, Flow, and some ESnext features. { - test: /\.(js|jsx|mjs)$/, + test: /\.(js|jsx|mjs|tsx?)$/, include: paths.srcPaths, exclude: [/[/\\\\]node_modules[/\\\\]/], use: [ @@ -225,7 +237,7 @@ module.exports = { { loader: require.resolve('thread-loader'), options: { - poolTimeout: Infinity // keep workers alive for more effective watch mode + poolTimeout: Infinity, // keep workers alive for more effective watch mode }, }, { @@ -259,14 +271,14 @@ module.exports = { // Process any JS outside of the app with Babel. // Unlike the application JS, we only compile the standard ES features. { - test: /\.js$/, + test: /\.(js)|(tsx?)$/, use: [ // This loader parallelizes code compilation, it is optional but // improves compile time on larger projects { loader: require.resolve('thread-loader'), options: { - poolTimeout: Infinity // keep workers alive for more effective watch mode + poolTimeout: Infinity, // keep workers alive for more effective watch mode }, }, { @@ -344,7 +356,7 @@ module.exports = { // its runtime that would otherwise be processed through "file" loader. // Also exclude `html` and `json` extensions so they get processed // by webpacks internal loaders. - exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], + exclude: [/\.(js|jsx|mjs|tsx?)$/, /\.html$/, /\.json$/], loader: require.resolve('file-loader'), options: { name: 'static/media/[name].[hash:8].[ext]', @@ -354,7 +366,7 @@ module.exports = { }, // ** STOP ** Are you adding a new loader? // Make sure to add the new loader(s) before the "file" loader. - ], + ].filter(Boolean), }, plugins: [ // Generates an `index.html` file with the