I have been recently working on projects that uses TypeScript with React. The easiest way to get started working with React + TypeScript is through the create-react-app boilerplate. (react-scripts@2.1.0 and above now supports typescript) You can now easily add TypeScript support by adding the --typescript
flag.
I got curious on how we can setup a TypeScript React app without the help of create-react-app. So today I’ll be sharing what I have learned this past few days.
In this post, I will be creating TypeScript React app from scratch without using the create-react-app boilerplate. This will also cover additional stuff such as integrating CSS Modules and adding ESLint with typescript-eslint parser.
I initially wrote this article as a cheatsheet for myself. So if you find something wrong or find something that can be improved, please do leave a message. 😅I’ll be updating this post from time to time.
Getting Started
This is going to be the folder structure:
react-webpack-babel-typescript/
├── config/
│ ├── index.html
│ ├── webpack.common.js
│ ├── webpack.dev.js
│ └── webpack.prod.js
├── src/
│ ├── components/
│ │ └── Title.tsx
│ ├── types/
│ │ └── css-modules.d.ts
│ ├── App.css
│ ├── App.tsx
│ ├── global.css
│ └── index.tsx
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── package.json
└── tsconfig.json
Let’s start off by creating a new project directory and generate package.json file with the command below:
mkdir typescript-react && cd typescript-react && npm init -y
The command above will create a directory called typescript-react and generates a package.json file inside the project directory.
Webpack
Install webpack and its plugins:
npm i -D webpack webpack-cli webpack-dev-server webpack-merge html-webpack-plugin clean-webpack-plugin
- webpack-dev-server – provides you with a simple web server and the ability to use live reloading.
- webpack-merge – a utility to merge webpack configurations.
- html-webpack-plugin – a plugin that simplifies creation of HTML files to serve your bundles
- clean-webpack-plugin – a plugin to remove/clean your build folder
config/webpack.common.js
I will start by writing the common webpack configuration that will be used for both development and production.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const config = {
entry: path.join(__dirname, '../src/index'),
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, './index.html'),
minify: {
removeComments: true,
collapseWhitespace: true,
},
}),
],
};
module.exports = config;
config/webpack.dev.js
This is the development specific configuration
const merge = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common');
const config = {
mode: 'development',
devtool: 'inline-source-map',
output: {
path: path.join(__dirname, '../build'),
filename: 'bundle.js',
},
};
module.exports = merge(common, config);
config/webpack.prod.js
This is the production specific configuration
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const path = require('path');
const common = require('./webpack.common');
const config = {
mode: 'production',
devtool: 'source-map',
output: {
path: path.join(__dirname, '../build'),
filename: 'js/main.[contentHash].js',
publicPath: './',
},
plugins: [new CleanWebpackPlugin()],
};
module.exports = merge(common, config);
config/index.html
This is the HTML template thats going to be used by the HTMLWebpackPlugin
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React + Webpack + Babel 7 + TypeScript</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
package.json
We will add the following scripts to package.json file
{
// ...
"scripts": {
"start": "webpack-dev-server --mode development --open --hot --config config/webpack.dev.js",
"build": "webpack --mode production --config config/webpack.prod.js",
},
// ...
}
Babel
npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
.babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
}
config/webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const config = {
// ...entry, resolve
module: {
rules: [
{
test: /\.(ts)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
// ...plugins
};
module.exports = config;
TypeScript
npm i typescript
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"strictNullChecks": true,
"noEmit": true,
"jsx": "react",
"lib": ["es6", "dom"],
"module": "commonjs",
"allowSyntheticDefaultImports": true
},
"include": ["./src"],
"exclude": ["node_modules", "build"]
}
React
npm i react react-dom @types/react @types/react-dom
It’s time to write some React components.
src/index.tsx
This is going to be our main entry point
import React from 'react';
import { render } from 'react-dom';
import App from './App';
render(<App />, document.getElementById('root'));
src/App.tsx
import React, { FC } from 'react';
import Title from './components/Title';
const App: FC = () => (
<div>
<Title title="React + Webpack + Babel 7 + Typescript" />
</div>
);
export default App;
src/components/Title.tsx
import React, { FunctionComponent } from 'react';
interface Props {
title: string;
}
const Title: FunctionComponent<Props> = ({ title }) => (
<h1>{title}</h1>
);
export default Title;
We can now start our app
npm start

ESLint
npm i -D eslint eslint-plugin-react @typescript-eslint/parser @typescript-eslint/eslint-plugin
.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
],
settings: {
react: {
version: 'detect',
},
},
rules: {
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowTypedFunctionExpressions: true,
},
],
'@typescript-eslint/indent': ['error', 2],
'react/prop-types': 'off',
},
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},
],
};
CSS Modules
Let us now add the ability to style our components using the style-loader and css-loader.
npm i -D css-loader style-loader
config/webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const config = {
// ...entry, resolve
module: {
rules: [
{
test: /\.(ts)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
],
},
// ...plugins
};
module.exports = config;
src/types/css-modules.d.ts
Without this, you’ll be getting the Cannot find module <module name> error.
declare module '*.css' {
const styles: { [className: string]: string };
export default styles;
}
We can now import css file that will be applied globally
src/global.css
@import url('https://fonts.googleapis.com/css?family=Muli:400,800&display=swap');
body {
margin: 0;
font-family: 'Muli', sans-serif;
}
src/index.tsx
import React from 'react';
import { render } from 'react-dom';
import App from './App';
import './global.css'
render(<App />, document.getElementById('root'));
Or you could write a locally scoped css style like the example below
src/App.css
.app {
display: flex;
background-color: #0a3d62;
flex-direction: column;
font-size: xx-large;
text-align: center;
text-shadow: 2px 3px rgba(0, 0, 0, 0.4);
justify-content: center;
align-items: center;
height: 100vh;
}
src/App.tsx
import React, { FC } from 'react';
import Title from './components/Title';
import styles from './App.css';
const App: FC = () => (
<div className={styles.app}>
<Title title="React + Webpack + Babel 7 + Typescript" />
</div>
);
export default App;
Or even write inline styles
import React, { FunctionComponent } from 'react';
interface Props {
title: string;
}
const Title: FunctionComponent<Props> = ({ title }) => (
<h1 style={{ color: '#f9ca24' }}>{title}</h1>
);
export default Title;
You should now have a page that looks something like this:

Hi!
Your setup looks interesting. Do you have the source code hosted in github so your readers can play around with it themselves?
Yeah, here’s the repository: https://github.com/onoya/ts-react-webpack-babel-boilerplate