Skip to content
On this page

代码分割

代码分割是现代 Web 应用性能优化的核心技术之一,它允许我们将代码拆分成更小的块,按需加载,从而减少初始加载时间。本指南将详细介绍各种代码分割策略和技术。

Webpack 代码分割

入口点分割

javascript
// webpack.config.js
module.exports = {
  mode: 'production',
  entry: {
    // 主入口点
    main: './src/index.js',
    // 第三方库入口点
    vendor: './src/vendor.js',
    // 特定功能入口点
    dashboard: './src/dashboard.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 分离第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },
        // 分离公共代码
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true
        },
        // 分离大库
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          chunks: 'all',
          priority: 20
        },
        // 分离样式
        styles: {
          name: 'styles',
          type: 'css/mini-extract',
          chunks: 'all',
          enforce: true
        }
      }
    },
    // 分离运行时代码
    runtimeChunk: {
      name: 'runtime'
    }
  }
};

动态导入分割

javascript
// 使用动态导入实现代码分割
class CodeSplitter {
  // 懒加载组件
  static async loadComponent(componentName) {
    switch (componentName) {
      case 'Dashboard':
        const { Dashboard } = await import(
          /* webpackChunkName: "dashboard" */ 
          './components/Dashboard'
        );
        return Dashboard;
      
      case 'UserProfile':
        const { UserProfile } = await import(
          /* webpackChunkName: "user-profile" */ 
          './components/UserProfile'
        );
        return UserProfile;
      
      case 'AdminPanel':
        const { AdminPanel } = await import(
          /* webpackChunkName: "admin-panel" */ 
          './components/AdminPanel'
        );
        return AdminPanel;
      
      default:
        throw new Error(`Unknown component: ${componentName}`);
    }
  }
  
  // 按功能模块分割
  static async loadFeature(featureName) {
    return await import(
      /* webpackChunkName: "feature-[request]" */ 
      `./features/${featureName}`
    );
  }
}

// 使用示例
async function initializeApp() {
  const dashboard = await CodeSplitter.loadComponent('Dashboard');
  const appContainer = document.getElementById('app');
  dashboard.render(appContainer);
}

React 代码分割

React.lazy 和 Suspense

jsx
import React, { lazy, Suspense } from 'react';

// 懒加载组件
const HomePage = lazy(() => import('./pages/Home'));
const AboutPage = lazy(() => import('./pages/About'));
const ContactPage = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

// 路由级别的代码分割
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <div className="app">
        <Navigation />
        <Suspense fallback={<div className="loading">Loading...</div>}>
          <Routes>
            <Route path="/" element={<HomePage />} />
            <Route path="/about" element={<AboutPage />} />
            <Route path="/contact" element={<ContactPage />} />
            <Route path="/dashboard/*" element={<DashboardRoutes />} />
          </Routes>
        </Suspense>
      </div>
    </BrowserRouter>
  );
}

// 嵌套路由的代码分割
function DashboardRoutes() {
  const UserManagement = lazy(() => import('./dashboard/UserManagement'));
  const Analytics = lazy(() => import('./dashboard/Analytics'));
  const Reports = lazy(() => import('./dashboard/Reports'));
  
  return (
    <Routes>
      <Route path="users" element={<UserManagement />} />
      <Route path="analytics" element={<Analytics />} />
      <Route path="reports" element={<Reports />} />
    </Routes>
  );
}

// 组件级别的代码分割
function FeatureToggle({ feature }) {
  const [component, setComponent] = useState(null);
  
  useEffect(() => {
    async function loadFeature() {
      let FeatureComponent;
      
      switch (feature) {
        case 'chat':
          FeatureComponent = (await import('./features/Chat')).default;
          break;
        case 'notifications':
          FeatureComponent = (await import('./features/Notifications')).default;
          break;
        case 'calendar':
          FeatureComponent = (await import('./features/Calendar')).default;
          break;
        default:
          return;
      }
      
      setComponent(FeatureComponent);
    }
    
    if (feature) {
      loadFeature();
    }
  }, [feature]);
  
  if (component) {
    const Component = component;
    return <Component />;
  }
  
  return null;
}

高级懒加载模式

jsx
// 预加载策略
class PreloadManager {
  constructor() {
    this.preloadedChunks = new Set();
    this.loadingPromises = new Map();
  }
  
  // 预加载组件
  async preloadComponent(importFunction, chunkName) {
    if (this.preloadedChunks.has(chunkName)) {
      return;
    }
    
    if (this.loadingPromises.has(chunkName)) {
      return this.loadingPromises.get(chunkName);
    }
    
    const promise = importFunction().then(() => {
      this.preloadedChunks.add(chunkName);
      this.loadingPromises.delete(chunkName);
    });
    
    this.loadingPromises.set(chunkName, promise);
    return promise;
  }
  
