Webpack, React, Typescript, React Hot Loader
Tags: webpack, react, typescript, javascript
Published: 2020-07-26 11:11:40

So in this post I show how to configure your next react project to use React Typescript and Webpack.

### Initialise Project
Create project folder, npm init, git init and open project in vs code.
```bash
mkdir myproject && cd myproject && yarn init -y && git init && code .
```

### Create an appropriate .gitignore file
```
node_modules
dist
```

### Install Webpack Dev Dependencies and Configure Webpack
To run webpack bundler we need the webpack tools
```bash
yarn add -D webpack webpack-cli
```

Let's add a script in our package.json
```json
{
  "name": "myproject",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^4.44.0",
    "webpack-cli": "^3.3.12"
  }
}

```

When you run "yarn build", there will be an error message:
```shell
ERROR in Entry module not found: Error: Can't resolve './src' 
```
Let's create an index.js in a "src" folder
#### **`src/index.js`**
```js
console.log("Hello");
```
Let's run "yarn build" and see that a "dist/main.js" was created.  Webpack without config looks for a "src/index.js" file and compiles to a "dist/main.js". To further control the configuration of webpack we need to create a webpack.config.js

#### **`webpack.config.js`**
```js
const path = require("path");

module.exports = {
    entry: "./src/index.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "bundle.js"
    }
}
```

### Create Home page
To run the bundle.js file within the browser, we need an index.html page. Create one in the src folder: "src/index.html"

#### **`src/index.html`**
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
```
Note: We've purposely left out the src reference. We'll use webpack to inject the compiled bundle.js the file. To do this install the HtmlWebpackPlugin. Webpack plugins are utilities used after compilation.
```bash
yarn add -D html-webpack-plugin
```

Update the webpack.config.js file to include the plugin
#### **`webpack.config.js`**
```js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");

module.exports = {
    entry: "./src/index.js",
    output: {
        filename: "bundle.js"
    },
    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html",
        hash: true, // This is useful for cache busting
        filename: '../index.html'
    })]
}
```
After build you'll now notice that a "dist/index.html" was created which includes the link to the bundle.js file

#### **`dist/index.html`**
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
  <script src="bundle.js?d645258de977f9a9c7c4"></script></body>
</html>

```
Let's configure our app for react.

### Install React
```bash
yarn add react react-dom
```
Let's change our "src/index.js":

#### **`src/index.js`**
```js
import React from "react";
import { render } from "react-dom";
import App from "./App";

const root = document.getElementById("root");
render(<App />, root);
```

#### **`src/App.js`**
```js
import React from "react";

const App = () => {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    const interval = setInterval(() => {
      setCount((count) => count + 1);
    }, 500);
    return () => clearInterval(interval);
  }, []);

  return <h2>Count: {count}</h2>;
};

export default App;
```
If you build again you'll get an error:
```shell
ERROR in ./src/index.js 6:16
Module parse failed: Unexpected token (6:16)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 
| const rootEl = document.getElementById('root');
> ReactDOM.render(<App />, rootEl);

```

For webpack to compile react we use [Babel](https://babeljs.io/). So install the babel dependencies
```bash
yarn add -D @babel/core @babel/preset-env @babel/preset-react babel-loader
```
To use babel we need a config file ".babelrc.js"
```js
module.exports = {
    presets: ["@babel/preset-react", "@babel/preset-env"]
}
```
Here we inform babel to use the env preset and the react preset.  

The @babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!

Once again we need to update the webpack.config.js file

#### **`webpack.config.js`**
```js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");

module.exports = {
    entry: "./src/index.js",
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            }
        ]
    },
    output: {
        filename: "bundle.js"
    },
    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html",
        hash: true, // This is useful for cache busting
        filename: '../index.html'
    })]
}
```

After running build we now can run the application from dist.


### Install Typescript
```bash
yarn add -D typescript @types/react @types/react-dom awesome-typescript-loader
```

To use typescript we need a tsconfig.json file. Generate on with 
```bash
tsc --init
```
Edit the tsconfig.json to allow typescript to handle react jsx. Change  "jsx": "preserve" to  "jsx": "react"


#### **`tsconfig.json`**
```json
{
  "compilerOptions": {
    "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
    "strict": true /* Enable all strict type-checking options. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  }
}


```

Update the webpack.config.js file 

#### **`webpack.config.js`**
```js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");

module.exports = {
    mode: "development",
    devtool: "source-map",
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            },
            {
                test: /\.(ts|tsx)?$/,
                loader: "awesome-typescript-loader",
                exclude: /node_modules/
            },
        ]
    },
    resolve: {
        extensions: ['.ts', '.js', '.json', ".tsx"]
    },
    output: {
        filename: "bundle.js"
    },

    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html",
        hash: true, // This is useful for cache busting
        filename: 'index.html'
    })]
}
```

Rename all react files to *.tsx extension and build app again. Update App.tsx with typescript code

```tsx
import React from "react";

const App = () => {
  const [count, setCount] = React.useState<number>(0);

  React.useEffect(() => {
    const interval = setInterval(() => {
      setCount((count) => count + 1);
    }, 500);
    return () => clearInterval(interval);
  }, []);

  function displayCount(message: string): string {
    return message;
  }

  return <h2>{displayCount(`Count: ${count}`)}</h2>;
};

export default App;

```

Lastly setup a dev environment to hot reload our changes as we update our components during coding.

### Install web dev server and react-hot-load
```bash
yarn add react-hot-load
yarn add -D webpack-dev-server
```
Update webpack.config.js for web-dev-server and hot reload

#### **`webpack.config.js`**
```js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");

module.exports = {
    mode: "development",
    devtool: "source-map",
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            },
            {
                test: /\.(ts|tsx)?$/,
                loader: "awesome-typescript-loader",
                exclude: /node_modules/
            },
        ]
    },
    resolve: {
        extensions: ['.ts', '.js', '.json', ".tsx"]
    },
    output: {
        filename: "bundle.js"
    },

    devServer: {
        port: 4000,
        open: true,
        hot: true
    },

    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html",
        hash: true, // This is useful for cache busting
        filename: 'index.html'
    })]
}
```

Update your App.tsx file for hot reload

#### **`src/App.js`**
```tsx
import { hot } from "react-hot-loader";
import React from "react";

const App = () => {
  const [count, setCount] = React.useState<number>(0);

  React.useEffect(() => {
    const interval = setInterval(() => {
      setCount((count) => count + 1);
    }, 500);
    return () => clearInterval(interval);
  }, []);

  function displayCount(message: string): string {
    return message;
  }

  return <h2>{displayCount(`Testing Count: ${count}`)}</h2>;
};

export default hot(module)(App);
```
Lastly to run the web dev server in dev, add the a script

#### **`package.json`**
```json
...
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
  },
...

```

Run "yarn dev", update some code in App.tsx and watch your changes hot reload without losing state.

Cheers for now!