What Is a .VUE File?
Vue source
Vue.js Single File Component (.vue)
Vue.js is a progressive JavaScript framework for building user interfaces and single-page applications. Vue Single File Components (SFCs) are a special file format that allows developers to encapsulate template, script, and style code for a Vue component in a single .vue file. This approach promotes better organization, maintainability, and development experience in Vue applications.
Technical Overview
Vue SFCs combine HTML-like template syntax, JavaScript logic, and CSS styling in a unified file structure. The Vue build tools (like Vite or webpack with vue-loader) compile these files into standard JavaScript modules that can be used in web applications. This architecture enables component-based development with clear separation of concerns while keeping related code together.
Key Features
- Single File Components: Template, script, and styles in one file
- Scoped CSS: Automatic style encapsulation per component
- Hot Module Replacement: Live editing during development
- TypeScript Support: First-class TypeScript integration
- Composition API: Modern reactive programming model
- CSS Preprocessors: Support for Sass, Less, Stylus, and more
Vue SFC Structure
Basic Component Structure
<template>
<div class="greeting">
<h1>{{ message }}</h1>
<button @click="updateMessage">Click me!</button>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
name: 'GreetingComponent',
setup() {
const message = ref('Hello, Vue!')
const items = reactive([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
])
const updateMessage = () => {
message.value = 'Button clicked!'
}
return {
message,
items,
updateMessage
}
}
}
</script>
<style scoped>
.greeting {
text-align: center;
padding: 20px;
}
h1 {
color: #42b883;
font-family: 'Arial', sans-serif;
}
button {
background-color: #42b883;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #369870;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 10px 0;
padding: 8px;
background-color: #f0f0f0;
border-radius: 4px;
}
</style>
TypeScript Setup
<template>
<div class="counter">
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watchEffect } from 'vue'
interface CounterProps {
initialValue?: number
step?: number
}
const props = withDefaults(defineProps<CounterProps>(), {
initialValue: 0,
step: 1
})
const emit = defineEmits<{
change: [value: number]
reset: []
}>()
const count = ref<number>(props.initialValue)
const isEven = computed(() => count.value % 2 === 0)
const increment = (): void => {
count.value += props.step
emit('change', count.value)
}
const decrement = (): void => {
count.value -= props.step
emit('change', count.value)
}
const reset = (): void => {
count.value = props.initialValue
emit('reset')
}
watchEffect(() => {
console.log(`Count changed to: ${count.value}`)
})
</script>
<style scoped>
.counter {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
button {
min-width: 60px;
padding: 8px 16px;
margin: 0 4px;
border: 1px solid #42b883;
background-color: white;
color: #42b883;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
button:hover {
background-color: #42b883;
color: white;
}
</style>
Advanced Vue Features
Composition API
<template>
<div class="user-profile">
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>
<img :src="user.avatar" :alt="user.name" />
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button @click="refreshUser">Refresh</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useAsyncData } from '@/composables/useAsyncData'
interface User {
id: number
name: string
email: string
avatar: string
}
const { data: user, loading, error, execute: fetchUser } = useAsyncData<User>(
() => fetch('/api/user/1').then(res => res.json())
)
const refreshUser = () => {
fetchUser()
}
onMounted(() => {
fetchUser()
})
</script>
Custom Composables
<!-- useAsyncData.ts -->
<script lang="ts">
import { ref, Ref } from 'vue'
export function useAsyncData<T>(
asyncFunction: () => Promise<T>
): {
data: Ref<T | null>
loading: Ref<boolean>
error: Ref<string | null>
execute: () => Promise<void>
} {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const execute = async () => {
try {
loading.value = true
error.value = null
data.value = await asyncFunction()
} catch (err) {
error.value = err instanceof Error ? err.message : 'An error occurred'
} finally {
loading.value = false
}
}
return {
data,
loading,
error,
execute
}
}
</script>
Slots and Dynamic Components
<template>
<div class="modal" v-if="isVisible" @click.self="close">
<div class="modal-content">
<header class="modal-header">
<slot name="header">
<h3>Default Header</h3>
</slot>
<button @click="close" class="close-btn">×</button>
</header>
<main class="modal-body">
<slot>
<p>Default content</p>
</slot>
</main>
<footer class="modal-footer">
<slot name="footer" :close="close">
<button @click="close">Close</button>
</slot>
</footer>
</div>
</div>
</template>
<script setup lang="ts">
interface ModalProps {
isVisible: boolean
}
defineProps<ModalProps>()
const emit = defineEmits<{
close: []
}>()
const close = () => {
emit('close')
}
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #eee;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
}
.modal-body {
padding: 16px;
}
.modal-footer {
padding: 16px;
border-top: 1px solid #eee;
text-align: right;
}
</style>
CSS Features and Preprocessors
Scoped Styles and CSS Modules
<template>
<div class="component">
<h1 class="title">Main Title</h1>
<p :class="$style.description">This uses CSS modules</p>
<button class="btn btn-primary">Click me</button>
</div>
</template>
<script setup lang="ts">
// Component logic
</script>
<!-- Scoped styles -->
<style scoped>
.component {
padding: 20px;
}
.title {
color: #333;
font-size: 2rem;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background-color: #007bff;
color: white;
}
</style>
<!-- CSS Modules -->
<style module>
.description {
font-style: italic;
color: #666;
margin: 16px 0;
}
</style>
<!-- Global styles -->
<style>
/* These styles are not scoped */
.global-utility {
margin: 0;
padding: 0;
}
</style>
Sass/SCSS Integration
<template>
<div class="card">
<header class="card-header">
<h3>{{ title }}</h3>
</header>
<div class="card-body">
<slot></slot>
</div>
<footer class="card-footer" v-if="hasFooter">
<slot name="footer"></slot>
</footer>
</div>
</template>
<script setup lang="ts">
interface CardProps {
title: string
hasFooter?: boolean
}
defineProps<CardProps>()
</script>
<style lang="scss" scoped>
$primary-color: #42b883;
$border-radius: 8px;
$shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.card {
border: 1px solid #ddd;
border-radius: $border-radius;
box-shadow: $shadow;
overflow: hidden;
&-header {
background-color: lighten($primary-color, 40%);
padding: 16px;
border-bottom: 1px solid #ddd;
h3 {
margin: 0;
color: darken($primary-color, 20%);
}
}
&-body {
padding: 16px;
}
&-footer {
background-color: #f8f9fa;
padding: 12px 16px;
border-top: 1px solid #ddd;
text-align: right;
}
// Responsive design
@media (max-width: 768px) {
&-header,
&-body,
&-footer {
padding: 12px;
}
}
}
</style>
Build Tools and Development
Vite Configuration
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@views': resolve(__dirname, 'src/views'),
'@utils': resolve(__dirname, 'src/utils'),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/variables.scss";'
}
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router'],
ui: ['@/components/ui/index.js']
}
}
}
}
})
Vue CLI Configuration
// vue.config.js
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src'))
.set('@components', resolve('src/components'))
},
css: {
loaderOptions: {
sass: {
additionalData: '@import "@/styles/variables.scss";'
}
}
},
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
}
Testing Vue Components
Unit Testing with Vitest
<!-- Counter.vue -->
<template>
<div>
<span data-testid="count">{{ count }}</span>
<button @click="increment" data-testid="increment">+</button>
<button @click="decrement" data-testid="decrement">-</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
const decrement = () => count.value--
</script>
// Counter.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from '@/components/Counter.vue'
describe('Counter', () => {
it('renders initial count', () => {
const wrapper = mount(Counter)
expect(wrapper.get('[data-testid="count"]').text()).toBe('0')
})
it('increments count when increment button is clicked', async () => {
const wrapper = mount(Counter)
await wrapper.get('[data-testid="increment"]').trigger('click')
expect(wrapper.get('[data-testid="count"]').text()).toBe('1')
})
it('decrements count when decrement button is clicked', async () => {
const wrapper = mount(Counter)
await wrapper.get('[data-testid="decrement"]').trigger('click')
expect(wrapper.get('[data-testid="count"]').text()).toBe('-1')
})
})
Best Practices
Component Organization
<template>
<!-- Keep templates clean and readable -->
<div class="user-card">
<UserAvatar
:src="user.avatar"
:alt="user.name"
size="large"
/>
<UserInfo
:name="user.name"
:email="user.email"
:status="user.status"
/>
<UserActions
@edit="handleEdit"
@delete="handleDelete"
:disabled="isLoading"
/>
</div>
</template>
<script setup lang="ts">
// Use composition API for better code organization
import { ref, computed } from 'vue'
import { useUser } from '@/composables/useUser'
import UserAvatar from './UserAvatar.vue'
import UserInfo from './UserInfo.vue'
import UserActions from './UserActions.vue'
interface User {
id: number
name: string
email: string
avatar: string
status: 'active' | 'inactive'
}
interface Props {
userId: number
}
const props = defineProps<Props>()
const emit = defineEmits<{
userUpdated: [user: User]
userDeleted: [userId: number]
}>()
const { user, isLoading, updateUser, deleteUser } = useUser(props.userId)
const handleEdit = () => {
// Implementation
}
const handleDelete = async () => {
await deleteUser()
emit('userDeleted', props.userId)
}
</script>
<style scoped>
.user-card {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
}
</style>
Performance Optimization
<template>
<div>
<!-- Use v-memo for expensive list items -->
<div
v-for="item in items"
:key="item.id"
v-memo="[item.id, item.selected]"
>
<ExpensiveComponent :item="item" />
</div>
<!-- Lazy load components -->
<Suspense>
<template #default>
<AsyncComponent v-if="showAsync" />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, shallowRef } from 'vue'
// Lazy load heavy components
const AsyncComponent = defineAsyncComponent(() =>
import('@/components/HeavyComponent.vue')
)
// Use shallowRef for large objects that don't need deep reactivity
const items = shallowRef([])
</script>
File Format Details
- MIME Type:
text/x-vue - File Extension:
.vue - Character Encoding: UTF-8
- Language: HTML, JavaScript/TypeScript, CSS/SCSS/Sass/Less
- Build Tools: Vite, Vue CLI, webpack with vue-loader
Vue Single File Components provide an elegant solution for component-based development, combining the power of modern JavaScript frameworks with intuitive templating and styling. The format's flexibility and tooling support make it an excellent choice for building maintainable and scalable web applications.
File Information
Vue source
Code
.vue
text/x-vue
Related File Types
Other file types in the Code category you might also need:
Start Analyzing VUE Files Now
Use our free AI-powered tool to detect and analyze Vue source files instantly with Google's Magika technology.
⚡Try File Detection Tool