vuejs/core
SFC <script setup>
<script setup> is the syntactic-sugar mode of Single-File Components where the entire <script> is the component's setup() body and top-level bindings are exposed to the template. The transform happens in @vue/compiler-sfc's compileScript (packages/compiler-sfc/src/compileScript.ts) at build time. This page traces what each macro does and how the pieces fit together.
What <script setup> produces
Given:
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{ count: number }>()
const emit = defineEmits<{ change: [v: number] }>()
const local = ref(0)
function inc() { local.value++; emit('change', local.value) }
</script>
<template>
<button @click="inc">{{ count }} + {{ local }}</button>
</template>compileScript produces approximately:
import { ref } from 'vue';
export default /*#__PURE__*/ _defineComponent({
__name: 'Counter',
props: { count: { type: Number, required: true } },
emits: ['change'],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const local = ref(0);
function inc() {
local.value++;
emit('change', local.value);
}
return { props, emit, local, inc, ref }; // exposed to template
},
});Plus a separate render function compiled from <template> that references _ctx.count, _ctx.local, _ctx.inc, etc.
The macros
Macros are not real functions — they are recognized by name and rewritten away. They live in packages/compiler-sfc/src/script/:
| Macro | File | What it does |
|---|---|---|
defineProps<{...}>() / defineProps({...}) |
defineProps.ts |
Extracts props options (object form) or runs resolveType on the type argument (type form), then injects a props: {...} field into the component options. |
defineEmits<{...}>() / defineEmits([...]) |
defineEmits.ts |
Same idea for emits. Type form is resolved through tuple-call signatures. |
defineModel<T>() |
defineModel.ts |
Generates props: { modelValue, modelModifiers } + emits: ['update:modelValue'] and a useModel runtime call. Supports custom prop names (defineModel<T>('name')). |
defineSlots<{...}>() |
defineSlots.ts |
Type-only; emits a slots typing helper. No runtime effect. |
defineExpose({...}) |
defineExpose.ts |
Replaces with __expose({...}) to expose only the listed bindings via template refs. |
defineOptions({...}) |
defineOptions.ts |
Inlines the options object into the component definition. Used for name, inheritAttrs, etc. |
withDefaults(defineProps<T>(), {...}) |
defineProps.ts |
Augments the type-form defineProps with default values. The defaults are emitted into the props runtime options. |
All macros share ScriptCompileContext (script/context.ts) which holds the Babel AST, the MagicString for incremental edits, the collected props/emits/models, and the binding metadata.
Type-only props/emits via resolveType
The hard part of <script setup lang="ts"> is making defineProps<{...}>() produce runtime prop options from a compile-time-only type. packages/compiler-sfc/src/script/resolveType.ts (~60KB) implements a partial TypeScript type resolver that:
- Walks type aliases and interfaces.
- Resolves imports across files (using the host bundler's TS plugin via
registerTS, or a built-in fallback). - Handles intersections, unions, mapped types, indexed accesses (
T['key']), and indexed-template strings. - Infers runtime types:
string→String,number→Number,boolean→Boolean, arrays →Array, function types →Function, unions → array of constructors, literal types → the constructor of their base type.
The resolver is intentionally limited; full type checking is left to Volar/the language server. __no_check directives on a type let users opt-out of resolution for cases the resolver doesn't handle.
Top-level await
<script setup> allows top-level await. compileScript wraps each top-level await in withAsyncContext (from packages/runtime-core/src/apiSetupHelpers.ts):
;[__temp, __restore] = withAsyncContext(() => fetch(...))
const data = ((__temp = await __temp), __restore(), __temp)withAsyncContext saves and restores the active component instance across the async boundary so lifecycle hooks registered before vs after the await still attach to the correct instance. The transform is in packages/compiler-sfc/src/script/topLevelAwait.ts.
A component that uses top-level await is async. Vue handles this through <Suspense> — see features/built-in-components.
Props destructuring (3.5+)
Since 3.5, defineProps supports reactive destructuring:
const { count = 0, name } = defineProps<{ count?: number; name: string }>();packages/compiler-sfc/src/script/definePropsDestructure.ts rewrites references to destructured names so they read through __props at the point of use, preserving reactivity. Defaults are emitted into the props runtime options.
What gets exposed to the template
packages/compiler-sfc/src/script/analyzeScriptBindings.ts walks the script body and produces a BindingMetadata map: name → BindingType (SETUP_REF, SETUP_CONST, SETUP_LET, SETUP_REACTIVE_CONST, LITERAL_CONST, PROPS, OPTIONS). The template compiler reads this metadata to optimize how the render function references each binding:
SETUP_REF→ use.valueaccess (because the template auto-unwraps).SETUP_CONST→ reference directly.LITERAL_CONST→ inline the literal.
This is the source of the optimization where binding import { ref } from 'vue'; const foo = ref(1) produces template code that does _ctx.foo.value directly without a runtime unref().
Asset URL transform
The template still goes through compileTemplate, which by default applies transformAssetUrl (packages/compiler-sfc/src/template/transformAssetUrl.ts). It rewrites <img src="./logo.png"> to <img :src="_imports_0"> and emits the corresponding import. The default mapping covers <img src>, <source src>, <image href>, <use href>, and <video poster>. Custom mappings live in AssetURLTagConfig.
CSS v-bind
In an SFC, <style> blocks can reference reactive bindings:
<style scoped>
.greeting { color: v-bind(color); }
</style>packages/compiler-sfc/src/style/cssVars.ts rewrites this to a generated CSS variable name (--xxxx-color) and emits the matching useCssVars() call in the script (added to setup return). At runtime useCssVars (packages/runtime-dom/src/helpers/useCssVars.ts) writes the bound values onto the host element's inline style.
Files to know
packages/compiler-sfc/src/parse.tspackages/compiler-sfc/src/compileScript.tspackages/compiler-sfc/src/script/context.tspackages/compiler-sfc/src/script/defineProps.tspackages/compiler-sfc/src/script/defineEmits.tspackages/compiler-sfc/src/script/defineModel.tspackages/compiler-sfc/src/script/definePropsDestructure.tspackages/compiler-sfc/src/script/resolveType.tspackages/compiler-sfc/src/script/topLevelAwait.tspackages/compiler-sfc/src/script/analyzeScriptBindings.tspackages/compiler-sfc/src/template/transformAssetUrl.tspackages/compiler-sfc/src/style/cssVars.tspackages/runtime-core/src/apiSetupHelpers.tspackages/runtime-core/src/helpers/useModel.tspackages/runtime-dom/src/helpers/useCssVars.ts
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.