Webpack 5 依然是大规模生产应用的主流打包工具,特别是需要模块联邦、复杂代码分割或从旧版迁移的场景。本指南涵盖从基础 loader 到将包体积减少 40-60% 的高级优化技术。
Webpack 5 基础与 Entry/Output
webpack.config.js 文件控制所有打包行为。
// webpack.config.js — Complete Production Config
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDev ? 'development' : 'production',
entry: {
main: './src/index.tsx',
// Multiple entry points for different pages
// admin: './src/admin/index.tsx',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isDev ? '[name].js' : '[name].[contenthash:8].js',
chunkFilename: isDev ? '[name].chunk.js' : '[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash:8][ext][query]',
clean: true, // Remove old files on each build
publicPath: '/',
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
},
},
// Persistent filesystem cache — huge speed improvement for incremental builds
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename], // Invalidate cache when config changes
},
},
devServer: {
port: 3000,
hot: true,
historyApiFallback: true,
compress: true,
},
};核心 Loaders
Loader 在文件加入依赖图之前对其进行转换,每个 loader 处理特定的文件类型。
// Essential Webpack Loaders Configuration
module.exports = {
module: {
rules: [
// 1. TypeScript/JavaScript with Babel
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: '>0.25%, not dead' }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
// Cache Babel results for faster rebuilds
cacheDirectory: true,
cacheCompression: false,
},
},
},
// 2. CSS / SCSS / PostCSS
{
test: /\.module\.(css|scss)$/, // CSS Modules
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { modules: true, importLoaders: 2 },
},
'postcss-loader',
'sass-loader',
],
},
{
test: /(?!\.module)\.(css|scss)$/, // Global CSS
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
// 3. Images and fonts (Webpack 5 Asset Modules — no loader needed)
{
test: /\.(png|jpg|jpeg|gif|webp)$/i,
type: 'asset', // Auto-chooses between inline and resource
parser: {
dataUrlCondition: { maxSize: 10 * 1024 }, // Inline if < 10KB
},
},
{
test: /\.svg$/,
use: ['@svgr/webpack'], // Import SVGs as React components
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
};代码分割与懒加载
代码分割是 Webpack 最重要的优化功能,可将代码分割为按需加载的多个 bundle。
// Code Splitting and Lazy Loading
// 1. Dynamic imports (automatic code splitting)
// React.lazy creates a separate chunk for each lazy component
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
// 2. Manual chunk naming with magic comments
const HeavyChart = lazy(
() => import(/* webpackChunkName: "charts" */ './components/HeavyChart')
);
// 3. Webpack SplitChunksPlugin configuration
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Vendor chunk: node_modules
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 20,
},
// Separate React from other vendors
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'react-vendor',
chunks: 'all',
priority: 30,
},
// Shared chunks used by 2+ modules
common: {
name: 'common',
minChunks: 2,
priority: 10,
reuseExistingChunk: true,
},
},
},
runtimeChunk: 'single', // Separate runtime chunk
},
};生产环境优化
生产构建需要积极优化来最小化 bundle 体积。
// Production Optimization Configuration
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // Use all CPU cores
terserOptions: {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true,
pure_funcs: ['console.log'],
},
format: { comments: false },
},
}),
new CssMinimizerPlugin(),
],
// Tree shaking — only include used exports
usedExports: true,
sideEffects: true, // Respect package.json sideEffects field
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
}),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
},
}),
// Only add analyzer when needed: ANALYZE=true npm run build
process.env.ANALYZE && new BundleAnalyzerPlugin(),
].filter(Boolean),
};常见问题
什么时候应该选择 Webpack 而非 Vite?
选择 Webpack 的场景:模块联邦(微前端)、已有复杂 Webpack 配置的大型项目、需要 IE11 支持。
如何分析 Webpack bundle 体积?
使用 webpack-bundle-analyzer,它会显示一个交互式树图,展示各部分的体积占比。
Webpack 5 中的模块联邦是什么?
模块联邦允许多个独立的 Webpack 构建在运行时共享代码,是微前端的主流架构。
为什么我的 Webpack 构建这么慢?
常见原因:未启用持久缓存、loader 链过长、babel-loader 未排除 node_modules、开发环境 source map 级别过高。