前端工程化概述
前端工程化是指将软件工程的方法和工具应用到前端开发中,以提高开发效率、代码质量和可维护性。
什么是前端工程化?
前端工程化涵盖了从项目初始化到部署上线的整个开发流程,包括但不限于:
- 模块化开发: ES Modules、CommonJS
- 组件化: React、Vue 组件
- 规范化: 代码规范、Git 规范、文档规范
- 自动化: 构建、测试、部署自动化
- 工具链: 构建工具、包管理器、调试工具
前端工程化体系
1. 开发规范
代码规范
JavaScript/TypeScript 规范:
javascript
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'prettier'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' : 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off'
}
};CSS 规范:
javascript
// .stylelintrc.js
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-prettier'
],
rules: {
'selector-class-pattern': '^[a-z][a-zA-Z0-9]+$',
'color-hex-length': 'short'
}
};Git 提交规范
bash
# Conventional Commits 规范
feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式调整
refactor: 重构
test: 测试相关
chore: 构建/工具链相关
# 示例
feat(user): add user login functionality
fix(api): resolve CORS issue in production
docs(readme): update installation instructions配置 commitlint:
javascript
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert']
],
'subject-case': [0]
}
};2. 构建工具
Vite
新一代前端构建工具,开发体验极佳。
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils')
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['antd', '@ant-design/icons']
}
}
},
chunkSizeWarningLimit: 1000
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
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');
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: 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'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
priority: 10
}
}
}
}
};3. 包管理
npm/yarn/pnpm 对比
| 特性 | npm | yarn | pnpm |
|---|---|---|---|
| 速度 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 磁盘空间 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 安全性 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Monorepo支持 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
pnpm 的优势:
bash
# 硬链接和符号链接节省磁盘空间
pnpm install
# 严格的依赖管理,避免幽灵依赖
pnpm add package-name
# Monorepo 支持
pnpm --filter @workspace/app1 buildpackage.json 最佳实践
json
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
"test": "vitest",
"test:ui": "vitest --ui",
"prepare": "husky install"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.45.0",
"husky": "^8.0.0",
"lint-staged": "^13.0.0",
"prettier": "^3.0.0",
"typescript": "^5.0.0",
"vite": "^4.4.0",
"vitest": "^0.34.0"
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,md}": "prettier --write"
}
}4. 代码质量
TypeScript 集成
typescript
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Path mapping */
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}Git Hooks 自动化
bash
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
# .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit $15. 测试策略
单元测试 (Vitest)
typescript
// src/utils/math.test.ts
import { describe, it, expect } from 'vitest';
import { add, multiply } from './math';
describe('Math Utils', () => {
it('should add two numbers correctly', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
it('should multiply two numbers correctly', () => {
expect(multiply(3, 4)).toBe(12);
expect(multiply(-2, 3)).toBe(-6);
});
});组件测试 (React Testing Library)
typescript
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import Button from './Button';
describe('Button Component', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick handler when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('disables button when disabled prop is true', () => {
render(<Button disabled>Disabled</Button>);
expect(screen.getByText('Disabled')).toBeDisabled();
});
});E2E 测试 (Playwright)
typescript
// e2e/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('User Login', () => {
test('should login successfully with valid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'Password123!');
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('[data-testid="user-name"]')).toContainText('User');
});
test('should show error with invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'wrong@example.com');
await page.fill('[data-testid="password"]', 'wrongpassword');
await page.click('[data-testid="login-button"]');
await expect(page.locator('[data-testid="error-message"]'))
.toContainText('Invalid credentials');
});
});6. CI/CD 流程
GitHub Actions 配置
yaml
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'pnpm'
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm run lint
- name: Type check
run: pnpm run type-check
- name: Run tests
run: pnpm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
build:
needs: lint-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'pnpm'
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: dist
path: dist/
- name: Deploy to production
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist7. 性能优化
构建优化
javascript
// vite.config.ts
export default defineConfig({
build: {
// 代码分割
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
// 将第三方库单独打包
return 'vendor';
}
}
}
},
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// 生成 source map
sourcemap: process.env.NODE_ENV !== 'production'
}
});资源优化
typescript
// 图片懒加载
import { lazy, Suspense } from 'react';
const LazyImage = lazy(() => import('./LazyImage'));
function Gallery() {
return (
<Suspense fallback={<Skeleton />}>
<LazyImage src="large-image.jpg" alt="Large image" />
</Suspense>
);
}
// 路由懒加载
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}8. Monorepo 管理
项目结构
my-monorepo/
├── packages/
│ ├── ui-components/ # 组件库
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── utils/ # 工具库
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── app/ # 应用
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── pnpm-workspace.yaml
├── package.json
└── tsconfig.base.jsonpnpm workspace 配置
yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'json
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"scripts": {
"dev": "pnpm --filter @my-app/app dev",
"build": "pnpm -r --filter @my-app/* build",
"lint": "pnpm -r lint",
"test": "pnpm -r test"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
"eslint": "^8.45.0",
"typescript": "^5.0.0"
}
}开发工具链
VSCode 配置
json
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}推荐插件
- ESLint
- Prettier
- TypeScript Vue Plugin (Volar)
- GitLens
- Error Lens
- Auto Rename Tag
- Path Intellisense
最佳实践总结
1. 项目初始化清单
- [ ] 选择合适的构建工具(Vite/Webpack)
- [ ] 配置 TypeScript
- [ ] 设置代码规范(ESLint + Prettier)
- [ ] 配置 Git Hooks(Husky + lint-staged)
- [ ] 设置提交规范(commitlint)
- [ ] 配置测试框架(Vitest/Jest)
- [ ] 设置 CI/CD 流程
- [ ] 配置环境变量管理
2. 开发流程规范
- 需求分析 → 确定技术方案
- 设计评审 → 确保架构合理
- 开发实现 → 遵循代码规范
- 自测 → 单元测试 + 手工测试
- 代码审查 → Peer Review
- 集成测试 → CI 自动化测试
- 部署上线 → CD 自动化部署
- 监控反馈 → 性能监控 + 错误追踪
3. 代码审查要点
- 代码风格是否符合规范
- 是否有潜在的性能问题
- 错误处理是否完善
- 是否有足够的测试覆盖
- 组件设计是否合理
- 是否有安全隐患
