onoya.dev

React + Webpack + Babel 7 + TypeScript Starter

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

App Page

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:

App Page With CSS