--- name: vue3-quasar description: > Vue.js 3 + Quasar Framework patterns with Composition API, Pinia state management. Trigger: When developing Vue.js 3 applications with Quasar Framework, Composition API, Pinia, or building SPA/PWA/mobile apps. license: Apache-2.0 metadata: author: gentleman-programming version: "2.0" --- ## When to Use - Building Vue.js 3 applications with Quasar Framework - Creating reusable, scalable, and decoupled components - Implementing Composition API patterns - Setting up Pinia for state management with persistence - Developing SPA, PWA, or mobile applications with Quasar ## Critical Patterns ### Component Architecture - **Composition API FIRST** — No Options API unless legacy - **SFC (Single File Component)** — Template, script, and style in one `.vue` file - **Composable for logic** — Extract complex logic to `useXxx()` composables in separate `.ts` files - **Single Responsibility** — One concern per component. If a component grows, split into smaller child components, not separate file types - **Props Interface** — Always define TypeScript interfaces for props - **Emits Definition** — Explicit emit declarations - **Slot Strategy** — Named slots for maximum flexibility ### Component File Organization **Every component lives in its own directory:** ``` ComponentName/ ├── ComponentName.vue # SFC: template + script setup + scoped styles ├── ComponentName.ts # Composable: useComponentName() with all logic └── ComponentName.scss # Styles (optional, only if complex styles) ``` **The `.vue` file** contains the template inline, imports the composable, and optionally uses ` ``` ### Page with Composable (no props/emits) **OnboardingPage.ts:** ```typescript import { ref, computed } from 'vue'; import { useRouter } from 'vue-router'; export function useOnboardingPage() { const router = useRouter(); const name = ref(''); const saving = ref(false); const isValid = computed(() => name.value.trim().length > 0); async function handleSubmit() { if (!isValid.value) return; saving.value = true; try { // save logic await router.push('/'); } finally { saving.value = false; } } return { name, saving, isValid, handleSubmit }; } ``` **OnboardingPage.vue:** ```vue ``` ### Use Case Pattern ```typescript // use-cases/createProfile.ts import type { UserProfileRepository } from '../repositories/profile.repository.port'; import type { UserProfile } from '../types/profile.types'; export function createProfile(repository: UserProfileRepository) { return async (data: Omit): Promise => { const existing = await repository.get(); if (existing) { throw new Error('Profile already exists. Use updateProfile instead.'); } return repository.save(data); }; } ``` **Key points:** - One file, one operation - Receives repository port (interface) as argument — never the concrete adapter - Returns an async function — the actual operation - Contains business validations and rules - Pure function, no framework dependencies ### Pinia Store Pattern (with Use Cases) ```typescript import { ProfileSQLiteRepository } from '../repositories/profile.sqlite-repository'; import { getProfile } from '../use-cases/getProfile'; import { createProfile } from '../use-cases/createProfile'; import { updateProfile } from '../use-cases/updateProfile'; export const useProfileStore = defineStore('profile', () => { const repository = new ProfileSQLiteRepository(); // Wire use cases with repository const get = getProfile(repository); const create = createProfile(repository); const update = updateProfile(repository); const profile = ref(null); const hasProfile = computed(() => profile.value !== null); async function loadProfile() { profile.value = await get(); } async function saveProfile(data: Omit) { profile.value = profile.value ? await update(data) : await create(data); } return { profile, hasProfile, loadProfile, saveProfile }; }); ``` **Key points:** - Store instantiates the repository and wires it into use cases - Store actions delegate to use cases, never call repository directly - Store manages reactive state only ### Shared Composable Pattern ```typescript // composables/useApi.ts export function useApi(url: MaybeRef) { const data = ref(null); const loading = ref(false); const error = ref(null); async function execute() { loading.value = true; error.value = null; try { data.value = await $fetch(unref(url)); } catch (err) { error.value = err instanceof Error ? err.message : 'Unknown error'; } finally { loading.value = false; } } return { data: readonly(data), loading: readonly(loading), error: readonly(error), execute, }; } ``` ## Commands ```bash # Development quasar dev # SPA dev server quasar dev -m capacitor -T android # Android dev quasar dev -m capacitor -T ios # iOS dev # Build quasar build # SPA production build quasar build -m capacitor -T android # Android build quasar build -m pwa # PWA build # Utilities quasar new component MyComponent # Generate component quasar new page MyPage # Generate page quasar new layout MyLayout # Generate layout ``` ## Resources - **Quasar Documentation**: https://quasar.dev/ - **Vue 3 Composition API**: https://vuejs.org/guide/extras/composition-api-faq.html - **Pinia Documentation**: https://pinia.vuejs.org/