Webpack 最佳实践
本章总结 Webpack 的最佳实践,帮助你构建更高效、更易维护的项目配置。这些实践来自实际项目经验,能够提升开发体验和项目质量。
📋 学习目标
- ✅ 掌握配置文件组织的最佳方式
- ✅ 学会管理环境变量
- ✅ 了解性能监控方法
- ✅ 掌握代码规范和版本管理策略
- ✅ 学习项目结构优化
1. 配置文件组织
分离开发和生产配置
将配置拆分为公共配置、开发配置和生产配置,使用 webpack-merge 合并。
项目结构
project/
├── webpack/
│ ├── webpack.common.js # 公共配置
│ ├── webpack.dev.js # 开发环境配置
│ ├── webpack.prod.js # 生产环境配置
│ └── webpack.config.js # 主配置文件
└── ...
公共配置(webpack.common.js)
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
publicPath: '/',
clean: true
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: {
'@': path.resolve(__dirname, '../src'),
'@components': path.resolve(__dirname, '../src/components'),
'@utils': path.resolve(__dirname, '../src/utils')
}
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
})
]
}开发环境配置(webpack.dev.js)
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = merge(common, {
mode: 'development',
output: {
filename: 'js/[name].js',
chunkFilename: 'js/[name].chunk.js'
},
devtool: 'eval-source-map',
module: {
rules: [
{
test: /\.(css|scss|sass)$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new ReactRefreshWebpackPlugin()
],
devServer: {
static: {
directory: path.join(__dirname, '../public')
},
port: 3000,
hot: true,
open: true,
historyApiFallback: true,
compress: true
},
optimization: {
runtimeChunk: 'single'
}
})生产环境配置(webpack.prod.js)
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{
test: /\.(css|scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css'
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
}
})主配置文件(webpack.config.js)
const { merge } = require('webpack-merge')
const common = require('./webpack/webpack.common.js')
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production'
if (isProduction) {
return merge(common, require('./webpack/webpack.prod.js'))
}
return merge(common, require('./webpack/webpack.dev.js'))
}2. 环境变量管理
使用 dotenv
安装依赖:
npm install --save-dev dotenv-webpack创建环境变量文件
.env.development
NODE_ENV=development
API_URL=http://localhost:8080
PUBLIC_PATH=/.env.production
NODE_ENV=production
API_URL=https://api.production.com
PUBLIC_PATH=/配置 Webpack
const Dotenv = require('dotenv-webpack')
module.exports = {
plugins: [
new Dotenv({
path: `.env.${process.env.NODE_ENV || 'development'}`,
safe: true, // 如果 .env.example 存在,使用它作为模板
systemvars: true // 加载系统变量
}),
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
]
}在代码中使用
// src/config/api.js
const API_URL = process.env.API_URL || 'http://localhost:8080'
export default {
baseURL: API_URL,
timeout: 10000
}使用 DefinePlugin
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'__DEV__': JSON.stringify(process.env.NODE_ENV === 'development'),
'__PROD__': JSON.stringify(process.env.NODE_ENV === 'production')
})
]
}3. 性能监控
使用 Speed Measure Plugin
安装依赖:
npm install --save-dev speed-measure-webpack-plugin配置
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
module.exports = smp.wrap({
// webpack 配置
entry: './src/index.js',
// ... 其他配置
})输出示例
SMP ⏱ Loaders
modules with no loaders took 0.23 secs
module count = 1
modules with loaders took 1.23 secs
module count = 124
babel-loader took 0.89 secs
module count = 124
css-loader took 0.15 secs
module count = 45
使用 Bundle Analyzer
安装依赖:
npm install --save-dev webpack-bundle-analyzer配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
...(process.env.ANALYZE ? [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: true,
reportFilename: 'bundle-report.html'
})
] : [])
]
}使用
{
"scripts": {
"build:analyze": "ANALYZE=true npm run build"
}
}使用 Webpack Stats
module.exports = {
stats: {
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
entrypoints: false,
warnings: false,
errors: true,
errorDetails: true
}
}4. 代码规范
ESLint 集成
安装依赖:
npm install --save-dev eslint eslint-loader
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin配置
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
enforce: 'pre',
use: [
{
loader: 'eslint-loader',
options: {
fix: true,
cache: true
}
}
]
}
]
}
}Prettier 集成
安装依赖:
npm install --save-dev prettier配置
.prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}Pre-commit 钩子
安装依赖:
npm install --save-dev husky lint-staged配置
package.json
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,md,json}": [
"prettier --write"
]
}
}5. 版本管理和缓存策略
使用 Content Hash
module.exports = {
output: {
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js'
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css'
})
]
}分离运行时代码
module.exports = {
optimization: {
runtimeChunk: 'single' // 分离运行时代码,避免 vendor 变化导致所有 chunk 重新构建
}
}合理的代码分割
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
// 框架代码(React、Vue 等)
framework: {
test: /[\\/]node_modules[\\/](react|react-dom|vue|vue-router)[\\/]/,
name: 'framework',
priority: 40,
reuseExistingChunk: true
},
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true
},
// 公共代码
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
}6. 项目结构优化
推荐的目录结构
project/
├── public/ # 静态资源
│ ├── index.html
│ └── favicon.ico
├── src/ # 源代码
│ ├── components/ # 组件
│ ├── pages/ # 页面
│ ├── utils/ # 工具函数
│ ├── styles/ # 样式
│ ├── assets/ # 资源文件
│ ├── config/ # 配置文件
│ └── index.js # 入口文件
├── webpack/ # Webpack 配置
│ ├── webpack.common.js
│ ├── webpack.dev.js
│ └── webpack.prod.js
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .eslintrc.js # ESLint 配置
├── .prettierrc # Prettier 配置
├── tsconfig.json # TypeScript 配置
└── package.json
路径别名配置
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
'@components': path.resolve(__dirname, '../src/components'),
'@pages': path.resolve(__dirname, '../src/pages'),
'@utils': path.resolve(__dirname, '../src/utils'),
'@styles': path.resolve(__dirname, '../src/styles'),
'@assets': path.resolve(__dirname, '../src/assets'),
'@config': path.resolve(__dirname, '../src/config')
}
}
}在 TypeScript 中使用
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@pages/*": ["src/pages/*"],
"@utils/*": ["src/utils/*"]
}
}
}7. 构建优化实践
使用缓存
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
},
cacheDirectory: path.resolve(__dirname, '.webpack-cache')
}
}减少解析范围
module.exports = {
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json']
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
use: 'babel-loader'
}
]
}
}使用多进程构建
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true, // 启用多进程
terserOptions: {
compress: {
drop_console: true
}
}
})
]
}
}8. 错误处理
友好的错误提示
module.exports = {
stats: {
errorDetails: true,
errorStack: true,
warnings: true
},
devServer: {
client: {
overlay: {
errors: true,
warnings: false
}
}
}
}自定义错误处理
class FriendlyErrorsPlugin {
apply(compiler) {
compiler.hooks.done.tap('FriendlyErrorsPlugin', (stats) => {
if (stats.hasErrors()) {
const errors = stats.compilation.errors
errors.forEach(error => {
console.error('\n❌', error.message)
})
}
})
}
}9. 安全实践
避免暴露敏感信息
// ❌ 错误:暴露敏感信息
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.API_KEY': JSON.stringify('secret-key-123')
})
]
}
// ✅ 正确:只在服务端使用
// 敏感信息应该通过环境变量在服务端处理内容安全策略
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
meta: {
'Content-Security-Policy': {
'http-equiv': 'Content-Security-Policy',
content: "default-src 'self'; script-src 'self' 'unsafe-inline'"
}
}
})
]
}10. 文档和注释
配置文件注释
/**
* Webpack 公共配置
* 包含开发和生产环境的共同配置
*/
module.exports = {
// 入口文件
entry: './src/index.js',
// 输出配置
output: {
// 输出目录
path: path.resolve(__dirname, '../dist'),
// 输出文件名(使用 contenthash 实现长期缓存)
filename: 'js/[name].[contenthash:8].js'
}
}README 文档
创建 webpack/README.md:
# Webpack 配置说明
## 配置文件结构
- `webpack.common.js` - 公共配置
- `webpack.dev.js` - 开发环境配置
- `webpack.prod.js` - 生产环境配置
- `webpack.config.js` - 主配置文件
## 环境变量
- `.env.development` - 开发环境变量
- `.env.production` - 生产环境变量
## 构建命令
- `npm run dev` - 开发模式
- `npm run build` - 生产构建
- `npm run build:analyze` - 分析打包结果完整配置示例
企业级配置模板
// webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const Dotenv = require('dotenv-webpack')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
publicPath: process.env.PUBLIC_PATH || '/',
clean: true
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: {
'@': path.resolve(__dirname, '../src')
}
},
plugins: [
new CleanWebpackPlugin(),
new Dotenv({
path: `.env.${process.env.NODE_ENV || 'development'}`
}),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: process.env.NODE_ENV === 'production'
})
]
}总结
核心最佳实践
- 配置文件分离 - 使用 webpack-merge 分离开发和生产配置
- 环境变量管理 - 使用 dotenv 管理环境变量
- 性能监控 - 使用工具监控构建性能
- 代码规范 - 集成 ESLint 和 Prettier
- 版本管理 - 使用 contenthash 实现长期缓存
- 代码分割 - 合理配置 splitChunks
- 项目结构 - 清晰的目录结构和路径别名
- 构建优化 - 使用缓存和多进程构建
- 错误处理 - 友好的错误提示
- 文档完善 - 清晰的注释和文档
下一步
- 常见问题排查 - 解决实际问题
- 性能优化 - 深入学习性能优化
- 实战项目:从零搭建 React 项目 - 实践最佳实践