开发工作流
构建现代化的Nuxt3开发工作流,掌握企业级开发工具链、团队协作和持续集成最佳实践
🎯 现代化开发工作流概览
高效的开发工作流是企业级项目成功的关键。本指南将帮你构建一套完整的Nuxt3开发工作流,包括代码质量保障、自动化测试、持续集成部署等关键环节。
现代化工作流的核心价值
- 🔄 自动化: 减少手动操作,提高开发效率
- 🛡️ 质量保障: 通过工具链确保代码质量
- 🤝 团队协作: 统一的开发规范和流程
- 🚀 快速交付: 持续集成和自动化部署
- 📊 可观测性: 完善的监控和日志系统
🛠️ 开发环境配置
IDE 配置 (VS Code)
.vscode/settings.json
.vscode/extensions.json
.vscode/launch.json
{
// 基础设置
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
// TypeScript 设置
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always",
// Vue 设置
"vue.codeActions.enabled": true,
"vue.complete.normalizeComponentImportName": true,
// Tailwind CSS 设置
"tailwindCSS.experimental.classRegex": [
["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"],
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
// 文件关联
"files.associations": {
"*.vue": "vue",
"*.md": "markdown"
},
// 排除文件
"files.exclude": {
"**/.nuxt": true,
"**/.output": true,
"**/node_modules": true,
"**/dist": true
},
// 搜索设置
"search.exclude": {
"**/.nuxt": true,
"**/.output": true,
"**/node_modules": true,
"**/pnpm-lock.yaml": true
}
}
{
"recommendations": [
// Vue 相关
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
// TypeScript 相关
"ms-vscode.vscode-typescript-next",
// 代码质量
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"streetsidesoftware.code-spell-checker",
// Tailwind CSS
"bradlc.vscode-tailwindcss",
// Git 相关
"eamodio.gitlens",
"GitHub.vscode-pull-request-github",
// 实用工具
"ms-vscode.vscode-json",
"redhat.vscode-yaml",
"ms-vscode.theme-materialkit",
"PKief.material-icon-theme",
// Nuxt 相关
"Nuxt.mdc",
"antfu.iconify"
]
}
{
"version": "0.2.0",
"configurations": [
{
"name": "Nuxt: Debug Server",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/nuxi/bin/nuxi.mjs",
"args": ["dev"],
"console": "integratedTerminal",
"env": {
"NODE_ENV": "development"
}
},
{
"name": "Nuxt: Debug Client",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
Git 配置
.gitignore
.gitmessage
.github/PULL_REQUEST_TEMPLATE.md
# Nuxt 相关
.nuxt
.output
.nitro
.cache
# 依赖
node_modules
.pnpm-store
# 日志
*.log*
logs
# 运行时
.DS_Store
Thumbs.db
# 环境变量
.env
.env.*
!.env.example
# IDE
.vscode/settings.json
.idea
# 测试
coverage
.nyc_output
# 构建产物
dist
build
# 临时文件
*.tmp
*.temp
.temp
# <type>(<scope>): <subject>
#
# <body>
#
# <footer>
# Type:
# feat: 新功能
# fix: 修复bug
# docs: 文档更新
# style: 代码格式调整
# refactor: 代码重构
# perf: 性能优化
# test: 测试相关
# chore: 构建过程或工具变动
#
# Scope: 影响范围 (components, utils, api, etc.)
# Subject: 简短描述 (50字符以内)
# Body: 详细描述
# Footer: 关联issue或重大变更
## 📝 变更说明
### 变更类型
- [ ] 🆕 新功能 (feat)
- [ ] 🐛 Bug修复 (fix)
- [ ] 📚 文档更新 (docs)
- [ ] 🎨 代码格式 (style)
- [ ] ♻️ 代码重构 (refactor)
- [ ] ⚡ 性能优化 (perf)
- [ ] ✅ 测试相关 (test)
- [ ] 🔧 构建配置 (chore)
### 详细描述
请详细描述你的变更内容...
### 测试
- [ ] 添加了相应的测试
- [ ] 通过了所有现有测试
- [ ] 手动测试通过
### 部署注意事项
- [ ] 需要数据库迁移
- [ ] 需要环境变量更新
- [ ] 需要依赖更新
### 相关链接
- Issues: #
- Documentation:
- Deploy Preview:
🔧 代码质量保障
ESLint 配置
eslint.config.js
package.json - scripts
import antfu from '@antfu/eslint-config';
export default antfu(
{
// 基础配置
vue: true,
typescript: true,
formatters: true,
// 样式相关
stylistic: {
indent: 2,
quotes: 'single',
semi: false
},
// 忽略文件
ignores: [
'.nuxt',
'.output',
'dist',
'node_modules'
]
},
// 自定义规则
{
rules: {
// Vue 相关规则
'vue/multi-word-component-names': 'off',
'vue/no-multiple-template-root': 'off',
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/component-definition-name-casing': ['error', 'PascalCase'],
// TypeScript 相关规则
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
disallowTypeAnnotations: false
}],
// 通用规则
'no-console': ['warn', { allow: ['warn', 'error'] }],
'prefer-const': 'error',
'object-shorthand': 'error',
'prefer-template': 'error'
}
},
// 特定文件规则
{
files: ['server/**/*.ts'],
rules: {
'no-console': 'off' // 服务端允许使用console
}
},
{
files: ['**/*.test.ts', '**/*.spec.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off' // 测试文件允许any
}
}
);
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"type-check": "vue-tsc --noEmit",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
Prettier 配置
.prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "avoid",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}
Husky 和 lint-staged
package.json - husky配置
.husky/pre-commit
.husky/commit-msg
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,less,html,json,md}": [
"prettier --write"
]
}
}
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 运行lint-staged
pnpm lint-staged
# 类型检查
pnpm type-check
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 提交信息验证
pnpm dlx commitlint --edit "$1"
🧪 测试策略
单元测试 (Vitest)
vitest.config.ts
tests/setup.ts
tests/components/UserCard.test.ts
import { resolve } from 'node:path';
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'happy-dom',
globals: true,
setupFiles: ['./tests/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'.nuxt/',
'.output/',
'tests/',
'**/*.d.ts',
'**/*.config.*',
'**/coverage/**'
],
thresholds: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, '.'),
'~': resolve(__dirname, '.')
}
}
});
import { vi } from 'vitest';
// Mock Nuxt 组合式函数
vi.mock('#app', () => ({
useNuxtApp: () => ({
$fetch: vi.fn(),
ssrContext: undefined
}),
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
back: vi.fn()
}),
useRoute: () => ({
params: {},
query: {},
path: '/'
}),
navigateTo: vi.fn(),
useState: vi.fn(),
useCookie: vi.fn()
}));
// 全局测试配置
global.IntersectionObserver = vi.fn(() => ({
disconnect: vi.fn(),
observe: vi.fn(),
unobserve: vi.fn()
}));
import type { User } from '@/types';
import { mount } from '@vue/test-utils';
import { describe, expect, it, vi } from 'vitest';
import UserCard from '@/components/UserCard.vue';
const mockUser: User = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'user',
createdAt: new Date(),
updatedAt: new Date()
};
describe('UserCard', () => {
it('渲染用户信息', () => {
const wrapper = mount(UserCard, {
props: {
user: mockUser
}
});
expect(wrapper.text()).toContain(mockUser.name);
expect(wrapper.text()).toContain(mockUser.email);
});
it('点击编辑按钮触发事件', async () => {
const wrapper = mount(UserCard, {
props: {
user: mockUser,
editable: true
}
});
await wrapper.find('[data-testid="edit-button"]').trigger('click');
expect(wrapper.emitted('edit')).toBeTruthy();
expect(wrapper.emitted('edit')?.[0]).toEqual([mockUser]);
});
it('不可编辑时隐藏编辑按钮', () => {
const wrapper = mount(UserCard, {
props: {
user: mockUser,
editable: false
}
});
expect(wrapper.find('[data-testid="edit-button"]').exists()).toBe(false);
});
});
E2E测试 (Playwright)
tests/e2e/auth.spec.ts
tests/e2e/ai-chat.spec.ts
import { expect, test } from '@playwright/test';
test.describe('用户认证', () => {
test('用户登录流程', async ({ page }) => {
// 访问登录页面
await page.goto('/login');
// 检查页面元素
await expect(page.getByRole('heading', { name: '登录' })).toBeVisible();
// 填写登录表单
await page.getByLabel('邮箱').fill('test@example.com');
await page.getByLabel('密码').fill('password123');
// 提交表单
await page.getByRole('button', { name: '登录' }).click();
// 验证登录成功
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('欢迎回来')).toBeVisible();
});
test('登录失败处理', async ({ page }) => {
await page.goto('/login');
// 使用错误的凭证
await page.getByLabel('邮箱').fill('invalid@example.com');
await page.getByLabel('密码').fill('wrongpassword');
await page.getByRole('button', { name: '登录' }).click();
// 验证错误消息
await expect(page.getByText('登录失败')).toBeVisible();
await expect(page).toHaveURL('/login');
});
});
import { expect, test } from '@playwright/test';
test.describe('AI聊天功能', () => {
test.beforeEach(async ({ page }) => {
// 登录并访问聊天页面
await page.goto('/login');
await page.getByLabel('邮箱').fill('test@example.com');
await page.getByLabel('密码').fill('password123');
await page.getByRole('button', { name: '登录' }).click();
await page.goto('/chat');
});
test('发送消息并接收回复', async ({ page }) => {
// 发送消息
const messageInput = page.getByPlaceholder('输入消息...');
await messageInput.fill('你好,请介绍一下Nuxt3');
await page.getByRole('button', { name: '发送' }).click();
// 检查消息显示
await expect(page.getByText('你好,请介绍一下Nuxt3')).toBeVisible();
// 等待AI回复
await expect(page.getByText('正在思考...')).toBeVisible();
await expect(page.getByText('正在思考...')).toBeHidden({ timeout: 30000 });
// 验证回复内容
await expect(page.locator('[data-testid="ai-message"]').last()).toBeVisible();
});
test('清空对话历史', async ({ page }) => {
// 发送一条消息
await page.getByPlaceholder('输入消息...').fill('测试消息');
await page.getByRole('button', { name: '发送' }).click();
// 清空对话
await page.getByRole('button', { name: '清空对话' }).click();
// 确认清空
await page.getByRole('button', { name: '确认' }).click();
// 验证对话已清空
await expect(page.getByText('测试消息')).not.toBeVisible();
});
});
📊 性能监控
性能预算配置
.lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/', 'http://localhost:3000/about'],
numberOfRuns: 3
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }]
}
},
upload: {
target: 'temporary-public-storage'
}
}
}
Bundle 分析
nuxt.config.ts - 性能配置
export default defineNuxtConfig({
// 构建优化
build: {
analyze: process.env.ANALYZE === 'true'
},
// Nitro 优化
nitro: {
compressPublicAssets: true,
minify: true,
experimental: {
wasm: true
}
},
// 性能监控
hooks: {
'build:before': () => {
console.log('📊 开始构建性能分析...');
},
'build:done': (builder) => {
console.log('✅ 构建完成,检查性能指标');
}
}
});
🚀 CI/CD 流水线
GitHub Actions 工作流
.github/workflows/ci.yml
.github/workflows/deploy.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
PNPM_VERSION: '8'
jobs:
# 代码质量检查
quality:
name: Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm type-check
- name: Lint
run: pnpm lint
- name: Format check
run: pnpm format:check
# 单元测试
test:
name: Unit Tests
runs-on: ubuntu-latest
needs: quality
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm test --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
# E2E测试
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: quality
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright
run: pnpm dlx playwright install
- name: Build application
run: pnpm build
- name: Start application
run: pnpm preview &
- name: Wait for app
run: sleep 10
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload test results
uses: actions/upload-artifact@v3
if: failure()
with:
name: playwright-report
path: test-results/
# 构建测试
build:
name: Build Test
runs-on: ubuntu-latest
needs: [test, e2e]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build application
run: pnpm build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-files
path: .output/
name: Deploy to Production
on:
push:
branches: [main]
workflow_run:
workflows: [CI Pipeline]
types: [completed]
branches: [main]
jobs:
deploy:
name: Deploy to Vercel
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: '8'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
env:
NUXT_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
NUXT_DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Deploy to Vercel
uses: vercel/action@v1
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: --prod
📈 监控和日志
应用监控配置
plugins/monitoring.client.ts
server/utils/logger.ts
import { defineNuxtPlugin } from '#app';
export default defineNuxtPlugin(() => {
// 性能监控
if (typeof window !== 'undefined') {
// Web Vitals
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
});
// 错误监控
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
// 发送到监控服务
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
// 发送到监控服务
});
}
});
import { createConsola } from 'consola';
export const logger = createConsola({
level: process.env.NODE_ENV === 'production' ? 2 : 4,
formatOptions: {
colors: true,
date: true,
compact: process.env.NODE_ENV === 'production'
},
reporters: [
// 控制台输出
{
log: (logObj) => {
console.log(logObj.args.join(' '));
}
},
// 生产环境文件输出
...(process.env.NODE_ENV === 'production' ? [{
log: (logObj) => {
// 写入日志文件或发送到日志服务
}
}] : [])
]
});
// 性能日志
export const performanceLogger = {
start: (label: string) => {
console.time(label);
},
end: (label: string) => {
console.timeEnd(label);
},
measure: async <T>(label: string, fn: () => Promise<T>): Promise<T> => {
const start = Date.now();
try {
const result = await fn();
const duration = Date.now() - start;
logger.info(`${label} completed in ${duration}ms`);
return result;
} catch (error) {
const duration = Date.now() - start;
logger.error(`${label} failed after ${duration}ms:`, error);
throw error;
}
}
};
🎯 团队协作规范
代码审查清单
docs/CODE_REVIEW.md
# 代码审查清单
## 📋 审查要点
### 🎯 功能性
- [ ] 功能是否按需求正确实现?
- [ ] 边界条件是否处理得当?
- [ ] 错误处理是否完善?
### 🏗️ 架构设计
- [ ] 代码结构是否清晰合理?
- [ ] 是否遵循项目的架构模式?
- [ ] 组件职责是否单一?
### 🔒 安全性
- [ ] 用户输入是否得到验证?
- [ ] 敏感信息是否得到保护?
- [ ] API接口是否有适当的权限控制?
### 🚀 性能
- [ ] 是否存在性能瓶颈?
- [ ] 图片和资源是否优化?
- [ ] 数据库查询是否高效?
### 🧪 测试
- [ ] 是否添加了适当的测试?
- [ ] 测试覆盖率是否足够?
- [ ] 测试是否可靠且易维护?
### 📚 文档
- [ ] 复杂逻辑是否有注释说明?
- [ ] API文档是否更新?
- [ ] README是否需要更新?
### 🎨 代码风格
- [ ] 是否遵循项目的编码规范?
- [ ] 变量和函数命名是否清晰?
- [ ] 代码是否易于理解和维护?
发布流程
scripts/release.sh
#!/bin/bash
# 发布脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}🚀 开始发布流程...${NC}"
# 检查当前分支
current_branch=$(git branch --show-current)
if [ "$current_branch" != "main" ]; then
echo -e "${RED}❌ 请在main分支上执行发布${NC}"
exit 1
fi
# 检查工作区是否干净
if [ -n "$(git status --porcelain)" ]; then
echo -e "${RED}❌ 工作区有未提交的更改${NC}"
exit 1
fi
# 拉取最新代码
echo -e "${YELLOW}📥 拉取最新代码...${NC}"
git pull origin main
# 运行测试
echo -e "${YELLOW}🧪 运行测试套件...${NC}"
pnpm test
# 构建项目
echo -e "${YELLOW}🔨 构建项目...${NC}"
pnpm build
# 版本号提升
echo -e "${YELLOW}📝 更新版本号...${NC}"
pnpm changeset version
pnpm install
# 提交版本更改
if [ -n "$(git status --porcelain)" ]; then
git add .
git commit -m "chore: release version"
fi
# 创建标签并推送
echo -e "${YELLOW}🏷️ 创建发布标签...${NC}"
pnpm changeset tag
git push origin main --tags
# 发布到npm (如果是包)
# pnpm changeset publish
echo -e "${GREEN}✅ 发布完成!${NC}"
🎯 最佳实践总结
🌟 工作流优化建议
- 自动化优先: 能自动化的都应该自动化
- 早期集成: 尽早发现和解决问题
- 持续反馈: 建立快速的反馈循环
- 质量门禁: 设置代码质量和测试覆盖率门槛
- 文档同步: 确保文档与代码同步更新
- 监控告警: 建立完善的监控和告警机制
关键指标监控
utils/metrics.ts
// 关键性能指标
export interface Metrics {
// 构建指标
buildTime: number;
bundleSize: number;
// 测试指标
testCoverage: number;
testPassRate: number;
// 部署指标
deploymentFrequency: number;
leadTime: number;
meanTimeToRecovery: number;
changeFailureRate: number;
// 质量指标
codeSmells: number;
technicalDebt: number;
duplicatedLines: number;
}
export function trackMetrics(metrics: Partial<Metrics>) {
// 发送指标到监控系统
console.log('📊 Metrics:', metrics);
}