mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-13 11:33:32 +00:00
12 KiB
12 KiB
description, applyTo
| description | applyTo |
|---|---|
| Comprehensive Vue 3 development standards and best practices: Composition API, `<script setup>`, the full reactivity system, compiler macros (defineModel/defineSlots/defineOptions), built-in components (Teleport/Suspense/Transition/KeepAlive), provide/inject, composables, Pinia, Vue Router, TypeScript, testing, performance, SSR, and security. | **/*.vue, **/*.ts, **/*.js, **/*.css, **/*.scss |
Vue 3 Development Instructions
Authoritative guidance for building production-grade Vue 3 applications. Default to the Composition API with <script setup lang="ts">, the modern reactivity system, and the official ecosystem (Pinia, Vue Router, Vite, Vitest). Prefer the idioms below over legacy Options API and Vue 2 patterns.
Project Context
- Vue 3.4+ (use 3.5+ features such as
useTemplateRef,useId, and reactive props destructuring where the project's version allows). <script setup lang="ts">single-file components (SFCs) as the default authoring style.- TypeScript everywhere: components, composables, stores, and router.
- Pinia for state management; Vue Router for routing; Vite for build/dev.
- Vitest + Vue Test Utils (or Testing Library for Vue) for tests.
Authoring Style & Component Design
- Use
<script setup>— it is more concise, faster, and has better type inference thansetup()or the Options API. - One responsibility per component; split large components into smaller focused ones plus composables.
- Order an SFC as
<script setup>, then<template>, then<style scoped>. - Name components in PascalCase; use multi-word names (e.g.
UserCard, notCard) to avoid clashing with native elements. - Co-locate component-specific types, and lift shared types into a
types/module.
Compiler Macros (no imports needed)
defineProps<T>()— declare typed props from a TypeScript interface/type for full inference.withDefaults(defineProps<T>(), { ... })— provide prop defaults (or use reactive props destructuring with defaults in 3.5+).defineEmits<{ change: [id: number]; update: [value: string] }>()— declare typed events.defineModel<T>()(3.4+) — the canonical way to implementv-modelon a component; supports multiple models, arguments, and modifiers.defineExpose({ ... })— explicitly expose a public imperative API; expose nothing by default.defineSlots<{ default(props: { item: T }): any }>()— type named/scoped slots.defineOptions({ name, inheritAttrs })— set component options inside<script setup>.- Never mutate props directly — emit an event, use
defineModel, or derive local state withcomputed/ref.
Reactivity System
Core primitives
ref()for primitives and single replaceable references; access via.valuein script (auto-unwrapped in templates).reactive()for deep-reactive objects/collections; never destructure it directly (breaks reactivity) — usetoRefs()/toRef().computed()for derived values; keep getters pure and side-effect free. Use writable computed (get/set) for two-way derived state.- Prefer
computedoverwatchwhenever you are deriving a value rather than performing a side effect.
Watchers
watch(source, cb, options)for explicit dependencies;watchEffect(cb)for auto-tracked dependencies.- Use watch options deliberately:
{ immediate: true },{ deep: true },{ once: true }(3.4+), andflush: 'post'when you need the DOM updated first. - Register cleanup with the
onCleanup/onWatcherCleanupcallback to cancel stale async work (debounce, fetch, listeners). - Stop manual watchers via their returned handle when they outlive their natural scope.
Advanced reactivity (use intentionally)
shallowRef/shallowReactivefor large or externally-managed data to skip deep tracking.readonly()to hand out immutable views of shared state.toRef/toRefsto keep reactivity when destructuring;toRaw/markRawto opt out for non-reactive objects (e.g. class instances, 3rd-party clients).effectScope()to group and dispose related effects together (useful in composables/libraries).customReffor debounced/throttled or storage-backed refs.
Composables (reusable logic)
- Extract stateful, reusable logic into
useXxx()functions undercomposables/. - Accept refs/getters as inputs and return refs/computed; use
toValue()/MaybeRefOrGetterto normalize ref-or-plain inputs. - Set up and tear down inside the composable (
onMounted/onUnmountedortryOnScopeDispose) so callers don't leak. - Keep composables synchronous in their setup phase; expose async actions as returned functions.
- Reach for VueUse for common needs instead of re-implementing (e.g.
useStorage,useEventListener,useDebounceFn).
Lifecycle & Effects
- Use
onMounted,onBeforeMount,onUpdated,onBeforeUnmount,onUnmounted,onActivated/onDeactivated(with<KeepAlive>), andonErrorCaptured. - Always clean up timers, listeners, observers, and subscriptions in
onUnmounted. - Guard browser-only APIs (
window,document) for SSR; run them inonMounted.
Template Best Practices
- Always set a stable, unique
:keyonv-for; never use the array index when items can reorder or mutate. - Never put
v-ifandv-foron the same element — filter via acomputedinstead. v-showfor frequently toggled elements;v-iffor conditional mounting.- Use
v-memoto skip re-rendering of expensive static subtrees, andv-oncefor content that renders a single time. - Use the
:(v-bind) and@(v-on) shorthands consistently; group nodes with<template>to avoid wrapper elements. - Avoid heavy expressions in templates — move them to
computedor methods.
Slots
- Use named slots for layout extension and scoped slots (
<slot :item="item" />+#default="{ item }") to expose data to the parent. - Provide sensible fallback slot content.
- Use the
v-slot(#) shorthand and dynamic slot names where appropriate.
Built-in Components
<Teleport to="body">for modals, toasts, and tooltips that must escape overflow/stacking contexts.<Suspense>with#default/#fallbackfor async setup and lazy components; pair with error handling.<Transition>/<TransitionGroup>for enter/leave and list animations (set:keyon grouped items).<KeepAlive>(withinclude/exclude/max) to cache component state across toggles; handleonActivated/onDeactivated.<component :is="...">for dynamic components;defineAsyncComponent(() => import('...'))for code-split/lazy loading with loading/error components.
Provide / Inject (dependency injection)
- Type injections with an
InjectionKey<T>(Symbol) for safety:provide(key, value)/inject(key). - Provide a default or assert presence to avoid
undefined. - Prefer
readonly()when providing state that children should not mutate; expose explicit updater functions instead. - Use injection for cross-cutting concerns; use Pinia for app-wide shared state.
Custom Directives & Plugins
- Author directives as objects with
mounted/updated(etc.) hooks; keep them DOM-focused (e.g.v-focus,v-click-outside). - Encapsulate global setup (router, pinia, i18n, UI libs) as plugins via
app.use(...); register global config inmain.ts.
State Management with Pinia
- Use Pinia for shared/cross-component state; keep component-only state local with
ref/reactive. - Prefer setup stores:
defineStore('user', () => { const user = ref(...); const isLoggedIn = computed(...); function login(){}; return { user, isLoggedIn, login } }). - One store per domain; keep actions for async/side effects and getters pure & synchronous.
- Destructure with
storeToRefs()to preserve reactivity; use$patchfor batched mutations,$resetto restore state, and$subscribe/$onActionfor cross-cutting concerns. - Handle SSR hydration correctly and keep stores serializable.
Routing with Vue Router
- Define routes with lazy
component: () => import('...')for automatic code splitting. - Use navigation guards (
beforeEach,beforeEnter,beforeRouteLeave) for auth and unsaved-changes checks; always resolve/next()exactly once. - Use
route.meta(typed) for per-route config likerequiresAuth. - Read params/query via
useRoute()and navigate viauseRouter(); treatroute.paramsas reactive (watch it, don't cache). - Configure
scrollBehaviorfor predictable scroll restoration; enable typed routes where available.
TypeScript Integration
- Type props/emits/slots through generic compiler macros, not runtime object syntax.
- Type refs explicitly when inference is too narrow:
ref<User | null>(null). - Type template refs with
useTemplateRef<HTMLInputElement>('input')(3.5+) orref<HTMLInputElement | null>(null). - Build generic components with
<script setup lang="ts" generic="T">. - Type provide/inject with
InjectionKey<T>; type Pinia stores via their inferred return types.
Styling
- Default to
<style scoped>; use:deep(),:slotted(), and:global()selectors deliberately. - Use
v-bind()in<style>to drive CSS from reactive state; prefer CSS custom properties for theming. - Consider CSS Modules (
<style module>) for class-name isolation in larger teams.
Forms & Validation
- Bind inputs with
v-model(anddefineModelfor custom inputs); use modifiers.lazy,.number,.trim. - Validate with a schema library (Zod/Yup) plus a form library (VeeValidate or FormKit) for non-trivial forms.
- Client validation is for UX only — always validate and sanitize on the server.
Error Handling
- Use
onErrorCapturedfor component-tree boundaries andapp.config.errorHandlerfor a global hook. - Wrap async/lazy boundaries with
<Suspense>+ an error fallback. - Surface user-friendly errors; log diagnostics to your monitoring pipeline.
Performance
- Code-split routes and heavy components (
defineAsyncComponent, dynamicimport()). - Use
computedfor caching,v-memo/v-oncefor static subtrees, andshallowRef/shallowReactivefor big datasets. - Virtualize long lists (e.g.
vue-virtual-scroller); paginate or window large data. - Avoid unnecessary deep reactivity and avoid creating new object/array literals inline in templates.
- In 3.5+, consider lazy hydration strategies for SSR to reduce time-to-interactive.
SSR / Meta-frameworks
- Prefer Nuxt for SSR/SSG/hybrid rendering unless you have a reason to hand-roll SSR.
- Keep code isomorphic: guard browser APIs, avoid module-level shared mutable state across requests, and ensure stores are request-scoped.
- Match server and client output to prevent hydration mismatches.
Accessibility
- Use semantic HTML first; add ARIA only to fill genuine gaps.
- Ensure full keyboard operability and visible focus states.
- Manage focus on route changes and when opening/closing dialogs (trap focus in modals).
- Give icon-only controls accessible names (
aria-label); associate labels with inputs.
Security
- Never render untrusted input with
v-html; sanitize (e.g. DOMPurify) if it is unavoidable. - Avoid dynamic
:is/:href/:srcfrom untrusted sources; validate URLs (blockjavascript:schemes). - Keep secrets server-side; only expose
VITE_-prefixed env vars intentionally meant to be public. - Apply a Content Security Policy and standard CSRF/XSS protections at the app layer.
Testing
- Unit-test composables as plain functions; component-test with Vue Test Utils / Testing Library.
- Test observable behavior and rendered output, not internal implementation details.
- Mock stores with
createTestingPinia; stub router and async boundaries as needed. - Cover critical user journeys with end-to-end tests (Playwright or Cypress).
Tooling
- Use Vite with the official Vue plugin; enable
vue-tscfor type-checking in CI. - Use ESLint (
eslint-plugin-vue) and Prettier; enable Volar/Vue official extension for the best DX. - Manage env via
import.meta.envwithVITE_-prefixed variables.
Anti-Patterns to Avoid
- Mixing Options API and Composition API arbitrarily in the same codebase.
- Mutating props directly, or destructuring
reactive()/Pinia stores withouttoRefs/storeToRefs. - Using
watchfor values that should becomputed. v-iftogether withv-foron one element; using array index as:key.- Heavy logic inside templates; unbounded deep reactivity on large data.
- Leaking timers/listeners by skipping
onUnmountedcleanup. - Rendering untrusted HTML via
v-html.