Vue Adapter
Vue 3 with Composition API, script setup, Vue Router, Pinia state management, and SFC (Single File Component) support — all zero-config.
Vue 3 · Composition API · Pinia
Vue 3 SFCs
Single File Components with <template>, <script setup>, and <style> blocks compiled natively.
Vue HMR
Component-level hot reload with official @vue/runtime-dom HMR API. State preserved on update.
Composition API
Full Composition API support including composables, provide/inject, and reactive refs.
script setup
<script setup> single-file syntax for concise component authoring with auto-exposed bindings.
Volar-compatible
TypeScript types via Volar. Configure tsconfig with @vue/language-tools for IDE support.
Auto-import
Use @lunx/plugin-auto-import to eliminate import statements for Vue APIs and Pinia stores.
Setup
bash
npm create lunx-dev@latest my-vue-app -- --framework vue --ts cd my-vue-app && npm install && npm run dev
typescriptlunx.config.ts
import { defineConfig } from 'lunx' export default defineConfig({ framework: 'vue', })
A Vue component with script setup
htmlsrc/components/Counter.vue
<script setup lang="ts"> import { ref, computed } from 'vue' const props = defineProps<{ initialValue?: number label?: string }>() const count = ref(props.initialValue ?? 0) const doubled = computed(() => count.value * 2) const canReset = computed(() => count.value !== 0) </script> <template> <div class="flex flex-col items-center gap-4 p-6 rounded-xl border border-zinc-800"> <h2 class="text-lg font-semibold text-white">{{ label ?? 'Counter' }}</h2> <p class="text-4xl font-bold tabular-nums text-blue-400">{{ count }}</p> <p class="text-sm text-zinc-500">doubled: {{ doubled }}</p> <div class="flex gap-2"> <button @click="count--" class="px-4 py-2 rounded-lg bg-zinc-800 text-white">−</button> <button @click="count++" class="px-4 py-2 rounded-lg bg-blue-600 text-white">+</button> <button @click="count = 0" :disabled="!canReset" class="px-4 py-2 rounded-lg bg-zinc-900 text-zinc-400 disabled:opacity-30">Reset</button> </div> </div> </template> <style scoped> /* Scoped styles only apply to this component */ button { transition: opacity 0.2s; } </style>
Vue Router 4
bash
npm install vue-router@4typescriptsrc/router/index.ts
import { createRouter, createWebHistory } from 'vue-router' export const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: () => import('@/pages/Home.vue'), // code-split automatically }, { path: '/about', component: () => import('@/pages/About.vue'), }, { path: '/users/:id', component: () => import('@/pages/UserProfile.vue'), props: true, }, { path: '/:pathMatch(.*)*', component: () => import('@/pages/NotFound.vue'), }, ], })
State management with Pinia
bash
npm install piniatypescriptsrc/stores/useUserStore.ts
import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useUserStore = defineStore('user', () => { // State const user = ref<{ id: number; name: string; email: string } | null>(null) const loading = ref(false) // Getters const isLoggedIn = computed(() => user.value !== null) const displayName = computed(() => user.value?.name ?? 'Guest') // Actions async function login(email: string, password: string) { loading.value = true try { const res = await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), headers: { 'Content-Type': 'application/json' }, }) user.value = await res.json() } finally { loading.value = false } } function logout() { user.value = null } return { user, loading, isLoggedIn, displayName, login, logout } })
Composables pattern
typescriptsrc/composables/useFetch.ts
import { ref, watchEffect, type Ref } from 'vue' export function useFetch<T>(url: Ref<string> | string) { const data = ref<T | null>(null) const loading = ref(true) const error = ref<string | null>(null) watchEffect(async (onCleanup) => { const controller = new AbortController() onCleanup(() => controller.abort()) loading.value = true error.value = null try { const res = await fetch(typeof url === 'string' ? url : url.value, { signal: controller.signal, }) data.value = await res.json() } catch (e) { if ((e as Error).name !== 'AbortError') { error.value = (e as Error).message } } finally { loading.value = false } }) return { data, loading, error } }
Enable Vue DevTools
Install the Vue DevTools browser extension to inspect component trees, Pinia stores, router history, and timeline events. Lunx enables devtools in development mode automatically.