在前端开发中,包体积大小直接影响页面加载速度和用户体验。本文将分享一个小型项目的包体积优化全过程,通过针对性的优化策略,将主包体积从 352KB 大幅缩减至 7.45KB,同时将 Ant Design 组件库体积减少近一半,希望能为你的项目优化提供参考。
项目网站地址 :www.bettysong.xyz/
个人react+node+mysql搭建的个人网站,记录前端成长之路
一、分析工具与初始状态
准备工作
项目使用 Create React App 结合 craco 进行构建,首先配置了包体积分析环境:
js
"scripts": {
"start": "craco start",
"start:analyze": "ANALYZE=true craco start",
"build": "craco build",
"build:analyze": "ANALYZE=true craco build"
}
刚开始打包工具craco.config.js没有做任何优化配置,只配置了可视化分析插件
js
module.exports = {
webpack: {
configure: (webpackConfig) => {
// 配置源码映射
if (process.env.NODE_ENV === 'development') {
// 开发环境使用高质量的源码映射
webpackConfig.devtool = 'eval-source-map';
}
// 只在 ANALYZE=true 时添加 BundleAnalyzerPlugin
if (process.env.ANALYZE === 'true') {
webpackConfig.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: process.env.NODE_ENV === 'production' ? 'static' : 'server',
analyzerHost: 'localhost',
analyzerPort: 8888,
openAnalyzer: true,
generateStatsFile: false,
reportFilename: 'bundle-report.html'
})
);
}
return webpackConfig;
},
},
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
},
},
},
],
};
执行构建分析命令后
js
npm run build:analyze

从图中可以看到压缩后,主包 main.js 大小为 352KB ,多个 chunk 体积超过 200KB,对于小型项目来说算很大了

主要问题识别
经过深入分析源码,发现导致 main.js 文件过大的三个主要原因:
- GSAP 库重复导入 :多个组件都在使用 GSAP 库,且导入了多个插件
- tsparticles 全量加载 :在主应用中全量加载了 tsparticles 库
- ByteMD 编辑器冗余 :只在 Create 页面使用但包含了多个插件
二、分阶段优化策略
1. tsparticles 懒加载优化
问题 :tsparticles 在 App.tsx 中全量加载,包含了许多项目中未使用的功能,导致首屏加载负担加重。
解决方案 :创建独立的 ParticlesBackground 组件,实现按需加载。
typescript
// src/components/ParticlesBackground/
index.tsx
import React, { useCallback } from 'react'
import Particles from 'react-tsparticles'
import { loadSlim } from
'tsparticles-slim'
import type { Container, Engine } from
'tsparticles-engine'
import particlesConfig from '@/assets/
particles/particle.json'
const ParticlesBackground: React.FC = ()
=> {
const particlesInit = useCallback(async
(engine: Engine) => {
// 使用 slim 版本减少包体积
await loadSlim(engine)
}, [])
const particlesLoaded = useCallback
(async (container: Container |
undefined) => {
// 粒子加载完成回调
}, [])
return (
<Particles
id="tsparticles"
init={particlesInit}
loaded={particlesLoaded}
options={particlesConfig as any}
/>
)
}
export default ParticlesBackground
优化效果 :减少主包体积约 50KB,且粒子效果组件仅在需要时加载,不阻塞首屏渲染
2. GSAP 按需加载优化
问题 :多个组件分别导入 GSAP 及其插件,导致代码冗余和重复打包,增加了包体积。
解决方案 :创建统一的 GSAP 工具文件,实现插件按需加载。
dart
// src/utils/gsap.ts
import gsap from 'gsap'
// GSAP 按需加载工具
export const loadGSAPPlugins = {
// 加载 ScrollTrigger 插件
scrollTrigger: async () => {
const { ScrollTrigger } = await import
('gsap/ScrollTrigger')
gsap.registerPlugin(ScrollTrigger)
return ScrollTrigger
},
// 加载 ScrollSmoother 插件
scrollSmoother: async () => {
const { ScrollSmoother } = await
import('gsap/ScrollSmoother')
gsap.registerPlugin(ScrollSmoother)
return ScrollSmoother
},
// 加载 Draggable 插件
draggable: async () => {
const { Draggable } = await import
('gsap/Draggable')
gsap.registerPlugin(Draggable)
return Draggable
},
// 加载 TextPlugin 插件
textPlugin: async () => {
const { TextPlugin } = await import
('gsap/TextPlugin')
gsap.registerPlugin(TextPlugin)
return TextPlugin
},
// 加载 MorphSVGPlugin 插件
morphSVG: async () => {
const { MorphSVGPlugin } = await
import('gsap/MorphSVGPlugin')
gsap.registerPlugin(MorphSVGPlugin)
return MorphSVGPlugin
}
}
export { gsap }
使用示例 :
javascript
// 在组件中使用
import { gsap, loadGSAPPlugins } from '@/
utils/gsap'
const MyComponent = () => {
useEffect(() => {
const initAnimations = async () => {
// 按需加载所需插件
const [ScrollTrigger, Draggable] =
await Promise.all([
loadGSAPPlugins.scrollTrigger(),
loadGSAPPlugins.draggable()
])
// 使用插件创建动画
gsap.to('.element', { duration: 1,
x: 100 })
}
initAnimations()
}, [])
}
优化效果 :
- 避免重复导入,减少代码冗余
- 实现真正的按需加载
- 减少主包体积约 30KB
3. ByteMD 编辑器懒加载优化
问题 :ByteMD 编辑器及其插件只在 Create 页面使用,但被打包到主包中。
解决方案 :创建懒加载的编辑器组件。
typescript
// src/components/LazyBytemdEditor/index.
tsx
import React, { Suspense } from 'react'
import { Spin } from 'antd'
// 懒加载 ByteMD 编辑器
const BytemdEditor = React.lazy(() =>
import('./BytemdEditor'))
interface LazyBytemdEditorProps {
value: string
onChange: (value: string) => void
}
const LazyBytemdEditor: React.
FC<LazyBytemdEditorProps> = ({ value,
onChange }) => {
return (
<Suspense fallback={<Spin
size="large" style={{ display:
'flex', justifyContent: 'center',
padding: '50px' }} />}>
<BytemdEditor value={value}
onChange={onChange} />
</Suspense>
)
}
export default LazyBytemdEditor
typescript
// src/components/LazyBytemdEditor/
BytemdEditor.tsx
import React from 'react'
import { Editor } from '@bytemd/react'
import breaks from '@bytemd/plugin-breaks'
import frontmatter from '@bytemd/
plugin-frontmatter'
import gfm from '@bytemd/plugin-gfm'
import highlight from '@bytemd/
plugin-highlight'
import rehypeSlug from 'rehype-slug'
import zhHans from 'bytemd/locales/
zh_Hans.json'
import 'bytemd/dist/index.min.css'
import 'highlight.js/styles/atom-one-dark.
css'
// 创建自定义插件
const headingAnchorPlugin = () => {
return {
rehype: (processor: any) => processor.
use(rehypeSlug),
}
}
const plugins = [
breaks(),
frontmatter(),
gfm(),
highlight(),
headingAnchorPlugin()
]
interface BytemdEditorProps {
value: string
onChange: (value: string) => void
}
const BytemdEditor: React.
FC<BytemdEditorProps> = ({ value,
onChange }) => {
return (
<Editor
locale={zhHans}
value={value}
plugins={plugins}
onChange={(v) => onChange(v)}
/>
)
}
export default BytemdEditor
优化效果 :
- 编辑器及插件只在需要时加载
- 减少主包体积约 40KB
- 提升首屏加载速度
优化结果
经过以上三个方面的优化,Bundle 分析结果显示:
- 优化前 :main.js 大小为 352KB
- 优化后 :main.js 大小为 273KB
- 优化幅度 :减少 79KB,优化比例达到 22.4%
详细的包大小分布(gzip 后):

