How to Translate Vue 3 setup() Examples to script setup Syntax
The <script setup> syntax is compile-time sugar that automatically executes the equivalent of a setup() function, eliminating boilerplate return statements and exposing top-level declarations directly to the template.
Vue 3's Composition API organizes component logic through the setup() function, but the newer <script setup> syntax streamlines this approach through compiler transformations. Understanding how to map traditional setup() patterns to the modern <script setup> style allows you to write cleaner, more maintainable components while leveraging the same underlying runtime mechanisms.
Understanding the Vue 3 setup Function Architecture
The traditional setup() function serves as the entry point for Composition API logic in Vue 3 components. Located within the component options object, this function receives props and context arguments, returning an object whose properties become available to the template.
According to the Vue 3 source code in packages/runtime-core/src/component.ts, the setupComponent() and setupStatefulComponent() functions handle the execution of user-defined setup() logic. These runtime functions invoke the setup function, process its return value, and bind the exposed properties to the component instance.
Mapping setup() Logic to script setup
When transitioning from classic setup() to <script setup>, you must understand how specific patterns translate to compiler macros.
Handling Props with defineProps
In traditional setup(), you access props through the first argument:
export default {
props: { msg: String },
setup(props) {
console.log(props.msg)
return { msg: props.msg }
}
}
With <script setup>, use the defineProps() compiler macro:
<script setup lang="ts">
const props = defineProps<{
msg: string
}>()
console.log(props.msg)
</script>
The compiler, specifically in packages/compiler-sfc/src/compileScript.ts, transforms defineProps() into the equivalent props declaration and setup function parameter.
Emitting Events with defineEmits
Classic setup() receives an emit function via the context object:
export default {
emits: ['update'],
setup(props, { emit }) {
const notify = () => emit('update', 'value')
return { notify }
}
}
In <script setup>, declare emits using defineEmits():
<script setup lang="ts">
const emit = defineEmits<{
(e: 'update', value: string): void
}>()
function notify() {
emit('update', 'value')
}
</script>
Exposing Methods with defineExpose
To expose methods to parent components in classic setup(), you call the expose function:
export default {
setup(props, { expose }) {
const reset = () => console.log('reset')
expose({ reset })
return {}
}
}
With <script setup>, use the defineExpose() compiler macro:
<script setup lang="ts">
function reset() {
console.log('reset')
}
defineExpose({
reset
})
</script>
Reactive State and Lifecycle Hooks
Both syntaxes use identical Composition API utilities for state and side effects. In packages/runtime-core/src/component.ts, the runtime handles ref, reactive, computed, watch, and lifecycle hooks like onMounted identically regardless of whether they originate from manual setup() or compiled <script setup>.
<script setup>
import { ref, watch, onMounted } from 'vue'
const name = ref('Vue')
watch(name, (newVal, oldVal) => {
console.log(`Name changed from ${oldVal} to ${newVal}`)
})
onMounted(() => {
console.log('Component is mounted')
})
</script>
Compiler Implementation Details
The transformation from <script setup> to executable code occurs in packages/compiler-sfc/src/compileScript.ts. This module parses the SFC, identifies compiler macros like defineProps and defineEmits, and generates a virtual setup() function that the runtime executes.
During runtime, packages/runtime-core/src/component.ts contains setupComponent() and setupStatefulComponent(), which invoke the generated setup function and bind the returned context to the component instance. The vnode preparation occurs in packages/runtime-core/src/vnode.ts via setupBlock().
Complete Code Examples
Classic setup() vs script setup
Classic setup() approach:
export default {
props: {
msg: String
},
emits: ['update'],
setup(props, { emit, expose }) {
const count = ref(0)
const inc = () => {
count.value++
emit('update', count.value)
}
expose({ reset: () => (count.value = 0) })
return { count, inc }
}
}
Equivalent <script setup>:
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{
msg: string
}>()
const emit = defineEmits<{
(e: 'update', value: number): void
}>()
const count = ref(0)
function inc() {
count.value++
emit('update', count.value)
}
defineExpose({
reset() {
count.value = 0
}
})
</script>
<template>
<div>
<p>{{ props.msg }} – {{ count }}</p>
<button @click="inc">Increase</button>
</div>
</template>
Using withDefaults for default prop values
<script setup lang="ts">
const props = withDefaults(
defineProps<{
title?: string
size?: 'sm' | 'md' | 'lg'
}>(),
{
title: 'Untitled',
size: 'md'
}
)
function capitalize(s: string) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
</script>
<template>
<h2>{{ capitalize(props.title) }}</h2>
<p>Size: {{ props.size }}</p>
</template>
Summary
- The
<script setup>syntax is compile-time sugar that eliminates boilerplate from the traditional Vue 3 setup function. - Use
defineProps()to declare typed props,defineEmits()for events, anddefineExpose()to expose methods to parent components. - Top-level declarations in
<script setup>are automatically available in the template without requiring a return statement. - The compiler transforms
<script setup>into a standardsetup()function inpackages/compiler-sfc/src/compileScript.ts, while the runtime executes it viasetupComponent()inpackages/runtime-core/src/component.ts. - All Composition API utilities (
ref,reactive,watch,onMounted, etc.) work identically in both syntaxes.
Frequently Asked Questions
What is the difference between setup() and script setup?
The setup() function is a component option that receives props and context arguments and must return an object to expose bindings to the template. The <script setup> syntax is compile-time sugar that automatically generates the setup() function for you, removing the need for export default, manual return statements, and the props/context arguments. According to the Vue 3 source code in packages/compiler-sfc/src/compileScript.ts, the compiler parses top-level declarations and transforms them into the equivalent setup function body.
How does defineProps work in script setup?
defineProps is a compiler macro that declares component props within <script setup>. It accepts either an object literal for runtime declaration or TypeScript type parameters for type-based declaration. When the compiler processes packages/compiler-sfc/src/compileScript.ts, it extracts the prop definitions and generates the appropriate props option for the component while also making the props available as local variables in the setup scope. This eliminates the need to access props via the first argument of a setup() function.
Can I use script setup with TypeScript?
Yes, <script setup> offers excellent TypeScript support. You can use type parameters with defineProps<{ msg: string }>() and defineEmits<{ (e: 'update', value: number): void }>() for full type inference. The withDefaults() helper allows you to provide default values while maintaining type safety. The compiler in packages/compiler-sfc/src/compileScript.ts preserves TypeScript types during transformation, and the runtime in packages/runtime-core/src/component.ts maintains type compatibility when executing the generated setup logic.
Where is the setup logic compiled in Vue 3 source code?
The compilation of <script setup> occurs in packages/compiler-sfc/src/compileScript.ts, where the compileScript() function parses the SFC, identifies compiler macros like defineProps and defineEmits, and generates the equivalent setup() function code. At runtime, the execution happens in packages/runtime-core/src/component.ts within setupComponent() and setupStatefulComponent(), which invoke the generated setup function and bind the returned context to the component instance. The vnode preparation occurs in packages/runtime-core/src/vnode.ts via setupBlock().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →