相关问题
关于 webpack打包构建优化,之前做过哪些?
-
代码分割(Code Splitting):使用 Webpack 的 SplitChunksPlugin 进行代码分割,将第三方库、公共代码与业务代码分离,提高缓存利用率和加载速度。
-
Tree Shaking: 通过配置 mode:'production'或使用 TerserPlugin,移除未引用的代码,减少包体积。
-
Lazy Loading(懒加载):使用 import()动态加载模块,实现按需加载,减少初始加载时间。
-
使用CDN:配置 externals,将常用的库如 React、Vue 等通过CDN 引入,减少打包体积。
-
缓存优化:通过配置 output.filename 和 output.chunkFilename 中的 [contenthash],生成基于文件内容的哈希值,避免不必要的缓存失效。
-
开启持久化缓存(Persistent Caching):配置 cache:{ type:'filesystem'},提高二次构建速度。
-
优化 Loader:使用多进程和缓存(如 thread-loader 和 cache-loader),提升构建速度。还可以通过限制 babel-Loader 等处理范围来加速构建。
-
优化开发体验:使用 webpack-dev-server 的HMR(热模块替換)功能,提高开发效率;或者通过配置 resolve.alias 缩短模块查找路径。
你认为 Vite 相对于 Webpack 有哪些优势?
-
极速启动:Vite 使用原生ES模块进行开发时的依赖加载,无需像Webpack一样对整个项目进行预打包。因此,Vite 的冷启动速度非常快,尤其是在大型项目中尤为明显。
-
即时热更新(HMR):Vite 的HMR速度更快更灵敏,因为它基于 ES 模块,仅更新受影响的模块,而不需要重新构建整个包。
-
更少的配置:Vite 的默认配置已经足够健全,开箱即用,开发者通常不需要像使用Webpack 一样编写大量的配置文件。
-
现代化浏览器支持:Vite 针对现代浏览器优化,默认使用ES6+语法,省去了对旧浏览器的兼容配置。
-
插件生态:虽然 Vite 插件生态相对年轻,但其设计简单且功能强大,能够满足大多数场景的需求。
-
构建速度快:Vite 使用esbuild 进行预构建,极大提高了依赖解析和打包的速度。此外,Vite 还使用Rollup 作为生产环境打包工具,具有较好的打包优化能力。
-
调试友好:Vite 生成的源码更接近开发者的源码,调试体验更好,错误追踪更准确。
相关资料
- SourceMap:developer.mozilla.org/en-US/docs/...
- SourceMap在sentry中的处理:docs.sentry.io/platforms/j...
- webpack优化:webpack.js.org/guides/buil...
- webpack code-splitting:webpack.js.org/guides/code...
- webpack DllPlugin:webpack.js.org/plugins/dll...
- esbuild插件列表:github.com/esbuild/com...
Webpack 5开发构建优化详解
开发模式配置
使用 mode: 'development'
配置 devtool: 'eval-cheap-module-source-map'
启用 HMR (Hot Module Replacement)
javascript
// webpack.dev.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: './src/index.js',
devtool: 'eval-cheap-module-source-map',
devServer: {
hot: true,
open: true,
compress: true,
port: 3000,
historyApiFallback: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
optimization: {
usedExports: true,
sideEffects: false,
}
};
模块解析优化
使用 resolve.alias
优化模块路径解析:配置 resolve.extensions
使用 resolve.modules
javascript
// webpack.config.js
module.exports = {
resolve: {
// 路径别名配置
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@assets': path.resolve(__dirname, 'src/assets'),
},
// 扩展名配置
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
// 模块搜索路径
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
// 优化 npm 包解析
mainFields: ['browser', 'module', 'main'],
// 缓存解析结果
cache: true,
// 指定解析目录
symlinks: false,
}
};
缓存优化
持久化缓存:启用文件系统缓存
使用 babel-loader 的 cacheDirectory 选项
javascript
// webpack.config.js
module.exports = {
// Webpack 5 持久化缓存
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
buildDependencies: {
config: [__filename]
},
version: '1.0'
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
}
}
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]--[hash:base64:5]'
}
}
}
]
}
]
}
};
其他优化
减少监听文件
使用多进程并行构建
合理使用 DllPlugin 和 DllReferencePlugin
javascript
// webpack.dll.js - DLL配置
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendor: [
'react',
'react-dom',
'lodash',
'moment'
]
},
output: {
path: path.resolve(__dirname, 'dll'),
filename: '[name].dll.js',
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.resolve(__dirname, 'dll/[name].manifest.json')
})
]
};
// webpack.config.js - 引用DLL
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dll/vendor.manifest.json')
})
]
};
多进程构建优化
javascript
// 使用 thread-loader 进行多进程构建
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2,
workerParallelJobs: 50,
workerNodeArgs: ['--max-old-space-size=1024'],
poolRespawn: false,
poolTimeout: 2000,
poolParallelJobs: 50,
name: 'js-pool'
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [
['@babel/preset-env', { targets: 'defaults' }],
'@babel/preset-react'
]
}
}
]
}
]
}
};
Webpack 5构建流程优化
构建流程图
构建性能监控
javascript
// webpack.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
// 其他配置...
plugins: [
// 分析包大小
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
openAnalyzer: true,
}),
],
// 性能配置
performance: {
hints: 'warning',
maxEntrypointSize: 250000,
maxAssetSize: 250000,
assetFilter: (assetFilename) => {
return assetFilename.endsWith('.js');
}
}
});
生产环境优化配置
代码压缩与优化
javascript
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
mode: 'production',
devtool: 'source-map',
optimization: {
minimize: true,
minimizer: [
// JS压缩
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
format: {
comments: false,
},
},
extractComments: false,
}),
// CSS压缩
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
},
],
},
}),
],
// 代码分割配置
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
reuseExistingChunk: true,
},
// 公共代码
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
},
// React相关
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
chunks: 'all',
priority: 20,
},
// UI库
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: 'antd',
chunks: 'all',
priority: 15,
},
},
},
// Runtime chunk
runtimeChunk: {
name: 'runtime',
},
},
plugins: [
// Gzip压缩
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
}),
],
};
代码分割策略详解
动态导入与懒加载
javascript
// 路由懒加载
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
// 动态导入组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</Router>
);
}
// 条件加载
async function loadUtility(type) {
let module;
switch (type) {
case 'chart':
module = await import('./utils/chart');
break;
case 'validation':
module = await import('./utils/validation');
break;
default:
throw new Error('Unknown utility type');
}
return module.default;
}
// 预加载
const preloadComponent = () => {
const componentImport = import('./components/HeavyComponent');
return componentImport;
};
// 在合适的时机预加载
document.addEventListener('mouseover', preloadComponent, { once: true });
代码分割流程图
Tree Shaking 优化
确保模块使用 ES6 模块语法
javascript
// 确保 package.json 中的 sideEffects 配置
{
"name": "my-project",
"sideEffects": false,
// 或者指定有副作用的文件
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
}
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
sideEffects: false,
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
modules: false, // 保持ES6模块语法
}]
]
}
}
}
]
}
};
// 正确的导入方式
import { debounce } from 'lodash-es'; // ✅ 支持Tree Shaking
// import _ from 'lodash'; // ❌ 会导入整个库
// 工具函数的正确导出
// utils/index.js
export { formatDate } from './date';
export { validateEmail } from './validation';
export { debounceClick } from './events';
// 使用时按需导入
import { formatDate, validateEmail } from './utils';
高级优化技巧
Module Federation(模块联邦)
javascript
// webpack.config.js - 主应用
const ModuleFederationPlugin = require('@module-federation/webpack');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
mfApp: 'mfApp@http://localhost:3001/remoteEntry.js',
},
}),
],
};
// webpack.config.js - 微前端应用
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'mfApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button',
'./Header': './src/Header',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
资源内联优化
javascript
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
},
// 内联小于 8kb 的资源
inlineSource: '.(js|css)$',
}),
],
module: {
rules: [
// 小图片内联为 base64
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192,
fallback: 'file-loader',
}
}
},
// 小字体文件内联
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
}
}
}
]
}
};
性能监控与分析
构建分析工具集成
javascript
// build-analysis.js
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
const config = {
// 基础配置...
plugins: [
// 重复包检查
new DuplicatePackageCheckerPlugin({
verbose: true,
emitError: false,
showHelp: false,
strict: false,
}),
// 包大小分析
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
}),
],
};
// 性能测量
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap(config);
实时性能监控
javascript
// performance-monitor.js
class PerformanceMonitor {
constructor() {
this.metrics = {
buildTime: 0,
bundleSize: 0,
chunkCount: 0,
};
}
measureBuildTime() {
const start = Date.now();
return () => {
this.metrics.buildTime = Date.now() - start;
console.log(`构建时间: ${this.metrics.buildTime}ms`);
};
}
analyzeBundleSize(stats) {
const assets = stats.compilation.assets;
this.metrics.bundleSize = Object.keys(assets)
.reduce((total, name) => total + assets[name].size(), 0);
console.log(`Bundle大小: ${(this.metrics.bundleSize / 1024).toFixed(2)}KB`);
}
generateReport() {
return {
timestamp: new Date().toISOString(),
metrics: this.metrics,
recommendations: this.getRecommendations(),
};
}
getRecommendations() {
const recommendations = [];
if (this.metrics.buildTime > 30000) {
recommendations.push('考虑启用持久化缓存');
}
if (this.metrics.bundleSize > 1024 * 1024) {
recommendations.push('考虑进一步代码分割');
}
return recommendations;
}
}
// webpack 插件集成
class PerformancePlugin {
apply(compiler) {
const monitor = new PerformanceMonitor();
compiler.hooks.compile.tap('PerformancePlugin', () => {
monitor.measureBuildTime();
});
compiler.hooks.done.tap('PerformancePlugin', (stats) => {
monitor.analyzeBundleSize(stats);
const report = monitor.generateReport();
console.log('性能报告:', report);
});
}
}
module.exports = PerformancePlugin;
实战优化案例
大型项目优化实践
javascript
// 完整的生产环境配置
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const PerformancePlugin = require('./plugins/PerformancePlugin');
module.exports = {
mode: 'production',
entry: {
app: './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: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
extensions: ['.js', '.jsx', '.ts', '.tsx'],
modules: ['node_modules', path.resolve(__dirname, 'src')],
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
optimization: {
minimize: true,
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all',
},
},
},
runtimeChunk: 'single',
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: { workers: 2 },
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
],
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
},
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[id].[contenthash:8].css',
}),
new PerformancePlugin(),
],
};
优化效果对比
优化项目 | 优化前 | 优化后 | 提升 |
---|---|---|---|
首次构建时间 | 45s | 12s | 73% |
增量构建时间 | 8s | 2s | 75% |
Bundle大小 | 2.1MB | 980KB | 53% |
首屏加载时间 | 3.2s | 1.8s | 44% |
总结
通过以上Webpack 5优化策略的实施,我们可以显著提升开发和构建体验:
- 开发效率提升:通过HMR、持久化缓存等技术,大幅缩短开发反馈周期
- 构建性能优化:利用多进程、缓存机制,显著减少构建时间
- 生产包优化:通过代码分割、Tree Shaking等技术,优化最终产物
- 性能监控:建立完善的性能监控体系,持续优化构建流程
这些优化措施需要根据项目实际情况进行调整和组合使用,以达到最佳的优化效果。
Webpack 5产物构建优化详解
生产模式配置
使用 mode: 'production'
生产模式会自动启用多种优化选项,包括代码压缩、Tree Shaking、作用域提升等。
javascript
// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production', // 启用生产模式优化
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, // 清理输出目录
},
// 生产环境推荐的 devtool
devtool: 'source-map',
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
],
};
使用 optimization.splitChunks
通过代码分割优化缓存策略和首屏加载性能。
javascript
// webpack.prod.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 对所有类型的chunk进行分割
minSize: 20000, // 最小分割大小
maxSize: 244000, // 最大分割大小
minChunks: 1, // 最小引用次数
maxAsyncRequests: 30, // 最大异步请求数
maxInitialRequests: 30, // 最大初始请求数
cacheGroups: {
// 默认分组
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
// 第三方库分组
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all',
reuseExistingChunk: true,
},
// React 相关库单独分割
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react-vendor',
chunks: 'all',
priority: 20,
},
// UI 组件库分割
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: 'antd-vendor',
chunks: 'all',
priority: 15,
},
// 工具库分割
utils: {
test: /[\\/]node_modules[\\/](lodash|moment|dayjs)[\\/]/,
name: 'utils-vendor',
chunks: 'all',
priority: 10,
},
// 公共业务组件
common: {
test: /[\\/]src[\\/]components[\\/]/,
name: 'common',
chunks: 'all',
minChunks: 2,
priority: 5,
},
},
},
// 运行时代码单独提取
runtimeChunk: {
name: 'runtime',
},
},
};
启用 optimization.minimize
配置代码压缩和优化选项。
javascript
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true, // 启用代码压缩
minimizer: [
// JavaScript 压缩
new TerserPlugin({
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
drop_console: true, // 移除 console
drop_debugger: true, // 移除 debugger
pure_funcs: ['console.log'], // 移除指定函数
},
mangle: {
safari10: true,
},
format: {
ecma: 5,
comments: false, // 移除注释
ascii_only: true,
},
},
parallel: true, // 多进程压缩
extractComments: false, // 不提取注释
}),
// CSS 压缩
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
normalizeUnicode: false,
},
],
},
parallel: true,
}),
],
},
};
Tree Shaking
确保使用 ES6 模块
Tree Shaking 依赖 ES6 模块的静态分析能力。
javascript
// webpack.config.js
module.exports = {
mode: 'production',
// 确保模块类型正确
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
modules: false, // 保持 ES6 模块格式,不转换为 CommonJS
useBuiltIns: 'usage',
corejs: 3,
targets: {
browsers: ['> 1%', 'last 2 versions'],
},
},
],
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
],
},
},
},
],
},
optimization: {
usedExports: true, // 标记未使用的导出
sideEffects: false, // 标记模块无副作用
},
};
清理无用代码:配置 sideEffects
在 package.json
中正确配置 sideEffects
。
javascript
// package.json
{
"name": "my-app",
"version": "1.0.0",
"sideEffects": false, // 表示所有模块都没有副作用
// 或者指定有副作用的文件
"sideEffects": [
"*.css",
"*.scss",
"*.less",
"./src/polyfills.js",
"./src/global.js"
]
}
// 工具函数的正确导出方式
// utils/index.js
export { formatDate } from './date';
export { validateEmail } from './validation';
export { debounce } from './debounce';
export { throttle } from './throttle';
// 正确的导入方式
import { formatDate, validateEmail } from './utils';
// 针对第三方库的优化导入
import { debounce } from 'lodash-es'; // ✅ 支持 Tree Shaking
// import _ from 'lodash'; // ❌ 会导入整个库
// 使用 babel-plugin-import 优化 UI 库导入
// .babelrc
{
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}
]
]
}
图片和资源优化
使用 image-webpack-loader 压缩图片
配置图片压缩以减少资源体积。
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[name].[contenthash:8].[ext]',
publicPath: '/',
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 75, // 图片质量
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.6, 0.8],
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 80, // WebP 质量
enabled: true,
},
svgo: {
plugins: [
{ name: 'removeViewBox', active: false },
{ name: 'removeEmptyAttrs', active: false },
],
},
},
},
],
},
// 针对不同格式的特殊处理
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: {
prettier: false,
svgo: true,
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
},
},
},
],
},
titleProp: true,
},
},
],
},
],
},
};
使用 url-loader 和 file-loader
根据文件大小决定是否内联资源。
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
// 小图片内联为 base64
{
test: /\.(png|jpe?g|gif)$/i,
use: {
loader: 'url-loader',
options: {
limit: 8192, // 8KB 以下的图片内联
fallback: {
loader: 'file-loader',
options: {
name: 'images/[name].[contenthash:8].[ext]',
publicPath: '/',
},
},
},
},
},
// 字体文件处理
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
use: {
loader: 'url-loader',
options: {
limit: 10240, // 10KB 以下内联
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[contenthash:8].[ext]',
publicPath: '/',
},
},
},
},
},
// 音视频文件
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i,
use: {
loader: 'file-loader',
options: {
name: 'media/[name].[contenthash:8].[ext]',
publicPath: '/',
},
},
},
],
},
};
代码分割和懒加载
使用 import() 动态导入实现代码懒加载
通过动态导入实现按需加载,提升首屏性能。
javascript
// 路由级别的懒加载
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import LoadingSpinner from './components/LoadingSpinner';
// 懒加载页面组件
const HomePage = lazy(() => import('./pages/HomePage'));
const ProductPage = lazy(() => import('./pages/ProductPage'));
const UserDashboard = lazy(() => import('./pages/UserDashboard'));
const AdminPanel = lazy(() =>
import('./pages/AdminPanel').then(module => ({
default: module.AdminPanel
}))
);
function App() {
return (
<Router>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/product/:id" element={<ProductPage />} />
<Route path="/dashboard" element={<UserDashboard />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
</Router>
);
}
// 组件级别的懒加载
function ProductList() {
const [showChart, setShowChart] = useState(false);
const [ChartComponent, setChartComponent] = useState(null);
const loadChart = async () => {
if (!ChartComponent) {
const module = await import('./components/Chart');
setChartComponent(() => module.default);
}
setShowChart(true);
};
return (
<div>
<button onClick={loadChart}>显示图表</button>
{showChart && ChartComponent && <ChartComponent />}
</div>
);
}
// 条件加载
async function loadFeature(featureType) {
let module;
switch (featureType) {
case 'chart':
module = await import('./features/chart');
break;
case 'editor':
module = await import('./features/editor');
break;
case 'calendar':
module = await import('./features/calendar');
break;
default:
throw new Error(`Unknown feature: ${featureType}`);
}
return module.default;
}
// 预加载策略
class FeaturePreloader {
constructor() {
this.preloadedModules = new Map();
}
// 预加载重要功能
preloadCriticalFeatures() {
const criticalFeatures = [
import('./features/user-profile'),
import('./features/search'),
import('./features/navigation')
];
return Promise.all(criticalFeatures);
}
// 空闲时预加载
preloadOnIdle() {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
import('./features/analytics');
import('./features/feedback');
});
}
}
// 基于用户交互预加载
preloadOnHover(featureName) {
if (!this.preloadedModules.has(featureName)) {
const promise = import(`./features/${featureName}`);
this.preloadedModules.set(featureName, promise);
}
}
}
const preloader = new FeaturePreloader();
// 在应用启动时预加载关键功能
preloader.preloadCriticalFeatures();
preloader.preloadOnIdle();
通过 splitChunks 进行代码分割
优化代码分割策略以获得最佳的缓存效果。
javascript
// webpack.config.js - 高级代码分割配置
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
// 基础框架库
framework: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'framework',
chunks: 'all',
priority: 40,
enforce: true,
},
// UI 组件库
ui: {
test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
name: 'ui-vendor',
chunks: 'all',
priority: 30,
},
// 工具库
lib: {
test: /[\\/]node_modules[\\/](lodash|moment|dayjs|date-fns)[\\/]/,
name: 'lib',
chunks: 'all',
priority: 20,
},
// 其他第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
reuseExistingChunk: true,
},
// 公共业务代码
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
enforce: true,
},
// 异步组件
async: {
test: /[\\/]src[\\/]components[\\/]async[\\/]/,
name: 'async-components',
chunks: 'async',
priority: 15,
},
},
},
},
};
代码分割流程图
输出产物分析【重要】
安装 webpack-bundle-analyzer
javascript
// 安装命令
npm install --save-dev webpack-bundle-analyzer
// 或使用 yarn
yarn add -D webpack-bundle-analyzer
在 Webpack 配置中使用
将分析器集成到构建流程中。
javascript
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
// 其他配置...
plugins: [
// 开发环境使用交互式分析
new BundleAnalyzerPlugin({
analyzerMode: 'server', // 启动分析服务器
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info',
}),
// 生产环境生成静态报告
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
generateStatsFile: true,
statsFilename: 'bundle-stats.json',
}),
],
};
作为 Webpack 插件使用
创建条件化的分析配置。
javascript
// webpack.analyze.js
const { merge } = require('webpack-merge');
const prodConfig = require('./webpack.prod.js');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = merge(prodConfig, {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'stats.json',
excludeAssets: /\.map$/,
}),
],
});
// package.json 脚本配置
{
"scripts": {
"build": "webpack --config webpack.prod.js",
"analyze": "webpack --config webpack.analyze.js",
"analyze:server": "webpack-bundle-analyzer dist/stats.json"
}
}
配置参数说明
详细的配置选项和最佳实践。
javascript
// 完整的分析器配置
const analyzerConfig = {
// 分析模式
analyzerMode: 'static', // 'server' | 'static' | 'disabled'
// 静态模式配置
reportFilename: 'bundle-report.html',
reportTitle: 'Bundle Analysis Report',
defaultSizes: 'parsed', // 'stat' | 'parsed' | 'gzip'
// 服务器模式配置
analyzerHost: '127.0.0.1',
analyzerPort: 'auto', // 自动选择端口
openAnalyzer: true,
// 统计文件配置
generateStatsFile: true,
statsFilename: 'stats.json',
statsOptions: {
source: false,
modules: false,
chunks: true,
chunkModules: true,
optimizationBailout: false,
},
// 排除特定资源
excludeAssets: null, // 正则表达式或函数
// 日志级别
logLevel: 'info', // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
};
// 环境特定配置
const getAnalyzerConfig = (env) => {
const baseConfig = {
generateStatsFile: true,
statsFilename: 'stats.json',
excludeAssets: /\.(map|txt|LICENSE)$/,
};
if (env === 'development') {
return {
...baseConfig,
analyzerMode: 'server',
openAnalyzer: true,
analyzerPort: 8888,
};
}
return {
...baseConfig,
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
};
};
通过 CLI 命令使用
直接使用命令行工具进行分析。
javascript
// package.json
{
"scripts": {
"build:stats": "webpack --config webpack.prod.js --json > stats.json",
"analyze:cli": "npx webpack-bundle-analyzer stats.json",
"analyze:size": "npx webpack-bundle-analyzer stats.json --mode static --report bundle-size-report.html",
"analyze:gzip": "npx webpack-bundle-analyzer stats.json --default-sizes gzip"
}
}
// 自定义分析脚本
// scripts/analyze.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
class BundleAnalyzer {
constructor(options = {}) {
this.options = {
outputDir: 'dist',
statsFile: 'stats.json',
reportFile: 'bundle-report.html',
...options,
};
}
// 生成构建统计
generateStats() {
console.log('🔄 生成构建统计...');
try {
execSync(
`webpack --config webpack.prod.js --json > ${this.options.statsFile}`,
{ stdio: 'inherit' }
);
console.log('✅ 统计文件生成成功');
} catch (error) {
console.error('❌ 统计文件生成失败:', error.message);
process.exit(1);
}
}
// 分析包大小
analyzeBundle() {
console.log('📊 分析包大小...');
const statsPath = path.resolve(this.options.statsFile);
if (!fs.existsSync(statsPath)) {
console.error('❌ 统计文件不存在,请先运行构建');
return;
}
try {
execSync(
`npx webpack-bundle-analyzer ${statsPath} --mode static --report ${this.options.reportFile}`,
{ stdio: 'inherit' }
);
console.log('✅ 分析报告生成成功');
} catch (error) {
console.error('❌ 分析失败:', error.message);
}
}
// 检查包大小警告
checkBundleSize() {
const statsPath = path.resolve(this.options.statsFile);
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
const warnings = [];
const limits = {
entrypoint: 250 * 1024, // 250KB
asset: 100 * 1024, // 100KB
};
// 检查入口点大小
Object.entries(stats.entrypoints).forEach(([name, entry]) => {
const size = entry.assets.reduce((total, asset) => {
const assetInfo = stats.assets.find(a => a.name === asset);
return total + (assetInfo ? assetInfo.size : 0);
}, 0);
if (size > limits.entrypoint) {
warnings.push(`入口点 "${name}" 大小 ${(size / 1024).toFixed(2)}KB 超过建议值`);
}
});
// 检查资源大小
stats.assets.forEach(asset => {
if (asset.size > limits.asset && !asset.name.includes('vendor')) {
warnings.push(`资源 "${asset.name}" 大小 ${(asset.size / 1024).toFixed(2)}KB 超过建议值`);
}
});
if (warnings.length > 0) {
console.warn('⚠️ 包大小警告:');
warnings.forEach(warning => console.warn(` - ${warning}`));
} else {
console.log('✅ 包大小检查通过');
}
}
// 运行完整分析
run() {
this.generateStats();
this.analyzeBundle();
this.checkBundleSize();
}
}
// 使用分析器
const analyzer = new BundleAnalyzer();
analyzer.run();
使用 webpack-bundle-analyzer 分析 Bundle
分析结果解读和优化建议。
javascript
// 分析结果处理工具
class BundleOptimizer {
constructor(statsFile) {
this.stats = JSON.parse(fs.readFileSync(statsFile, 'utf8'));
}
// 分析重复依赖
findDuplicateDependencies() {
const modules = this.stats.modules || [];
const dependencies = new Map();
modules.forEach(module => {
if (module.name && module.name.includes('node_modules')) {
const match = module.name.match(/node_modules\/(.*?)[\\/]/);
if (match) {
const packageName = match[1];
if (!dependencies.has(packageName)) {
dependencies.set(packageName, []);
}
dependencies.get(packageName).push(module);
}
}
});
const duplicates = Array.from(dependencies.entries())
.filter(([, modules]) => modules.length > 1)
.map(([packageName, modules]) => ({
package: packageName,
count: modules.length,
totalSize: modules.reduce((sum, mod) => sum + (mod.size || 0), 0),
}));
return duplicates;
}
// 分析大文件
findLargeAssets(threshold = 100 * 1024) {
return this.stats.assets
.filter(asset => asset.size > threshold)
.sort((a, b) => b.size - a.size)
.map(asset => ({
name: asset.name,
size: `${(asset.size / 1024).toFixed(2)}KB`,
sizeBytes: asset.size,
}));
}
// 生成优化建议
generateRecommendations() {
const recommendations = [];
const duplicates = this.findDuplicateDependencies();
const largeAssets = this.findLargeAssets();
if (duplicates.length > 0) {
recommendations.push({
type: 'duplicate-dependencies',
title: '发现重复依赖',
description: '以下包存在多个版本,考虑使用 webpack 的 resolve.alias 或升级依赖版本',
items: duplicates,
});
}
if (largeAssets.length > 0) {
recommendations.push({
type: 'large-assets',
title: '大文件警告',
description: '以下文件较大,考虑进行代码分割或压缩优化',
items: largeAssets,
});
}
return recommendations;
}
// 生成报告
generateReport() {
const recommendations = this.generateRecommendations();
const totalSize = this.stats.assets.reduce((sum, asset) => sum + asset.size, 0);
return {
summary: {
totalSize: `${(totalSize / 1024).toFixed(2)}KB`,
assetsCount: this.stats.assets.length,
chunksCount: this.stats.chunks.length,
},
recommendations,
};
}
}
产物分析流程图
性能监控和持续优化
构建性能指标监控
javascript
// 性能监控插件
class BuildPerformancePlugin {
constructor(options = {}) {
this.options = {
outputFile: 'build-performance.json',
enableDetailedMetrics: true,
...options,
};
this.metrics = {
startTime: 0,
endTime: 0,
moduleCount: 0,
chunkCount: 0,
assetSize: 0,
};
}
apply(compiler) {
compiler.hooks.compile.tap('BuildPerformancePlugin', () => {
this.metrics.startTime = Date.now();
});
compiler.hooks.done.tap('BuildPerformancePlugin', (stats) => {
this.metrics.endTime = Date.now();
this.metrics.buildTime = this.metrics.endTime - this.metrics.startTime;
this.metrics.moduleCount = stats.compilation.modules.size;
this.metrics.chunkCount = stats.compilation.chunks.size;
this.metrics.assetSize = Array.from(stats.compilation.assets.keys())
.reduce((total, name) => {
return total + stats.compilation.assets[name].size();
}, 0);
this.generateReport(stats);
});
}
generateReport(stats) {
const report = {
timestamp: new Date().toISOString(),
metrics: this.metrics,
recommendations: this.getOptimizationSuggestions(),
comparison: this.compareWithPrevious(),
};
if (this.options.outputFile) {
fs.writeFileSync(
this.options.outputFile,
JSON.stringify(report, null, 2)
);
}
this.logMetrics();
}
getOptimizationSuggestions() {
const suggestions = [];
if (this.metrics.buildTime > 30000) {
suggestions.push('构建时间过长,考虑启用缓存或使用多进程构建');
}
if (this.metrics.assetSize > 5 * 1024 * 1024) {
suggestions.push('产物体积较大,考虑进一步代码分割和压缩');
}
if (this.metrics.chunkCount > 50) {
suggestions.push('代码块数量过多,可能影响HTTP/1.1性能');
}
return suggestions;
}
logMetrics() {
console.log('\n📊 构建性能报告:');
console.log(`⏱️ 构建时间: ${this.metrics.buildTime}ms`);
console.log(`📦 模块数量: ${this.metrics.moduleCount}`);
console.log(`🎯 代码块数量: ${this.metrics.chunkCount}`);
console.log(`📊 总体积: ${(this.metrics.assetSize / 1024).toFixed(2)}KB`);
}
}
module.exports = BuildPerformancePlugin;
通过以上全面的产物构建优化策略,我们可以显著提升应用的加载性能和用户体验。这些优化措施需要根据具体项目特点和性能目标进行调整和组合使用。