Webpack Multi-Page Project Build Practice
The article walks through configuring Webpack to build a multi‑page Vue application—showing why the default CLI SPA setup falls short, how to generate dynamic entry points, set output paths, apply loaders for JS, CSS, images, and use plugins such as MiniCssExtractPlugin, CopyWebpackPlugin and HtmlWebpackPlugin to produce separate bundles and HTML files for each page.
This article shares practical experience of using Webpack to bundle a multi‑page Vue project. It starts with a brief introduction, explains why the default Vue CLI single‑page setup does not meet the needs of a project that contains several independent pages (login, game, payment, etc.), and then describes how to adjust the Webpack configuration to support multiple entry points and corresponding HTML templates.
Directory structure
project
├───bin
│ └───vb.js
├───build
│ ├───dev.js
│ ├───release.js
│ ├───webpack.config.base.js
│ ├───webpack.config.build.js
│ └───webpack.config.dev.js
├───src
│ ├───components
│ │ ├───count.vue
│ │ ├───dialog.vue
│ │ └───errortips.vue
│ ├───game
│ │ ├───game.htm
│ │ ├───game.js
│ │ └───game.vue
│ ├───login
│ │ ├───login.htm
│ │ ├───login.js
│ │ └───login.vue
│ ├───pay
│ │ ├───pay_result.htm
│ │ ├───pay_result.js
│ │ ├───pay_result.vue
│ │ ├───pay.htm
│ │ ├───pay.js
│ │ └───pay.vue
│ └───…The core concepts that need to be configured are the four Webpack fundamentals: entry , output , loader , and plugins .
1. Entry configuration
const config = {
entry: {
game: './src/game/game.js',
login: './src/login/login.js',
pay: './src/pay/pay.js',
pay_result: './src/pay/pay_result.js'
}
};Because the number of pages may change, a helper getEntry() function is introduced to scan the src folder and automatically generate the entry object only for pages that have a matching .htm template.
const fs = require('fs');
const glob = require('glob');
function getEntry() {
const entry = {};
glob.sync('./src/*/*.js') // find all js files under src/*
.forEach(function (filePath) {
var name = filePath.match(/\/src\/(.+)\/.*\.js/)[1];
if (!fs.existsSync('./src/' + name + '.htm')) { return; }
entry[name] = filePath;
});
return entry;
}
module.exports = {
entry: getEntry()
};2. Output configuration
const config = {
output: {
path: path.join(__projectDir, __setting.distJs),
publicPath: __setting.domainJs, // public URL for static assets
filename: '[name][hash].js'
}
};The publicPath determines how the bundles are referenced in the browser. It can be a CDN URL, e.g. publicPath: "https://cdn.example.com/assets/" , which results in script tags like <script src="https://cdn.example.com/assets/bundle.js"></script> . The special variable __webpack_public_path__ can be set at the top of an entry file to override the global publicPath at runtime.
3. Loader configuration
Loaders enable Webpack to process non‑JavaScript files.
// JavaScript (babel)
module: {
rules: [{
test: /\.js$/,
include: [path.resolve(__projectDir, 'src')],
exclude: /node_modules/,
loader: "babel-loader"
}]
}For CSS and pre‑processors:
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
{ // SCSS / SASS
test: /\.(sc|sa)ss$/,
use: [
{ loader: 'vue-style-loader' },
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
{ loader: 'sass-loader', options: { sourceMap: true } },
{ loader: 'sass-resources-loader', options: { sourceMap: true, resources: [path.resolve('./src/public/css/common.scss')] } }
]
}Image handling can be done with file-loader or url-loader (the latter inlines small files as Base64):
{
test: /\.(gif|png|jpe?g)$/,
loader: 'file-loader'
}
{ // inline if < 10KB
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: [{
loader: 'url-loader',
options: { limit: 10240, name: 'image/[name][hash].[ext]' }
}]
}For legacy libraries that expose a global variable (e.g., Zepto), script-loader together with exports-loader can be used:
{
test: require.resolve('zepto'),
use: ['exports-loader?window.Zepto', 'script-loader']
}4. Plugins
Commonly used plugins are demonstrated:
// Extract CSS into separate files
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}]
},
plugins: [
new MiniCssExtractPlugin({ filename: 'css/[hash].css' })
]
}; // Copy static assets
const CopyWebpackPlugin = require('copy-webpack-plugin');
plugins.push(
new CopyWebpackPlugin([
{ from: { glob: './src/public/*.htm', dot: true },
to: path.join(__setting.distTpl, 'public', '[name].htm') }
], { copyUnmodified: true })
); // Generate HTML for each entry
const HtmlWebpackPlugin = require('html-webpack-plugin');
const htmlPluginArray = [];
function getEntry() { /* same as before */ }
// after building entry object, push a plugin per page
htmlPluginArray.push(new HtmlWebpackPlugin({
filename: `${__setting.distTpl}/${name}.htm`,
template: `./src/${name}.htm`,
inject: 'body',
minify: { removeComments: true, collapseWhitespace: true },
chunks: [name],
inlineSource: '.(js|css)'
}));
module.exports = {
plugins: [new MiniCssExtractPlugin({ filename: 'css/[hash].css' })]
.concat(htmlPluginArray)
};Additional plugins such as html-webpack-inline-source-plugin and a custom scriptInlineHtml plugin are shown to inline assets directly into the generated HTML.
5. Other configurations
Resolve aliases simplify imports, e.g. alias: { '@': path.resolve(__projectDir, 'src') } , allowing let img = require('@/public/image/common/ico-back.png').default;
The development server is configured with hot module replacement (HMR) and a custom publicPath :
devServer: {
contentBase: __projectDir,
publicPath: '/',
port: 8080,
host: '127.0.0.1',
open: true,
hot: true
}Finally, the article concludes that many more topics (environment separation, caching, additional plugins, etc.) can be explored, but the presented configuration already covers the essential steps for building a multi‑page Vue application with Webpack.
37 Interactive Technology Team
37 Interactive Technology Center
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.