common-rules.md 12 KB

Common Rules & Best Practices

File Organization

src/
├── api/              # API request functions (axios)
├── components/       # Reusable Vue components
├── router/          # Vue Router configuration
├── stores/          # Pinia stores (one file per store)
├── styles/          # Global SCSS files
├── utils/           # Utility functions
├── views/           # Page components (routed)
├── App.vue          # Root component
└── main.js          # Entry point

Vue 3 Composition API Rules

Component Structure

<template>
  <!-- Template first -->
</template>

<script setup>
// Imports
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'

// Reactive state
const count = ref(0)

// Computed properties
const doubled = computed(() => count.value * 2)

// Methods
const increment = () => count.value++

// Lifecycle hooks
onMounted(() => {
  // Setup logic
})
</script>

<style scoped lang="scss">
// Component styles
</style>

Rules

  • ✅ Use <script setup> syntax (modern, cleaner)
  • ✅ Use ref() for primitive values, reactive() for objects
  • ✅ Use computed() for derived state
  • ✅ Use onMounted(), onUnmounted() for lifecycle
  • ✅ Always use scoped attribute on <style> tags
  • ❌ Don't use data(), methods, computed options (Options API)
  • ❌ Don't use this keyword

Styling Rules

UnoCSS Usage

  • Use UnoCSS utility classes for layout, spacing, colors
  • Example: class="flex items-center justify-between gap-4 p-6 bg-white rounded-lg"
  • Responsive: class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
  • Dark mode: class="dark:bg-slate-900 dark:text-white"

SCSS Usage

  • Use SCSS only for:
    • Complex animations
    • Mixins and functions
    • Component-specific styling that can't be done with utilities
    • Global theme variables (in styles/variables.scss)

Variables (SCSS)

// styles/variables.scss
$primary-color: #1890ff;
$border-radius: 4px;
$transition-duration: 0.3s;

Rules

  • ✅ Prefer UnoCSS utilities over custom CSS
  • ✅ Use scoped styles in components
  • ✅ Use SCSS variables for theme colors
  • ✅ Use lang="scss" in style tags
  • ❌ Don't use inline styles (use classes)
  • ❌ Don't use global CSS (use scoped or utilities)
  • ❌ Don't use CSS-in-JS libraries

Pinia Store Rules

Store Structure

// stores/user.js
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useUserStore = defineStore('user', () => {
  // State
  const user = ref(null)
  const isLoading = ref(false)

  // Computed
  const isLoggedIn = computed(() => user.value !== null)

  // Actions
  const fetchUser = async () => {
    isLoading.value = true
    try {
      const response = await api.getUser()
      user.value = response.data
    }
    finally {
      isLoading.value = false
    }
  }

  const logout = () => {
    user.value = null
  }

  return {
    user,
    isLoading,
    isLoggedIn,
    fetchUser,
    logout
  }
})

Rules

  • ✅ Use Composition API style (setup function)
  • ✅ One store per domain (user, notes, settings, etc.)
  • ✅ Export store with useXxxStore naming
  • ✅ Keep stores focused and single-responsibility
  • ✅ Use ref() for state, computed() for derived state
  • ❌ Don't use Options API style stores
  • ❌ Don't mix multiple domains in one store

API & Axios Rules

Request Setup

// utils/request.js
import axios from 'axios'

const instance = axios.create({
  baseURL: '/api',
  timeout: 10000
})

// Request interceptor
instance.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// Response interceptor
instance.interceptors.response.use(
  response => response.data,
  error => Promise.reject(error)
)

export default instance

API Functions

// api/user.js
import request from '@/utils/request'

export const getUser = () => request.get('/user')
export const updateUser = data => request.put('/user', data)
export const deleteUser = () => request.delete('/user')

Usage in Components

<script setup>
import { onMounted, ref } from 'vue'
import { getUser } from '@/api/user'

const user = ref(null)
const loading = ref(false)

onMounted(async () => {
  loading.value = true
  try {
    user.value = await getUser()
  }
  catch (error) {
    console.error('Failed to fetch user:', error)
  }
  finally {
    loading.value = false
  }
})
</script>

Rules

  • ✅ Centralize API calls in api/ folder
  • ✅ Use request interceptors for auth tokens
  • ✅ Handle errors gracefully
  • ✅ Use async/await pattern
  • ✅ Return response.data directly from interceptor
  • ❌ Don't make API calls directly in components
  • ❌ Don't hardcode URLs

Router Rules

Route Definition

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFound.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

Rules

  • ✅ Use lazy loading with () => import()
  • ✅ Use named routes
  • ✅ Use kebab-case for paths
  • ✅ Add 404 catch-all route
  • ✅ Use createWebHistory() for clean URLs
  • ❌ Don't use hash mode (#/)
  • ❌ Don't hardcode component imports

Component Rules

Naming

  • Files: PascalCase (e.g., UserCard.vue)
  • Components: PascalCase (e.g., <UserCard />)
  • Props: camelCase (e.g., userName)
  • Events: kebab-case (e.g., @user-updated)

Props & Emits

<script setup>
defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})

defineEmits(['update', 'delete'])

function handleUpdate() {
  emit('update', newValue)
}
</script>

Rules

  • ✅ Use defineProps() and defineEmits()
  • ✅ Always type props
  • ✅ Provide default values
  • ✅ Use descriptive prop names
  • ✅ Keep components focused (single responsibility)
  • ❌ Don't mutate props directly
  • ❌ Don't use v-model for complex logic

Vant Component Rules

