Build a React app from scratch using Webpack and TypeScript


I hope this is not the first blog on react which you're reading. If it is, checkout the create-react-app package. It'll help you set up your project and help you kick start your react story pretty easily. When you're learning something new, it always helps to get your hands dirty as fast as possible and get the feel of living in the new environment. It'll help you understand React faster, with all the concepts of component based web dev, virtual DOM, props and states etc etc.


Now that you're familiar with the world of React, let's try and get everything in our own hands! (Just to be clear, I don't mean to rewrite the entire process in vanilla JS. But you may give it a shot after this!) Let's begin our journey then. The following sections will walk you through setting up a React app with TypeScript and Webpack.


Initialise things

Considering that the first few lines in this blog were read and it's known that we need NPM, let's initialise an empty directory as a node project.


> mkdir react-scratch
> cd react-scratch
> npm init


Running these command should set up an empty directory with the name react-scratch, containing a package.json file.



Begin with React

Let's start by React itself. Along with react, we need React-DOM to render react code in normal HTML and since we're using Typescript, we should also install the typings for these 2 packages. Let's install all these 4 packages using the following command.


> npm install --save-dev react react-dom @types/react @types/react-dom


Now that we have the react packages we can begin building our app. Ideally, we place all our react code inside an src directory under root, so create a folder called src under root. Also, to use whatever react code we write, we need React-DOM to render the react code into an HTML file. Now, we need a place where we make use of React-DOM to render React code in HTML, right? Let's create an index.tsx file inside src folder. This is our react entry point.


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

ReactDOM.render(<App />, document.getElementById("root"));


Now, we haven't written our App component yet, but the idea here is that any react component can be rendered by using ReactDOM. Let's look at the second parameter to the render function, document.getElementById("root"). We know that browsers understand HTML as views. So in order to display something on a browser, we need an HTML file. React, after all, is a JS framework and the output of all the rendering and JS execution, has to finally become HTML only. So all that this line does here is that, in some HTML file, it'll render all React code through the imported component, under an element on the page whose id is root!


<html>
  <head>
    <meta charset="UTF-8" />
    <title>React-Scratch</title>
    <script src="./node_modules/react/umd/react.development.js"></script>
    <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
  </head>


  <body style="margin: 0px;">
    <div id="root"></div>
  </body>
  
</html>


We can have our HTML file as simple as this. Specify the title, add few meta tags and include react scripts. But the most important one is the div inside body. Without a div with id root, our ReactDOM won't know where to render our base component. Place this HTML file inside /public/ directory for now in your project. Well, you can place it anywhere you want as long as you are making sure everything comes together to make sense.


Install and configure Webpack

You wrote few components, added a React entry point and even added the base HTML file! But what do you do now? You can't just expect the index.tsx file to start running magically, can you?

You need some application to start processing whatever you wrote. Also, remember you asked ReactDOM to render your component inside root? ReactDOM is just an interface between HTML and react world. It's similar to how parent components render children components. ReactDOM renders the top most React component in an HTML page. Your browsers don't understand .tsx files. They need plain old JS to understand what to do. And there comes the need of webpack.


Webpack is a JS bundler. It takes all your fancy code which you've written in React (or for many other frameworks), and converts them into plain JS which the browsers can understand. Check out their documentation to know what else can it do. Let's go ahead and install webpack and webpack-cli so that we can run webpack from command line too.


> npm install --save-dev webpack webpack-cli


Now that we have webpack, let's configure it to so that it knows how to read our project. Create a new webpack.config.js file in your project's root dir and put the below code.


const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const path = require("path");

module.exports = {
    entry: "./src/index.tsx",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "app.js",
    },
    devtool: "source-map",
    resolve: {
        extensions: [".ts", ".tsx", ".js"],
    },
    plugins: [
        new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
        new HtmlWebpackPlugin({
        template: path.resolve(__dirname, "public/index.html"),
        filename: "index.html",
        }),
    ],
    devServer: {
        port: 3000,
    },
    module: {
        rules: [
        {
            test: /\.ts(x?)$/,
            exclude: /node_modules/,
            use: [
            {
                loader: "ts-loader",
            },
            ],
        },
        {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
        },
        {
            test: /\.(png|jpg|svg|gif)?$/,
            use: "file-loader",
        },
        {
            enforce: "pre",
            test: /\.js$/,
            loader: "source-map-loader",
        },
        ],
    },
    externals: {
        react: "React",
        "react-dom": "ReactDOM",
    },
};


Here's what most of the lines above do.

  1. First 2 lines are plugins for our webpack config where they control the output of the run. clean-webpack-plugin makes sure that there is no stale data in the output directory which our app doesn't use. html-webpack-plugin is simply used to parse the base HTML file and insert the generated JS into it as a script tag.
  2. entry: Specifies the entry point from where webpack should begin bundling
  3. output: Controls the output filename for the bundles JS
  4. resolve: Controls what kind of files to consider
  5. plugins: Read the first point again.
  6. module: Controls how different modules in the project will be treated. Such as, in case of files ending with tsx, ts-loader will process them etc Here I've specified 4 rules, you can have a look at the documentation for more options. Each module needs to be separately installed as a package. Follow the commands below.


> npm install --save-dev webpack-dev-server ts-loader style-loader css-loader file-loader source-map-loader


Don't forget to install the plugins too!


> npm install clean-webpack-plugin html-webpack-plugin


Install and configure Typescript

Okay, time to install typescript.


> npm install --save-dev typescript


Because we're using Typescript in our project, we should specify how do we want to control the ts-loader to behave. A tsconfig.json file generally describes a set of options which the typescript processor will use while transpiling the files. Create a tsconfig.json file in the root directory of your project and use the following configurations.


{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "lib": ["es5", "es2015", "es2016", "dom"],
    "module": "esnext",
    "moduleResolution": "node",
    "target": "es6",
    "jsx": "react"
  }
}


This blog is already too long and in the interest of time, as well as to make sure you all read the docs, I'll not cover what all do these options mean. Have a look at the Typescript documentation and you'll find everything you need there.


Running the project

Finally, the last step. Finally!


Everything seems set now. Webpack will read the entry point, parse all the dependencies and bundle them into a vanilla JS output, using the tsconfig and the ts-loader. For other types of file it'll use the other module loaders. ReactDOM in entry point will make sure that the base component is mounted in the container div once the script is executed.


So how do we run them then? We have webpack and webpack-dev-server to start the webpack process. Let's modify our package.json and add 2 scripts to handle them.


{
    ... 
    "scripts": {
    "build": "webpack",
    "dev": "webpack serve"
    }
}


Run the scripts using npm and enjoy your efforts!

About the author
Ankit is a Software Engineer at Google and has over 3 years of experience building performant frontend experiences for top industries.
"I have been a Frontend engineer almost for the entirety of my career and have worked with technologies around the React ecosystem. Besides professional work, I love working fullstack on my side projects playing around with technologies around the MERN stack."