skip to content

What does a Vue .vue file become after @vue/compiler-sfc and the build step, and what happens to each of its blocks?

level: middleimportance: must knowfreq 55%

answer

  1. parse into a descriptor first
  2. template becomes a render function
  3. the script's export gains it
  4. styles become plain CSS
  5. one standard ES module

basics

~10 s

A .vue file becomes a standard ES module default-exporting a component object: the template becomes a render function, script setup becomes setup(), and styles become plain CSS, injected in development or extracted in production.

solid answer

~40 s

`@vue/compiler-sfc` first **parses** the file into a descriptor with `template`, `script`, `scriptSetup`, `styles` and `customBlocks`. Then each block is compiled: the **template** goes through `@vue/compiler-dom` into a JavaScript `render` function, and relative asset URLs such as `<img src="./logo.png">` become module imports; the **script** blocks become the component options object, with `<script setup>` compiled into `setup()` and the template often inlined into it; each **style** block becomes plain CSS, with scoped selectors rewritten and module classes turned into a class map. The build integration stitches the results into **one standard ES module** that default-exports the component, with `render`, a scope id when styles are scoped, and a `__name` inferred from the filename. CSS is injected as `<style>` tags in development and typically extracted into a CSS file for production.

code

ts · 19 lines
ts
// Illustrative shape of a compiled Greeting.vue with <script setup> and <style scoped>.
// Exact output depends on the build integration and dev/prod mode.
import { ref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from 'vue'

const _sfc_main = {
  __name: 'Greeting',
  setup(__props: any) {
    const greeting = ref('Hello World!')
    // inlined template: a closure over setup's local bindings
    return (_ctx: any, _cache: any) =>
      (_openBlock(), _createElementBlock('p', { class: 'greeting' }, _toDisplayString(greeting.value), 1))
  },
}

// added by the build integration because a <style scoped> block exists
;(_sfc_main as any).__scopeId = 'data-v-7ba5bd90'

export default _sfc_main
// the CSS `.greeting[data-v-7ba5bd90] { ... }` is emitted separately

go deeper

for a junior

Recall that a .vue file is compiled at build time into a normal JavaScript module plus CSS, with the template turned into a render function.

for a middle

Walk through parse, per-block compile and assembly: descriptor fields, render function, setup(), scope id, asset URL imports, and where CSS ends up.

for a senior

Use this model to debug: read compiled output in the SFC playground, explain inlined versus separate templates, and trace a missing style or asset to the right step.

for a principal

Explain to stakeholders why the compile step is an asset, enabling pre-compiled templates, scoped CSS and cross-block optimisation, and what it costs in build tooling.

## The short version A browser cannot run a `.vue` file. The SFC compiler, driven by a build tool, turns it into **standard JavaScript and CSS**. The Vue docs put it plainly: a compiled SFC is a standard JavaScript (ES) module, which is why `import MyComponent from './MyComponent.vue'` works in a project with the right build setup. ## Step 1: parse `parse()` from `@vue/compiler-sfc` splits the file into a **descriptor**: - `template`: the single template block, if any; - `script` and `scriptSetup`: the normal and setup script blocks; - `styles`: an array of style blocks with their `scoped`, `module` and `lang` attributes; - `customBlocks`: everything else; - `cssVars`: the expressions used with `v-bind()` in styles. ## Step 2: compile each block | Block | Compiler step | Result | |---|---|---| | `<template>` | `compileTemplate`, built on `@vue/compiler-dom` | a `render(_ctx, _cache)` function that creates virtual DOM nodes | | `<script>` / `<script setup>` | `compileScript` | the component options object; `<script setup>` becomes `setup()` | | `<style>` | `compileStyle` | plain CSS, with scoped rewriting, module class maps and `v-bind()` variables | | custom blocks | none in Vue | handed to tooling as separate import requests | A simplified compiled template looks like this: ```js import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("p", { class: "greeting" }, _toDisplayString(_ctx.greeting), 1)) } ``` Two template details matter in interviews: 1. **Asset URLs become imports.** By default, relative `src` values on `img`, `video`, `source` and some SVG elements are converted into module imports, so the bundler can hash and copy the files. 2. **Inlining with `<script setup>`.** With `<script setup>`, the template can be compiled **into** the `setup()` function as a closure, so it reads bindings directly without going through the instance proxy; during development the compiler may keep the template separate and return the bindings instead. ## Step 3: assemble one module The build integration, a bundler plugin or loader, stitches the results together: - the script's component object becomes the module's **default export**; - the render function is attached as its `render` option, unless it was inlined into `setup()`; - when any style block is scoped, the component gets a **scope id** (`__scopeId`) that the runtime uses to stamp `data-v-...` attributes on elements; - a `__name` from the filename supports dev warnings, DevTools and recursive self-reference; - module class maps are attached so `$style` works. ## Step 4: where the CSS goes The Vue docs describe the usual split: during development, `<style>` blocks are typically **injected as native `<style>` tags** to support hot updates; for production they can be **extracted and merged into a single CSS file**. Either way, the CSS is static: `v-bind()` values change through CSS custom properties, not by rewriting the stylesheet. ## Seeing it for yourself - The **Vue SFC Playground** shows the compiled JavaScript and CSS for any SFC you type, which is the fastest way to answer "what does this become?". - In a project, the browser's developer tools show the transformed module that the dev server served for a `.vue` file. - `vue/compiler-sfc` can be called directly in a script: `parse`, then `compileScript`, `compileTemplate` and `compileStyle` on the descriptor's blocks. ## What the runtime never sees - The `.vue` syntax itself: no parser for SFCs ships to the browser. - Template source text: the template compiler runs at build time, so the application does not need to include it. - Macros such as `defineProps`: they have been replaced by options. ## Summary A `.vue` file is parsed into blocks, each block is compiled by its own compiler, and the build integration reassembles them into one ES module that default-exports a component object with a render function, plus CSS emitted separately. Everything Vue-specific about the file format is gone before the code reaches the browser.

  • Why can import Logo from './logo.png' be avoided in a Vue template that uses <img src="./logo.png">?
    The template compiler's asset URL transform converts relative `src` values on elements such as `img`, `video` and `source` into module imports by default. The bundler then resolves, hashes and copies the file, exactly as if you had imported it yourself.
  • Why is the template compiled into setup() in some builds and attached as render in others?
    With `<script setup>`, the compiler can inline the template as a closure inside `setup()`, which reads bindings directly and avoids the instance proxy. For development it may instead keep the template separate and return the bindings, which suits hot updates; the observable behaviour is the same.

Compiling an SFC is like a restaurant kitchen taking one order ticket: the ticket is split between stations, the template to the grill, the scripts to prep, the styles to pastry, and the plates are reassembled on one tray, the ES module, before anything leaves the kitchen.

saying these in an interview costs you the question

  • The browser downloads .vue files and Vue parses them at runtime.
  • The template stays as an HTML string that Vue compiles on first render.
  • Application code must import a .vue file's template, script and styles separately.
  • defineProps is shipped to the browser and called at runtime.
  • Scoped styles are applied by JavaScript rewriting class names at runtime.