Skip to content

现代前端工程化实践

随着前端项目复杂度的不断提升,工程化已成为现代前端开发的核心。本文将深入探讨前端工程化的各个方面,帮助你构建高效、可维护的前端项目。

1. 项目架构设计

目录结构规范

project/
├── src/
│   ├── components/          # 通用组件
│   │   ├── ui/             # 基础 UI 组件
│   │   └── business/       # 业务组件
│   ├── pages/              # 页面组件
│   ├── hooks/              # 自定义 Hooks
│   ├── utils/              # 工具函数
│   ├── services/           # API 服务
│   ├── stores/             # 状态管理
│   ├── types/              # TypeScript 类型定义
│   ├── assets/             # 静态资源
│   └── styles/             # 样式文件
├── public/                 # 公共资源
├── tests/                  # 测试文件
├── docs/                   # 项目文档
├── scripts/                # 构建脚本
└── config/                 # 配置文件

模块化设计原则

typescript
// 单一职责原则
export class UserService {
  async getUser(id: string): Promise<User> {
    return this.apiClient.get(`/users/${id}`)
  }
  
  async updateUser(id: string, data: Partial<User>): Promise<User> {
    return this.apiClient.put(`/users/${id}`, data)
  }
}

// 依赖注入
interface ApiClient {
  get<T>(url: string): Promise<T>
  post<T>(url: string, data: any): Promise<T>
  put<T>(url: string, data: any): Promise<T>
  delete<T>(url: string): Promise<T>
}

class HttpClient implements ApiClient {
  constructor(private baseURL: string) {}
  
  async get<T>(url: string): Promise<T> {
    const response = await fetch(`${this.baseURL}${url}`)
    return response.json()
  }
  
  // 其他方法实现...
}

2. 构建工具配置

Vite 配置优化

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia'],
      resolvers: [ElementPlusResolver()],
      dts: true,
    }),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: true,
    }),
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@components': resolve(__dirname, 'src/components'),
      '@utils': resolve(__dirname, 'src/utils'),
      '@types': resolve(__dirname, 'src/types'),
    },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          ui: ['element-plus'],
        },
      },
    },
    sourcemap: true,
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
})

Webpack 配置优化

javascript
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')

module.exports = {
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
    process.env.ANALYZE && new BundleAnalyzerPlugin(),
  ].filter(Boolean),
}

3. 代码质量保障

ESLint 配置

javascript
// .eslintrc.js
module.exports = {
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:vue/vue3-recommended',
    'prettier',
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/explicit-function-return-type': 'warn',
    'vue/multi-word-component-names': 'off',
    'vue/component-definition-name-casing': ['error', 'PascalCase'],
  },
}

Prettier 配置

json
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80,
  "bracketSpacing": true,
  "arrowParens": "avoid",
  "endOfLine": "lf"
}

Husky + lint-staged

json
// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss,less}": [
      "stylelint --fix",
      "prettier --write"
    ]
  }
}

4. 测试策略

单元测试

typescript
// utils/format.test.ts
import { describe, it, expect } from 'vitest'
import { formatDate, formatCurrency } from './format'

describe('format utils', () => {
  describe('formatDate', () => {
    it('should format date correctly', () => {
      const date = new Date('2024-01-15')
      expect(formatDate(date, 'YYYY-MM-DD')).toBe('2024-01-15')
    })
    
    it('should handle invalid date', () => {
      expect(formatDate(null)).toBe('')
    })
  })
  
  describe('formatCurrency', () => {
    it('should format currency with default locale', () => {
      expect(formatCurrency(1234.56)).toBe('¥1,234.56')
    })
    
    it('should format currency with custom locale', () => {
      expect(formatCurrency(1234.56, 'en-US')).toBe('$1,234.56')
    })
  })
})

组件测试

typescript
// components/UserCard.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserCard from './UserCard.vue'

describe('UserCard', () => {
  const mockUser = {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    avatar: 'https://example.com/avatar.jpg'
  }
  
  it('should render user information correctly', () => {
    const wrapper = mount(UserCard, {
      props: { user: mockUser }
    })
    
    expect(wrapper.text()).toContain('John Doe')
    expect(wrapper.text()).toContain('john@example.com')
    expect(wrapper.find('img').attributes('src')).toBe(mockUser.avatar)
  })
  
  it('should emit edit event when edit button is clicked', async () => {
    const wrapper = mount(UserCard, {
      props: { user: mockUser }
    })
    
    await wrapper.find('[data-testid="edit-button"]').trigger('click')
    
    expect(wrapper.emitted('edit')).toBeTruthy()
    expect(wrapper.emitted('edit')?.[0]).toEqual([mockUser])
  })
})

