diff --git a/README.md b/README.md index fddffcbdd..038ab1233 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Create React apps (with Typescript) with no build configuration. + * [Getting Started](#tldr) – How to create a new app. + * [User Guide](https://github.com/wmonk/create-react-app-typescript/blob/master/packages/react-scripts/template/README.md) – How to develop apps bootstrapped with react scripts ts. + _Do you know react and want to try out typescript? Or do you know typescript and want to try out react?_ Get all the benefits from `create-react-app` but you use typescript! 🚀 ## tl;dr diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index e860d9b04..76c165ba4 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -43,8 +43,8 @@ const getPublicUrl = appPackageJson => // like /todos/42/static/js/bundle.7289d.js. We have to know the root. function getServedPath(appPackageJson) { const publicUrl = getPublicUrl(appPackageJson); - const servedUrl = - envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); + const servedUrl = envPublicUrl || + (publicUrl ? url.parse(publicUrl).pathname : '/'); return ensureSlash(servedUrl, true); } @@ -61,6 +61,7 @@ module.exports = { testsSetup: resolveApp('src/setupTests.ts'), appNodeModules: resolveApp('node_modules'), appTsConfig: resolveApp('tsconfig.json'), + appTsLint: resolveApp('tslint.json'), publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')), }; @@ -83,6 +84,7 @@ module.exports = { appNodeModules: resolveApp('node_modules'), appTsConfig: resolveApp('tsconfig.json'), appTsTestConfig: resolveApp('tsconfig.test.json'), + appTsLint: resolveApp('tslint.json'), publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')), // These properties only exist before ejecting: @@ -92,8 +94,7 @@ module.exports = { const ownPackageJson = require('../package.json'); const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`); -const reactScriptsLinked = - fs.existsSync(reactScriptsPath) && +const reactScriptsLinked = fs.existsSync(reactScriptsPath) && fs.lstatSync(reactScriptsPath).isSymbolicLink(); // config before publish: we're in ./packages/react-scripts/config/ @@ -114,6 +115,7 @@ if ( testsSetup: resolveOwn('template/src/setupTests.ts'), appNodeModules: resolveOwn('node_modules'), appTsConfig: resolveOwn('template/tsconfig.json'), + appTsLint: resolveOwn('template/tslint.json'), appTsTestConfig: resolveOwn('template/tsconfig.test.json'), publicUrl: getPublicUrl(resolveOwn('package.json')), servedPath: getServedPath(resolveOwn('package.json')), diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 07c0a3adf..6c619f4f4 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -18,6 +18,7 @@ const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const getClientEnvironment = require('./env'); const paths = require('./paths'); @@ -134,14 +135,6 @@ module.exports = { // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. // { parser: { requireEnsure: false } }, - // First, run the linter. - // It's important to do this before Babel processes the JS. - { - test: /\.(ts|tsx)$/, - loader: require.resolve('tslint-loader'), - enforce: 'pre', - include: paths.appSrc, - }, { test: /\.js$/, loader: require.resolve('source-map-loader'), @@ -168,7 +161,15 @@ module.exports = { { test: /\.(ts|tsx)$/, include: paths.appSrc, - loader: require.resolve('ts-loader'), + use: [ + { + loader: require.resolve('ts-loader'), + options: { + // disable type checker - we will use it in fork plugin + transpileOnly: true, + }, + }, + ], }, // "postcss" loader applies autoprefixer to our CSS. // "css" loader resolves paths in CSS and adds assets as dependencies. @@ -262,6 +263,13 @@ module.exports = { // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // You can remove this if you don't use Moment.js: new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // Perform type checking and linting in a separate process to speed up compilation + new ForkTsCheckerWebpackPlugin({ + async: false, + watch: paths.appSrc, + tsconfig: paths.appTsConfig, + tslint: paths.appTsLint, + }), ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 9582ee731..366dcd108 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -19,6 +19,7 @@ const ManifestPlugin = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const paths = require('./paths'); const getClientEnvironment = require('./env'); @@ -137,7 +138,6 @@ module.exports = { // TODO: Disable require.ensure as it's not a standard language feature. // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. // { parser: { requireEnsure: false } }, - // First, run the linter. // It's important to do this before Typescript runs. { @@ -167,11 +167,19 @@ module.exports = { name: 'static/media/[name].[hash:8].[ext]', }, }, - //Compile .tsx? + // Compile .tsx? { test: /\.(ts|tsx)$/, include: paths.appSrc, - loader: require.resolve('ts-loader') + use: [ + { + loader: require.resolve('ts-loader'), + options: { + // disable type checker - we will use it in fork plugin + transpileOnly: true, + }, + }, + ], }, // The notation here is somewhat confusing. // "postcss" loader applies autoprefixer to our CSS. @@ -341,6 +349,12 @@ module.exports = { // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // You can remove this if you don't use Moment.js: new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // Perform type checking and linting in a separate process to speed up compilation + new ForkTsCheckerWebpackPlugin({ + async: false, + tsconfig: paths.appTsConfig, + tslint: paths.appTsLint, + }), ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 01bb852f5..cc3a6f9d1 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -28,6 +28,7 @@ "dotenv": "4.0.0", "extract-text-webpack-plugin": "3.0.0", "file-loader": "0.11.2", + "fork-ts-checker-webpack-plugin": "^0.2.8", "fs-extra": "3.0.1", "html-webpack-plugin": "2.29.0", "jest": "20.0.4", @@ -59,3 +60,4 @@ "fsevents": "1.1.2" } } + diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index c55ccdf94..c57a0ebd7 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -1244,9 +1244,9 @@ There is a broad spectrum of component testing techniques. They range from a “ Different projects choose different testing tradeoffs based on how often components change, and how much logic they contain. If you haven’t decided on a testing strategy yet, we recommend that you start with creating simple smoke tests for your components: -```js -import React from 'react'; -import ReactDOM from 'react-dom'; +```ts +import * as React from 'react'; +import * asReactDOM from 'react-dom'; import App from './App'; it('renders without crashing', () => { @@ -1255,26 +1255,34 @@ it('renders without crashing', () => { }); ``` -This test mounts a component and makes sure that it didn’t throw during rendering. Tests like this provide a lot value with very little effort so they are great as a starting point, and this is the test you will find in `src/App.test.js`. +This test mounts a component and makes sure that it didn’t throw during rendering. Tests like this provide a lot value with very little effort so they are great as a starting point, and this is the test you will find in `src/App.test.tsx`. When you encounter bugs caused by changing components, you will gain a deeper insight into which parts of them are worth testing in your application. This might be a good time to introduce more specific tests asserting specific expected output or behavior. If you’d like to test components in isolation from the child components they render, we recommend using [`shallow()` rendering API](http://airbnb.io/enzyme/docs/api/shallow.html) from [Enzyme](http://airbnb.io/enzyme/). To install it, run: ```sh -npm install --save enzyme react-test-renderer +npm install --save-dev enzyme @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16 react-test-renderer @types/react-test-renderer ``` Alternatively you may use `yarn`: ```sh -yarn add enzyme react-test-renderer +yarn add --dev enzyme @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16 react-test-renderer @types/react-test-renderer +``` + +#### `src/setupTests.ts` +```ts +import * as Enzyme from 'enzyme'; +import * as Adapter from 'enzyme-adapter-react-16'; + +Enzyme.configure({ adapter: new Adapter() }); ``` You can write a smoke test with it too: -```js -import React from 'react'; +```ts +import * as React from 'react'; import { shallow } from 'enzyme'; import App from './App'; @@ -1289,8 +1297,8 @@ You can read the [Enzyme documentation](http://airbnb.io/enzyme/) for more testi Here is an example from Enzyme documentation that asserts specific output, rewritten to use Jest matchers: -```js -import React from 'react'; +```ts +import * as React from 'react'; import { shallow } from 'enzyme'; import App from './App'; @@ -1323,7 +1331,7 @@ Alternatively you may use `yarn`: yarn add jest-enzyme ``` -Import it in [`src/setupTests.js`](#initializing-test-environment) to make its matchers available in every test: +Import it in [`src/setupTests.ts`](#initializing-test-environment) to make its matchers available in every test: ```js import 'jest-enzyme'; @@ -1346,12 +1354,12 @@ and then use them in your tests like you normally do. >Note: this feature is available with `react-scripts@0.4.0` and higher. -If your app uses a browser API that you need to mock in your tests or if you just need a global setup before running your tests, add a `src/setupTests.js` to your project. It will be automatically executed before running your tests. +If your app uses a browser API that you need to mock in your tests or if you just need a global setup before running your tests, add a `src/setupTests.ts` to your project. It will be automatically executed before running your tests. For example: -#### `src/setupTests.js` -```js +#### `src/setupTests.ts` +```ts const localStorageMock = { getItem: jest.fn(), setItem: jest.fn(),