ITRavels

šŸ‘€

React with Typescript from scratch

Whenever I start a new project, I owe to myself to learn something new from it, especially if that project is an easy one.

This time I will describe step by step how to start a react application using Webpack and Typescript. Weā€™re going to learn together.

Letā€™s get started!

First, a bit of history

Javascript was introduced by Netscape Communications in 1995 as a weakly typed scripting language, with similar syntax with Java. Reason behind this decision was the addition of more features to the already existing HTML, something thatā€™ll make it more reactive. Its purpose was intended to complement HTML and satisfy some immediate needs like easiness of use by web designers.

In 1996 Netscape Communications submitted JavaScript to ECMA International to create a standard specification for browsers to adopt and this led to ECMAScript as a language specification one year later.

Fast forward 20 years and big browsers now support version 5 of ECMAScript, ES5. Although ES continues to improve (ES9 in 2019), browsers are not always that fast in adopting new features and usually the aim of developers is to make sure they end up having ES5 code when the project is done.

Why TypeScript ?

TypeScript aims to be a strongly typed JavaScript. Its main aim is to instruct mature IDEā€™s to flag common errors in the code and therefore help with debugging and development process. You can see the introductory video from 2012 here. Compile-time type checking is something that is still very useful in large projects.

What is Webpack ?

Webpack is a ā€˜static module bundler for modern JavaScript applicationsā€™. Very simply put, it does two things:

  1. it builds a dependency map based on an entry point, in this case index.js
  2. based on that, it packs the necessary code in one bundle, in this case bundle.js

Project initialisation

Make sure youā€™ve got npm installed, then go ahead and create a folder and inside it run:

npm init && mkdir src

This will initialise our npm project and also create a folder src that will contain all our source code. Folder src will be our working folder, we will write code here and every file created here will be processed in one way or another to generate the projectā€™s final state.

At this point, we should make sure we configure our module bundler. Webpack uses loaders as helpers to do its job. We will install a few of them and I will explain what each of them does:

npm install --save-dev webpack webpack-cli typescript webpack-dev-server style-loader css-loader sass-loader ts-loader html-loader url-loader file-loader clean-webpack-plugin html-webpack-plugin 

I think weā€™ve got all we need for now. Letā€™s configure our project.

Project configuration

Iā€™m going to split this section in 3 parts: Typescript configuration, Webpack configuration and NPM configuration.

Typescript configuration

Typescript reads its configuration from a file called tsconfig.json. Below is a simple config, enough to start a project, but if youā€™re curious you can see all options here.

{
  "compilerOptions": {
    "outDir": "./dist/",
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "strict": true,
    "allowSyntheticDefaultImports": true
  },
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "./node_modules"
  ]
}

In big lines, the configuration file tells TypeScript the following:

Webpack configuration

Webpack configuration file is webpack.config.js, letā€™s create this file and add the following code:

module.exports =(env) => {
 return require(`./webpack.${env}.js`)
}

What the above code does is read the ā€“env cli parameter from webpack and require the necessary files based on it. For example, if we run webpack --env dev then itā€™ll include webpack.dev.js configuration file. Reason we do this is, based on the environment we run the project on, we will have different bundling configuration files that will instruct webpack to do different things.

Now, you might think that some things from one config file, might also be needed into the other. How do we respect the DRY principle in this case ? Well, for this, we will use a package called webpack-merge. Letā€™s install it first:

npm install --save-dev webpack-merge

Now, we will create a file called webpack.common.js which will include common configuration options that will be applied throughout all other environment configurations, then weā€™ll make sure we merge this file inside every environment configuration file.

Hereā€™s what we have in webpack.common.js:

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

module.exports = {
    entry: "./src/index.tsx",
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,
                use: 'ts-loader',
                exclude: /(node_modules)/
            },
            {
                test:/\.(s*)css$/,
                use:['style-loader','css-loader', 'sass-loader']
            },
            {
                test: /\.html$/,
                use: 'html-loader'
            },
            {
                test: /\.(png|jpg|ttf|)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192
                        }
                    }
                ]
            }
        ]
    },
    resolve: { extensions: [".tsx", ".ts", ".js", ".jsx"] },
    output: {
        path: path.resolve(__dirname, "dist/"),
        publicPath: "/",
        filename: "bundle.js"
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'src/index.html'
        })        
    ]
};

In big lines, it does the following:

In webpack.dev.js we merge the common configuration and add some other configurations needed only for DEV:

const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require("webpack");
const path = require("path");

module.exports = merge(common, {
    mode: "development",
    devServer: {
        contentBase: path.join(__dirname, "dist/"),
        port: 3000,
        publicPath: "http://localhost:3000/",
        hot: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
});

In addition to what common config does, this one only configures the webpack-dev-server to find index.html inside dist folder and hot reload every change it detects in the code files.

The webpack.production.js configuration file looks like this:

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: "production"
});

Ok, I think weā€™re done with Webpack, letā€™s move on to the last bit, NPM configuration.

NPM Configuration

The hard part is over, we only need to create a few scripts in package.json so we can easily start a webpack dev server, build a dev environment and a production one. Under the ā€œscriptsā€ key in your package.json, letā€™s add the following lines:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --mode development --env dev",
    "build:dev": "webpack --env dev",
    "build:prod": "webpack --env production"
}

And there we have it, three commands to do the following:

Did I mention React ?

And we still havenā€™t written any piece of code, have we ? Letā€™s install React:

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

We will work in the src folder, therefore letā€™s create a simple application there:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>React Starter</title>
</head>

<body>
<div id="root"></div>
<noscript>
  You need to enable JavaScript to run this app.
</noscript>
</body>
import React from "react";
import ReactDOM from "react-dom";

import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
import React, { Component} from "react";

class App extends Component{
    render(){
        return(
            <div className="App">
                <h1> Yo! </h1>
            </div>
        );
    }
}

export default App;

Fire it up with npm start, go to http://localhost:3000 and if you donā€™t see your app on your page then youā€™re lying to me :stuck_out_tongue_winking_eye:.

Conclusions

Ok, I know, this has been a long one, but hey, at least now weā€™re one step closer towards understanding all that magic behind projects like create-react-app and the like. Now, we could improve this, of course. For one, we miss uglifier for our js code, we havenā€™t used minifiers for our css code, but I will let you check it out in Webpack Guides. Also, Webpack configuration files are all over the place, we can structure them in a separate folder. Tests have not even been mentioned and that might be the topic for a full new article.

There is though one aspect that we should discuss, itā€™s the elephant called Babel.