skip to content

Why do Vue single-file components need a build step, and what changes when you use Vue without one and compile templates in the browser?

level: middleimportance: should knowfreq 50%

answer

  1. a format browsers cannot load
  2. the compiler ships to users
  3. compile cost at startup
  4. no script setup, no scoped styles
  5. the HTML parser runs first

basics

~20 s

Browsers cannot load .vue files, so @vue/compiler-sfc must compile them ahead of time. Without a build, templates are strings or in-DOM markup compiled in the browser: the compiler ships to users, and script setup and scoped styles disappear.

solid answer

~40 s

An SFC is a framework-specific format, so it must be **pre-compiled** by `@vue/compiler-sfc`; in return you get pre-compiled templates with no runtime compilation cost, scoped CSS, `<script setup>`, cross-block compile-time optimisations, IDE type-checking of templates and hot module replacement. Vue also works **without a build step**: components are plain objects with a `template` string, or the template is read from the page's DOM, and Vue compiles it **in the browser**. That requires a Vue build that includes the template compiler, so users download the compiler and pay compile time when components first render. `<script setup>` and SFC style features are unavailable, and in-DOM templates pass through the browser's HTML parser first, so tags and attributes are lower-cased and self-closing component tags break.

code

html · 23 lines
html
<div id="app">
  <!-- in-DOM template: the browser parses this HTML before Vue compiles it -->
  <todo-item v-for="t in todos" :key="t.id" :item-title="t.title"></todo-item>
</div>

<script type="module">
  // 'vue' is mapped by an import map to a browser ES module build of Vue
  // that includes the template compiler
  import { createApp, ref } from 'vue'

  const TodoItem = {
    props: ['itemTitle'],
    template: `<li>{{ itemTitle }}</li>`,
  }

  createApp({
    components: { TodoItem },
    setup() {
      const todos = ref([{ id: 1, title: 'Write docs' }])
      return { todos }
    },
  }).mount('#app')
</script>

go deeper

for a junior

Recall that .vue files must be compiled by a build tool, and that Vue can also run without one using template strings compiled in the browser.

for a middle

Explain what the build step buys, what in-browser compilation costs in download size and startup, and which features, script setup and scoped styles, disappear without it.

for a senior

Diagnose no-build setups: a template option on a build without the compiler, in-DOM casing and self-closing bugs, and when to move a growing page onto SFCs.

for a principal

Decide per product whether a build pipeline is justified, such as progressive enhancement of server-rendered pages versus an SPA, and plan the migration path between the two.

## Why a build step is required A `.vue` file is a **framework-specific file format**. Browsers understand HTML, CSS and JavaScript modules, not a file with `<template>`, `<script setup>` and `<style scoped>` blocks. The Vue docs are explicit: an SFC must be pre-compiled by `@vue/compiler-sfc` into standard JavaScript and CSS. That compilation happens in a build tool, so choosing SFCs means choosing a build step. The docs list what that step buys: - modular components written in familiar HTML, CSS and JavaScript syntax; - **pre-compiled templates without runtime compilation cost**; - component-scoped CSS; - the more ergonomic `<script setup>` syntax; - more compile-time optimisations from analysing template and script together; - IDE support with auto-completion and type-checking for template expressions; - hot module replacement out of the box. ## Using Vue without a build step Vue remains usable as plain JavaScript loaded with a `<script>` tag or as a browser ES module. Components are then written without the SFC format: ```js const Counter = { setup() { const count = Vue.ref(0) return { count } }, template: `<button @click="count++">{{ count }}</button>` } ``` or the template is taken from the page itself, an **in-DOM template**. Either way the template arrives as text, and Vue must compile it **in the browser** before the component can render. ## What changes | Aspect | SFC with a build step | No build, in-browser compilation | |---|---|---| | Template compilation | at build time | in the browser, when components first render | | Template compiler in the download | not needed | included in the Vue build you load | | `<script setup>` | available | not available; use the `setup()` option | | Scoped CSS, CSS modules, `v-bind()` in CSS | available | not available | | Template type-checking in the IDE | available | not available | | In-DOM parsing caveats | none | apply when templates live in the page | Details behind the table: 1. **The compiler must ship.** A Vue build without the compiler only runs pre-compiled render functions; given a `template` option it warns that runtime compilation is not supported in this build of Vue. Which build to load is a separate topic, but the consequence is that the compiler's code is downloaded by every user. 2. **Compile cost moves to users.** Each template is compiled at runtime, the first time its component is used, on the user's device. 3. **Composition API still works**, through an explicit `setup()` function that returns bindings; the docs point there because `<script setup>` requires build tools. 4. **In-DOM caveats.** A template written directly in the page is parsed by the browser's HTML parser before Vue sees it. Tag and attribute names are case-insensitive, so PascalCase components and camelCase props must be written in kebab-case; self-closing component tags like `<my-component />` are not valid HTML and swallow following content; elements with placement rules, such as rows inside `<table>`, need workarounds. These caveats do not apply to SFCs or to template strings. ## A symptom checklist for no-build pages - **A component renders nothing and the console warns that runtime compilation is not supported in this build of Vue**: the page loaded a Vue build without the template compiler while using `template` strings. - **A component inside a table renders outside it**: the browser's parser moved the unknown tag out of the `<table>` before Vue read the in-DOM template. - **A camelCase prop is always undefined**: the browser lower-cased the attribute in an in-DOM template; write it in kebab-case. - **Styles leak between components**: there is no `scoped` without the SFC compiler, so rely on class naming conventions instead. ## Choosing - **SPAs, static site generation and any non-trivial frontend** where a build can be justified: the docs recommend SFCs. - **Enhancing mostly static HTML with small interactive islands**: using Vue without a build is reasonable, and the docs also point to petite-vue, a small subset optimised for progressive enhancement. - **Prototypes and demos**: a no-build setup starts instantly, at the cost of the features above. ## Summary The build step exists because `.vue` is a custom format that only `@vue/compiler-sfc` understands, and it pays for itself with pre-compiled templates, scoped CSS, `<script setup>` and tooling. Without it, templates are compiled in the browser, the compiler ships to users, and SFC-only features and in-DOM parsing caveats become your constraints.

  • Why must a component used in an in-DOM template be written <todo-item></todo-item> rather than <TodoItem />?
    The browser's HTML parser reads the page before Vue does. HTML tag names are case-insensitive, so `TodoItem` becomes `todoitem`, and `/>` does not close a non-void element, so following siblings end up inside it. The Vue docs therefore require kebab-case and explicit closing tags in in-DOM templates only.
  • Can you use the Composition API without a build step?
    Yes, through the `setup()` option on a plain component object, returning the bindings the template needs. What is unavailable is `<script setup>`, which is SFC syntax that only the SFC compiler understands.

saying these in an interview costs you the question

  • Browsers can load .vue files natively with type="module".
  • Without a build step you lose the Composition API entirely.
  • In-browser template compilation costs nothing measurable, so shipping the compiler is harmless.
  • Scoped CSS works the same in a no-build setup.
  • In-DOM templates can use PascalCase self-closing component tags like SFCs.