一、项目背景与性能现状
1.1 项目概述
仿1688首页是一个基于React + TypeScript + Webpack构建的大型电商首页,包含:
200+组件(轮播图、商品卡片、分类导航、推荐模块等)
50+第三方库(React、Antd、Echarts、Swiper等)
100+图片资源(Banner、商品图片、图标等)
复杂的状态管理和路由配置
1.2 优化前性能数据
构建性能
初始构建时间: 45.2s
热更新启动: 8.3s
生产包大小: 18.7MB
JS文件数: 23个
CSS文件数: 9个
运行时性能
首次内容渲染(FCP): 4.8s
最大内容绘制(LCP): 6.2s
首字节时间(TTFB): 1.3s
累计布局偏移(CLS): 0.25
1.3 核心优化目标
✅ 构建速度:开发环境构建时间减少60%
✅ 包体积:生产包体积减少50%
✅ 加载性能:FCP < 2s, LCP < 3s
✅ 用户体验:CLS < 0.1
二、Webpack配置深度优化
2.1 基础配置结构
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
entry: {
main: './src/index.tsx',
vendor: ['react', 'react-dom', 'antd'] // 初始vendor配置
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction ? '[name].[contenthash:8].js' : '[name].js',
clean: true
},
module: {
rules: [
{
test: /.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader'
]
},
{
test: /.(png|jpe?g|gif|svg)$/,
type: 'asset/resource'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
...(isProduction ? [new MiniCssExtractPlugin()] : [])
],
optimization: {
minimizer: [new TerserPlugin()],
splitChunks: {
chunks: 'all'
}
}
};
};
2.2 优化后的完整配置
2.2.1 开发环境配置
// webpack.dev.js
const path = require('path');
const webpack = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
// 开发服务器配置
devServer: {
static: {
directory: path.join(__dirname, 'public'),
},
port: 3000,
hot: true,
open: true,
compress: true,
historyApiFallback: true,
// 代理配置(解决跨域)
proxy: {
'/api': {
target: 'https://api.1688.com',
changeOrigin: true,
secure: false,
pathRewrite: {
'^/api': ''
}
}
}
},
// 缓存配置
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
plugins: [
// 热更新
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin(),
// 定义环境变量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development')
})
],
optimization: {
// 开发环境不压缩
minimize: false,
// 模块ID优化
moduleIds: 'named',
chunkIds: 'named',
// 移除runtime chunk
runtimeChunk: false
}
};
2.2.2 生产环境配置
// webpack.prod.js
const path = require('path');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'static/media/[name].[hash:8][ext]',
publicPath: '/'
},
module: {
rules: [
// TypeScript优化
{
test: /.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
}
],
exclude: /node_modules/
},
// CSS处理优化
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
auto: true,
localIdentName: '[hash:base64:8]'
}
}
},
'postcss-loader'
]
},
// 图片优化
{
test: /\.(png|jpe?g|gif|svg|webp)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb以下转base64
}
},
generator: {
filename: 'static/images/[name].[hash:8][ext]'
}
},
// 字体优化
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'static/fonts/[name].[hash:8][ext]'
}
}
]
},
plugins: [
// 提取CSS
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
}),
// 压缩CSS
new CssMinimizerPlugin(),
// 预编译资源
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dll/vendor-manifest.json')
}),
// Gzip压缩
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
}),
// Brotli压缩
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
compressionOptions: {
level: 11
},
threshold: 8192,
minRatio: 0.8
}),
// 打包分析
...(process.env.ANALYZE ? [new BundleAnalyzerPlugin()] : [])
],
optimization: {
minimize: true,
minimizer: [
// JavaScript压缩
new TerserPlugin({
parallel: true,
terserOptions: {
parse: {
ecma: 8
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2
},
mangle: {
safari10: true
},
output: {
ecma: 5,
comments: false,
ascii_only: true
}
}
}),
// CSS压缩
new CssMinimizerPlugin()
],
// 代码分割优化
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库
vendor: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'all',
minChunks: 1
},
// React相关
react: {
name: 'chunk-react',
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
priority: 20,
chunks: 'all'
},
// Antd相关
antd: {
name: 'chunk-antd',
test: /[\\/]node_modules[\\/]antd[\\/]/,
priority: 15,
chunks: 'all'
},
// 公共代码
commons: {
name: 'chunk-commons',
minChunks: 2,
priority: 5,
chunks: 'all'
}
}
},
// runtime chunk
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
}
},
performance: {
maxAssetSize: 512000,
maxEntrypointSize: 512000,
hints: 'warning'
}
});
三、关键优化技术详解
3.1 构建速度优化
3.1.1 缓存策略
// 缓存配置详解
cache: {
type: 'filesystem', // 使用文件系统缓存
buildDependencies: {
config: [__filename], // 配置文件变化时缓存失效
},
name: ${process.env.NODE_ENV || 'development'}-cache,
version: '1.0.0' // 缓存版本
},
// 配合loader缓存
module: {
rules: [
{
test: /.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true, // 只转译不检查类型
happyPackMode: true, // 启用多进程
experimentalWatchApi: true
}
}
],
exclude: /node_modules/
},
{
test: /.jsx?$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 启用babel缓存
cacheCompression: false
}
}
],
exclude: /node_modules/
}
]
}
3.1.2 多进程构建
// 安装thread-loader
const TerserPlugin = require('terser-webpack-plugin');
const ThreadLoader = require('thread-loader');
// 预热thread-loader
ThreadLoader.warmup(
{
workers: 2,
poolTimeout: Infinity
},
['babel-loader', 'ts-loader']
);
// 配置thread-loader
module: {
rules: [
{
test: /.tsx?$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2,
poolTimeout: Infinity
}
},
{
loader: 'ts-loader',
options: {
happyPackMode: true
}
}
],
exclude: /node_modules/
}
]
},
// 并行压缩
optimization: {
minimizer: [
new TerserPlugin({
parallel: true, // 启用多进程压缩
terserOptions: {
// 压缩配置
}
})
]
}
3.1.3 模块解析优化
resolve: {
// 指定扩展名查找顺序
extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
// 配置别名,减少查找层级
alias: {
'@': path.resolve(dirname, 'src'),
'@components': path.resolve(dirname, 'src/components'),
'@utils': path.resolve(dirname, 'src/utils'),
'@assets': path.resolve(dirname, 'src/assets'),
react: path.resolve(__dirname, 'node_modules/react')
},
// 指定模块查找目录
modules: [
path.resolve(dirname, 'src'),
path.resolve(dirname, 'node_modules'),
'node_modules'
],
// 优先使用ES模块
mainFields: ['browser', 'module', 'main']
}
3.2 包体积优化
3.2.1 代码分割策略
// 动态导入示例
const ProductList = React.lazy(() =>
import(/ webpackChunkName: "product-list" / './components/ProductList')
);
const CategoryPage = React.lazy(() =>
import(/ webpackChunkName: "category-page" / './pages/CategoryPage')
);
// 预加载关键资源
const HomeBanner = React.lazy(() =>
import(
/ webpackChunkName: "home-banner" /
/ webpackPrefetch: true /
'./components/HomeBanner'
)
);
// 按需加载第三方库
import { Modal, Button } from 'antd';
import { debounce } from 'lodash';
// 替代全量引入
import debounce from 'lodash/debounce';
import Modal from 'antd/es/modal';
import 'antd/es/modal/style/css';
3.2.2 Tree Shaking优化
// package.json配置
{
"name": "my-app",
"sideEffects": [
".css",
".scss",
"@babel/polyfill"
]
}
// Webpack配置
optimization: {
usedExports: true, // 标记未使用代码
sideEffects: false // 启用sideEffects
}
3.2.3 图片优化
// 安装image-webpack-loader
{
test: /.(png|jpe?g|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'static/images/[name].[hash:8].[ext]'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
}
3.3 运行时性能优化
3.3.1 预加载关键资源
3.3.2 长缓存策略
output: {
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js'
},
// 分离runtime chunk
optimization: {
runtimeChunk: {
name: entrypoint => runtime-${entrypoint.name}
}
}
3.3.3 第三方库优化
// 使用DLLPlugin预编译第三方库
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendor: [
'react',
'react-dom',
'react-router-dom',
'antd',
'axios',
'lodash',
'moment'
]
},
output: {
path: path.resolve(dirname, 'dll'),
filename: '[name].js',
library: '[name][hash]'
},
plugins: [
new webpack.DllPlugin({
name: '[name][hash]',
path: path.resolve(dirname, 'dll/vendor-manifest.json')
})
]
};
四、性能监控与验证
4.1 优化前后对比
4.1.1 构建性能对比
优化前
构建时间: 45.2s
热更新启动: 8.3s
生产包大小: 18.7MB
JS文件数: 23个
CSS文件数: 9个
优化后
构建时间: 12.8s (提升71.7%)
热更新启动: 2.1s (提升74.7%)
生产包大小: 6.3MB (提升66.3%)
JS文件数: 8个 (提升65.2%)
CSS文件数: 3个 (提升66.7%)
4.1.2 运行时性能对比
优化前
FCP: 4.8s
LCP: 6.2s
TTFB: 1.3s
CLS: 0.25
优化后
FCP: 1.6s (提升66.7%)
LCP: 2.8s (提升54.8%)
TTFB: 0.8s (提升38.5%)
CLS: 0.08 (提升68%)
4.2 性能监控脚本
// scripts/performance-monitor.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
class PerformanceMonitor {
constructor() {
this.results = {
buildTime: 0,
bundleSize: 0,
lighthouseScore: {}
};
}
// 测量构建时间
measureBuildTime() {
console.log('🏗️ 测量构建时间...');
const startTime = Date.now();
execSync('npm run build', { stdio: 'inherit' });
const endTime = Date.now();
this.results.buildTime = (endTime - startTime) / 1000;
console.log(`构建时间: ${this.results.buildTime}s`);
}
// 测量包体积
measureBundleSize() {
console.log('📦 测量包体积...');
const distPath = path.join(__dirname, '../dist');
const files = fs.readdirSync(distPath, { recursive: true });
let totalSize = 0;
files.forEach(file => {
if (fs.statSync(path.join(distPath, file)).isFile()) {
totalSize += fs.statSync(path.join(distPath, file)).size;
}
});
this.results.bundleSize = totalSize / 1024 / 1024; // MB
console.log(`包体积: ${this.results.bundleSize.toFixed(2)}MB`);
}
// 运行Lighthouse测试
async runLighthouse() {
console.log('🔍 运行Lighthouse测试...');
try {
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
const options = {
logLevel: 'info',
output: 'json',
onlyCategories: ['performance'],
port: chrome.port
};
const runnerResult = await lighthouse('http://localhost:3000', options);
this.results.lighthouseScore = {
performance: runnerResult.lhr.categories.performance.score * 100,
fcp: runnerResult.lhr.audits['first-contentful-paint'].numericValue,
lcp: runnerResult.lhr.audits['largest-contentful-paint'].numericValue,
cls: runnerResult.lhr.audits['cumulative-layout-shift'].numericValue
};
await chrome.kill();
console.log('Lighthouse性能评分:', this.results.lighthouseScore.performance);
} catch (error) {
console.error('Lighthouse测试失败:', error);
}
}
// 生成报告
generateReport() {
console.log('\n📊 性能优化报告:');
console.log('===================');
console.log(构建时间: ${this.results.buildTime}s);
console.log(包体积: ${this.results.bundleSize.toFixed(2)}MB);
console.log(FCP: ${this.results.lighthouseScore.fcp}ms);
console.log(LCP: ${this.results.lighthouseScore.lcp}ms);
console.log(CLS: ${this.results.lighthouseScore.cls});
console.log(性能评分: ${this.results.lighthouseScore.performance}%);
}
}
// 运行监控
const monitor = new PerformanceMonitor();
monitor.measureBuildTime();
monitor.measureBundleSize();
monitor.runLighthouse().then(() => {
monitor.generateReport();
});
4.3 Bundle分析
// 使用webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
...(process.env.ANALYZE ? [new BundleAnalyzerPlugin()] : [])
]
};
// package.json脚本
{
"scripts": {
"analyze": "ANALYZE=true npm run build",
"analyze:dev": "ANALYZE=true webpack serve --mode development"
}
}
五、最佳实践总结
5.1 关键优化配置
5.1.1 开发环境
// webpack.dev.js
module.exports = {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
cache: { type: 'filesystem' },
devServer: { hot: true, compress: true },
plugins: [new ReactRefreshWebpackPlugin()]
};
5.1.2 生产环境
// webpack.prod.js
module.exports = {
mode: 'production',
devtool: 'source-map',
optimization: {
splitChunks: { chunks: 'all' },
runtimeChunk: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()]
},
plugins: [
new MiniCssExtractPlugin(),
new CompressionPlugin()
]
};
5.2 优化技巧总结
构建速度:
使用filesystem缓存
启用thread-loader多进程
优化模块解析路径
分离第三方库
包体积:
代码分割和Tree Shaking
按需加载第三方库
图片压缩和WebP格式
Gzip/Brotli压缩
运行时性能:
预加载关键资源
长缓存策略
减少重排重绘
虚拟滚动和懒加载
5.3 优化检查清单
[ ] 启用Webpack缓存
[ ] 配置多进程构建
[ ] 优化代码分割策略
[ ] 启用Tree Shaking
[ ] 压缩图片资源
[ ] 配置Gzip压缩
[ ] 预加载关键资源
[ ] 监控构建性能
[ ] 定期Bundle分析
六、完整配置文件
6.1 最终webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
const baseConfig = {
entry: {
main: './src/index.tsx'
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@assets': path.resolve(__dirname, 'src/assets'),
react: path.resolve(__dirname, 'node_modules/react')
},
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules'),
'node_modules'
]
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2,
poolTimeout: Infinity
}
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
}
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
auto: true,
localIdentName: isProduction
? '[hash:base64:8]'
: '[name]__[local]--[hash:base64:5]'
}
}
},
'postcss-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg|webp)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
},
generator: {
filename: 'static/images/[name].[hash:8][ext]'
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'static/fonts/[name].[hash:8][ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
} : false
})
]
};
if (isProduction) {
return {
...baseConfig,
mode: 'production',
devtool: 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'static/media/[name].[hash:8][ext]',
publicPath: '/'
},
plugins: [
...baseConfig.plugins,
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
}),
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
}),
...(env.ANALYZE ? [new BundleAnalyzerPlugin()] : [])
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
parse: { ecma: 8 },
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2
},
mangle: { safari10: true },
output: {
ecma: 5,
comments: false,
ascii_only: true
}
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'all'
},
react: {
name: 'chunk-react',
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
priority: 20,
chunks: 'all'
},
antd: {
name: 'chunk-antd',
test: /[\\/]node_modules[\\/]antd[\\/]/,
priority: 15,
chunks: 'all'
},
commons: {
name: 'chunk-commons',
minChunks: 2,
priority: 5,
chunks: 'all'
}
}
},
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
}
}
};
} else {
return {
...baseConfig,
mode: 'development',
devtool: 'eval-cheap-module-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
chunkFilename: '[name].chunk.js',
assetModuleFilename: 'static/media/[name].[ext]',
publicPath: '/'
},
devServer: {
static: {
directory: path.join(__dirname, 'public')
},
port: 3000,
hot: true,
open: true,
compress: true,
historyApiFallback: true,
proxy: {
'/api': {
target: 'https://api.1688.com',
changeOrigin: true,
secure: false,
pathRewrite: { '^/api': '' }
}
}
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
plugins: [
...baseConfig.plugins,
new webpack.HotModuleReplacementPlugin()
],
optimization: {
minimize: false,
moduleIds: 'named',
chunkIds: 'named',
runtimeChunk: false
}
};
}
};
七、总结
7.1 优化成果
通过系统的Webpack优化,我们实现了:
构建速度提升71.7%:从45.2s降至12.8s
包体积减少66.3%:从18.7MB降至6.3MB
加载性能提升66.7%:FCP从4.8s降至1.6s
用户体验显著改善:CLS从0.25降至0.08
7.2 核心优化点
缓存策略:文件系统缓存 + loader缓存
多进程构建:thread-loader + 并行压缩
代码分割:智能分割策略 + 动态导入
资源优化:图片压缩 + 字体优化
运行时优化:预加载 + 长缓存
7.3 后续建议
持续监控:定期运行性能测试
渐进式优化:按需引入优化策略
新技术探索:Vite、Turbopack等构建工具
用户体验:结合用户体验指标优化
通过本指南,你可以:
✅ 掌握Webpack深度优化技巧
✅ 构建高性能React应用
✅ 显著提升用户体验
✅ 建立性能优化最佳实践