Common Components

  • <van-button> - Buttons
  • <van-cell> - List items
  • <van-form> - Forms
  • <van-field> - Form inputs
  • <van-popup> - Modals/Popups
  • <van-tab> - Tabs
  • <van-list> - Infinite scroll lists
  • <van-loading> - Loading indicator
  • <van-toast> - Notifications

Usage

<template>
  <van-button type="primary" @click="handleClick">
    Click me
  </van-button>

  <van-field
    v-model="form.name"
    label="Name"
    placeholder="Enter name"
  />

  <van-toast message="Success!" />
</template>

<script setup>
import { showToast } from 'vant'
import { ref } from 'vue'

const form = ref({ name: '' })

function handleClick() {
  showToast('Button clicked!')
}
</script>

Rules

  • ✅ Use Vant components for UI
  • ✅ Use Vant utilities (showToast, showDialog, etc.)
  • ✅ Customize with UnoCSS classes
  • ✅ Check Vant docs for component props
  • ❌ Don't create custom buttons/inputs (use Vant)
  • ❌ Don't override Vant styles globally

Performance Rules

Code Splitting

  • ✅ Use lazy loading for routes: () => import('@/views/Page.vue')
  • ✅ Use lazy loading for heavy components
  • ✅ Use <Suspense> for async components

Optimization

  • ✅ Use computed() instead of methods for derived state
  • ✅ Use v-show for frequently toggled elements
  • ✅ Use v-if for rarely rendered elements
  • ✅ Use key binding for list items
  • ✅ Avoid unnecessary re-renders with ref and reactive

Bundle Size

  • ✅ Tree-shake unused imports
  • ✅ Use dynamic imports for large libraries
  • ✅ Monitor bundle size with vite build --analyze

Error Handling

Try-Catch Pattern

async function fetchData() {
  try {
    const data = await api.getData()
    return data
  }
  catch (error) {
    console.error('Error fetching data:', error)
    showToast('Failed to load data')
    throw error
  }
}

Rules

  • ✅ Always wrap async operations in try-catch
  • ✅ Show user-friendly error messages
  • ✅ Log errors for debugging
  • ✅ Handle network errors gracefully
  • ❌ Don't silently fail
  • ❌ Don't expose technical error details to users

Testing Rules

Unit Tests (if applicable)

  • Use Vitest for unit tests
  • Test stores, utilities, and API functions
  • Aim for 80%+ coverage on critical paths

E2E Tests (if applicable)

  • Use Playwright or Cypress
  • Test user workflows
  • Test critical features

Git & Commit Rules

Commit Messages

  • Use conventional commits: feat:, fix:, docs:, style:, refactor:, test:, chore:
  • Example: feat: add user authentication
  • Keep messages concise and descriptive

Branch Naming

  • Feature: feature/user-auth
  • Bug fix: fix/login-error
  • Docs: docs/readme-update

Environment Variables

.env Files

# .env (shared)
VITE_API_BASE_URL=/api

# .env.development (dev only)
VITE_DEBUG=true

# .env.production (prod only)
VITE_DEBUG=false

Usage in Code

const apiUrl = import.meta.env.VITE_API_BASE_URL

Rules

  • ✅ Use VITE_ prefix for client-side variables
  • ✅ Keep sensitive data in .env.local (not committed)
  • ✅ Document all env variables
  • ❌ Don't commit .env.local
  • ❌ Don't expose API keys in client code

Accessibility Rules

HTML Semantics

  • ✅ Use semantic HTML: <button>, <nav>, <main>, <article>
  • ✅ Use <label> for form inputs
  • ✅ Use alt text for images
  • ✅ Use ARIA attributes when needed

Keyboard Navigation

  • ✅ All interactive elements must be keyboard accessible
  • ✅ Use tabindex carefully (usually not needed)
  • ✅ Provide focus indicators

Color & Contrast

  • ✅ Don't rely on color alone to convey information
  • ✅ Ensure 4.5:1 contrast ratio for text
  • ✅ Test with accessibility tools

Code Quality

Linting & Formatting

  • Use ESLint for code quality
  • Use Prettier for code formatting
  • Run before committing

Naming Conventions

  • Variables: camelCase
  • Constants: UPPER_SNAKE_CASE
  • Classes/Components: PascalCase
  • Files: kebab-case (except components)

Comments

  • ✅ Use comments for complex logic
  • ✅ Use JSDoc for functions
  • ❌ Don't over-comment obvious code
  • ❌ Don't leave commented-out code

Security Rules

XSS Prevention

  • ✅ Vue auto-escapes template content
  • ✅ Use v-text instead of {{ }} for user input
  • ✅ Use v-html only for trusted content
  • ❌ Don't use innerHTML directly

CSRF Protection

  • ✅ Use CSRF tokens in forms
  • ✅ Validate tokens on backend
  • ✅ Use SameSite cookies

Authentication

  • ✅ Store tokens securely (httpOnly cookies preferred)
  • ✅ Validate tokens on every request
  • ✅ Implement token refresh logic
  • ❌ Don't store sensitive data in localStorage

Debugging

Vue DevTools

  • Install Vue DevTools browser extension
  • Inspect component state and props
  • Track store mutations

Console Logging

// Development only
if (import.meta.env.DEV) {
  console.log('Debug info:', data)
}

Network Debugging

  • Use browser DevTools Network tab
  • Check API requests and responses
  • Monitor performance

Documentation

README

  • Include setup instructions
  • Document environment variables
  • List available scripts
  • Provide examples

Code Comments

  • Document complex algorithms
  • Explain non-obvious decisions
  • Use JSDoc for functions

Commit Messages

  • Reference issues: fixes #123
  • Explain why, not what
  • Keep concise