- 发布于
前端工程化最佳实践:代码规范、构建优化与部署策略
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
前端工程化最佳实践:代码规范、构建优化与部署策略
前端工程化是现代Web开发的基石,本文将分享在实际项目中积累的工程化经验,涵盖代码规范、构建优化、部署策略等方面的最佳实践。
代码规范与质量控制
ESLint配置最佳实践
// .eslintrc.js - 完整的ESLint配置
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'prettier', // 必须放在最后
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
project: './tsconfig.json',
},
plugins: [
'react',
'@typescript-eslint',
'react-hooks',
'jsx-a11y',
'import',
],
rules: {
// 代码质量规则
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
'no-unused-vars': 'off', // 由TypeScript处理
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// React相关规则
'react/react-in-jsx-scope': 'off', // React 17+不需要
'react/prop-types': 'off', // 使用TypeScript
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// 导入规则
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'import/no-unresolved': 'error',
'import/no-cycle': 'error',
// TypeScript规则
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/prefer-const': 'error',
'@typescript-eslint/no-var-requires': 'error',
},
settings: {
react: {
version: 'detect',
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
},
},
overrides: [
{
files: ['**/*.test.{js,jsx,ts,tsx}', '**/*.spec.{js,jsx,ts,tsx}'],
env: {
jest: true,
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
],
};
Prettier配置与集成
// .prettierrc.json
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "lf",
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"proseWrap": "preserve"
}
// .prettierignore
# 构建产物
dist/
build/
coverage/
# 依赖
node_modules/
# 配置文件
*.min.js
*.min.css
# 文档
CHANGELOG.md
Git Hooks自动化
// package.json - husky和lint-staged配置
{
"scripts": {
"prepare": "husky install",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
"type-check": "tsc --noEmit"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"src/**/*.{json,css,md}": [
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-push": "npm run type-check && npm run test"
}
}
}
#!/bin/sh
# .husky/pre-commit
. "$(dirname "$0")/_/husky.sh"
# 运行lint-staged
npx lint-staged
# 类型检查
npm run type-check
# 运行测试
npm run test:staged
提交信息规范
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能
'fix', // 修复bug
'docs', // 文档更新
'style', // 代码格式调整
'refactor', // 重构
'perf', // 性能优化
'test', // 测试相关
'chore', // 构建过程或辅助工具的变动
'ci', // CI配置文件和脚本的变动
'build', // 构建系统或外部依赖的变动
'revert', // 回滚
],
],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'header-max-length': [2, 'always', 72],
},
};
构建优化策略
Webpack配置优化
// webpack.config.js - 生产环境优化配置
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const CompressionPlugin = require('compression-webpack-plugin');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
mode: isProduction ? 'production' : 'development',
entry: {
main: './src/index.tsx',
vendor: ['react', 'react-dom'], // 分离第三方库
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction
? 'js/[name].[contenthash:8].js'
: 'js/[name].js',
chunkFilename: isProduction
? 'js/[name].[contenthash:8].chunk.js'
: 'js/[name].chunk.js',
publicPath: '/',
clean: true, // 清理输出目录
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@hooks': path.resolve(__dirname, 'src/hooks'),
'@types': path.resolve(__dirname, 'src/types'),
},
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }],
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-transform-runtime',
isProduction && 'babel-plugin-transform-remove-console',
].filter(Boolean),
},
},
],
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
modules: {
auto: true,
localIdentName: isProduction
? '[hash:base64:8]'
: '[name]__[local]--[hash:base64:5]',
},
},
},
'postcss-loader',
],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8KB以下转为base64
},
},
generator: {
filename: 'images/[name].[hash:8][ext]',
},
},
],
},
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
}),
new CssMinimizerPlugin(),
],
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,
},
},
},
runtimeChunk: {
name: 'runtime',
},
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
} : false,
}),
isProduction && new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
isProduction && new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
}),
process.env.ANALYZE && new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
].filter(Boolean),
devServer: {
hot: true,
open: true,
historyApiFallback: true,
compress: true,
port: 3000,
},
};
Vite配置优化
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { createHtmlPlugin } from 'vite-plugin-html';
export default defineConfig(({ command, mode }) => {
const isProduction = mode === 'production';
return {
plugins: [
react(),
createHtmlPlugin({
minify: isProduction,
inject: {
data: {
title: 'My App',
},
},
}),
isProduction && visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true,
}),
].filter(Boolean),
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@hooks': resolve(__dirname, 'src/hooks'),
'@types': resolve(__dirname, 'src/types'),
},
},
build: {
target: 'es2015',
outDir: 'dist',
assetsDir: 'assets',
sourcemap: !isProduction,
minify: isProduction ? 'terser' : false,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
ui: ['antd', '@ant-design/icons'],
},
chunkFileNames: 'js/[name].[hash].js',
entryFileNames: 'js/[name].[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.');
const ext = info[info.length - 1];
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
return `media/[name].[hash].${ext}`;
}
if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) {
return `images/[name].[hash].${ext}`;
}
if (ext === 'css') {
return `css/[name].[hash].${ext}`;
}
return `assets/[name].[hash].${ext}`;
},
},
},
chunkSizeWarningLimit: 1000,
},
server: {
port: 3000,
open: true,
cors: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
};
});
自动化部署策略
GitHub Actions CI/CD
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
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 to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
NODE_ENV: production
REACT_APP_API_URL: ${{ secrets.API_URL }}
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-files
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: build-files
path: dist/
- name: Deploy to S3
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync files to S3
run: |
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }} --delete
aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_ID }} --paths "/*"
Docker化部署
# Dockerfile - 多阶段构建
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production && npm cache clean --force
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产阶段
FROM nginx:alpine AS production
# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf
# 复制构建产物
COPY /app/dist /usr/share/nginx/html
# 暴露端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# 缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# SPA路由支持
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
}
环境配置管理
多环境配置
// src/config/index.ts
interface Config {
apiUrl: string;
appName: string;
version: string;
environment: 'development' | 'staging' | 'production';
features: {
analytics: boolean;
debugging: boolean;
mockApi: boolean;
};
}
const configs: Record<string, Config> = {
development: {
apiUrl: 'http://localhost:8080/api',
appName: 'My App (Dev)',
version: process.env.REACT_APP_VERSION || '1.0.0',
environment: 'development',
features: {
analytics: false,
debugging: true,
mockApi: true,
},
},
staging: {
apiUrl: 'https://staging-api.example.com/api',
appName: 'My App (Staging)',
version: process.env.REACT_APP_VERSION || '1.0.0',
environment: 'staging',
features: {
analytics: true,
debugging: true,
mockApi: false,
},
},
production: {
apiUrl: 'https://api.example.com/api',
appName: 'My App',
version: process.env.REACT_APP_VERSION || '1.0.0',
environment: 'production',
features: {
analytics: true,
debugging: false,
mockApi: false,
},
},
};
const environment = process.env.NODE_ENV || 'development';
export const config = configs[environment];
项目脚本管理
// package.json - 完整的脚本配置
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build:analyze": "npm run build && npx vite-bundle-analyzer",
"preview": "vite preview",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
"type-check": "tsc --noEmit",
"clean": "rimraf dist",
"prepare": "husky install",
"release": "standard-version",
"deploy:staging": "npm run build && aws s3 sync dist/ s3://staging-bucket",
"deploy:prod": "npm run build && aws s3 sync dist/ s3://prod-bucket"
}
}
总结
前端工程化的核心要点:
- 代码规范:统一的ESLint、Prettier配置,自动化检查
- 构建优化:合理的代码分割、资源压缩、缓存策略
- 自动化部署:CI/CD流水线,多环境管理
- 质量保证:测试覆盖率、性能监控、错误追踪
- 团队协作:统一的开发环境、文档规范
良好的工程化实践不仅能提高开发效率,还能保证代码质量和项目的可维护性。