# React with Webpack From Scratch

Maybe you are just curious, maybe you want more control, or maybe create-react-app just feels dirty. Whatever the reason, let's build a simple React App from scratch using React ^18 and Webpack ^5!

First some disclaimers:

**First**. Because this is Javascript there are like a billion ways to do the same thing. What follows is a process I've adopted through preference, tinkering, and whatever got returned on a web search. My hope is this tutorial will give you enough insight to explore your preferences.

**Second**. I'm only going to explain how to set this up for a development environment. If your project is production bound, that's a separate step I will not go into.

**Third**. This is long. I go into detail and that takes space and time. This is not a quick and easy copy and past post. If you want to get something out of this, follow along and do each step.

Ok. Let's start.

## 1. Setting up the Project

System prerequisites:

- `node -v` = ^18.6
- `npm -v` = ^8.13

I use VS Code (1.69.2 as of this writing)

Setting up the project:

You need a root directory. Obviously. I'm going to call this one **ReactFromScratch**.
Inside that directory initialize all the typical things:

`git init`
`npm init`

For the sake of this article, we don't care what these commands create. All we need is the package.json file for dependencies.

Next, you'll want a project directory. This is typically called `src` or `app` but you can call it anything you want. I'm going to stick with `src`. This is where all of the project files go. Inside that directory make 2 files:

1. `index.html`
2. `index.js`

You can name these files anything you want to. What's listed are conventional names and it might be handy to keep it that way until you understand their role. We'll populate them with stuff later.

To recap:
This is what our project file looks like so far.

```bash
.
├── src
│   ├── index.html
│   └── index.js
├── package.json
```

## 1. The Build Directive

### A Little Background

Before I go into the details of setting up a Webpack build, it's worth understanding how a webpage, specifically a React app, ends up in your browser. 

The simplest webpage you can create is just an HTML file. You can load that directly into the browser. A React App is no different. That `index.html` file we created before ***becomes*** the portal for the app. I emphasize *becomes* because simply trying to load that specific file will not go well. You need to build first.

