WittCode💻

Create a React App with Typescript and Webpack

By

Learn how to create a custom react application that uses TypeScript by configuring webpack. We will also learn how to configure the TypeScript compiler by using tsconfig.json, what webpack loaders are, and what babel presets are.

Table of Contents 📖

Breaking Up webpack.config.js for Development and Production

To begin, lets split our webpack.config.js file into development and production webpack files. Create two files, webpack.dev.js and webpack.prod.js at the top level of the project. The webpack.dev.js file will contain webpack configuration that is only needed for development and the webpack.prod.js file will contain webpack configuration that is only needed for production. The file webpack.config.js will contain the commonalities between both development and production.

[object Object]

Before we add any contents to these new webpack files, lets install the library webpack-merge as a development dependency. This library will merge the contents of webpack.config.js with both webpack.dev.js and webpack.prod.js.

npm i webpack-merge --save-dev

Now lets move everything that involves developing our react application from webpack.config.js to webpack.dev.js. We will also import and use the webpack-merge library to merge webpack.config.js with webpack.dev.js. Place the following inside webpack.dev.js.

const { merge } = require("webpack-merge");
const webpackConfig = require('./webpack.config');

module.exports = merge(webpackConfig, {
    mode: "development",
    entry: './src/index.tsx',
});

We import the webpack-merge library's merge function to combine webpack.config.js with webpack.dev.js. We also set the webpack mode configuration to development which tells webpack not to minimize the code. We then set the entry point of our application to be our index.tsx file. Now lets move everything that involves production from webpack.config.js to webpack.prod.js. Place the following inside webpack.prod.js.

const path = require('path');
const { merge } = require("webpack-merge");
const webpackConfig = require('./webpack.config');

module.exports = merge(webpackConfig, {
    mode: "production",
    output: {
        path: path.join(__dirname, '/dist'),
        filename: 'bundle.js'
    }
});

Once again we use the webpack-merge library's merge function to combine webpack.config.js with webpack.prod.js. We also specify the mode to be production so webpack will make the code much smaller. This mode should always be used in production. After moving all this, our webpack.config.js file should look like the following.

const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        new HTMLWebpackPlugin({
            template: './src/index.html'
        })
    ],
    module: {
        rules: [
            {
                test: /.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            }
        ]
    }
}

Now webpack.config.js contains the commonalities for our react application in development and production while the contents of webpack.dev.js are specific to developing our react application and webpack.prod.js contains configuration that is specific to our react application in production.

Creating Scripts to Run our Development and Production Webpack Files

We now need to change our script commands to use these new configuration files. Change the scripts in package.json to the following.

"scripts": { "start": "webpack-dev-server --open --hot --config webpack.dev.js", "build": "webpack --config webpack.prod.js" },

The --config option allows us to specify the configuration file that we want webpack to use. By default webpack looks for webpack.config.js so if we use a different file we have to specify the name.

Installing TypeScript Packages

To begin using TypeScript with react, we need to install some required dependencies. The first one we need to install is TypeScript itself.

npm i typescript

The TypeScript library adds optional types to JavaScript and compiles to readable JavaScript. Now lets install some additional required dependencies. Install @types/react and @types/react-dom as development dependencies with --save-dev.

npm i @types/react @types/react-dom --save-dev

The npm scope @types is used for obtaining type definitions with npm. The @types/react package contains type definitions for react and the @types/react-dom package contains type definitions for react-dom. We also need to install a TypeScript Babel preset for babel as a development dependency. A babel preset is a shareable set of babel configurations.

npm i @babel/preset-typescript --save-dev

The @babel/preset-typescript package includes the @babel/plugin-transform-typescript plugin which adds support for types syntax used by TypeScript. Now we need to install the webpack loader ts-loader. A webpack loader is used to process files during bundling. The ts-loader will pass TypeScript files to the TypeScript compiler.

npm i ts-loader --save-dev

Changing Webpack to Handle TypeScript

Lets now inform webpack that we are going to be using the ts-loader for TypeScript files. In other words, files ending with the extension .ts or .tsx. The webpack.config.js files should now look like this.

const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        new HTMLWebpackPlugin({
            template: './src/index.html'
        })
    ],
    module: {
        rules: [
            {
                test: /.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            },
            {
                test: /.(ts|tsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: 'ts-loader',
                    options: {
                        presets: ['@babel/preset-typescript']
                    }
                }
            }
        ]
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.jsx', '.js']
    }
}

This tells webpack that for files ending in .ts or .tsx, we need to use the ts-loader with the @babel/preset-typescript preset. We also tell webpack to exclude the node_modules folder as we don't need the ts-loader compiling anything in there. The resolve key configures how webpack resolves modules. Now, when we import files within modules, webpack will first attempt to resolve the file as a .tsx file, then .ts, etc.

Creating a tsconfig.json File

Now we need to create a tsconfig.json file. A tsconfig.json file is the configuration file for the TypeScript compiler. Create a file called tsconfig.json at the root of the project.

[object Object]

Now lets configure our TypeScript compiler with this newly created tsconfig.json file. We will create a JSON object where each key and value changes the configuration of the compiler.

{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "strictNullChecks": true,
        "esModuleInterop": true,
        "jsx": "react-jsx"
    },
    "include": [
        "src"
    ]
}

The key compilerOptions contains the rules that the TypeScript compiler needs to enforce. Setting target to es2017 tells the compiler that we are using the 2017 ECMA standards for JavaScript. Setting module to commonjs tells the compiler that we are using import and export for modules. Setting the strictNullChecks key to true tells the compiler that variables can only have null or undefined values if they are assigned those values.

Setting esModuleInterop to true allows us to import CommonJS modules into an ES6 module codebase. The jsx key controls how JSX compiles into JavaScript. Finally, the include key tells the compiler what files these rules should be applied to. Specifying src tells the compiler to apply these rules to every file inside the src folder.

Changing .js Extension to .tsx

Now lets change all our .js files to .tsx files. A .tsx file is a TypeScript file written using JSX syntax. Lets change our App.js file to App.tsx and our index.js file to index.tsx.

[object Object]

Adding Interfaces to App.tsx

Now lets use some TypeScript syntax. Specifically, lets create an interface for both the state and props that are passed to the App component.

import { Component } from 'react';

interface AppProps {
    message: string
}
interface AppState {
    rendered: boolean;
}

class App extends Component<AppProps, AppState> {
    constructor(props: AppProps) {
        super(props);
        this.state = {
            rendered: false
        }
    }
    render() {
        return <h1>{this.props.message}</h1>
    }
}
export default App;

An interface is a structure that defines a contract in the application. Now, the App component will require a prop with the key message. Lets go to index.tsx and pass a message to the App component.

import ReactDOM from "react-dom";
import App from "./components/App";

ReactDOM.render(<App message="Hello world how are you!" />, document.getElementById('root'));

Running the Application

To run the application in development mode, open a terminal and run npm start.

npm start

The application will be served at localhost:8080. To run the application in production, producing the minimized version of the application in a dist folder, run the command npm run build.

npm run build

Now look for the presence of the dist folder.

[object Object]