  // 智能预加载
  preloadBasedOnRoute(currentRoute) {
    const preloadMap = {
      '/': ['dashboard', 'analytics'],
      '/dashboard': ['reports', 'settings'],
      '/profile': ['settings', 'notifications']
    };
    
    const toPreload = preloadMap[currentRoute] || [];
    toPreload.forEach(chunk => {
      this.preloadComponent(
        () => import(`./chunks/${chunk}`),
        chunk
      );
    });
  }
}

const preloadManager = new PreloadManager();

// 自定义懒加载 Hook
function useLazyComponent(importFunction, chunkName) {
  const [Component, setComponent] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let isCancelled = false;
    
    async function loadComponent() {
      try {
        setLoading(true);
        const module = await importFunction();
        if (!isCancelled) {
          setComponent(module.default || module);
          setLoading(false);
        }
      } catch (err) {
        if (!isCancelled) {
          setError(err);
          setLoading(false);
        }
      }
    }
    
    loadComponent();
    
    return () => {
      isCancelled = true;
    };
  }, []);
  
  return { Component, loading, error };
}

// 使用自定义 Hook
function DynamicComponent({ componentName }) {
  const { Component, loading, error } = useLazyComponent(
    () => import(`./components/${componentName}`),
    componentName
  );
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error loading component</div>;
  if (Component) return <Component />;
  
  return null;
}

Vue 代码分割

Vue Router 代码分割

javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';

// 使用动态导入分割路由
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue'),
    children: [
      {
        path: 'analytics',
        name: 'Analytics',
        component: () => import(/* webpackChunkName: "analytics" */ '@/views/Analytics.vue')
      },
      {
        path: 'reports',
        name: 'Reports',
        component: () => import(/* webpackChunkName: "reports" */ '@/views/Reports.vue')
      }
    ]
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

Vue 组件懒加载

vue
<template>
  <div class="app">
    <!-- 懒加载组件 -->
    <component :is="currentComponent" v-if="currentComponent" />
    <div v-else>Loading...</div>
    
    <!-- 功能开关 -->
    <button @click="loadFeature('chat')">Load Chat</button>
    <button @click="loadFeature('calendar')">Load Calendar</button>
  </div>
</template>

<script>
import { defineAsyncComponent } from 'vue';

