Open-Source Wikis

/

Vue.js

/

Packages

/

compiler-sfc

vuejs/core

compiler-sfc

Active contributors: Evan You, edison, 三咲智子 Kevin Deng

Purpose

@vue/compiler-sfc is the toolchain for compiling Vue Single-File Components (.vue files). It parses the SFC into descriptor blocks, compiles <template> to a render function, transforms <script setup> (with type-only macros like defineProps<{...}>()), processes <style> blocks (scoped, modules, v-bind in CSS), and rewrites asset URLs in templates. It is consumed by vite-plugin-vue, vue-loader, the SFC Playground, Volar, and most other Vue tooling.

The package is the second-largest compiler package (~8,100 lines, 30 files). One file alone — script/resolveType.ts — is over 60KB; it implements a partial TypeScript type resolver for defineProps<{...}> and defineEmits<{...}>.

Directory layout

packages/compiler-sfc/src/
├── index.ts              # public API barrel + version + parseCache
├── parse.ts              # SFC source → SFCDescriptor (~13KB)
├── compileTemplate.ts    # invokes compiler-dom; handles SSR via compiler-ssr
├── compileScript.ts      # the <script setup> transform (~45KB, the spine)
├── compileStyle.ts       # postcss pipeline for scoped/module/v-bind
├── rewriteDefault.ts     # `export default` ⇄ `const _sfc_main =` rewriting
├── cache.ts              # LRU caches for parse/compile
├── warn.ts
├── shims.d.ts
├── script/
│   ├── context.ts                 # ScriptCompileContext (shared mutable state)
│   ├── analyzeScriptBindings.ts   # discover what setup returns
│   ├── definePropsDestructure.ts  # const { foo } = defineProps<...>() rewrite
│   ├── defineProps.ts             # the macro
│   ├── defineEmits.ts             # the macro
│   ├── defineModel.ts             # the macro (since 3.4)
│   ├── defineSlots.ts             # the macro
│   ├── defineExpose.ts            # the macro
│   ├── defineOptions.ts           # the macro
│   ├── importUsageCheck.ts        # determines unused imports
│   ├── normalScript.ts            # rewrites a non-setup <script>
│   ├── resolveType.ts             # type-only props/emits resolver (~60KB)
│   ├── topLevelAwait.ts           # wraps top-level await in withAsyncContext
│   └── utils.ts
├── style/
│   ├── pluginScoped.ts            # <style scoped> hash-based rewriting
│   ├── pluginTrim.ts              # whitespace trimming
│   ├── cssVars.ts                 # SFC v-bind in CSS
│   └── preprocessors.ts           # SCSS/LESS/Stylus/PostCSS adapters
└── template/
    ├── transformAssetUrl.ts       # rewrites <img src="/wikis/vuejs-core/packages/foo.png">
    ├── transformSrcset.ts         # rewrites srcset with multiple URLs
    └── templateUtils.ts

The SFC pipeline

graph TD
    src[".vue source"]
    parse["parse()<br/>parse.ts"]
    desc["SFCDescriptor<br/>(template, script, scriptSetup, styles)"]
    ct["compileTemplate()"]
    cs["compileScript()"]
    cstyle["compileStyle()"]
    out["render fn + JS module + CSS"]
    src --> parse --> desc
    desc --> ct
    desc --> cs
    desc --> cstyle
    ct --> out
    cs --> out
    cstyle --> out

The host bundler (e.g., vite-plugin-vue) calls parse(source) once, caches the descriptor, then calls compileTemplate, compileScript, and compileStyle for the corresponding blocks. Hot reload signals which block changed so only that block needs re-compiling.

parse

packages/compiler-sfc/src/parse.ts runs the template parser with __SFC__ mode (just enough to split into top-level blocks) and returns an SFCDescriptor:

interface SFCDescriptor {
  filename: string
  source: string
  template: SFCTemplateBlock | null
  script: SFCScriptBlock | null
  scriptSetup: SFCScriptBlock | null
  styles: SFCStyleBlock[]
  customBlocks: SFCBlock[]
  cssVars: string[]
  slotted: boolean
  shouldForceReload: (prevImports: ...) => boolean
}

parseCache is an LRU cache (re-exported as a Map in index.ts) keyed by source; the host bundler decides when to invalidate.

compileScript