4. 代码分割 (Code Splitting) 与 Tree Shaking 优化
接下来配置webpack的代码分割Code Splitting,treeshaking,
js
if (process.env.NODE_ENV === 'production') {
// 配置代码分割
webpackConfig.optimization = {
...webpackConfig.optimization,
splitChunks: {
chunks: 'all',
cacheGroups: {
// 将 React 相关库单独打包
react: {
name: 'react-vendors',
test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)[\\/]/,
priority: 20,
chunks: 'all',
},
// 将 Ant Design 单独打包
antd: {
name: 'antd-vendors',
test: /[\\/]node_modules[\\/](@ant-design|antd)[\\/]/,
priority: 15,
chunks: 'all',
},
// 将其他第三方库打包
vendors: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'all',
minSize: 30000,
maxSize: 250000,
},
// 公共代码
common: {
name: 'common',
minChunks: 2,
priority: 5,
chunks: 'all',
enforce: true,
},
},
},
// 启用 Tree Shaking
usedExports: true,
sideEffects: false,
};
}
现在mian.js的大小只有7.45 KB,实现了质的飞跃。
5. Ant Design 按需加载优化
注意到antd有265KB,项目中只使用了少量组件,和部分icons,可以用按需导入的方法来优化
babel:
plugins: [
['import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}, 'antd'],
['import', {
libraryName: '@ant-design/icons',
libraryDirectory: 'es/icons',
camel2DashComponentName: false,
}, 'antd-icons']
]
}
优化效果:Ant Design 相关包体积从 265KB 降至 139KB,减少了近一半,优化效果显著。
三、优化成果总结
通过一系列有针对性的优化措施,项目包体积得到了全面优化:
优化阶段 | 主包体积 | 优化幅度 | 其他成果 |
---|---|---|---|
初始状态 | 352KB | - | 多个 chunk 体积超 200KB |
库按需加载后 | 273KB | 22.4% | 减少 79KB |
代码分割后 | 7.45KB | 97.9% | 相比初始状态减少 97.9% |
AntD 按需加载后 | 7.45KB | 保持不变 | AntD 包体积减少 |