Web性能优化 web-performance-optimization

通过代码分割、懒加载、缓存、压缩和监控等技术手段提升Web应用的性能,改善用户体验和页面加载速度。

前端开发 0 次安装 0 次浏览 更新于 3/4/2026

name: web-performance-optimization description: 优化Web应用程序性能,使用代码分割、懒加载、缓存、压缩和监控。用于提高核心Web Vitals和用户体验。

Web性能优化

概览

实现性能优化策略,包括懒加载、代码分割、缓存、压缩和监控,以提高核心Web Vitals和用户体验。

何时使用

  • 页面加载速度慢
  • 最大内容绘制(LCP)高
  • 捆绑包尺寸大
  • 频繁的累积布局偏移(CLS)
  • 移动性能问题

实施示例

1. 代码分割和懒加载(React)

// utils/lazyLoad.ts
import React from 'react';

export const lazyLoad = (importStatement: Promise<any>) => {
  return React.lazy(() =>
    importStatement.then(module => ({
      default: module.default
    }))
  );
};

// routes.tsx
import { lazyLoad } from './utils/lazyLoad';

export const routes = [
  {
    path: '/',
    component: () => import('./pages/Home'),
    lazy: lazyLoad(import('./pages/Home'))
  },
  {
    path: '/dashboard',
    lazy: lazyLoad(import('./pages/Dashboard'))
  },
  {
    path: '/users',
    lazy: lazyLoad(import('./pages/Users'))
  }
];

// App.tsx with Suspense
import { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

export const App = () => {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          {routes.map(route => (
            <Route key={route.path} path={route.path} element={<route.lazy />} />
          ))}
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        },
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  }
};

2. 图片优化

<!-- Picture元素与srcset响应式图片 -->
<picture>
  <source media="(min-width: 1024px)" srcset="image-large.jpg, image-large@2x.jpg 2x" />
  <source media="(min-width: 640px)" srcset="image-medium.jpg, image-medium@2x.jpg 2x" />
  <source srcset="image-small.jpg, image-small@2x.jpg 2x" />
  <img src="image-fallback.jpg" alt="描述" loading="lazy" />
</picture>

<!-- WebP格式与回退 -->
<picture>
  <source srcset="image.webp" type="image/webp" />
  <img src="image.jpg" alt="描述" loading="lazy" />
</picture>

<!-- TypeScript图片组件 -->
<script lang="typescript">
interface ImageProps {
  src: string;
  alt: string;
  width: number;
  height: number;
  sizes?: string;
  loading?: 'lazy' | 'eager';
}

const OptimizedImage: React.FC<ImageProps> = ({
  src,
  alt,
  width,
  height,
  sizes = '100vw',
  loading = 'lazy'
}) => {
  const webpSrc = src.replace(/\.(jpg|png)$/, '.webp');

  return (
    <picture>
      <source srcSet={webpSrc} type="image/webp" />
      <img
        src={src}
        alt={alt}
        width={width}
        height={height}
        sizes={sizes}
        loading={loading}
        decoding="async"
      />
    </picture>
  );
};
</script>

3. HTTP缓存和服务工作者

// service-worker.ts
const CACHE_NAME = 'v1';
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/css/style.css',
  '/js/app.js'
];

self.addEventListener('install', (event: ExtendableEvent) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(ASSETS_TO_CACHE);
    })
  );
});

self.addEventListener('fetch', (event: FetchEvent) => {
  // 缓存优先,回退到网络
  event.respondWith(
    caches.match(event.request).then(response => {
      if (response) return response;

      return fetch(event.request).then(response => {
        // 克隆响应
        const cloned = response.clone();

        // 缓存成功的响应
        if (response.status === 200) {
          caches.open(CACHE_NAME).then(cache => {
            cache.put(event.request, cloned);
          });
        }

        return response;
      }).catch(() => {
        // 如果有离线页面则返回
        return caches.match('/offline.html');
      });
    })
  );
});

// 注册服务工作者
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .catch(err => console.error('SW注册失败:', err));
  });
}

4. Gzip压缩和资产优化

// webpack.config.js带压缩
const CompressionPlugin = require('compression-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true
          }
        }
      })
    ]
  },
  plugins: [
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8
    })
  ]
};

// .htaccess(Apache)
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
</IfModule>

# nginx.conf
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_proxied any;

5. 性能监控

// utils/performanceMonitor.ts
interface PerformanceMetrics {
  fcp: number;  // 第一次内容绘制
  lcp: number;  // 最大内容绘制
  cls: number;  // 累积布局偏移
  fid: number; // 第一次输入延迟
  ttfb: number; // 第一个字节时间
}

export const observeWebVitals = (callback: (metrics: Partial<PerformanceMetrics>) => void) => {
  const metrics: Partial<PerformanceMetrics> = {};

  // LCP
  const lcpObserver = new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const lastEntry = entries[entries.length - 1];
    metrics.lcp = lastEntry.renderTime || lastEntry.loadTime;
    callback(metrics);
  });

  try {
    lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
  } catch (e) {
    console.warn('LCP观察者不支持');
  }

  // CLS
  const clsObserver = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (!(entry as any).hadRecentInput) {
        metrics.cls = (metrics.cls || 0) + (entry as any).value;
        callback(metrics);
      }
    }
  });

  try {
    clsObserver.observe({ entryTypes: ['layout-shift'] });
  } catch (e) {
    console.warn('CLS观察者不支持');
  }

  // FID通过INP
  const inputObserver = new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const firstEntry = entries[0];
    metrics.fid = firstEntry.processingDuration;
    callback(metrics);
  });

  try {
    inputObserver.observe({ entryTypes: ['first-input', 'event'] });
  } catch (e) {
    console.warn('FID观察者不支持');
  }

  // TTFB
  const navigationTiming = performance.getEntriesByType('navigation')[0];
  if (navigationTiming) {
    metrics.ttfb = (navigationTiming as any).responseStart - (navigationTiming as any).requestStart;
    callback(metrics);
  }
};

// 使用
observeWebVitals((metrics) => {
  console.log('性能指标:', metrics);
  // 发送到分析
  fetch('/api/metrics', {
    method: 'POST',
    body: JSON.stringify(metrics)
  });
});

// Chrome DevTools协议性能测试
import puppeteer from 'puppeteer';

async function measurePagePerformance(url: string) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto(url, { waitUntil: 'networkidle2' });

  const metrics = JSON.parse(
    await page.evaluate(() => JSON.stringify(window.performance))
  );

  console.log('页面加载时间:', metrics.timing.loadEventEnd - metrics.timing.navigationStart);
  console.log('DOM内容已加载:', metrics.timing.domContentLoadedEventEnd - metrics.timing.navigationStart);

  await browser.close();
}

最佳实践

  • 通过代码分割最小化捆绑包大小
  • 使用适当的格式优化图像
  • 策略性地实现懒加载
  • 使用HTTP缓存头
  • 启用gzip/brotli压缩
  • 持续监控核心Web Vitals
  • 实施服务工作者
  • 延迟非关键JavaScript
  • 优化关键渲染路径
  • 在真实设备和网络上进行测试

资源