941 lines
20 KiB
Markdown
941 lines
20 KiB
Markdown
# 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
|
||
**下次更新**: 根據開發需求調整工具配置 |