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

Entry Point: main.ts

src/main.ts:

typescript
import { createApp } from "vue";
import App from "./App.vue";
 
createApp(App).mount("#app");

index.html:

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:

  • createApp creates an application instance
  • .mount('#app') replaces the empty div with your component tree
  • type="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 template
  • ref — reactive wrapper; use .value in script, auto-unwrapped in template
  • {{ message }} — text interpolation
  • @click — shorthand for v-on:click
  • scoped — 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:

BlockRole
<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:

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 .vue files like ES modules
  • defineProps declares data from parent—details in Components

Options API Glimpse (Read-Only)

You may see older code like this:

javascript
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

  1. Open Vue DevTools in browser
  2. Select App and HelloBanner
  3. Edit count in DevTools—UI updates (reactivity demo)

Practice Exercise

  1. Add a decrement button
  2. Add reset that sets count to 0
  3. Disable decrement when count === 0 (preview of v-bind:disabledTemplate 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.