Creating React SPA Boilerplate with Webpack
Tags: react, webpack
Published: 2023-01-15 16:34:05

First, initiate the project by running 
```javascript
npm init
```
After it's done, let's install the packages we need to run a basic React Single Page Application.


```javascript
npm i react react-dom react-router-dom
```

For us to be able to use modern Javascript we need babel to compile our codes.

```javascript
npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader
```

create a `.babelrc` file to place our babel configuration, for now, only set it to use the preset-env and preset-react

```javascript
// .babelrc

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

Now let's bring webpack into our project

```javascript
npm i -D webpack webpack-cli webpack-dev-server webpack-merge html-webpack-plugin
```

we will use different webpack configurations for the development and production server, so will split our webpack configuration into several files, so later we need to merge it with `webpack-merge`. 

The `html-webpack-plugin` will generate an HTML5 file for us that includes all your webpack bundles. The `webpack-dev-server` is required to serve our project in development, and we will use the `webpack-cli` to build and serve our project.

Now we have to setup the webpack configurations and the app's entry point. First, we create the common webpack configuration in a file named `webpack.common.js` in there we will set the app's entry point, how files are resolved, and bundles output and we will also configure webpack to handle asset files like images and font.

```javascript
// webpack.common.js

const path = require("path");

module.exports = {
  entry: {
    app: "./src/index.js",
  },
  resolve: {
    extensions: ["*", ".js", ".jsx"],
  },
  output: {
    filename: "[name]-[fullhash].js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  optimization: { runtimeChunk: true },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        test: /\.(svg|ico|png|jpg|jpeg|gif|webp)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name]-[fullhash].[ext]",
            outputPath: "assets/img",
          },
        },
      },
      {
        test: /\.(otf|eot|ttf|woff2|woff)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name]-[fullhash].[ext]",
            outputPath: "assets/font",
          },
        },
      },
    ],
  },
};
```

Next, let's create a configuration for the development, and name the file `webpack.dev.js` we will merge the common configration with this development-only configuration in which we set the dev server, and type of source-map and we will also configure webpack to handle CSS files and HTML templates.

```javascript
// webpack.dev.js

const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(common, {
  mode: "development",
  devtool: "eval-source-map",
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  devServer: {
    static: {
      directory: path.join(__dirname, "dist"),
    },
    liveReload: true,
    open: true,
    port: 8080,
    historyApiFallback: true,
    compress: true,
  },
  optimization: {
    minimize: false,
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "react app",
      template: "./src/index.html",
    }),
  ],
});
```

Finally, we create a configuration for the production. for the production build, we want to minimize the bundles so we add additional plugins to optimize our file, for CSS we use `css-minimizer-webpack-plugin` to minify the CSS using nanocss under the hood and `mini-css-extract-plugin` to extract our CSS into separated bundle file. so let's install both of them.

```javascript
npm i -D css-minimizer-webpack-plugin mini-css-extract-plugin
```

For HTML optimization, we will use the `HtmlWebpackPlugin` to minify our document, while for Javascript we can use terser which is included in webpack 5 and enabled by default.

```javascript
// webpack.prod.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const common = require("./webpack.common.js");
const { merge } = require("webpack-merge");

module.exports = merge(common, {
  mode: "production",
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  optimization: {
    minimize: true,
    minimizer: [
      `...`,
      new CssMinimizerPlugin(),
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        minify: {
          collapseWhitespace: true,
          removeComments: true,
          removeRedundantAttributes: true,
        },
      }),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
});

```

For the React app's entry point, first let's create a folder called `src` inside the folder, create an HTML5 document to attach our React app into 

```html
// src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
        properly without JavaScript enabled. Please enable it to
        continue.</strong
      >
    </noscript>
    <div id="app"></div>
  </body>
</html>
```

Let's also create a React component with a little hello world in it

```javascript
// src/App.jsx

import React from "react";

const App = () => {
  return <h1>Hello World</h1>;
};

export default App;

```

Finally, create a Javascript to render our main React app and setup `react-router` routes

```javascript
// src/index.js

import React from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./App";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
  },
]);

createRoot(document.getElementById("app")).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
```

Let's also add scripts to run and build our project to the `package.json`

```javascript
  "scripts": {
    "dev": "webpack serve --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
```

Now try running `npm run dev` command and it should work and launch a development server