What top-level blocks can a Vue single-file component contain, and how many of each does the SFC format allow?
answer
- view, logic, styling, extras
- one of each script kind
- styles can repeat
- custom blocks need tooling
basics
~20 sA .vue file holds at most one <template>, one <script> and one <script setup>, any number of <style> blocks mixing scoped, module and global, and optional custom blocks such as <docs> that build tooling handles.
solid answer
~40 sA Vue SFC is an HTML-like file with top-level **language blocks**. `<template>` holds the markup and may appear **at most once**; it is compiled into the component's render function. `<script>` is an ES module whose default export is the component options object, and `<script setup>` is compiled into `setup()`; a file may have **one of each**. `<style>` blocks may appear **several times**, and one file can mix global, `scoped` and `module` blocks. Anything else at the top level is a **custom block**, such as `<docs>` or `<i18n>`, whose meaning is defined by whatever tool handles it. A duplicate `<template>` or `<script>` is a parse error: the compiler reports that a single file component can contain only one such element. Top-level comments use HTML syntax.
code
vue · 21 lines<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<button class="toggle" @click="open = !open">{{ open ? 'Hide' : 'Show' }}</button>
</template>
<style scoped>
.toggle { padding: 4px 8px; }
</style>
<style>
/* global rule in the same file */
body.modal-open { overflow: hidden; }
</style>
<docs>
A toggle button. Handled only if a docs tool is configured for <docs> blocks.
</docs>go deeper
Recall the three standard blocks, their limits of one template, one script of each kind and many styles, and that custom blocks exist.
Explain what each block becomes, how parse() represents them in a descriptor, and that custom blocks depend entirely on build tooling.
Use the block rules to judge file organisation, for example when a second style or script block is justified and when a custom block creates hidden tool coupling.
Decide whether custom blocks such as i18n or docs belong in components at all, weighing colocation against coupling every component file to specific build plugins.
## What a `.vue` file is A Vue **single-file component** (SFC) is a framework-specific file format, conventionally with the `.vue` extension, that describes one component using an HTML-like syntax. It is syntactically compatible with HTML, but it is not served to the browser as is: `@vue/compiler-sfc` parses it into separate blocks and compiles them into standard JavaScript and CSS. The file is made of **top-level blocks**. Their content is written in the block's own language, and top-level comments between blocks use HTML comment syntax, `<!-- ... -->`. ## The blocks and their limits | Block | How many | What it is for | |---|---|---| | `<template>` | at most one | the markup, compiled into a render function | | `<script>` | at most one | module-scope code; its default export is the component options object | | `<script setup>` | at most one | Composition API code compiled into `setup()` | | `<style>` | any number | CSS; each block may be global, `scoped` or `module` | | custom blocks | any | project-specific content handled by build tooling | Details worth stating precisely: - **Template**: its content is passed to `@vue/compiler-dom`, pre-compiled into a JavaScript render function and attached to the exported component as its `render` option. A component may have no template at all if it provides its own render function. - **Script**: executed as an **ES module**. Its default export should be a component options object, either plain or wrapped in `defineComponent`. - **Script setup**: may coexist with one normal `<script>`; the two count separately, which is why a file can have two script tags but not two of the same kind. - **Style**: several blocks are allowed, and "multiple `<style>` tags with different encapsulation modes can be mixed in the same component", in the words of the SFC specification. - **Custom blocks**: any other top-level tag, for example `<docs>`, `<i18n>` or a GraphQL query block. Vue itself gives them no meaning. ## What the parser enforces `compiler-sfc`'s `parse()` produces a **descriptor** with the fields `template`, `script`, `scriptSetup`, `styles` (an array), and `customBlocks` (an array). The shape mirrors the limits: single slots for the template and each script kind, arrays for styles and custom blocks. A second `<template>` or a second `<script>` of the same kind is reported as a syntax error saying a single file component can contain only one such element. ## Custom blocks in practice Custom blocks are how the format stays extensible: 1. The SFC compiler records each custom block with its tag name, attributes and content. 2. The build integration turns each one into an import of the same `.vue` file with a different request query. 3. A tool-specific plugin or loader must transform that request into JavaScript, for example turning an `<i18n>` block's JSON into translation messages attached to the component. If nothing in the toolchain is configured for a custom block, it contributes nothing to the component, so a custom block is only as useful as the tooling behind it. ## Inspecting the blocks yourself `@vue/compiler-sfc` is re-exported as `vue/compiler-sfc`, so you can look at a descriptor directly, which is a quick way to settle an argument about how a file is read: ```ts import { parse } from 'vue/compiler-sfc' const { descriptor, errors } = parse(source, { filename: 'Toggle.vue' }) descriptor.template?.content // the raw template text descriptor.scriptSetup?.lang // 'ts' for <script setup lang="ts"> descriptor.styles.map((s) => s.scoped) // [true, undefined] for one scoped, one global descriptor.customBlocks.map((b) => b.type) // ['docs'] errors // duplicate-block errors land here ``` Each block records its `type`, `content`, `attrs`, `lang` and `src`, which is all the later compile steps need. ## Two conventions from the specification - **Name inference.** An SFC infers its component name from its **filename** for development warnings, DevTools and recursive self-reference: `FooBar.vue` can render `<FooBar/>` inside itself. - **Block order is free.** Many teams put `<script setup>` first and `<template>` second, but the format does not require any order. ## Summary A `.vue` file is a container of blocks: one template, one script of each kind, any number of styles, and optional custom blocks. The compiler enforces the single-block limits at parse time, and custom blocks are passed on to tooling rather than interpreted by Vue.
- Can a .vue file contain both <script> and <script setup>, and why would it?Yes, one of each. The normal `<script>` runs once in module scope, so it holds named exports or run-once code, while `<script setup>` becomes the per-instance `setup()`. Two blocks of the same kind are rejected by the parser.
- What happens to a <docs> block if no tool in the build is configured for it?Vue gives custom blocks no meaning. The SFC compiler records the block, and the build integration turns it into an import request that a matching plugin or loader is expected to transform; with no such handler it contributes nothing to the component.
An SFC is like a parcel with labelled compartments: one for the drawing (template), one each for the two kinds of instructions (scripts), as many as you like for paint (styles), and spare pockets (custom blocks) that only a courier who knows the label will open.
saying these in an interview costs you the question
- A .vue file can contain only one <style> block.
- Two <template> blocks are allowed for conditional layouts.
- Vue itself interprets custom blocks like <docs> at runtime.
- A file cannot have both <script> and <script setup>.
- The browser can load a .vue file directly without compilation.