# 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 = ` ` // 測試模板 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 ``` ### 代碼模板 ```json // .vscode/vue.code-snippets { "Vue Composition Component": { "prefix": "vcomp", "body": [ "", "", "", "", "" ], "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 **下次更新**: 根據開發需求調整工具配置