compileScript.ts is the most complex file in the SFC compiler (~45KB). It handles:

  • Both regular <script> (passes through with minimal rewriting via rewriteDefault.ts) and <script setup> (full transform).
  • The macro suite (defineProps, defineEmits, defineModel, defineSlots, defineExpose, defineOptions, withDefaults). Macros are recognized by name; their AST nodes are removed and replaced with the appropriate runtime code.
  • Top-level await wrapping via withAsyncContext so <script setup> can use await directly.
  • Hoisting and exposing top-level bindings to the template.
  • The definePropsDestructure rewrite (since Vue 3.5) for const { foo, bar = 1 } = defineProps<{...}>().
  • Generating _sfc_main and merging the SFC's "render" / "ssrRender" / "**file" / "**scopeId" properties.

The macros all share ScriptCompileContext (script/context.ts), which holds:

  • The Babel AST of the script.
  • A MagicString for incremental edits.
  • The collected props, emits, models.
  • The binding metadata that the template compiler will use to optimize identifier resolution.

Each macro's logic lives in its own file in script/. They are invoked from compileScript.ts's main pass.

resolveType

packages/compiler-sfc/src/script/resolveType.ts (~60KB) implements a small slice of TypeScript's type checker so defineProps<{...}>() can extract runtime prop information from interfaces, type aliases, and imported types:

  • Resolves type references across imports (import type { … } from './...').
  • Walks type aliases, intersections, unions, mapped types, indexed accesses.
  • Infers runtime types for string, number, boolean, arrays, unions, literal types.
  • Honors __no_check directive comments to skip resolution.

It uses @babel/parser and a small TS type-resolver protocol; full typing is left to the language server (Volar). The resolver supports custom file system providers via SimpleTypeResolveOptions so editors and bundlers can plug in their own readers.

compileTemplate

A thin wrapper around compiler-dom.compile (or compiler-ssr.compile when ssr: true). It also threads the asset-URL transforms (transformAssetUrl, transformSrcset) so <img src="./logo.png"> becomes _imports_0 and gets bundled.

compileStyle

A PostCSS pipeline. Pre-processors (SCSS, LESS, Stylus) are run via style/preprocessors.ts. Then the pluginScoped plugin walks selectors and adds the scope hash ([data-v-xxxx]); the cssVars plugin rewrites v-bind(foo) calls into CSS variable references that the runtime will set. Async mode (compileStyleAsync) returns a Promise so users can supply async preprocessors.

Key abstractions

Symbol File Description
parse(source, options) packages/compiler-sfc/src/parse.ts SFC source → descriptor + errors.
compileTemplate(options) packages/compiler-sfc/src/compileTemplate.ts Template block → render function.
compileScript(descriptor, options) packages/compiler-sfc/src/compileScript.ts <script setup> transform; emits a SFCScriptBlock with bindings and loc.
compileStyle(options) / compileStyleAsync(options) packages/compiler-sfc/src/compileStyle.ts Style block → CSS, with errors.
rewriteDefault(input, as, parserPlugins?) packages/compiler-sfc/src/rewriteDefault.ts Rewrites a script's export default so it can be merged with setup output.
resolveTypeElements, inferRuntimeType, invalidateTypeCache, registerTS packages/compiler-sfc/src/script/resolveType.ts The type resolver; registerTS lets Volar/IDE inject its TypeScript instance.
walkIdentifiers, extractIdentifiers, isInDestructureAssignment, isStaticProperty re-exported from @vue/compiler-core AST utilities used by all script transforms.

Integration points

  • vite-plugin-vue and vue-loader are the primary external consumers.
  • compiler-dom is invoked for templates (and compiler-ssr for SSR).
  • @vue/compiler-core is re-exported for ID utilities.
  • The parseCache is exported as a Map to give bundlers cache-control without exposing LRU types.
  • MagicString and babelParse are re-exported as conveniences for downstream tools that want to do their own AST work.

Entry points for modification

  • New SFC macro? Add a file under script/, hook into compileScript.ts, update bindings/exposes lists.
  • New CSS feature? Add a PostCSS plugin under style/, register in compileStyle.ts.
  • New asset URL pattern? Edit template/transformAssetUrl.ts or template/transformSrcset.ts.
  • New TypeScript construct in defineProps<> types? script/resolveType.ts. Bring patience.

For how the template block is compiled, see packages/compiler-core and packages/compiler-dom. For SSR templates, packages/compiler-ssr.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

compiler-sfc – Vue.js wiki | Factory