E2E 测试

typescript
// e2e/user-management.spec.ts
import { test, expect } from '@playwright/test'

test.describe('User Management', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/users')
  })
  
  test('should display user list', async ({ page }) => {
    await expect(page.locator('[data-testid="user-list"]')).toBeVisible()
    await expect(page.locator('.user-card')).toHaveCount(10)
  })
  
  test('should create new user', async ({ page }) => {
    await page.click('[data-testid="add-user-button"]')
    await page.fill('[data-testid="name-input"]', 'New User')
    await page.fill('[data-testid="email-input"]', 'newuser@example.com')
    await page.click('[data-testid="save-button"]')
    
    await expect(page.locator('text=User created successfully')).toBeVisible()
  })
})

5. 性能监控

构建分析

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

config.plugins.push(
  new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    openAnalyzer: true,
  })
)

webpack(config, (err, stats) => {
  if (err || stats.hasErrors()) {
    console.error('Build failed')
    return
  }
  console.log('Build completed successfully')
})

运行时性能监控

typescript
// utils/performance.ts
class PerformanceMonitor {
  private static instance: PerformanceMonitor
  
  static getInstance(): PerformanceMonitor {
    if (!this.instance) {
      this.instance = new PerformanceMonitor()
    }
    return this.instance
  }
  
  measurePageLoad(): void {
    window.addEventListener('load', () => {
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
      const metrics = {
        FCP: this.getFCP(),
        LCP: this.getLCP(),
        FID: this.getFID(),
        CLS: this.getCLS(),
        TTFB: navigation.responseStart - navigation.requestStart,
        domContentLoaded: navigation.domContentLoadedEventEnd - navigation.navigationStart,
        loadComplete: navigation.loadEventEnd - navigation.navigationStart,
      }
      
      this.sendMetrics(metrics)
    })
  }
  
  private getFCP(): number {
    const entries = performance.getEntriesByName('first-contentful-paint')
    return entries.length > 0 ? entries[0].startTime : 0
  }
  
  private getLCP(): Promise<number> {
    return new Promise(resolve => {
      new PerformanceObserver(list => {
        const entries = list.getEntries()
        const lastEntry = entries[entries.length - 1]
        resolve(lastEntry.startTime)
      }).observe({ entryTypes: ['largest-contentful-paint'] })
    })
  }
  
  private sendMetrics(metrics: Record<string, number>): void {
    // 发送到监控服务
    fetch('/api/metrics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(metrics),
    })
  }
}

// 使用
PerformanceMonitor.getInstance().measurePageLoad()

6. CI/CD 流程

GitHub Actions 配置

yaml
# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linting
        run: npm run lint
      
      - name: Run type checking
        run: npm run type-check
      
      - name: Run tests
        run: npm run test:coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
  
  build:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build application
        run: npm run build
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: dist
          path: dist/
      
      - name: Deploy to production
        run: |
          # 部署脚本
          echo "Deploying to production..."

Docker 配置

dockerfile
# Dockerfile
FROM node:18-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

7. 最佳实践总结

开发规范

  1. 代码规范:统一的代码风格和命名规范
  2. 组件设计:单一职责、可复用、可测试
  3. 状态管理:合理的状态划分和数据流
  4. 性能优化:懒加载、代码分割、缓存策略

工程化工具链

  1. 构建工具:Vite/Webpack + 插件生态
  2. 代码质量:ESLint + Prettier + TypeScript
  3. 测试框架:Vitest + Testing Library + Playwright
  4. CI/CD:GitHub Actions + Docker + 自动化部署

监控与维护

  1. 性能监控:Core Web Vitals + 自定义指标
  2. 错误监控:Sentry + 日志收集
  3. 依赖管理:定期更新 + 安全扫描
  4. 文档维护:API 文档 + 组件文档

总结

现代前端工程化是一个系统性工程,需要从项目架构、构建工具、代码质量、测试策略、性能监控、CI/CD 等多个维度进行考虑。通过建立完善的工程化体系,我们可以:

  • 提高开发效率和代码质量
  • 降低项目维护成本
  • 确保产品的稳定性和性能
  • 支持团队协作和知识传承

工程化不是一蹴而就的,需要根据项目实际情况逐步完善和优化。

相关文章

用代码构建未来,让技术改变世界 🚀 | 专注前端技术,分享开发经验