Create a React project from scratch, with TypeScript and Webpack

A step by step guide on how to create a React project from scratch, with TypeScript and Webpack.

You can find the full source code here: https://github.com/alexadam/project-templates/tree/master/projects/react-app

Setup

Prerequisites:

Create the project's folder:

mkdir react-app
cd react-app

Generate a default package.json file with yarn:

yarn init -y

Install React, TypeScript and Webpack:

yarn add react react-dom

yarn add --dev @types/react \
        @types/react-dom \
        ts-loader \
        css-loader \
        html-webpack-plugin \
        sass \
        sass-loader \
        style-loader \
        typescript \
        webpack \
        webpack-cli \
        webpack-dev-server

Add build, dev & clean scripts in the package.json file:

react-app/package.json
.... }, "scripts": { "clean": "rm -rf dist/*", "build": "webpack", "dev": "webpack serve" }

Configure TypeScript by creating the file tsconfig.json with:

react-app/tsconfig.json
{ "compilerOptions": { "incremental": true, "target": "es5", "module": "commonjs", "lib": ["dom", "dom.iterable", "es6"], "allowJs": true, "jsx": "react", "sourceMap": true, "outDir": "./dist/", "rootDir": ".", "removeComments": true, "strict": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "experimentalDecorators": true }, "include": [ "./client" ], "exclude": [ "./node_modules", "./build", "./dist" ] }

To configure Webpack, make a file webpack.config.js containing:

react-app/webpack.config.js
const path = require("path"); const app_dir = __dirname + '/client'; const HtmlWebpackPlugin = require('html-webpack-plugin'); const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ template: app_dir + '/index.html', filename: 'index.html', inject: 'body' }); const config = { mode: 'development', entry: app_dir + '/app.tsx', output: { path: __dirname + '/dist', filename: 'app.js', publicPath: '/' }, module: { rules: [{ test: /\.s?css$/, use: [ 'style-loader', 'css-loader', 'sass-loader' ] }, { test: /\.tsx?$/, loader: "ts-loader", exclude: /(node_modules|bower_components)/ }, { test: /\.(woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, exclude: [/node_modules/], loader: "file-loader" }, { test: /\.(jpe?g|png|gif|svg)$/i, exclude: [/node_modules/], loader: "file-loader" }, { test: /\.(pdf)$/i, exclude: [/node_modules/], loader: "file-loader", options: { name: '[name].[ext]', }, }, ] }, plugins: [HTMLWebpackPluginConfig], resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] }, optimization: { removeAvailableModules: false, removeEmptyChunks: false, splitChunks: false, }, devServer: { port: 8080, hot: true, historyApiFallback: true, }, }; module.exports = config;

Example App

Create a folder named client (in the project's folder):

mkdir client
cd client

Make a simple React component, in the file numbers.tsx:

react-app/client/numbers.tsx
import React, {useState} from 'react'; interface INumberProps { initValue: number } const Numbers = (props: INumberProps) => { const [value, setValue] = useState(props.initValue) const onIncrement = () => { setValue(value + 1) } const onDecrement = () => { setValue(value - 1) } return ( <div> Number is {value} <div> <button onClick={onIncrement}>+</button> <button onClick={onDecrement}>-</button> </div> </div> ) } export default Numbers /// // The same component as a Class // interface INumbersProps { // initValue: number // } // interface INumbersState { // value: number // } // export default class Numbers // extends React.Component<INumbersProps, INumbersState> { // constructor(props: INumbersProps) { // super(props) // // this.state = { value: this.props.initValue }; // } // componentWillMount = () => { // this.setState({ // value: this.props.initValue // }) // } // onIncrement = () => { // let newVal = this.state.value + 1 // this.setState({ // value: newVal // }) // } // onDecrement = () => { // let newVal = this.state.value - 1 // this.setState({ // value: newVal // }) // } // render = () => { // return ( // <div> // Number is {this.state.value} // <div> // <button onClick={this.onIncrement}>+</button> // <button onClick={this.onDecrement}>-</button> // </div> // </div> // ) // } // }

Create the main React component (the entry point), in the file app.tsx:

react-app/client/app.tsx
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import Numbers from './numbers'; ReactDOM.render( <Numbers initValue={42} />, document.getElementById('app') as HTMLElement );

Next, add the index.html:

react-app/client/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React TypeScript</title> </head> <body> <div id="app"></div> </body> </html>

Then, run yarn dev and open http://localhost:8080/ in a browser.

Use this project as a template

You can save the Setup steps as a shell script:

#!/bin/sh

rm -rf node_modules
rm -rf dist
rm -rf build
rm package.json
rm yarn.lock

yarn init --yes

yarn add react react-dom

yarn add --dev @types/react \
        @types/react-dom \
        ts-loader \
        css-loader \
        html-webpack-plugin \
        sass \
        sass-loader \
        style-loader \
        typescript \
        webpack \
        webpack-cli \
        webpack-dev-server

# Remove the last line
sed -i.bak '$ d' package.json && rm package.json.bak

# append the scripts commads
cat <<EOT >> package.json
   ,"scripts": {
      "clean": "rm -rf dist/*",
      "build": "webpack",
      "dev": "webpack serve"
   }
}

Delete the node-modules folder and, when you want to start a new project, you can copy the contents of react-app to the new location:

mkdir new-project
cd new-project

# copy the react-app folder content to the new project
rsync -rtv /path/to/../react-app/ .

./init.sh