Behind the scenes a React app generates a lot of HTML, but that all happens in JavaScript. Hence the `index.js` file. Again, you can create a simple HTML file, load some Javascript into it and have it running fine without any fancy build tools. In the old days, this is exactly how web pages with Javascript were created. But those Javascript files quickly became enormous monolithic tombs, prone to bugs and nearly impossible to maintain. I recommend reading [Why Webpack](https://webpack.js.org/concepts/why-webpack/) for a crash course on how builders like Webpack came to be. 

Anyway, a React app is big and complex and not even native JavaScript. You simply cannot load this directly into the browser. It won't know what to do. So we use tools like Webpack to bridge our development of an app to something a browser can understand and load. It also allows us to do cool things like [Lazy Loading](https://webpack.js.org/guides/lazy-loading/), [Optimizations](https://webpack.js.org/configuration/optimization/), and [Transpiling](https://webpack.js.org/loaders/). I'll cover some of this, as well as using [Hot Loading](https://webpack.js.org/concepts/hot-module-replacement/), only possible when using a build tool like Webpack!

Anyway, it's important to understand that the end goal of our build directive is to create an HTML file that can load javascript. The two files, `index.html and index.js`, we created above will be used by Webpack as templates to build the actual files we can run. They are not functional in any other way.

### Entry and Output

Back in the root directory next to the package.json file, create a new file and call it `webpack.config.js`. You *could* name this anything you want, but I don't recommend it, and I'm not going to cover how to make that work. This is the file name Webpack will look for when you run Webpack commands.

Open that file and put the following into it.

```javascript
const path = require('path')

module.exports = () => {
  return {
    mode: 'development'
  }
}
```
> Webpack expects a static JSON object as its configuration. You could simply export a static object but Webpack also supports a function call as an intermediary to return a static JSON object. This becomes useful later on.

We'll build the config one step at a time and I'll explain what each attribute does and why we need it.

You'll notice I'm requiring 'path' right away. This becomes handy in a Webpack config to resolve absolute paths. It can be tricky to know what paths should be relative vs absolute. I'll make note of that when we get to it.

I've also set the `mode` to development. The mode affects optimizations and handles some other things related to running a dev environment. Dive deeper [webpack mode](https://webpack.js.org/configuration/mode/).

#### Entry
```javascript
entry: './src/index.js'
```

This is where Webpack will go to start compiling all the project files. It's the root node of a large dependency tree. I recommend trying out [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) if you're interested in getting a feel for what this looks like. Our entry is going to be that `index.js` file we created earlier. It doesn't matter what that file is named or where it is located, so long as Webpack knows how to get to it.

> The `entry` config is a *relative* path. We're using it in its simplest form. See [entry docs](https://webpack.js.org/configuration/entry-context/#entry) for details on its many nuances and variations.

#### Output 
```javascript
output: {
      filename: 'main.js',
      path: path.resolve(__dirname, 'dist'),
}
```
This is the reverse of `entry`. This is where Webpack puts all the compiled code. As I mentioned earlier, this is going to be that giant `.js` file that can be loaded by the browser. There are two parts to this config, and they do what the names imply.

- `filename` This will be the name of the file.
- `path` This will be where the file is put.

> The `path` config is an *absolute* path, and we need to resolve it with __dirname. We're more or less doing the minimum amount of work to get webpack working. Output is a very large config option and has many many options. See [output docs](https://webpack.js.org/configuration/output/).

### A Side Trip to "Hello World"

Let's take a small side trip and run our first build. 

First, open the `index.js` file and add this:
```javascript
const node = document.createElement("div");
const textnode = document.createTextNode("Hello World");
node.appendChild(textnode);

document.getElementById("root").appendChild(node);
```

> All we're doing here printing "Hello World" using vanilla javascript. No fancy compiling is needed. Later we'll put our call to React in this file to build with webpack we need to install 2 dependencies:
`npm install --save-dev webpack@^5  webpack-cli@^4`

> `webpack` is the main deal that does all the build stuff. `webpack-cli` is how we tell webpack to do stuff via the command line.

In the `package.json` file add this line in the `scripts` object:

```json
"scripts": {
      "build": "webpack"
}
```

> You have 3 options for running webpack. Add it as a script like the above (recommended) and run it with 'npm run build', run it directly using `npx webpack`, or install webpack globally (not really recommended) and call it via `webpack`.

Now from the console run `npm run build`. You'll notice a new folder was created in your root called `dist`. Inside of that folder is your compiled javascript file called 'main.js'. Open that file and you'll notice it looks quite a bit different from our original code.

> When using development mode webpack defaults your devtool option to `eval`. You see this in action when you look at the compiled code which is now wrapped in an eval() call. More on this later.

So we built the javascript file. Cool. How about that HTML file?

We could simply create an HTML file in the dist folder and have it load the javascript, but that's not the webpack way. Instead, we generate an HTML file during the build process. We use the `HtmlWebpackPlugin` to do this task for us. 

First, install it.

`npm install --save-dev html-webpack-plugin@^5`

Then add it to our `webpack.config.js' file.

```javascript
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = () => {
    return {
        mode: "development",
        entry: './src/index.js',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, 'dist'),
       },
       plugins: [
            new HtmlWebpackPlugin()
       ]
    }
}
```

Now build again (`npm run build`) and open the generated HTML file in the `dist` folder. You'll notice it knows about our `main.js` file and loads it. Webpack is so smart!

But this isn't enough. If you loaded this HTML (try it if you like), nothing will happen because we never established a "root" node for our Hello World code to attach to. To do this we need to create a template HTML that includes that node and tell HtmlWebpackPlugin where our template is.

Copy that generated HTML into the 'index.html' file and add in the <body> tags:

```html
<body>
  <div id="root"></div>
</body>
```

You might as well change the `<title>` while you're at it.
Now back in the `webpack.config.js` file make our HtmlWebpackPlugin look like this so it's aware of our template:

```javascript
new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'src', 'index.html'),
})
```

Here's what our project structure looks like with the `dist` directory now.

```bash
.
├── dist
│   ├── index.html
│   └── main.js
├── src
│   ├── index.html
│   └── index.js
├── package.json
└── webpack.config.js
```

Now build again (`npm run build`) and open the `index.html` file in the `dist` folder in your browser. Hopefully, you see a little message there :)

It might feel a little overkill to go into such detail on this part of the build process, but it's also important, and often the source of confusion. Nothing magical is happening here. Now we have a simple but working build process. All we do from here is add functionality to the build to do bigger better and more productive things.

## 2. Adding React

First step is to install React and React DOM.

`npm install react@^18 react-dom@^18`

Next is to create the app. I'm going to keep this really simple. Create a new directory in `src` called app and create a new file called `App.jsx` in there. This will be the main entry for the React app. Inside that file add something simple, like:

```jsx
import React from "react"