export default {
  name: 'App',
  data() {
    return {
      currentComponent: null,
      loading: false
    };
  },
  methods: {
    // 使用 Vue 的 defineAsyncComponent
    async loadFeature(featureName) {
      if (this.loading) return;
      
      this.loading = true;
      
      try {
        let component;
        
        switch (featureName) {
          case 'chat':
            component = defineAsyncComponent(() => 
              import(/* webpackChunkName: "chat" */ '@/components/Chat.vue')
            );
            break;
          case 'calendar':
            component = defineAsyncComponent(() => 
              import(/* webpackChunkName: "calendar" */ '@/components/Calendar.vue')
            );
            break;
          default:
            throw new Error(`Unknown feature: ${featureName}`);
        }
        
        this.currentComponent = component;
      } catch (error) {
        console.error('Failed to load component:', error);
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

高级分割策略

基于用户行为的分割

javascript
// 行为驱动的代码分割
class BehaviorDrivenSplitter {
  constructor() {
    this.userActions = new Map();
    this.predictionThreshold = 0.7; // 预测阈值
  }
  
  // 记录用户行为
  recordAction(userId, action, timestamp = Date.now()) {
    if (!this.userActions.has(userId)) {
      this.userActions.set(userId, []);
    }
    
    this.userActions.get(userId).push({
      action,
      timestamp
    });
  }
  
  // 预测用户下一步可能的行为
  predictNextAction(userId) {
    const actions = this.userActions.get(userId) || [];
    if (actions.length < 3) return null;
    
    // 简单的预测算法(实际应用中可能需要更复杂的机器学习模型)
    const recentActions = actions.slice(-5); // 最近5个动作
    const actionCounts = {};
    
    recentActions.forEach(action => {
      actionCounts[action.action] = (actionCounts[action.action] || 0) + 1;
    });
    
    const sortedActions = Object.entries(actionCounts)
      .sort(([,a], [,b]) => b - a);
    
    // 返回最可能的动作
    return sortedActions[0]?.[0] || null;
  }
  
  // 根据预测预加载
  async predictivePreload(userId) {
    const predictedAction = this.predictNextAction(userId);
    if (!predictedAction) return;
    
    // 预加载相关的代码块
    switch (predictedAction) {
      case 'view-dashboard':
        await import(/* webpackChunkName: "dashboard" */ './dashboard');
        break;
      case 'open-chat':
        await import(/* webpackChunkName: "chat" */ './chat');
        break;
      case 'upload-file':
        await import(/* webpackChunkName: "file-upload" */ './file-upload');
        break;
    }
  }
}

const splitter = new BehaviorDrivenSplitter();

// 监听用户行为
document.addEventListener('click', (e) => {
  const action = e.target.dataset.action;
  if (action) {
    splitter.recordAction('current-user', action);
    splitter.predictivePreload('current-user');
  }
});

基于设备能力的分割

javascript
// 设备能力检测和相应分割
class DeviceAwareSplitter {
  static detectCapabilities() {
    return {
      // 检测设备内存
      deviceMemory: navigator.deviceMemory || 4, // 假设4GB如果不可用
      // 检测硬件并发数
      hardwareConcurrency: navigator.hardwareConcurrency || 4,
      // 检测网络状况
      connection: navigator.connection || { effectiveType: '4g' },
      // 检测存储配额
      storageEstimate: navigator.storage && navigator.storage.estimate ?
        navigator.storage.estimate() : Promise.resolve({ quota: 10 * 1024 * 1024 * 1024 }) // 10GB 默认
    };
  }
  
  static async loadByDeviceCapabilities() {
    const capabilities = await this.detectCapabilities();
    
    // 根据设备能力加载不同版本的功能
    if (capabilities.deviceMemory >= 6) {
      // 高内存设备加载完整版
      return await import(/* webpackChunkName: "feature-full" */ './features/full');
    } else if (capabilities.deviceMemory >= 4) {
      // 中等内存设备加载标准版
      return await import(/* webpackChunkName: "feature-standard" */ './features/standard');
    } else {
      // 低内存设备加载精简版
      return await import(/* webpackChunkName: "feature-lite" */ './features/lite');
    }
  }
  
  // 根据网络状况分割
  static async loadByNetworkCondition() {
    const connection = navigator.connection;
    
    if (!connection) {
      // 网络信息不可用,假设良好网络
      return await this.loadStandardFeatures();
    }
    
    switch (connection.effectiveType) {
      case 'slow-2g':
      case '2g':
        return await import(/* webpackChunkName: "features-offline-first" */ './features/offline-first');
      
      case '3g':
        return await import(/* webpackChunkName: "features-reduced" */ './features/reduced');
      
      case '4g':
      default:
        return await this.loadStandardFeatures();
    }
  }
  
  static async loadStandardFeatures() {
    return await import(/* webpackChunkName: "features-standard" */ './features/standard');
  }
}

// 使用设备感知分割
async function initializeApp() {
  // 根据设备能力加载合适的功能
  const features = await DeviceAwareSplitter.loadByDeviceCapabilities();
  const appContainer = document.getElementById('app');
  
  features.initializeApp(appContainer);
}

分割优化工具

自定义分割插件

javascript
// Webpack 插件:智能分割分析
class IntelligentSplitPlugin {
  constructor(options = {}) {
    this.options = {
      minChunkSize: options.minChunkSize || 30000, // 30KB
      maxChunkSize: options.maxChunkSize || 244000, // 244KB
      cacheGroups: options.cacheGroups || {},
      ...options
    };
  }
  
  apply(compiler) {
    compiler.hooks.compilation.tap('IntelligentSplitPlugin', (compilation) => {
      // 分析模块依赖图
      compilation.hooks.optimizeChunks.tap('IntelligentSplitPlugin', (chunks) => {
        this.optimizeChunks(compilation, chunks);
      });
      
      // 生成分割报告
      compilation.hooks.afterOptimizeChunks.tap('IntelligentSplitPlugin', (chunks) => {
        this.generateReport(chunks);
      });
    });
  }
  
  optimizeChunks(compilation, chunks) {
    // 实现智能分割逻辑
    chunks.forEach(chunk => {
      if (chunk.size() > this.options.maxChunkSize) {
        this.splitLargeChunk(compilation, chunk);
      }
    });
  }
  
  splitLargeChunk(compilation, chunk) {
    // 根据模块使用频率和依赖关系进行分割
    const modules = Array.from(chunk.modulesIterable);
    const frequentModules = modules.filter(m => 
      m.getNumberOfUses() > 10 // 使用频率高的模块
    );
    
    if (frequentModules.length > 0) {
      // 创建新的共享块
      const newChunk = compilation.addChunk(
        `${chunk.name}-shared-${Date.now()}`
      );
      
      frequentModules.forEach(module => {
        chunk.removeModule(module);
        newChunk.addModule(module);
      });
    }
  }
  
  generateReport(chunks) {
    const report = {
      totalChunks: chunks.length,
      totalSize: chunks.reduce((sum, chunk) => sum + chunk.size(), 0),
      largestChunk: Math.max(...chunks.map(chunk => chunk.size())),
      smallestChunk: Math.min(...chunks.map(chunk => chunk.size())),
      averageChunkSize: chunks.reduce((sum, chunk) => sum + chunk.size(), 0) / chunks.length,
      chunks: chunks.map(chunk => ({
        name: chunk.name,
        size: chunk.size(),
        modules: chunk.getNumberOfModules()
      }))
    };
    
    console.log('Code Splitting Report:', report);
  }
}

// 在 Webpack 配置中使用
module.exports = {
  // ... 其他配置
  plugins: [
    new IntelligentSplitPlugin({
      minChunkSize: 20000,
      maxChunkSize: 200000
    })
  ]
};

性能监控和分析

分割性能监控

javascript
// 代码分割性能监控
class SplitPerformanceMonitor {
  constructor() {
    this.metrics = {
      chunkLoadTimes: [],
      cacheHitRates: [],
      bundleSizes: [],
      loadingSequences: []
    };
  }
  
  // 监控代码块加载
  async monitorChunkLoad(chunkName, loadFunction) {
    const startTime = performance.now();
    const startMark = `chunk-load-start-${chunkName}`;
    const endMark = `chunk-load-end-${chunkName}`;
    
    performance.mark(startMark);
    
    try {
      const result = await loadFunction();
      const endTime = performance.now();
      
      performance.mark(endMark);
      performance.measure(`chunk-load-${chunkName}`, startMark, endMark);
      
      // 记录指标
      this.metrics.chunkLoadTimes.push({
        chunkName,
        loadTime: endTime - startTime,
        timestamp: Date.now()
      });
      
      return result;
    } catch (error) {
      console.error(`Chunk load error for ${chunkName}:`, error);
      throw error;
    }
  }
  
  // 分析加载序列
  analyzeLoadingSequence(routes) {
    const sequence = routes.map((route, index) => ({
      route: route.name,
      estimatedLoadTime: this.estimateLoadTime(route.component),
      sequencePosition: index
    }));
    
    this.metrics.loadingSequences.push(sequence);
    return sequence;
  }
  
  estimateLoadTime(componentPath) {
    // 基于历史数据估计加载时间
    const historicalData = this.metrics.chunkLoadTimes
      .filter(item => item.chunkName.includes(componentPath.split('/').pop()))
      .map(item => item.loadTime);
    
    if (historicalData.length > 0) {
      return historicalData.reduce((a, b) => a + b, 0) / historicalData.length;
    }
    
    return 100; // 默认估计值
  }
  
  // 获取优化建议
  getOptimizationSuggestions() {
    const suggestions = [];
    
    // 检查是否有加载时间过长的块
    const slowChunks = this.metrics.chunkLoadTimes
      .filter(item => item.loadTime > 1000) // 1秒以上
      .map(item => item.chunkName);
    
    if (slowChunks.length > 0) {
      suggestions.push(`Split large chunks: ${slowChunks.join(', ')}`);
    }
    
    // 检查是否有过多的小块
    const smallChunks = this.metrics.bundleSizes
      .filter(size => size < 5000) // 5KB 以下
      .length;
    
    if (smallChunks > 5) {
      suggestions.push(`Merge small chunks to reduce HTTP requests`);
    }
    
    return suggestions;
  }
  
  getMetrics() {
    return {
      ...this.metrics,
      avgLoadTime: this.metrics.chunkLoadTimes.reduce((sum, item) => sum + item.loadTime, 0) / 
                   (this.metrics.chunkLoadTimes.length || 1)
    };
  }
}

const splitMonitor = new SplitPerformanceMonitor();

// 使用监控的代码分割
async function loadComponentWithMonitoring(componentName) {
  return await splitMonitor.monitorChunkLoad(
    componentName,
    () => import(`./components/${componentName}`)
  );
}

代码分割最佳实践

分割策略选择指南

场景推荐策略说明
第三方库Vendor 分割将第三方库分离,利用浏览器缓存
路由级别动态导入按需加载路由组件
功能模块功能分割将不常用功能分离
共享代码Common 分割提取多处使用的代码
Polyfills条件加载根据浏览器支持情况加载

检查清单

  • [ ] 实现路由级别的代码分割
  • [ ] 使用动态导入分割大型组件
  • [ ] 分离第三方库到 vendor 块
  • [ ] 提取公共代码到共享块
  • [ ] 实现懒加载和预加载策略
  • [ ] 根据设备能力调整分割策略
  • [ ] 监控和分析分割效果
  • [ ] 优化块大小平衡请求数量
  • [ ] 实施错误处理和降级方案
  • [ ] 使用适当的缓存策略

通过实施这些代码分割策略,您可以显著改善应用的加载性能和用户体验。