Skip to content

Commit

Permalink
TypeScript support
Browse files Browse the repository at this point in the history
  • Loading branch information
brunolemos committed Oct 20, 2018
1 parent 473fde0 commit 506d173
Show file tree
Hide file tree
Showing 22 changed files with 281 additions and 40 deletions.
4 changes: 1 addition & 3 deletions README.md
Expand Up @@ -142,7 +142,7 @@ Please refer to the [User Guide](https://facebook.github.io/create-react-app/doc

Your environment will have everything you need to build a modern single-page React app:

- React, JSX, ES6, and Flow syntax support.
- React, JSX, ES6, TypeScript and Flow syntax support.
- Language extras beyond ES6 like the object spread operator.
- Autoprefixed CSS, so you don’t need `-webkit-` or other prefixes.
- A fast interactive unit test runner with built-in support for coverage reporting.
Expand Down Expand Up @@ -175,8 +175,6 @@ 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).

- Finally, if you need **more customization**, check out [Neutrino](https://neutrino.js.org/) and its [React preset](https://neutrino.js.org/packages/react/).

All of the above tools can work with little to no configuration.
Expand Down
45 changes: 45 additions & 0 deletions docusaurus/docs/adding-typescript.md
@@ -0,0 +1,45 @@
---
id: adding-typescript
title: Adding TypeScript
---

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

Recent versions of [TypeScript](https://www.typescriptlang.org/) work with Create React App projects out of the box thanks to Babel 7. Beware that Babel 7 TypeScript does not allow some features of TypeScript such as constant enum and namespaces.

To add TypeScript to a Create React App project, follow these steps:

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

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

> Note: If you prefer to run type checking separately from the build process, you can run `npm uninstall fork-ts-checker-webpack-plugin` (or `yarn remove fork-ts-checker-webpack-plugin`) to remove the `fork-ts-checker-webpack-plugin` dependency and then `npx tsc -w` on a new terminal tab.
We recommend using [VSCode](https://code.visualstudio.com/) for a better integrated experience.

To learn more about TypeScript, check out [its documentation](https://www.typescriptlang.org/).
4 changes: 3 additions & 1 deletion docusaurus/docs/running-tests.md
Expand Up @@ -109,6 +109,8 @@ import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
```

> Note: When using TypeScript with Babel, all your files need to have at least one export, otherwise you will get the error `Cannot compile namespaces when the '--isolatedModules' flag is provided.`. To fix this, you can add `export default undefined` to `src/setupTests.ts`.
> Note: Keep in mind that if you decide to "eject" before creating `src/setupTests.js`, the resulting `package.json` file won't contain any reference to it. [Read here](#initializing-test-environment) to learn how to add this after ejecting.
Now you can write a smoke test with it:
Expand Down Expand Up @@ -283,7 +285,7 @@ Example package.json:
"name": "your-package",
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"src/**/*.{js,jsx,ts,tsx}",
"!<rootDir>/node_modules/",
"!<rootDir>/path/to/dir/"
],
Expand Down
4 changes: 2 additions & 2 deletions docusaurus/docs/setting-up-your-editor.md
Expand Up @@ -120,14 +120,14 @@ Next we add a 'lint-staged' field to the `package.json`, for example:
// ...
},
+ "lint-staged": {
+ "src/**/*.{js,jsx,json,css}": [
+ "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
+ "prettier --single-quote --write",
+ "git add"
+ ]
+ },
"scripts": {
```

Now, whenever you make a commit, Prettier will format the changed files automatically. You can also run `./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx}"` to format your entire project for the first time.
Now, whenever you make a commit, Prettier will format the changed files automatically. You can also run `./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}"` to format your entire project for the first time.

Next you might want to integrate Prettier in your favorite editor. Read the section on [Editor Integration](https://prettier.io/docs/en/editors.html) on the Prettier GitHub page.
2 changes: 1 addition & 1 deletion docusaurus/docs/supported-browsers-features.md
Expand Up @@ -17,7 +17,7 @@ This project supports a superset of the latest JavaScript standard. In addition
- [Object Rest/Spread Properties](https://github.com/tc39/proposal-object-rest-spread) (ES2018).
- [Dynamic import()](https://github.com/tc39/proposal-dynamic-import) (stage 3 proposal)
- [Class Fields and Static Properties](https://github.com/tc39/proposal-class-public-fields) (part of stage 3 proposal).
- [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) and [Flow](https://flow.org/) syntax.
- [JSX](https://facebook.github.io/react/docs/introducing-jsx.html), [Flow](./adding-flow) and [TypeScript](./adding-typescript).

Learn more about [different proposal stages](https://babeljs.io/docs/plugins/#presets-stage-x-experimental-presets-).

Expand Down
3 changes: 3 additions & 0 deletions docusaurus/website/i18n/en.json
Expand Up @@ -36,6 +36,9 @@
"adding-relay": {
"title": "Adding Relay"
},
"adding-typescript": {
"title": "Adding TypeScript"
},
"advanced-configuration": {
"title": "Advanced Configuration"
},
Expand Down
1 change: 1 addition & 0 deletions docusaurus/website/sidebars.json
Expand Up @@ -31,6 +31,7 @@
"using-global-variables",
"adding-bootstrap",
"adding-flow",
"adding-typescript",
"adding-relay",
"adding-a-router",
"adding-custom-environment-variables",
Expand Down
21 changes: 12 additions & 9 deletions packages/babel-preset-react-app/README.md
Expand Up @@ -32,19 +32,22 @@ 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

Flow is enabled by default. Make sure you have a `.flowconfig` file at the root directory. You can also use the `flow` option on `.babelrc`:

```
{
"presets": [["react-app", { "flow": true, "typescript": false }]]
}
```

To use this package with [`@babel/preset-typescript`](https://www.npmjs.com/package/@babel/preset-typescript), you need to disable `@babel/preset-flow` first.
## Usage with TypeScript

You can achieve this by doing:
TypeScript is enabled by default. Make sure you have a `tsconfig.json` file at the root directory. You can also use the `typescript` option on `.babelrc`:

```
{
"presets": [
["react-app", {
"flow": false
}],
"@babel/typescript"
]
"presets": [["react-app", { "flow": false, "typescript": true }]]
}
```
6 changes: 6 additions & 0 deletions packages/babel-preset-react-app/create.js
Expand Up @@ -30,6 +30,11 @@ module.exports = function(api, opts, env) {
var isEnvTest = env === 'test';

var isFlowEnabled = validateBoolOption('flow', opts.flow, true);
var isTypeScriptEnabled = validateBoolOption(
'typescript',
opts.typescript,
true
);
var areHelpersEnabled = validateBoolOption('helpers', opts.helpers, true);
var useAbsoluteRuntime = validateBoolOption(
'absoluteRuntime',
Expand Down Expand Up @@ -97,6 +102,7 @@ module.exports = function(api, opts, env) {
useBuiltIns: true,
},
],
isTypeScriptEnabled && [require('@babel/preset-typescript').default],
].filter(Boolean),
plugins: [
// Strip flow types before any other transform, emulating the behavior
Expand Down
1 change: 1 addition & 0 deletions packages/babel-preset-react-app/package.json
Expand Up @@ -29,6 +29,7 @@
"@babel/plugin-transform-runtime": "7.1.0",
"@babel/preset-env": "7.1.0",
"@babel/preset-react": "7.0.0",
"@babel/preset-typescript": "7.1.0",
"@babel/runtime": "7.0.0",
"babel-loader": "8.0.4",
"babel-plugin-dynamic-import-node": "2.2.0",
Expand Down
44 changes: 38 additions & 6 deletions packages/react-scripts/config/paths.js
Expand Up @@ -46,18 +46,46 @@ function getServedPath(appPackageJson) {
return ensureSlash(servedUrl, true);
}

const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];

// Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);

if (extension) {
return resolveFn(`${filePath}.${extension}`);
}

return resolveFn(`${filePath}.js`);
};

// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveApp('src/index.js'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveApp('src/setupTests.js'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
Expand All @@ -74,11 +102,12 @@ module.exports = {
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveApp('src/index.js'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveApp('src/setupTests.js'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
Expand All @@ -105,11 +134,12 @@ if (
appBuild: resolveOwn('../../build'),
appPublic: resolveOwn('template/public'),
appHtml: resolveOwn('template/public/index.html'),
appIndexJs: resolveOwn('template/src/index.js'),
appIndexJs: resolveModule(resolveOwn, 'template/src/index'),
appPackageJson: resolveOwn('package.json'),
appSrc: resolveOwn('template/src'),
appTsConfig: resolveOwn('template/tsconfig.json'),
yarnLockFile: resolveOwn('template/yarn.lock'),
testsSetup: resolveOwn('template/src/setupTests.js'),
testsSetup: resolveModule(resolveOwn, 'template/src/setupTests'),
proxySetup: resolveOwn('template/src/setupProxy.js'),
appNodeModules: resolveOwn('node_modules'),
publicUrl: getPublicUrl(resolveOwn('package.json')),
Expand All @@ -120,3 +150,5 @@ if (
};
}
// @remove-on-eject-end

module.exports.moduleFileExtensions = moduleFileExtensions;
33 changes: 29 additions & 4 deletions packages/react-scripts/config/webpack.config.dev.js
Expand Up @@ -8,7 +8,9 @@
// @remove-on-eject-end
'use strict';

const fs = require('fs');
const path = require('path');
const resolve = require('resolve');
const webpack = require('webpack');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
Expand Down Expand Up @@ -146,7 +148,7 @@ 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: ['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx'],
extensions: paths.moduleFileExtensions.map(ext => `.${ext}`),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
Expand Down Expand Up @@ -220,7 +222,7 @@ module.exports = {
// Process application JS with Babel.
// The preset includes JSX, Flow, and some ESnext features.
{
test: /\.(js|mjs|jsx)$/,
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
Expand Down Expand Up @@ -353,7 +355,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|mjs|jsx)$/, /\.html$/, /\.json$/],
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
Expand Down Expand Up @@ -406,7 +408,30 @@ module.exports = {
fileName: 'asset-manifest.json',
publicPath: publicPath,
}),
],
// TypeScript type checking
fs.existsSync(paths.appTsConfig) &&
(() => {
let ForkTsCheckerWebpackPlugin;
try {
ForkTsCheckerWebpackPlugin = require(resolve.sync(
'fork-ts-checker-webpack-plugin',
{ basedir: paths.appNodeModules }
));
} catch (e) {
// Fail silently.
// Type checking using this plugin is optional.
// The user may decide to install `fork-ts-checker-webpack-plugin` or use `tsc -w`.
return null;
}

return new ForkTsCheckerWebpackPlugin({
async: false,
checkSyntacticErrors: true,
tsconfig: paths.appTsConfig,
watch: paths.appSrc,
});
})(),
].filter(Boolean),

// 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.
Expand Down

0 comments on commit 506d173

Please sign in to comment.