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.tsThe 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 --> outThe 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 viarewriteDefault.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
withAsyncContextso<script setup>can useawaitdirectly. - Hoisting and exposing top-level bindings to the template.
- The
definePropsDestructurerewrite (since Vue 3.5) forconst { foo, bar = 1 } = defineProps<{...}>(). - Generating
_sfc_mainand 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
MagicStringfor 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_checkdirective 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-vueandvue-loaderare the primary external consumers.compiler-domis invoked for templates (andcompiler-ssrfor SSR).@vue/compiler-coreis re-exported for ID utilities.- The
parseCacheis exported as aMapto give bundlers cache-control without exposing LRU types. MagicStringandbabelParseare 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 intocompileScript.ts, update bindings/exposes lists. - New CSS feature? Add a PostCSS plugin under
style/, register incompileStyle.ts. - New asset URL pattern? Edit
template/transformAssetUrl.tsortemplate/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.