简历描述模板(STAR法则)
模板一:工程化建设视角
主导公司级 Monorepo 架构改造,统一管理 15+ 前端项目,通过 pnpm workspace + Turborepo 将多项目构建时间从 180s 缩短至 45s(提升 75%),包依赖冲突减少 90%,新项目搭建时间从 2 天缩短至 30 分钟。
- 设计统一的构建配置和代码规范体系,制定组件库、工具库复用机制
- 引入 Turborepo 增量构建和缓存策略,实现跨项目构建提速
- 搭建自动化版本发布流程,支持批量发包和 Changelog 自动生成
模板二:团队协作视角
推动团队从多仓库模式迁移至 Monorepo 架构,解决跨项目依赖管理混乱、构建效率低下问题。整合 8 个独立仓库为统一 Monorepo,团队协作效率提升 60%,代码复用率从 30% 提升至 75%。
- 使用 pnpm workspace 管理多包依赖,解决幽灵依赖和依赖提升问题
- 配置 Turborepo 管道任务并行执行,构建时间降低 70%
- 建立统一的 CI/CD 流程,支持按需构建和部署
模板三:业务价值视角
重构前端基础设施,从 Multi-repo 升级为 Monorepo 架构,支撑公司 20+ 业务线前端项目的高效开发。通过统一依赖管理和构建优化,减少 Bug 引入率 45%,新业务线接入成本降低 80%。
- 设计跨项目依赖治理方案,建立组件库、hooks 库、工具库三层复用体系
- 实现版本统一管理,避免依赖版本不一致导致的线上问题
- 配置自动化发布流程,支持单包发布、批量发布和 Lerna 版本管理
面试话术模板
场景一:面试官问"你们为什么要用 Monorepo?"
【核心回答框架】
"我们做的是系统性的工程化升级,而不是为了技术而技术。"
背景(Situation): 我们当时有 15 个前端项目分散在不同仓库,存在三个核心痛点:
- 依赖管理混乱:同一个组件库在不同项目有 5 个版本,经常出现 API 不一致导致的线上问题
- 协作效率低:跨项目修改需要同时改 3-5 个仓库,发布流程繁琐
- 构建时间长:每个项目独立构建,没有缓存复用,CI 流水线耗时 180 秒
任务(Task): 我负责设计并落地 Monorepo 架构改造方案,目标是提升开发效率、统一技术栈、减少维护成本。
行动(Action): 我从"依赖管理、构建优化、协作流程"三个维度切入:
- 依赖管理层面
- 选择 pnpm workspace 而非 yarn/npm:利用硬链接节省磁盘空间 50%,解决幽灵依赖
- 建立三层依赖结构:shared-ui(组件库)、shared-utils(工具库)、shared-config(配置)
- 统一版本策略:核心依赖锁定版本,避免依赖地狱
- 构建优化层面
- 引入 Turborepo 增量构建:只构建变更的包,利用缓存加速
- 配置任务管道:build、test、lint 并行执行
- 远程缓存:团队共享构建缓存,新同事首次构建也能复用
- 协作流程层面
- 统一脚手架:一键创建新项目,自动注入标准配置
- 自动化发布:Changeset 管理版本,自动生成 Changelog
- 代码共享:原子化组件库,业务代码跨项目复用
结果(Result)
- 构建时间:从 180s 降至 45s(提升 75%)
- 依赖冲突:版本不一致问题减少 90%
- 协作效率:跨项目修改从半天缩短到 30 分钟
- 新项目搭建:从 2 天降至 30 分钟
- 代码复用率:从 30% 提升到 75%
场景二:面试官追问"为什么选 pnpm 而不是 yarn/npm?"
【对比思维回答】
"我做过详细的技术选型对比,pnpm 在我们的场景下有三个关键优势:"
| 对比维度 | npm/yarn | pnpm |
|---|---|---|
| 磁盘空间 | 每个项目独立安装 | 硬链接共享,节省 50%+ |
| 幽灵依赖 | 会提升依赖,可访问未声明的包 | 严格隔离,避免隐患 |
| 安装速度 | 较慢 | 快 2-3 倍 |
| Monorepo 支持 | workspace 基础功能 | 原生优化,性能更好 |
实际案例:
- 我们有个项目依赖了
lodash,但没在 package.json 声明,用 npm 能跑,换个环境就崩了 - pnpm 的
.pnpm目录扁平化管理,15 个项目共用依赖,磁盘从 8GB 降到 3GB
代码对比:
# npm/yarn 问题
node_modules/
├── lodash/ # 可以直接 import,但没声明
└── your-package/
# pnpm 严格隔离
node_modules/
├── .pnpm/ # 所有包在这里
└── your-package/ # 只能访问声明的依赖
场景三:面试官问"Turborepo 具体怎么优化的?"
【深度技术回答】
"Turborepo 的核心价值是'增量构建 + 任务编排 + 远程缓存',让我展开讲讲:"
1. 任务依赖图(Pipeline)
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // 依赖包先构建
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"cache": false // 测试不缓存
},
"lint": {} // 可并行执行
}
}
运行机制:
- Turbo 分析依赖关系,构建拓扑排序
- 并行执行无依赖任务(lint、test 同时跑)
- 增量构建:只重新构建变更的包
2. 缓存策略
# 本地缓存
.turbo/cache/
├── abc123def.tar.gz # 基于 inputs hash
└── xyz789uvw.tar.gz
# 远程缓存(Vercel/自建)
turbo run build --cache-dir=.turbo --remote-cache
实际效果:
- 首次构建:180s(所有包)
- 二次构建(无变更):3s(全命中缓存)
- 修改一个包:25s(只构建该包及依赖它的包)
3. 任务过滤
bash
# 只构建变更的包
turbo run build --filter=...@origin/main
# 只构建特定包
turbo run build --filter=@myapp/ui
# 并行构建
turbo run build test lint --parallel
数据对比:
优化前(串行构建):
├── @app/admin build: 60s
├── @app/mobile build: 50s
└── @app/web build: 70s
总计:180s
优化后(Turbo并行+缓存):
├── @app/admin build: 22s(并行)
├── @app/mobile build: 18s(并行)
└── @app/web build: 25s(并行+缓存命中部分)
总计:45s(最长任务时间)
场景四:面试官问"依赖治理具体怎么做的?"
【方法论回答】
"我建立了三层依赖架构 + 版本管控机制:"
三层架构:
packages/
├── shared-ui/ # L1: 基础组件库
│ ├── Button/
│ ├── Table/
│ └── Form/
├── shared-utils/ # L2: 工具函数库
│ ├── request/
│ ├── auth/
│ └── storage/
├── shared-config/ # L3: 配置库
│ ├── eslint-config/
│ ├── tsconfig/
│ └── vite-config/
└── apps/ # 业务应用
├── admin/
├── mobile/
└── web/
版本管控:
// 根目录 package.json
{
"pnpm": {
"overrides": {
"react": "^18.2.0", // 强制统一版本
"lodash": "^4.17.21"
}
},
"devDependencies": {
"@types/react": "^18.2.0" // 全局共享
}
}
依赖分析工具:
# 检查重复依赖
pnpm list --depth=Infinity | grep "lodash"
# 依赖关系图
pnpm why react
# 清理幽灵依赖
pnpm install --shamefully-hoist=false
治理规则:
- 核心依赖统一:React、Vue、TypeScript 全局锁版本
- 组件库内部依赖:使用 workspace:* 协议
- 第三方库升级:统一升级,避免碎片化
- Peer Dependencies:明确声明,避免隐式依赖
实际案例:
// ❌ 问题写法
"dependencies": {
"@myapp/ui": "1.2.3" // 硬编码版本
}
// ✅ 正确写法
"dependencies": {
"@myapp/ui": "workspace:*" // 永远指向本地最新
}
场景五:面试官问"自动化发布是怎么实现的?"
【流程化回答】
"我们用 Changesets 实现自动化发布,整个流程零人工介入:"
发布流程图:
开发提交代码
↓
执行 pnpm changeset(生成变更记录)
↓
PR 合并到 main
↓
CI 自动创建 "Version Packages" PR
↓
合并后自动发布到 npm
↓
自动生成 GitHub Release + Changelog
核心配置:
// .changeset/config.json
{
"changelog": "@changesets/changelog-github",
"commit": true,
"linked": [], // 独立版本
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch"
}
开发者使用:
# 1. 修改代码后,执行
pnpm changeset
# 2. 选择变更类型
? Which packages would you like to include?
✓ @myapp/ui
✓ @myapp/utils
? What type of change is this for @myapp/ui?
○ patch (bug fix)
○ minor (new feature)
● major (breaking change)
# 3. 生成 .changeset/xxx.md
---
"@myapp/ui": major
---
重构 Button 组件 API
CI 自动化:
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
steps:
- uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
成果数据:
- 发布时间:从 30 分钟降至 5 分钟
- 错误率:人工发布错误率 15% → 自动化 0%
- Changelog:自动生成,包含 PR 链接和贡献者
核心代码实现
1. pnpm workspace 配置
pnpm-workspace.yaml
packages:
# 所有 packages 下的包
- 'packages/*'
# 所有 apps 下的应用
- 'apps/*'
# 排除测试目录
- '!**/test/**'
根目录 package.json
{
"name": "monorepo-root",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint",
"clean": "turbo run clean && rm -rf node_modules",
"changeset": "changeset",
"version": "changeset version",
"release": "pnpm build && changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
"turbo": "^1.10.0",
"typescript": "^5.0.0",
"eslint": "^8.45.0",
"prettier": "^3.0.0"
},
"pnpm": {
"overrides": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"peerDependencyRules": {
"ignoreMissing": ["react", "react-dom"]
}
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
}
}
2. Turborepo 配置
turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
".env",
"tsconfig.json"
],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "build/**"],
"env": ["NODE_ENV"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": false
},
"lint": {
"outputs": []
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
},
"clean": {
"cache": false
}
},
"remoteCache": {
"enabled": true
}
}
3. 共享配置包
packages/shared-config/eslint-config/index.js
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
env: {
browser: true,
es2021: true,
node: true
},
settings: {
react: {
version: 'detect'
}
},
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'@typescript-eslint/no-explicit-any': 'warn',
'react/react-in-jsx-scope': 'off'
}
};
packages/shared-config/tsconfig/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": false,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true,
"incremental": true
},
"exclude": ["node_modules", "dist", "build"]
}
packages/shared-config/tsconfig/react.json
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2020", "DOM", "DOM.Iterable"]
}
}
4. 组件库包示例
packages/shared-ui/package.json
{
"name": "@myapp/ui",
"version": "1.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./Button": {
"import": "./dist/Button.mjs",
"require": "./dist/Button.js",
"types": "./dist/Button.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src/",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@myapp/eslint-config": "workspace:*",
"@myapp/tsconfig": "workspace:*",
"@types/react": "^18.2.0",
"tsup": "^7.2.0",
"typescript": "^5.0.0"
}
}
packages/shared-ui/tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts', 'src/Button/index.tsx'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
external: ['react', 'react-dom'],
treeshake: true,
minify: process.env.NODE_ENV === 'production'
});
5. 应用包示例
apps/admin/package.json
{
"name": "@myapp/admin",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint src/",
"type-check": "tsc --noEmit"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@myapp/ui": "workspace:*",
"@myapp/utils": "workspace:*"
},
"devDependencies": {
"@myapp/eslint-config": "workspace:*",
"@myapp/tsconfig": "workspace:*",
"@myapp/vite-config": "workspace:*",
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.4.0",
"typescript": "^5.0.0"
}
}
apps/admin/tsconfig.json
json
{
"extends": "@myapp/tsconfig/react.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"references": [
{ "path": "../../packages/shared-ui" },
{ "path": "../../packages/shared-utils" }
]
}
6. 自动化脚本
scripts/check-deps.js
#!/usr/bin/env node
/**
* 检查重复依赖
*/
const { execSync } = require('child_process');
const duplicates = execSync('pnpm list --depth=Infinity --json', {
encoding: 'utf-8'
});
const parsed = JSON.parse(duplicates);
const depMap = new Map();
function traverse(deps) {
if (!deps) return;
Object.entries(deps).forEach(([name, info]) => {
const key = `${name}@${info.version}`;
if (!depMap.has(name)) {
depMap.set(name, new Set());
}
depMap.get(name).add(info.version);
if (info.dependencies) {
traverse(info.dependencies);
}
});
}
traverse(parsed[0]?.dependencies);
console.log('\n🔍 检查重复依赖:\n');
let hasDuplicates = false;
depMap.forEach((versions, name) => {
if (versions.size > 1) {
hasDuplicates = true;
console.log(`⚠️ ${name}: ${[...versions].join(', ')}`);
}
});
if (!hasDuplicates) {
console.log('✅ 没有重复依赖');
} else {
console.log('\n💡 建议在根 package.json 中配置 pnpm.overrides 统一版本');
process.exit(1);
}
scripts/create-package.js
#!/usr/bin/env node
/**
* 快速创建新包
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const packageName = process.argv[2];
const packageType = process.argv[3] || 'lib'; // lib | app
if (!packageName) {
console.error('❌ 请提供包名: pnpm create:pkg my-package');
process.exit(1);
}
const isApp = packageType === 'app';
const dir = path.join(
process.cwd(),
isApp ? 'apps' : 'packages',
packageName
);
if (fs.existsSync(dir)) {
console.error(`❌ 目录已存在: ${dir}`);
process.exit(1);
}
// 创建目录结构
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
// 生成 package.json
const pkgJson = {
name: `@myapp/${packageName}`,
version: '0.0.1',
private: isApp,
main: isApp ? undefined : './dist/index.js',
types: isApp ? undefined : './dist/index.d.ts',
scripts: {
dev: isApp ? 'vite' : 'tsup --watch',
build: isApp ? 'vite build' : 'tsup',
lint: 'eslint src/',
'type-check': 'tsc --noEmit'
},
dependencies: {},
devDependencies: {
'@myapp/eslint-config': 'workspace:*',
'@myapp/tsconfig': 'workspace:*',
typescript: '^5.0.0'
}
};
fs.writeFileSync(
path.join(dir, 'package.json'),
JSON.stringify(pkgJson, null, 2)
);
// 生成 tsconfig.json
const tsconfig = {
extends: '@myapp/tsconfig/base.json',
compilerOptions: {
baseUrl: '.',
outDir: 'dist'
},
include: ['src']
};
fs.writeFileSync(
path.join(dir, 'tsconfig.json'),
JSON.stringify(tsconfig, null, 2)
);
// 生成示例文件
const indexTs = isApp
? `console.log('Hello from ${packageName}');`
: `export const hello = () => '${packageName}';`;
fs.writeFileSync(path.join(dir, 'src/index.ts'), indexTs);
console.log(`✅ 创建成功: ${dir}`);
console.log(`\n📦 下一步:`);
console.log(` cd ${isApp ? 'apps' : 'packages'}/${packageName}`);
console.log(` pnpm install`);
console.log(` pnpm dev`);
7. CI/CD 配置
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type Check
run: pnpm type-check
- name: Build
run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
- name: Test
run: pnpm test
release:
needs: lint-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: pnpm release
commit: 'chore: release packages'
title: 'chore: release packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
性能对比数据
构建时间对比
| 场景 | Multi-repo | Monorepo (无缓存) | Monorepo (有缓存) |
|---|---|---|---|
| 全量构建 | 180s | 90s | 45s |
| 单包构建 | 60s | 25s | 3s |
| 增量构建 | 60s | 30s | 8s |
磁盘空间对比
| 模式 | node_modules 大小 | .turbo 缓存 | 总计 |
|---|---|---|---|
| Multi-repo | 8.2 GB | 0 | 8.2 GB |
| Monorepo | 3.1 GB | 1.5 GB | 4.6 GB |