dramaling-app/docs/04_technical/03_frontend/vue-tools-configuration.md

941 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Vue.js 生態系工具配置指南
## 🛠️ 開發工具配置
### VS Code 配置
#### 推薦擴展插件
```json
// .vscode/extensions.json
{
"recommendations": [
// Vue.js 開發核心
"Vue.volar", // Vue Language Features
"Vue.vscode-typescript-vue-plugin", // TypeScript Vue Plugin
// 程式碼品質
"esbenp.prettier-vscode", // Prettier代碼格式化
"dbaeumer.vscode-eslint", // ESLint檢查
"stylelint.vscode-stylelint", // CSS/SCSS檢查
// TypeScript支援
"ms-vscode.vscode-typescript-next", // TypeScript語言服務
// 開發輔助
"formulahendry.auto-rename-tag", // 自動重命名標籤
"bradlc.vscode-tailwindcss", // Tailwind CSS支援
"christian-kohler.path-intellisense", // 路徑智能提示
"ms-vscode.vscode-json", // JSON支援
// Git工具
"eamodio.gitlens", // Git增強
"mhutchie.git-graph", // Git圖形化
// 測試工具
"ZixuanChen.vitest-explorer", // Vitest測試瀏覽器
// 其他實用工具
"PKief.material-icon-theme", // 圖標主題
"GitHub.copilot", // AI編程助手
"ms-vscode.live-server" // Live Server
]
}
```
#### VS Code設定
```json
// .vscode/settings.json
{
// 編輯器設定
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// Vue.js相關設定
"vue.inlayHints.missingProps": true,
"vue.inlayHints.inlineHandlerLeading": true,
"vue.inlayHints.optionsWrapper": true,
// TypeScript設定
"typescript.preferences.includePackageJsonAutoImports": "on",
"typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always",
// Prettier設定
"prettier.configPath": ".prettierrc",
"prettier.requireConfig": true,
// ESLint設定
"eslint.validate": ["vue", "typescript", "javascript"],
"eslint.format.enable": true,
// 檔案關聯
"files.associations": {
"*.vue": "vue"
},
// Emmet設定
"emmet.includeLanguages": {
"vue-html": "html",
"vue": "html"
},
// 自動儲存
"files.autoSave": "onFocusChange",
// 排除檔案
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/.nuxt": true,
"**/coverage": true
},
// 終端機設定
"terminal.integrated.defaultProfile.osx": "zsh",
"terminal.integrated.fontSize": 14
}
```
### Prettier 配置
```json
// .prettierrc
{
"semi": false,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "lf",
"vueIndentScriptAndStyle": false,
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": "*.vue",
"options": {
"parser": "vue"
}
},
{
"files": "*.json",
"options": {
"parser": "json"
}
}
]
}
```
### StyleLint 配置
```json
// .stylelintrc.json
{
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-standard-vue"
],
"rules": {
"string-quotes": "single",
"selector-class-pattern": null,
"custom-property-pattern": null,
"scss/dollar-variable-pattern": null,
"scss/at-mixin-pattern": null,
"scss/at-function-pattern": null,
"value-keyword-case": null,
"function-name-case": null,
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": [
"tailwind",
"apply",
"variants",
"responsive",
"screen"
]
}
]
},
"ignoreFiles": [
"node_modules/**/*",
"dist/**/*",
"coverage/**/*"
]
}
```
### Husky Git Hooks 配置
#### 安裝和配置
```bash
# 安裝 Husky
npm install --save-dev husky lint-staged
# 初始化 Husky
npx husky install
# 創建 pre-commit hook
npx husky add .husky/pre-commit "npx lint-staged"
# 創建 commit-msg hook (選用)
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'
```
#### lint-staged 配置
```json
// package.json 中的 lint-staged 配置
{
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,vue}": [
"stylelint --fix",
"prettier --write"
],
"*.{md,json}": [
"prettier --write"
]
}
}
```
#### Commitlint 配置 (選用)
```json
// .commitlintrc.json
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat", // 新功能
"fix", // bug修復
"docs", // 文檔
"style", // 格式化
"refactor", // 重構
"perf", // 性能優化
"test", // 測試
"build", // 建構
"ci", // CI/CD
"chore", // 其他
"revert" // 回退
]
],
"subject-case": [0]
}
}
```
## 🧪 測試工具配置
### Vitest 配置
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
test: {
// 測試環境
globals: true,
environment: 'happy-dom',
// 設置檔案
setupFiles: ['tests/setup.ts'],
// 包含和排除
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
exclude: ['node_modules', 'dist', '.idea', '.git', '.cache'],
// 覆蓋率配置
coverage: {
provider: 'v8',
reporter: ['text', 'json-summary', 'html', 'lcov'],
reportsDirectory: './coverage',
exclude: [
'node_modules/',
'tests/',
'**/*.d.ts',
'**/*.config.*',
'src/main.ts',
'src/router/index.ts'
],
threshold: {
global: {
branches: 75,
functions: 75,
lines: 75,
statements: 75
}
}
},
// 監控模式
watchExclude: ['**/node_modules/**', '**/dist/**'],
// 測試超時
testTimeout: 10000,
hookTimeout: 10000,
// 並發設置
threads: true,
maxThreads: 4,
minThreads: 1
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'~': path.resolve(__dirname, 'src')
}
}
})
```
### 測試設置檔案
```typescript
// tests/setup.ts
import { config } from '@vue/test-utils'
import { createPinia } from 'pinia'
import { Quasar } from 'quasar'
// Vue Test Utils 全域配置
config.global.plugins = [
createPinia(),
[Quasar, {}]
]
// 模擬 ResizeObserver (Quasar需要)
global.ResizeObserver = class ResizeObserver {
constructor(cb: any) {}
observe() {}
unobserve() {}
disconnect() {}
}
// 模擬 IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
constructor(cb: any, options?: any) {}
observe() {}
unobserve() {}
disconnect() {}
}
// 模擬 matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
}))
})
```
### Cypress E2E 測試配置
```typescript
// cypress.config.ts
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true,
setupNodeEvents(on, config) {
// 任務配置
on('task', {
log(message) {
console.log(message)
return null
}
})
},
// 測試檔案配置
specPattern: 'tests/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'tests/e2e/support/e2e.ts',
fixturesFolder: 'tests/e2e/fixtures',
screenshotsFolder: 'tests/e2e/screenshots',
videosFolder: 'tests/e2e/videos',
// 環境變數
env: {
apiUrl: 'http://localhost:5000/api',
testUser: {
email: 'test@example.com',
password: 'testpassword123'
}
}
},
component: {
devServer: {
framework: 'vue',
bundler: 'vite'
}
}
})
```
## 📦 建構和部署工具
### GitHub Actions CI/CD
```yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
jobs:
# 代碼檢查和測試
quality-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run type-check
- name: Lint check
run: npm run lint
- name: Style lint check
run: npm run lint:style
- name: Run unit tests
run: npm run test:coverage
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
# E2E測試
e2e-test:
runs-on: ubuntu-latest
needs: quality-check
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Run E2E tests
uses: cypress-io/github-action@v6
with:
start: npm run preview
wait-on: 'http://localhost:4173'
# 建構和部署
build-deploy:
runs-on: ubuntu-latest
needs: quality-check
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
VITE_API_BASE_URL: ${{ secrets.PRODUCTION_API_URL }}
VITE_STRIPE_PUBLIC_KEY: ${{ secrets.STRIPE_PUBLIC_KEY }}
- name: Build Docker image
run: docker build -t dramaling-web:${{ github.sha }} .
- name: Deploy to production
run: |
echo "Deploying to production server..."
# 部署邏輯
```
### Docker 配置
#### 開發環境 Dockerfile
```dockerfile
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
# 安裝依賴
COPY package*.json ./
RUN npm ci
# 複製源代碼
COPY . .
# 開放端口
EXPOSE 3000
# 開發模式啟動
CMD ["npm", "run", "dev"]
```
#### 生產環境 Dockerfile
```dockerfile
# Dockerfile
# 建構階段
FROM node:18-alpine AS builder
WORKDIR /app
# 複製依賴文件
COPY package*.json ./
# 安裝依賴(包含 devDependencies
RUN npm ci
# 複製源代碼
COPY . .
# 建構應用
RUN npm run build
# 生產階段
FROM nginx:alpine AS production
# 安裝工具
RUN apk add --no-cache curl
# 複製建構結果
COPY --from=builder /app/dist /usr/share/nginx/html
# 複製 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 健康檢查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# 非 root 用戶
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001
USER nodejs
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
### 環境配置管理
#### 開發環境配置
```bash
# scripts/dev-setup.sh
#!/bin/bash
echo "🔧 設置開發環境..."
# 檢查 Node.js 版本
node_version=$(node -v | sed 's/v//' | cut -d. -f1)
if [ "$node_version" -lt 18 ]; then
echo "❌ Node.js 版本需要 >= 18當前版本: $(node -v)"
exit 1
fi
# 安裝依賴
echo "📦 安裝依賴..."
npm ci
# 設置 Git hooks
echo "🪝 設置 Git hooks..."
npm run prepare
# 創建環境變數檔案
if [ ! -f .env.local ]; then
echo "📝 創建本地環境變數檔案..."
cp .env.development .env.local
fi
# 運行初始化檢查
echo "🧪 運行檢查..."
npm run type-check
npm run lint
npm run test --run
echo "✅ 開發環境設置完成!"
echo "運行 'npm run dev' 開始開發"
```
#### 部署腳本
```bash
# scripts/deploy.sh
#!/bin/bash
set -e
echo "🚀 開始部署流程..."
# 環境變數檢查
if [ -z "$PRODUCTION_API_URL" ]; then
echo "❌ 缺少 PRODUCTION_API_URL 環境變數"
exit 1
fi
# 建構應用
echo "🏗️ 建構應用..."
npm run build
# 運行測試
echo "🧪 運行測試..."
npm run test --run
# Docker 建構
echo "🐳 建構 Docker 映像..."
docker build -t dramaling-web:latest .
# 部署到服務器
echo "🌐 部署到生產環境..."
# 這裡會是實際的部署命令
echo "✅ 部署完成!"
```
## 🔧 開發工具腳本
### package.json 腳本
```json
{
"scripts": {
// 開發相關
"dev": "vite --host",
"dev:https": "vite --https --host",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
// 測試相關
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
"test:coverage": "vitest --coverage",
"test:e2e": "cypress run",
"test:e2e:dev": "cypress open",
// 代碼檢查
"lint": "eslint . --ext .vue,.ts,.tsx --fix",
"lint:check": "eslint . --ext .vue,.ts,.tsx",
"lint:style": "stylelint **/*.{css,scss,vue} --fix",
"type-check": "vue-tsc --noEmit",
// 建構和部署
"build:analyze": "vite build --mode analyze",
"build:docker": "docker build -t dramaling-web .",
"deploy:staging": "npm run build && ./scripts/deploy-staging.sh",
"deploy:production": "npm run build && ./scripts/deploy-production.sh",
// 開發工具
"clean": "rm -rf node_modules dist coverage .nuxt",
"fresh-install": "npm run clean && npm install",
"update-deps": "npx npm-check-updates -u && npm install",
"prepare": "husky install",
// 代碼生成
"generate:component": "node scripts/generate-component.js",
"generate:page": "node scripts/generate-page.js",
"generate:store": "node scripts/generate-store.js"
}
}
```
### 開發輔助腳本
#### 組件生成腳本
```javascript
// scripts/generate-component.js
const fs = require('fs')
const path = require('path')
const componentName = process.argv[2]
if (!componentName) {
console.log('請提供組件名稱: npm run generate:component ComponentName')
process.exit(1)
}
const componentDir = path.join('src/components/business', componentName)
const componentFile = path.join(componentDir, `${componentName}.vue`)
const testFile = path.join('tests/unit/components', `${componentName}.test.ts`)
// 創建目錄
if (!fs.existsSync(componentDir)) {
fs.mkdirSync(componentDir, { recursive: true })
}
// 組件模板
const componentTemplate = `<template>
<div class="${componentName.toLowerCase()}">
<h2>{{ title }}</h2>
<slot />
</div>
</template>
<script setup lang="ts">
interface Props {
title: string
}
const props = defineProps<Props>()
</script>
<style scoped lang="scss">
.${componentName.toLowerCase()} {
// 樣式
}
</style>
`
// 測試模板
const testTemplate = `import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import ${componentName} from '@/components/business/${componentName}/${componentName}.vue'
describe('${componentName}', () => {
it('renders correctly', () => {
const wrapper = mount(${componentName}, {
props: {
title: 'Test Title'
}
})
expect(wrapper.find('h2').text()).toBe('Test Title')
})
})
`
// 創建文件
fs.writeFileSync(componentFile, componentTemplate)
fs.writeFileSync(testFile, testTemplate)
console.log(`✅ 組件 ${componentName} 創建成功!`)
console.log(`📁 組件文件: ${componentFile}`)
console.log(`🧪 測試文件: ${testFile}`)
```
## 📊 性能監控配置
### Bundle 分析配置
```typescript
// vite.config.ts 中的 bundle 分析
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
// 其他插件...
// Bundle 分析器
process.env.ANALYZE === 'true' && visualizer({
filename: 'dist/bundle-analysis.html',
open: true,
gzipSize: true,
brotliSize: true
})
].filter(Boolean)
})
```
### 性能監控
```typescript
// src/utils/performance.ts
export class PerformanceMonitor {
private static instance: PerformanceMonitor
static getInstance() {
if (!this.instance) {
this.instance = new PerformanceMonitor()
}
return this.instance
}
// 頁面載入性能
measurePageLoad(routeName: string) {
if (typeof window === 'undefined') return
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
const metrics = {
route: routeName,
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
firstPaint: this.getFirstPaint(),
firstContentfulPaint: this.getFirstContentfulPaint()
}
this.sendMetrics('page_load', metrics)
}
// 用戶互動性能
measureInteraction(actionName: string, startTime: number) {
const duration = performance.now() - startTime
this.sendMetrics('user_interaction', {
action: actionName,
duration
})
}
// 獲取 Web Vitals
private getFirstPaint(): number {
const paintEntries = performance.getEntriesByType('paint')
const fpEntry = paintEntries.find(entry => entry.name === 'first-paint')
return fpEntry ? fpEntry.startTime : 0
}
private getFirstContentfulPaint(): number {
const paintEntries = performance.getEntriesByType('paint')
const fcpEntry = paintEntries.find(entry => entry.name === 'first-contentful-paint')
return fcpEntry ? fcpEntry.startTime : 0
}
private sendMetrics(type: string, data: any) {
// 發送到分析服務
console.log(`[Performance] ${type}:`, data)
// 實際實作可以發送到 Google Analytics、Mixpanel 等
if (window.gtag) {
window.gtag('event', type, data)
}
}
}
```
## 🔗 IDE整合配置
### WebStorm 配置
```xml
<!-- .idea/jsLibraryMappings.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{dramaling-web/node_modules}" />
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
```
### 代碼模板
```json
// .vscode/vue.code-snippets
{
"Vue Composition Component": {
"prefix": "vcomp",
"body": [
"<template>",
" <div class=\"${1:component-name}\">",
" ${2}",
" </div>",
"</template>",
"",
"<script setup lang=\"ts\">",
"interface Props {",
" ${3}",
"}",
"",
"const props = defineProps<Props>()",
"${4}",
"</script>",
"",
"<style scoped lang=\"scss\">",
".${1:component-name} {",
" ${5}",
"}",
"</style>"
],
"description": "Vue Composition API component template"
},
"Pinia Store": {
"prefix": "pstore",
"body": [
"import { defineStore } from 'pinia'",
"",
"export const use${1:StoreName}Store = defineStore('${2:storeName}', () => {",
" const ${3:state} = ref(${4:initialValue})",
" ",
" const ${5:getter} = computed(() => {",
" return ${3:state}.value",
" })",
" ",
" const ${6:action} = async () => {",
" ${7}",
" }",
" ",
" return {",
" ${3:state}: readonly(${3:state}),",
" ${5:getter},",
" ${6:action}",
" }",
"})"
],
"description": "Pinia store template"
}
}
```
---
**文檔狀態**: 🟢 完成工具配置指南
**最後更新**: 2025-09-09
**下次更新**: 根據開發需求調整工具配置