export default function App() {
    return <div>Hello World!</div>
}
```
Now back in the index.js file in our `src` directory replace the existing code with this boiler plate React render code:

```jsx
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './app/App.jsx'

const container = document.getElementById('root')
const root = createRoot(container)
root.render(<App />)
```

Let's see what happens when we build now.
`npm run build`

I get this:

```bash
Module parse failed: Unexpected token (6:12)
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 container = document.getElementById('root')
| const root = createRoot(container)
> root.render(<App />)
```

Remember when I said React isn't native javascript? I wasn't kidding. So yes, we need an appropriate loader. Something can read React and turn it into native javascript. Thankfully we have one and webpack gives us the ability to add this to the build pipeline. There are few steps here so let's go through each one.

First we need something that can transpile those `.jsx` files. There are a few options, but babel is really all you need. Babel has a transpiler specifically for react. Install it:

`npm install --save-dev @babel/preset-react@^7 `

> Babels @presets are actually a bundle of plugins. If you want to see what's included in `preset-react` check out [@babel/preset-react](https://babeljs.io/docs/en/babel-preset-react). Also check out [babel's git repo](https://github.com/babel/babel). Community development at its best.

The way bable works is by looking in a file called `.babelrc` in the root directory of the project to see how it should handle your raw javascript, including .jsx files. This is where we'll add `@preset-react`, which is the transpiler for React (jsx) files. 

Create a file in the root called `.babelrc` and add the following in it:

```json
{
  "presets": ["@babel/preset-react"]
}
```

And here's what our final directory structure looks like for this tutorial.

```bash
.
├── dist
│   ├── index.html
│   └── main.js
├── src
│   ├── app
│   │   └── App.jsx
│   ├── index.html
│   └── index.js
├── .babelrc
├── package.json
└── webpack.config.js
```

If you built the project again you'd still get the same error from before. Now we need to tell webpack that we're using bable to handle our jsx files. Navigate to the `webpack.config.js` file and modify it to look like this:

```js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = () => {
    return {
        mode: "development",
        entry: './src/index.js',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, 'dist'),
        },
        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                    },
                }
            ]
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, 'src', 'index.html'),
            })
       ]
    }
}
```

What we added was the `module` key with some `rules`. These rules tell webpack what to do when it encounters a certain file type. We tell it what filetype by giving it a `test`, in this case we're testing for `js` and `jsx` files. We're also telling it to exclude `/node_modules/` because we don't want to transpile our dependencies. They should already be transpiled. 

Finally we tell it what to use to load these file types, and in this case we're using `babel-loader`. Babel loader can be thought of a bridge between webpack and that `.babelrc` file. Webpack does the work of gathering up the files and passing it off to the loader that does the work of transpiling it.

> I should mention that the need for a .bablerc file is not required. You can add babel configurations directly in the webpack.config file. I'm not covering that here. See [babel-loader](https://www.npmjs.com/package/babel-loader) for examples.

We still need to install `babel-loader` though.

`npm install --save-dev babel-loader@^8`

With that done we should now be able to build. `npm run build`. With any luck it builds correctly and if you open the `index.html` file in the `dist` directory you should see "Hello World!"

Congrats! You turned React into vanilla javascript!

## 3. Server Setup and Hot Loading

This section used to be a lot longer, but webpack now has a super easy built in HMR (hot module replacement) functionality, and [react-refresh](https://github.com/pmmmwh/react-refresh-webpack-plugin/) has replaced the somewhat cumbersome [React Hot Loader](https://www.npmjs.com/package/react-hot-loader). It's now very easy to get this working.

> What is hot module replacement? This gives a developer the ability to see realtime changes to the code in the browser without the need to reload the website. Right now every time you make a change we'll need to rebuild the code and refresh the webpage.

Webpack ^5 gives us the ability to integrate HMR with just a few lines added to the `webpack.config.js` file. Make that file look like this:

```js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = () => {
    return {
        mode: "development",
        entry: './src/index.js',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, 'dist'),
        },
        devServer: {
            port: 3000,
            hot: true,
            open: true,
        },
        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                    },
                }
            ]
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, 'src', 'index.html'),
            })
       ]
    }
}
```

What's been added is the `devServer` key. In its configuration we've added a few parameters. *Port*: which what port to run the server on. *Hot*: enable HMR. *Open*: open the webpage in your default browser on server start. That's it. We also need to install `webpack-dev-server`. Do that now.

`npm install --save-dev webpack-dev-server@^4`

> Webpack-dev-server makes your webpage dynamic during development. It listens for changes to your code, rebuilds the code (with HMR), and serves the new code to the browser. This automates the process of building your project and manually opening it.

With `webpack-dev-server` installed we can add a new script to the `package.json` file. Under the build script we've been using add this entry: `"start": "webpack serve",`. This tells webpack to build our project then start a server to handle it. Try it now!

`npm run start`

Your project should have automatically been opened in the browser. You'll also notice the output in the terminal is new. A bunch of `[webpack-dev-server]` stuff as well as the fact that the process is still running. You can kill the server with `control c`.

Now make a change to the `App.jsx` file, like "Hello Universe!" or something. When you hit save you'll see a bunch of action in the terminal. This is the server handling your change. Navigate back to your open webpage and notice the change is already there. Your productivity just increased by a lot!

This gets us pretty far, but when developing in React we have one extra step. The problem comes when you start adding state to your application. Webpack's HMR will reset state which get really annoying when the application grows. To handle this we'll add `react-refresh-webpack-plugin`. Let's just install that now:

`npm install --save-dev @pmmmwh/react-refresh-webpack-plugin@^0 react-refresh@^0`

> You'll notice that we also installed `react-refresh`. The React team has created this api which allows the wiring up of `Fast Refresh`, a built in feature in React 18, for bundler plugins. I found [this article](https://www.gatsbyjs.com/docs/reference/local-development/fast-refresh/) helpful in understanding this feature. React-refresh-webpack-plugin uses this api to do it's hot loading magic.

Now that we have `react-refresh-webpack-plugin` installed we enable it by adding a babel plugin and a webpack plugin. First the babel plugin.

Make the `.babelrc` look like this:

```json
{
    "presets": ["@babel/preset-react"],
    "plugins": ["react-refresh/babel"]
}
```

Now the webpack plugin. Make the `webpack.config.js` file look like this:

```js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

