Multiple HTML Files Using Webpack
Tags: webpack
Published: 2024-06-10 09:13:36

Frameworks like React and Vue are single-page applications, which means generating a single HTML file with all JS referenced and executed within this page.

In this article, we're going to discuss how to package multi-page application in webpack, that is, generating multiple HTML files during the packaging process, because there might still be scenarios where multi-page applications are used in some older project.

We will use the html-webpack-plugin to achieve this.

## Let's Write Some Code

Besides the entry file index.js, we will create two more files: list.js and detail.js. We will use these three as the files to be bundled.

```js
// index.js
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
class App extends Component {
    render() {
        return <div>Index Page</div>;
    }
}
const root = createRoot(document.getElementById('app'));
root.render(React.createElement(App));

// list.js
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
class App extends Component {
    render() {
        return <div>List Page</div>;
    }
}
const root = createRoot(document.getElementById('app'));
root.render(React.createElement(App));

// details.js
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
class App extends Component {
    render() {
        return <div>Details Page</div>;
    }
}
const root = createRoot(document.getElementById('app'));
root.render(React.createElement(App));

```

Now we have three pages, and I want to generate three HTML pages: index.html, list.html, and details.html. How should we configure this?

## Configuring Multiple Entries

Now that we have three js files, we need to configure three entry points:

```js
...

module.exports = {
  entry: {
    main: "./src/index.js",
    list: "./src/list.js",
    details: "./src/details.js",
  },
  output: {
    clean: true,
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html', // to import index.html file inside index.js
      filename: 'index.html',
    })
  ],
optimization: {
    usedExports: true,
    runtimeChunk: {
      name: 'runtime',
    },
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors',
        }
      }
    },
  ...
}

...
```

After running 'npm run build', we can see that it has bundled details..js and list..js:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8lig5rw1khv5szh3y3m.png)

However, there is only one HTML file, and this HTML page includes all the js files we have bundled.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8h2szrj74rhgc845z43p.png)

This is not the result we want. What we want is to include index.js in index.html, list.js in list.html, and details.js in details.html.

In this case, we need to use html-webpack-plugin to help us generate several more pages, and include the corresponding js files or chunks.

## Configuring html-webpack-plugin

To generate multiple HTML files, we still need to use the handy tool html-webpack-plugin. It has many parameters that you can refer to in the official html-webpack-plugin documentation. Here, we will use the 'filename' and 'chunks' parameters, which correspond to the names of the generated HTML files and the chunks to be included in the page, respectively. We modify the webpack.config.js configuration and add two more instances of html-webpack-plugin:

```js
...

module.exports = {
  entry: {
    main: "./src/index.js",
    list: "./src/list.js",
    details: "./src/details.js",
  },
  ...
  plugins: [
    ...
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html',
      chunks: ['runtime', 'vendors', 'main'],
    }),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'list.html',
      chunks: ['runtime', 'vendors', 'list'],
    }),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'details.html',
      chunks: ['runtime', 'vendors', 'details'],
    }),
    ...
  ],
  optimization: {
    usedExports: true,
    runtimeChunk: {
    	name: 'runtime',
    },
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors',
        }
      }
    },
  },
}

...
```

The 'chunks' configuration in HtmlWebpackPlugin refers to the JavaScript files, which are included into the HTML after being bundled by webpack.

Let's re-package it by running 'npm run build'. Three HTML files are generated in the 'dist' directory, and each HTML file includes the corresponding JavaScript:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbv98rdjvoujlbqcxb0l.png)

### index.html

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9zd9dj2gprgjbmdewjeh.png)

### list.html

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5altubqyrks8i3ad5gh9.png)


### details.html

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pvhx66lgkmupy1jcnoux.png)

Everything has been successfully bundled. When we open each page, they all run normally.

## Configuration Optimization

If we add a new entry point, we need to manually add another html-webpack-plugin and set the corresponding parameters.

How do we automatically add a new html-webpack-plugin based on the entry point, let's just do it.

We can assemble the html-webpack-plugin based on the entry file:

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

const entry = {
  main: "./src/index.js",
  list: "./src/list.js",
  detail: "./src/detail.js",
}

module.exports = {
...
  entry: entry,
  output: {
    clean: true,
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js'
  },
  plugins: [
    ...Object.keys(entry).map(item => // loop entry files and map HtmlWebpackPlugin
      new HtmlWebpackPlugin({
        template: 'src/index.html',
        filename: `${item}.html`,
        chunks: ['runtime', 'vendors', item]
      })
    )
  ],
  optimization: {
    usedExports: true,
    runtimeChunk: {
      name: 'runtime',
    },
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors',
        }
      }
    },
  }
...
};

```

Let's re-package it by running 'npm run build'. The page has been successfully bundled.

At this point, when we add a new entry, all we need to do is configure the entry file. For example, if we want to add a 'userInfo' page, we just need to configure it in the entry file:

```js
entry: {
    index: "./src/index.js",
    list: "./src/list.js",
    details: "./src/details.js",
    userInfo: "./src/userInfo.js"
},
```