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.
// 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:
...
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:
However, there is only one HTML file, and this HTML page includes all the js files we have bundled.
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:
...
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:
index.html
list.html
details.html
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:
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:
entry: {
index: "./src/index.js",
list: "./src/list.js",
details: "./src/details.js",
userInfo: "./src/userInfo.js"
},
Top comments (1)
instead of
html-webpack-plugin
you can use modern html-bundler-webpack-plugin to simplify multiple page configuration.Using the HTML Bundler Plugin:
This plugin replaces the functionality of the plugins and loaders:
Then you webpack config will be yet more simple and clear:
Each page template should contains its owns SCSS and JS files, e.g.:
src/index.html
src/details.html
etc.
So you can exactly define source files of styles and scripts at the exactly position in HTML.
The generated HTML contains URLs of the output filenames:
dist/details.html
etc.