First Vue Application and SFC
Introduction
Your first Vue app mounts a root component into the page and updates the UI when reactive state changes. This chapter walks through main.ts, the single-file component (SFC) format, template interpolation, and a click counter—plus hot module replacement (HMR) so you see edits instantly.
Prerequisites
- Environment and create-vue Project
- A running dev server:
npm run dev
Entry Point: main.ts
src/main.ts:
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>Code explanation:
createAppcreates an application instance.mount('#app')replaces the empty div with your component treetype="module"enables ES module imports in the browser (Vite handles bundling)
Root Component App.vue
src/App.vue:
Code explanation:
<script setup>— Composition API syntax sugar; top-level bindings are exposed to templateref— reactive wrapper; use.valuein script, auto-unwrapped in template{{ message }}— text interpolation@click— shorthand forv-on:clickscoped— styles apply only to this component’s elements
Save the file—the browser updates without a full reload (HMR).
Single-File Component Structure
Every .vue file can have three blocks:
| Block | Role |
|---|---|
<script setup> | Logic, imports, reactive state |
<template> | HTML-like markup with Vue directives |
<style> | CSS; scoped limits to this component |
Why SFCs?
- Colocate markup, logic, and styles per component
- Build tools compile and optimize at build time
- Tree-shake unused code from dependencies
Compare with native Web Components—Vue compiles to efficient DOM updates without requiring Shadow DOM for every case.
Extract a Child Component
src/components/HelloBanner.vue:
Use in App.vue:
<script setup lang="ts">
import { ref } from "vue";
import HelloBanner from "./components/HelloBanner.vue";
const count = ref(0);
</script>
<template>
<HelloBanner title="Hello Vue" />
<p>Count: {{ count }}</p>
<button type="button" @click="count++">Add one</button>
</template>Code explanation:
- Import
.vuefiles like ES modules definePropsdeclares data from parent—details in Components
Options API Glimpse (Read-Only)
You may see older code like this:
export default {
data() {
return { count: 0 };
},
methods: {
increment() {
this.count += 1;
},
},
};Vue 3 supports both styles. New Hello Code chapters use <script setup> unless comparing legacy patterns.
DevTools Check
- Open Vue DevTools in browser
- Select
AppandHelloBanner - Edit
countin DevTools—UI updates (reactivity demo)
Practice Exercise
- Add a
decrementbutton - Add
resetthat setscountto 0 - Disable decrement when
count === 0(preview ofv-bind:disabled—Template Syntax)
FAQ
Why ref().value in script but not in template?
Template compiler auto-unwraps refs; script must use .value.
Can I use plain JavaScript instead of TypeScript?
Yes—rename to main.js, drop type annotations, remove lang="ts".
Multiple root elements in template?
Vue 3 allows fragments—multiple roots without a wrapper div.
Where does Vue come from?
vue package in node_modules—imported in main.ts.
HMR not working?
Restart npm run dev; check file saved; some edits to main.ts need full reload.
Is @click="count++" bad style?
Fine for demos; named functions increment read better in larger components.