module.exports = () => {
    return {
        mode: "development",
        entry: './src/index.js',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, 'dist'),
        },
        devServer: {
            port: 3000,
            hot: true,
            open: true,
        },
        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                    },
                }
            ]
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, 'src', 'index.html'),
            }),
            new ReactRefreshWebpackPlugin()
            
       ]
    }
}
```

We require `ReactRefreshWebpackPlugin` then add a new instance of it to the plugins key. That's it! Start your server again.

`npm run start`

It may seem like nothing has changed, but like before modifications to your app propagate automatically to the browser. The difference is state. Let's test this. Add some simple state to the `App.jsx` file. I did this:

```jsx
import React from "react"

export default function App() {
    const[state, setState] = React.useState("World")

    function hello() {
        if (state == "World") {
            setState("Universe")
        } else {
            setState("World")
        }
    }

    return <div onClick={hello}>Hello {state}</div>
}
```

When we click on "Hello World" state will change to "Universe" and visa-versa. Now restart the server. `npm run start`. Change the state so it says "Universe" then modify `App.jsx` to say "Goodbye". You'll notice that our state has not changed! It still says "Goodbye Universe".

Now stop the server and comment out the plugins in both the `.bablerc` file and the `webpack.config.js` file. Restart the server and do the same thing above. Everytime you modify your `App.jsx' file the state reverts back to "World". So now you understand why `React Refresh` is so cool!

## 4. Recap

This tutorial got us as far as setting up React with Webpack without using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app). Hopefully you learned not just how to do this, but how each related part of Webpack works to get us there.

If you want a reference here's the working git repo for this tutorial: [React From Scratch](https://github.com/MrAaronOlsen/React-From-Scratch)

