Single-File Components and Project Structure

Beginner
11 min

Single-File Components and Project Structure

A Vue application is built out of .vue files called Single-File Components (SFCs). Each file bundles the markup, logic and styles of one component in a single place, which is the format you will use for every real project. In this lesson you will learn the anatomy of an SFC, how <script setup> works, what each folder in a scaffolded Vite project is for, and how the application is mounted onto the page.

Anatomy of a .vue File

An SFC has up to three top-level blocks. Only <template> is required, but almost every component uses the first two.

vue
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button class="counter" @click="count++"> Clicked {{ count }} times </button> </template> <style scoped> .counter { padding: 0.5rem 1rem; border-radius: 6px; } </style>
  • <script setup> holds the component logic. Every top-level binding (variables, functions, imported components) is automatically available in the template.
  • <template> contains the HTML with Vue directives and {{ }} interpolation.
  • <style scoped> restricts the CSS to this component only. Vue adds a data-v-xxxxxxx attribute to the component's elements and rewrites the selectors accordingly, so .counter here cannot leak into other components.

The browser cannot load .vue files directly. Vite (via the @vitejs/plugin-vue plugin) compiles each SFC into a plain JavaScript module during development and at build time.

Why script setup?

Vue 3 supports two ways to write the logic block. The older <script> with export default { ... } is the Options API. <script setup> is compile-time syntactic sugar for the Composition API and is the recommended default:

| Feature | <script> + export default | <script setup> | | --- | --- | --- | | Boilerplate | Must return values from setup() | Top-level bindings exposed automatically | | Imported components | Registered in components | Usable directly in the template | | TypeScript | Works | Best inference, defineProps<T>() |

The two styles can coexist in one project; the Options API is explained in a later lesson. New code should use <script setup>.

The Scaffolded Project

npm create vue@latest generates a Vite project. The important files:

bash
my-app/ ├── index.html # the single HTML page; contains <div id="app"> ├── package.json ├── vite.config.js # Vite and plugin configuration ├── public/ # static files copied as-is (favicon, robots.txt) └── src/ ├── main.js # entry point: creates and mounts the app ├── App.vue # root component ├── assets/ # images and CSS processed by Vite └── components/ # your reusable components

index.html lives at the project root because Vite treats it as the entry point and rewrites its <script type="module" src="/src/main.js"> tag. Files in public/ are served at the site root without processing, while files in src/assets/ are imported and hashed for cache busting.

Mounting the Application

src/main.js is where the component tree becomes a running application:

javascript
import { createApp } from 'vue' import App from './App.vue' import './assets/main.css' const app = createApp(App) app.mount('#app')

createApp receives the root component and returns an application instance. Plugins (Vue Router, Pinia), global components and configuration are registered on this instance before mount. mount('#app') renders the tree inside the element matched by the selector in index.html. The <div id="app"> element itself stays in the DOM; Vue renders into it.

Using Components Inside Other Components

Import a child component in <script setup> and use it as a tag: import HelloWorld from './components/HelloWorld.vue' followed by <HelloWorld msg="Welcome" /> in the template. No registration step is needed. Give components multi-word PascalCase names (TodoItem, not Todo) so they never clash with existing or future HTML elements, and name the file after the component.

Tips

  • Run npm run dev for the dev server with hot module replacement; edits to a .vue file update the page without a full reload.
  • Install the "Vue - Official" extension in VS Code for highlighting, template type checking and autocompletion inside .vue files.
  • Use <style scoped> by default. Reserve unscoped <style> for global rules such as CSS resets, and keep those in assets/main.css.
Quick Quiz
Question 1 of 2

What does `<style scoped>` do?

Key Takeaways

  • A .vue file combines <script setup>, <template> and <style> for one component.
  • <script setup> exposes every top-level binding to the template with no boilerplate.
  • scoped styles are rewritten with a data attribute so they cannot leak.
  • main.js calls createApp(App).mount('#app'); plugins are registered on the app instance before mounting.
  • public/ is copied verbatim, src/assets/ is processed and hashed by Vite.

Next lesson: Template Syntax and Reactivity — interpolation, expressions in templates and how Vue keeps the DOM in sync with your data.

Single-File Components and Project Structure - Vue.js | CodeYourCraft | CodeYourCraft