Webpack4 实战
约 1536 字大约 5 分钟
webpack打包工具实战
2026-04-15
Webpack4 核心概念
Webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler)。它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将它们打包成一个或多个 bundle 文件。
四个核心概念
- Entry(入口):指示 webpack 从哪个文件开始构建内部依赖图
- Output(输出):指示 webpack 在哪里输出打包后的 bundle 文件
- Loader(加载器):让 webpack 能够处理非 JavaScript 文件
- Plugin(插件):执行范围更广的任务,如打包优化、压缩等
Entry 入口配置
入口起点(entry point)告诉 webpack 应该使用哪个模块,作为构建其内部依赖图的开始。
单入口配置
最常见的情况,只有一个入口起点:
module.exports = {
entry: './src/index.js',
// 其他配置...
};多入口配置
当应用有多个页面或需要分离 vendor 时:
module.exports = {
entry: {
app: './src/app.js',
vendor: './src/vendor.js',
admin: './src/admin.js'
},
// 其他配置...
};动态入口
使用函数动态返回入口配置:
module.exports = {
entry: () => {
return new Promise((resolve) => {
resolve({
app: './src/app.js',
vendor: './src/vendor.js'
});
});
}
};Output 输出配置
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。
基础配置
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};多入口输出
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
};
// 输出: dist/app.js, dist/search.js重要输出选项
module.exports = {
output: {
// 输出文件名模板
filename: '[name].[contenthash:8].js',
// 输出目录
path: path.resolve(__dirname, 'dist'),
// 公共资源基础路径
publicPath: '/',
// 非入口 chunk 文件名
chunkFilename: '[name].[contenthash].chunk.js',
// 清理输出目录(webpack 5 内置)
clean: true,
// 库打包配置
library: {
name: 'MyLibrary',
type: 'umd'
}
}
};filename 模板变量
[name]:chunk 名称[hash]:模块标识符的 hash[chunkhash]:chunk 内容的 hash[contenthash]:提取内容的 hash[id]:chunk 的唯一标识[query]:chunk 的查询参数
Loaders 配置
Loader 用于对模块的源代码进行转换。它们可以将文件从不同的语言转换为 JavaScript,或将内联图像转换为 data URL。
Loader 特性
- Loader 支持链式传递,从右到左执行
- 每个 Loader 都是一个函数,接收源内容并返回转换后的内容
- 第一个 Loader 接收原始文件内容,最后一个 Loader 返回 JavaScript
常用 Loader 配置
module.exports = {
module: {
rules: [
// JavaScript/TypeScript 编译
{
test: /\.(js|jsx|ts|tsx)$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
},
exclude: /node_modules/
},
// CSS 处理
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: true
}
}
]
},
// Less 处理
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
// 图片资源
{
test: /\.(png|jpg|gif|svg)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192, // 小于 8KB 转为 base64
name: '[name].[hash:8].[ext]',
outputPath: 'images/'
}
}
},
// 字体文件
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: 'fonts/'
}
}
}
]
}
};Loader 执行顺序
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader', // 3. 将 CSS 注入 DOM
'css-loader', // 2. 解析 @import 和 url()
'postcss-loader' // 1. 后处理 CSS
]
}
]
}
};Plugins 配置
插件目的在于解决 Loader 无法实现的其他事情。
常用 Plugin 配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');
module.exports = {
plugins: [
// 自动生成 HTML 文件
new HtmlWebpackPlugin({
title: 'My App',
template: './src/index.html',
filename: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true
},
chunks: ['app']
}),
// 清理构建目录
new CleanWebpackPlugin(),
// 提取 CSS 到单独文件
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
}),
// 提供全局变量
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
React: 'react'
}),
// 定义环境变量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.API_URL': JSON.stringify('https://api.example.com')
}),
// 拷贝静态资源
new CopyWebpackPlugin({
patterns: [
{ from: 'public', to: 'public' }
]
})
]
};文件指纹
文件指纹用于在文件名中添加 hash 值,实现浏览器缓存控制。
三种 Hash 的区别
module.exports = {
output: {
// [hash] - 整个项目的构建 hash
// 任何文件改动都会导致所有文件 hash 改变
filename: '[name].[hash].js',
// [chunkhash] - 根据 chunk 内容生成 hash
// 只有该 chunk 内容改变时 hash 才会改变
filename: '[name].[chunkhash].js',
// [contenthash] - 根据文件内容生成 hash
// 常用于提取的 CSS 文件
filename: '[name].[contenthash].css'
}
};实战建议
- 开发环境:使用
[name].js,不需要 hash - 生产环境:
- JS 文件使用
[name].[chunkhash:8].js - CSS 文件使用
[name].[contenthash:8].css - 图片/字体使用
[name].[hash:8].[ext]
- JS 文件使用
完整实战配置
开发环境配置
// webpack.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
port: 3000,
hot: true,
open: true,
historyApiFallback: true
},
devtool: 'eval-source-map'
};生产环境配置
// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[chunkhash:8].js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /\.(png|jpg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8192
}
},
generator: {
filename: 'images/[name].[hash:8][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true
}
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
runtimeChunk: 'single'
},
devtool: 'source-map'
};常见问题
1. 如何区分开发环境和生产环境?
使用 webpack-merge 合并配置:
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const dev = require('./webpack.dev.js');
const prod = require('./webpack.prod.js');
module.exports = (env, argv) => {
if (argv.mode === 'production') {
return merge(common, prod);
}
return merge(common, dev);
};2. 如何处理多页面应用?
module.exports = {
entry: {
index: './src/index.js',
about: './src/about.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
template: './src/about.html',
filename: 'about.html',
chunks: ['about']
})
]
};最佳实践
- 分离配置:开发、生产配置分开,使用
webpack-merge合并公共配置 - 合理使用 Hash:生产环境使用
chunkhash和contenthash - 优化 Loader:使用
include/exclude限制 Loader 处理范围 - 代码分割:使用
splitChunks提取公共代码 - 缓存策略:合理配置
runtimeChunk避免缓存失效 - TypeScript 支持:使用
ts-loader或babel-loader+@babel/preset-typescript - 环境变量:使用
.env文件管理环境变量
