Appearance
Webpack 代码分割
代码分割是 Webpack 的核心功能之一,它允许我们将代码拆分成多个 bundle,以便按需加载,从而优化应用程序的性能和加载时间。
代码分割基础概念
代码分割的类型
javascript
// 1. 入口点分割
module.exports = {
entry: {
home: './src/home.js',
about: './src/about.js',
contact: './src/contact.js'
}
};
// 2. 动态导入分割
// src/index.js
import('./components/LazyComponent').then(module => {
const LazyComponent = module.default;
// 使用组件
});
// 3. 配置分割
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
代码分割的好处
javascript
// 减少初始加载时间
// - 用户只加载需要的代码
// - 更小的初始 bundle
// - 并行加载多个小文件
// - 更好的缓存策略
动态导入
基础动态导入
javascript
// 动态导入单个模块
async function loadLodash() {
const { default: _ } = await import('lodash');
return _;
}
// 动态导入多个模块
async function loadModules() {
const [homeModule, aboutModule] = await Promise.all([
import('./home'),
import('./about')
]);
return {
home: homeModule.default,
about: aboutModule.default
};
}
// 条件导入
async function loadFeature(featureName) {
switch (featureName) {
case 'calendar':
const { Calendar } = await import('./calendar');
return Calendar;
case 'chart':
const { Chart } = await import('./chart');
return Chart;
default:
return null;
}
}
React 中的动态导入
javascript
import React, { Suspense, lazy } from 'react';
// 懒加载组件
const Home = lazy(() => import('./components/Home'));
const About = lazy(() => import('./components/About'));
const Contact = lazy(() => import('./components/Contact'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</div>
);
}
Vue 中的动态导入
javascript
// Vue 路由懒加载
const routes = [
{
path: '/',
name: 'Home',
component: () => import('./views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('./views/About.vue')
},
{
path: '/profile',
name: 'Profile',
component: () => import('./views/Profile.vue')
}
];
// 组件懒加载
export default {
components: {
LazyComponent: () => import('./LazyComponent.vue')
}
};
SplitChunksPlugin 配置
基础配置
javascript
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 'initial' | 'async' | 'all'
minSize: 20000, // 最小大小,小于这个值的不会分割
maxSize: 244000, // 最大大小,超过这个值会被分割
minChunks: 1, // 最少被引用次数
maxAsyncRequests: 30, // 按需加载最大请求数
maxInitialRequests: 30, // 入口点最大并行请求数
automaticNameDelimiter: '~', // 文件名连接符
cacheGroups: { // 缓存组
default: {
minChunks: 2, // 默认缓存组配置
priority: -20,
reuseExistingChunk: true // 重用已存在的块
}
}
}
}
};
高级缓存组配置
javascript
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
chunks: 'all'
},
// React 生态
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20,
chunks: 'all'
},
// 样式文件
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true // 强制创建 chunk
},
// 大型库单独分割
largeLibraries: {
test: /[\\/]node_modules[\\/](moment|lodash|axios)[\\/]/,
name: 'large-libraries',
priority: 15,
chunks: 'all'
},
// 公共代码
common: {
name: 'common',
minChunks: 2, // 至少被 2 个 chunk 使用
priority: 5,
chunks: 'all',
minSize: 0 // 即使很小也要分割
}
}
}
}
};
按功能分割
javascript
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 认证相关
auth: {
test: /[\\/]src[\\/]auth[\\/]/,
name: 'auth',
priority: 8,
chunks: 'all'
},
// UI 组件
ui: {
test: /[\\/]src[\\/]components[\\/]/,
name: 'ui',
priority: 8,
chunks: 'all'
},
// 工具函数
utils: {
test: /[\\/]src[\\/]utils[\\/]/,
name: 'utils',
priority: 8,
chunks: 'all'
}
}
}
}
};
预加载和预获取
Webpack 魔法注释
javascript
// 预加载 - 立即加载
const component = import(
/* webpackPreload: true */
'./CriticalComponent'
);
// 预获取 - 空闲时加载
const component = import(
/* webpackPrefetch: true */
'./FutureComponent'
);
// 自定义 chunk 名称
const component = import(
/* webpackChunkName: "my-chunk" */
'./MyComponent'
);
// 预加载和自定义名称
const component = import(
/* webpackPreload: true */
/* webpackChunkName: "critical" */
'./CriticalComponent'
);
实际应用示例
javascript
// 路由级别的预加载
const routes = [
{
path: '/dashboard',
component: () => import(
/* webpackPreload: true */
'./Dashboard'
)
},
{
path: '/profile',
component: () => import(
/* webpackPrefetch: true */
'./Profile'
)
}
];
// 事件驱动的预获取
function onHover() {
import(
/* webpackPrefetch: true */
'./HeavyComponent'
);
}
// 条件预加载
if (window.Connection && navigator.connection.effectiveType === '4g') {
import(
/* webpackPreload: true */
'./HighQualityAssets'
);
}
高级代码分割策略
按页面分割
javascript
// 多页面应用配置
module.exports = {
entry: {
home: './src/pages/Home/index.js',
about: './src/pages/About/index.js',
contact: './src/pages/Contact/index.js'
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 公共页面代码
pageCommon: {
test: /[\\/]src[\\/]pages[\\/]/,
minChunks: 2,
name: 'page-common',
priority: 10,
chunks: 'all'
},
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 20,
chunks: 'all'
}
}
}
}
};
按功能分割
javascript
// 按功能模块分割
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 认证功能
auth: {
test: module => {
return (
module.resource &&
(
module.resource.includes('auth') ||
module.resource.includes('login') ||
module.resource.includes('register')
)
);
},
name: 'auth',
priority: 15
},
// 数据处理功能
data: {
test: module => {
return (
module.resource &&
(
module.resource.includes('api') ||
module.resource.includes('services') ||
module.resource.includes('store')
)
);
},
name: 'data',
priority: 15
},
// UI 组件
ui: {
test: module => {
return (
module.resource &&
module.resource.includes('components')
);
},
name: 'ui',
priority: 10
}
}
}
}
};
按使用频率分割
javascript
// 高频使用组件
const FrequentlyUsedComponent = lazy(() => import('./FrequentlyUsedComponent'));
// 低频使用组件
const RarelyUsedComponent = lazy(() =>
import(
/* webpackPrefetch: true */
'./RarelyUsedComponent'
)
);
// 一次性使用组件
const OneTimeComponent = lazy(() =>
import(
/* webpackChunkName: "one-time" */
'./OneTimeComponent'
)
);
代码分割最佳实践
1. 合理的分割粒度
javascript
// 不要分割得太细
// ❌ 太细的分割
const Button = lazy(() => import('./Button'));
const Input = lazy(() => import('./Input'));
const Modal = lazy(() => import('./Modal'));
// ✅ 合理的分割
const UIComponents = lazy(() => import('./UIComponents'));
2. 考虑加载时机
javascript
// 立即需要的功能 - 预加载
const Dashboard = lazy(() => import(
/* webpackPreload: true */
'./Dashboard'
));
// 将来可能需要的功能 - 预获取
const Settings = lazy(() => import(
/* webpackPrefetch: true */
'./Settings'
));
3. 网络状况感知
javascript
// 根据网络状况调整策略
function getNetworkAwareImport(componentPath, options = {}) {
const connection = navigator.connection;
if (connection) {
const { effectiveType } = connection;
if (effectiveType === 'slow-2g' || effectiveType === '2g') {
// 慢网络下不使用预加载
return import(componentPath);
} else if (effectiveType === '3g') {
// 3G 网络下谨慎使用预加载
return import(
options.prefetch ?
/* webpackPrefetch: true */ componentPath :
componentPath
);
} else {
// 快速网络下可以预加载
return import(
/* webpackPreload: true */ componentPath
);
}
}
// 默认情况
return import(componentPath);
}
监控和分析
代码分割分析
javascript
// webpack-bundle-analyzer 配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-analysis.html'
})
]
};
加载性能监控
javascript
// 监控动态导入性能
async function loadWithMonitoring(path, name) {
const startTime = performance.now();
try {
const module = await import(path);
const endTime = performance.now();
console.log(`${name} loaded in ${endTime - startTime}ms`);
return module;
} catch (error) {
console.error(`Failed to load ${name}:`, error);
throw error;
}
}
// 使用示例
const component = await loadWithMonitoring('./MyComponent', 'MyComponent');
代码分割配置示例
javascript
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 10,
maxAsyncRequests: 10,
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 20,
chunks: 'all',
maxSize: 244000
},
// React 生态
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'react',
priority: 30,
chunks: 'all'
},
// 公共代码
common: {
name: 'common',
minChunks: 2,
priority: 10,
chunks: 'all',
minSize: 0
},
// 样式
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
},
// 运行时代码分离
runtimeChunk: {
name: 'runtime'
}
}
};
小结
代码分割是优化 Webpack 构建结果的重要手段。通过合理的分割策略,可以显著提升应用的加载性能。关键是要根据应用的特点选择合适的分割方式,并结合预加载和预获取技术,为用户提供最佳的加载体验。