前端目录、构建、依赖、代码组织
- Standard:前端项目统一采用以下顶层目录结构,适用于 React / Vue 项目。
├── public/ # 不经过构建的静态资源(favicon、robots.txt)
│ ├── assets/ # 经过构建处理的静态资源(图片、字体、样式)
│ ├── components/ # 通用组件(跨页面复用)
│ ├── hooks/ # 自定义 Hooks / Composables
│ ├── types/ # TypeScript 类型定义
│ ├── styles/ # 全局样式、主题变量
│ └── main.tsx / main.ts # 应用入口
- Requirement:页面级组件放在
pages/,复用组件放在 components/,禁止混用。
- Standard:按功能就近组织,单一功能的组件、hooks、样式放在同一目录下。
- Standard:
utils/ 按职责拆分文件,禁止创建万能 utils/index.ts 堆积所有方法。
- Caution:
components/ 仅放跨页面复用的通用组件,页面私有组件放在对应 pages/xxx/components/ 下。
| 反模式 | 问题 | 建议 |
|---|
所有组件堆在 components/ 根目录 | 无法区分通用与私有 | 页面私有组件放 pages/xxx/components/ |
utils/index.ts 堆积所有方法 | 难以查找、循环依赖风险 | 按职责拆分文件 |
资源直接散放在 src/ 根目录 | 结构混乱 | 归入 assets/ 对应子目录 |
- Default:优先使用 Vite 作为构建工具,旧项目可保留 Webpack,新立项必须使用 Vite。
- Requirement:构建配置文件(
vite.config.ts / webpack.config.js)必须提交到仓库,禁止本地保留未提交的配置。
- Requirement:必须配置
@ 别名指向 src/,禁止在导入中使用多层 ../../ 相对路径。
// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
});
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
- Standard:产物输出到
dist/ 目录,资源按类型分目录(assets/js、assets/css、assets/img)。
- Standard:文件名携带 hash 用于缓存破坏,格式为
[name].[hash:8].[ext]。
// vite.config.ts
export default defineConfig({
build: {
outDir: 'dist',
assetsDir: 'assets',
rollupOptions: {
output: {
entryFileNames: 'assets/js/[name].[hash:8].js',
chunkFileNames: 'assets/js/[name].[hash:8].js',
assetFileNames: 'assets/[ext]/[name].[hash:8].[ext]',
},
},
},
});
- Requirement:环境变量必须以
VITE_ 前缀开头(Vite)或 REACT_APP_ 前缀(CRA),禁止在代码中直接读取 process.env 下的非约定变量。
- Standard:不同环境的配置放在
.env.development、.env.staging、.env.production,.env.local 不提交到仓库。
# .env.development
VITE_API_BASE_URL=https://dev-api.example.com
VITE_APP_ENV=development
# .env.production
VITE_API_BASE_URL=https://api.example.com
VITE_APP_ENV=production
// 正确
const baseURL = import.meta.env.VITE_API_BASE_URL;
// 错误
const baseURL = process.env.API_BASE_URL;
- Standard:配置构建分析工具(
rollup-plugin-visualizer / webpack-bundle-analyzer),定期检查包体积。
- Caution:单 chunk 超过 500KB 时必须拆包或做懒加载优化。
- Requirement:必须提交
package-lock.json(npm)或 pnpm-lock.yaml(pnpm)或 yarn.lock(yarn),保证团队成员安装的依赖版本一致。
- Requirement:
package.json 中生产依赖使用精确版本号(1.2.3),禁止使用 ^ 或 ~ 前缀,避免非预期的小版本升级。
{
"dependencies": {
"react": "18.2.0",
"vue": "3.3.4"
},
"devDependencies": {
"typescript": "5.1.6",
"eslint": "8.45.0"
}
}
- Requirement:运行时依赖放入
dependencies,开发依赖(构建、测试、lint)放入 devDependencies,禁止混淆。
- Caution:引入新依赖前必须评估包大小、维护状态、许可证,禁止引入无人维护或体积过大的包。
- Standard:定期执行
npm audit 或 pnpm audit 检查安全漏洞,高危漏洞必须在 7 天内修复。
- Suggestion:使用
depcheck 检测未使用的依赖,及时清理。
- Standard:团队统一使用一种包管理器,优先 pnpm,在
package.json 中通过 packageManager 字段声明。
{
"packageManager": "pnpm@8.6.12"
}
| 反模式 | 问题 | 建议 |
|---|
^1.2.3 宽松版本号 | 非预期升级可能引入 bug | 使用精确版本号 |
| 不提交 lock 文件 | 成员依赖版本不一致 | 提交 lock 文件 |
| 生产依赖放 devDependencies | 构建产物缺失依赖 | 正确分类依赖 |
| 重复实现已有工具函数 | 代码冗余 | 优先使用 lodash 等成熟库 |
- Standard:导入语句按以下顺序排列,每组之间空一行:
- 第三方库(
react、vue、antd 等)
- 项目内部模块(
@/components、@/utils 等)
- 相对路径模块(
./、../)
- 样式文件(
.css、.scss)
import { useState, useEffect } from 'react';
import { Button, Modal } from 'antd';
import { useUserStore } from '@/store/user';
import { fetchUserList } from '@/api/user';
import { formatDate } from './utils';
import styles from './UserList.module.css';
- Requirement:单个文件只承担一种职责,组件文件不混入接口请求逻辑,工具函数不混入组件定义。
- Standard:单个文件不超过 400 行,超过时按职责拆分。
- Standard:优先使用命名导出,默认导出仅用于页面组件和路由组件。
// 正确:命名导出
export function UserTable() { ... }
export interface UserTableProps { ... }
// 正确:默认导出用于页面
export default function UserListPage() { ... }
// 错误:工具函数默认导出
export default function formatDate() { ... }
- Standard:全局共享的类型定义放在
src/types/,组件专有类型定义在组件文件顶部或同目录的 types.ts。
- Requirement:禁止使用
any,如确实需要使用 unknown 并做类型收窄。
| 反模式 | 问题 | 建议 |
|---|
| 单文件超过 500 行 | 难以阅读和维护 | 按职责拆分 |
| 组件内直接写请求 | 逻辑耦合、难以测试 | 抽取到 api/ 层 |
到处使用 any | 失去类型保护 | 使用具体类型或 unknown |
| 导入顺序混乱 | 可读性差 | 按规则排列并启用 import-sort |
- Requirement:项目必须配置 ESLint + Prettier + TypeScript,且 CI 中强制校验,未通过的代码不得合并。
| 工具 | 用途 | 配置文件 |
|---|
| ESLint | 代码质量检查 | .eslintrc.cjs |
| Prettier | 代码格式化 | .prettierrc |
| Stylelint | CSS 规范检查 | .stylelintrc |
| Husky | Git Hooks | .husky/ |
| lint-staged | 增量检查 | package.json |
// .eslintrc.cjs
module.exports = {
root: true,
env: { browser: true, es2022: true, node: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
ignorePatterns: ['dist', 'node_modules'],
};
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "always"
}
- Requirement:配置
pre-commit hook 执行 lint-staged,对暂存文件运行 ESLint 和 Prettier。
- Requirement:配置
commit-msg hook 校验提交信息是否符合 Conventional Commits 规范。
// package.json
{
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"format": "prettier --write \"src/**/*.{ts,tsx,css,scss}\""
},
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["stylelint --fix", "prettier --write"]
}
}
- Requirement:CI 流水线必须包含
lint、type-check、build 步骤,全部通过后才能合并。
# .github/workflows/ci.yml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm type-check
- run: pnpm build