如何分析并优化 Bundle 大小?

76 阅读3分钟

如何分析并优化 Bundle 大小?

Bundle 分析工具

1. Next.js 内置分析

# 安装分析工具
npm install --save-dev @next/bundle-analyzer

# 或使用 yarn
yarn add -D @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
  // 其他配置
})
# 运行分析
ANALYZE=true npm run build
# 或
npm run build && npm run analyze

2. Webpack Bundle Analyzer

// next.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')

module.exports = {
  webpack: (config, { isServer }) => {
    if (process.env.ANALYZE === 'true') {
      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'server',
          openAnalyzer: true,
        })
      )
    }
    return config
  },
}

Bundle 大小优化策略

1. 代码分割 (Code Splitting)

// 动态导入组件
import dynamic from 'next/dynamic'

// 懒加载组件
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <p>Loading...</p>,
  ssr: false, // 禁用服务端渲染
})

// 条件加载
const AdminPanel = dynamic(() => import('./AdminPanel'), {
  loading: () => <p>Loading admin panel...</p>,
})

function MyPage({ user }) {
  return (
    <div>
      <h1>Main Content</h1>
      <HeavyComponent />
      {user.isAdmin && <AdminPanel />}
    </div>
  )
}

2. 路由级别代码分割

// app/dashboard/page.js - 自动代码分割
export default function Dashboard() {
  return <div>Dashboard Content</div>
}

// app/admin/page.js - 自动代码分割
export default function Admin() {
  return <div>Admin Content</div>
}

3. 第三方库优化

// 按需导入
import { debounce } from 'lodash/debounce'
// 而不是
import _ from 'lodash'

// 使用更轻量的替代品
import { format } from 'date-fns'
// 而不是
import moment from 'moment'

// 动态导入大型库
const Chart = dynamic(() => import('react-chartjs-2'), {
  loading: () => <div>Loading chart...</div>,
})

4. Tree Shaking 优化

// 使用 ES6 模块语法
import { specificFunction } from 'large-library'

// 避免默认导入
import * as utils from 'utils' // ❌ 导入整个模块
import { specificUtil } from 'utils' // ✅ 只导入需要的

// 配置 package.json
{
  "sideEffects": false, // 启用 tree shaking
  "module": "esm/index.js", // ES 模块入口
  "main": "cjs/index.js" // CommonJS 入口
}

实际优化案例

1. 电商网站优化

// 优化前:所有组件都在主 bundle 中
import ProductList from './ProductList'
import ProductDetail from './ProductDetail'
import ShoppingCart from './ShoppingCart'
import Checkout from './Checkout'

// 优化后:按需加载
const ProductList = dynamic(() => import('./ProductList'))
const ProductDetail = dynamic(() => import('./ProductDetail'))
const ShoppingCart = dynamic(() => import('./ShoppingCart'))
const Checkout = dynamic(() => import('./Checkout'))

function EcommerceApp() {
  const [currentPage, setCurrentPage] = useState('list')

  return (
    <div>
      {currentPage === 'list' && <ProductList />}
      {currentPage === 'detail' && <ProductDetail />}
      {currentPage === 'cart' && <ShoppingCart />}
      {currentPage === 'checkout' && <Checkout />}
    </div>
  )
}

2. 博客网站优化

// 优化前:所有功能都在主 bundle 中
import CodeHighlighter from './CodeHighlighter'
import ImageGallery from './ImageGallery'
import CommentSystem from './CommentSystem'

// 优化后:按需加载
const CodeHighlighter = dynamic(() => import('./CodeHighlighter'), {
  loading: () => <div>Loading code highlighter...</div>,
})

const ImageGallery = dynamic(() => import('./ImageGallery'), {
  loading: () => <div>Loading gallery...</div>,
})

const CommentSystem = dynamic(() => import('./CommentSystem'), {
  loading: () => <div>Loading comments...</div>,
})

function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />

      {post.hasCode && <CodeHighlighter code={post.code} />}
      {post.hasImages && <ImageGallery images={post.images} />}
      {post.allowComments && <CommentSystem postId={post.id} />}
    </article>
  )
}

高级优化技巧

1. 预加载关键资源

// 预加载关键路由
import Link from 'next/link'

function Navigation() {
  return (
    <nav>
      <Link href="/dashboard" prefetch={true}>
        Dashboard
      </Link>
      <Link href="/profile" prefetch={true}>
        Profile
      </Link>
    </nav>
  )
}

// 预加载关键组件
const CriticalComponent = dynamic(() => import('./CriticalComponent'), {
  loading: () => <div>Loading...</div>,
})

// 在页面加载时预加载
useEffect(() => {
  import('./CriticalComponent')
}, [])

2. 共享依赖优化

// next.config.js
module.exports = {
  webpack: (config, { isServer }) => {
    // 优化共享依赖
    config.optimization.splitChunks = {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          enforce: true,
        },
      },
    }
    return config
  },
}

3. 图片和字体优化

// 使用 next/image 优化图片
import Image from 'next/image'

function OptimizedImage({ src, alt }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={800}
      height={600}
      priority={false} // 非关键图片不优先加载
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,..."
    />
  )
}

// 使用 next/font 优化字体
import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  preload: true,
})

监控和分析

1. 构建时分析

# 分析构建输出
npm run build

# 查看构建统计
npm run build -- --profile

# 分析 bundle 大小
ANALYZE=true npm run build

2. 运行时监控

// 监控 bundle 加载性能
function PerformanceMonitor() {
  useEffect(() => {
    // 监控资源加载时间
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === 'resource') {
          console.log(`${entry.name}: ${entry.duration}ms`)
        }
      })
    })

    observer.observe({ entryTypes: ['resource'] })

    return () => observer.disconnect()
  }, [])

  return null
}

3. 持续监控

// 设置性能预算
// next.config.js
module.exports = {
  experimental: {
    webpackBuildWorker: true,
  },
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.optimization.splitChunks = {
        chunks: 'all',
        maxSize: 244000, // 设置最大 chunk 大小
      }
    }
    return config
  },
}

最佳实践总结

1. 代码分割策略

// 按路由分割
const Dashboard = dynamic(() => import('./Dashboard'))
const Profile = dynamic(() => import('./Profile'))

// 按功能分割
const Chart = dynamic(() => import('./Chart'))
const Table = dynamic(() => import('./Table'))

// 按用户权限分割
const AdminPanel = dynamic(() => import('./AdminPanel'))

2. 依赖优化

// 使用轻量级替代品
import { format } from 'date-fns' // 而不是 moment
import { debounce } from 'lodash/debounce' // 而不是整个 lodash

// 按需导入
import { Button } from 'antd' // 而不是整个 antd

3. 资源优化

// 图片优化
<Image src="/image.jpg" width={800} height={600} />

// 字体优化
const font = Inter({ display: 'swap' })

// 脚本优化
<Script src="/script.js" strategy="afterInteractive" />

总结

Bundle 大小优化策略:

分析工具

  • Next.js 内置分析器
  • Webpack Bundle Analyzer
  • 构建时和运行时监控

优化策略

  • 代码分割和动态导入
  • Tree shaking 和按需导入
  • 第三方库优化
  • 共享依赖优化

最佳实践

  • 按路由和功能分割代码
  • 使用轻量级替代品
  • 预加载关键资源
  • 设置性能预算
  • 持续监控和优化