dramaling-app/sop/archive/20250910155305_vue-tools-co...

20 KiB
Raw Blame History

Vue.js 生態系工具配置指南

🛠️ 開發工具配置

VS Code 配置

推薦擴展插件

// .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設定

// .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 配置

// .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 配置

// .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 配置

安裝和配置

# 安裝 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 配置

// 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 配置 (選用)

// .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 配置

// 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')
    }
  }
})

測試設置檔案

// 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 測試配置

// 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

# .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.dev
FROM node:18-alpine

WORKDIR /app

# 安裝依賴
COPY package*.json ./
RUN npm ci

# 複製源代碼
COPY . .

# 開放端口
EXPOSE 3000

# 開發模式啟動
CMD ["npm", "run", "dev"]

生產環境 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;"]

環境配置管理

開發環境配置

# 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' 開始開發"

部署腳本

# 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 腳本

{
  "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"
  }
}

開發輔助腳本

組件生成腳本

// 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 分析配置

// 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)
})

性能監控

// 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 配置

<!-- .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>

代碼模板

// .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
下次更新: 根據開發需求調整工具配置