Explaining The New script setup Type in Vue 3 – Major Takeaways from the RFC

If you’ve been working in Vite and Vue 3 recently, you’ll notice that when you start a new project, your script section looks like this with this script syntax in your Vue components.

<script setup>
import HelloWorld from './components/HelloWorld.vue'
// This starter template is using Vue 3 experimental <script setup> SFCs
// Check out https://github.com/vuejs/rfcs/blob/script-setup-2/active-rfcs/0000-script-setup.md
view raw ViteStarterCode.vue hosted with ❤ by GitHub

You may be wondering, “What is this? Is this the Options API? Composition API? where’s the setup method?”

The <script setup> type is a proposed change in the Vue’s Git RFCs. To be clear, this is not intended to completely replace any of the current ways to write code. Its purpose is to provide developers with a more concise syntax to write their single file components.

In this article, we’re going to be taking a look at exactly how it works and some of the ways that it can be useful. 

Alright – let’s go. 

Rather watch a video explanation??

A rundown of script setup

In <script setup>, we don’t have to declare an export default and a setup method – instead, all top-level bindings are exposed to the template

In the Composition API, we’re used to having to create our setup method and then return anything that we want exposed. Something like this…

import { ref, computed } from 'vue'
export default {
setup () {
const a = ref(3)
const b = computed(() => a.value + 2)
const changeA = () => { a.value = 4 }
return { a, b, changeA } // have to return everything!
view raw App.vue hosted with ❤ by GitHub

But with <script setup>, we can rewrite the same code like this..

<script setup>
import { ref, computed } from 'vue'
// all of these are automatically bound to the template
const a = ref(3)
const b = computed(() => a.value + 2)
const changeA = () => { a.value = 4 }

And it’s not just data, computed properties, and methods! Even imported directives and components that are on the top level of our setup scope are automatically available in our template. 

Read Also:  A Guide to Vue $emit - How to Emit Custom Events in Vue

Look at this example of importing a component.

<component-b />
<script setup>
import ComponentB from './components/ComponentB.vue' // really that's it!

Amazing, right?

So….what’s the point of this?

Long story short, this syntax makes single file components simpler. 

In the exact words of the RFC, “the proposal’s main goal is reducing the verbosity of Composition API usage inside SFCs by directly exposing the context of <script setup> to the template.” 

The proposal’s main goal is reducing the verbosity of Composition API usage inside SFCs by directly exposing the context of <script setup> to the template.” 

Vue script setup rfc

And that’s exactly what we just saw, by not having to worry about creating a setup method and returning exactly what we want to expose, we can simplify our code.

Plus – there’s no matter forgetting to return something from our setup method (something I know I do all the time).

More advanced usage of <script setup>

Now that we know what <script setup> even is and why it can be useful, let’s take a look at some of its more advanced features.

Accessing props, emitting events, etc.

First off, you may be wondering how to perform standard Vue operations like….

In the Composition API, these were simply arguments on our setup method,

setup (props, context) {
    // context has attrs, slots, and emit

However, in the script setup syntax, we can access these same options with 3 imports from Vue. 

  • defineProps – as the name suggests, allows us to define props for our component
  • defineEmits – lets us define the events that our component can emit
  • useContext – gives us access to the slots and attributes of our component
 <button @click="$emit('change')"> Click Me </button>
<script setup>
  import { defineProps, defineEmit, useContext } from 'vue'

  const props = defineProps({
    foo: String,
  const emit = defineEmit(['change', 'delete'])

  const { slots, attrs } = useContext()

With these 3 imports we can get the functionality that we’re used to having on our traditional setup method. 

Read Also:  The Beginner's Guide to Vue Template Refs - with Vue 3 Updates

Creating an async setup function

Another cool feature of the script setup is how easy it is to create an async setup function. 

This is useful for loading in apis as your component is created, and even tying in your code to the experimental <suspense> feature

All we have to do to make our setup function asynchronous, is use a top level await inside our script setup. 

For example, if we’re using the Fetch API, we can just use await like this…

<script setup>   
   const post = await fetch(`/api/pics`).then((a) => a.json())

…and our resulting setup() function will be asynchronous just like that.

It’s that simple.

Using <script setup> with a normal <script>

<script setup> creates its own script scope for its top level bindings. But in certain cases, there is code that must be executed in the module scope. 

The 2 specific examples in this RFC are…

  1. Declaring named exports
  2. Creating global side effects that only execute once.

This can be done by adding a normal <script> block alongside your script setup like this. 


  // this can be imported as `import { named } from './*.vue'`
  export const named = 1

<script setup>
  // code here

And there you have it!

Currently, this script setup is opt-in only so if you want to try it out, just add setup to your script tag. 


If you never want to think about it and just want to write your code the way you’re used to, go for it. The choice is yours.

To learn more about the script setup, here’s the link to the full RFC with its motivations, exact syntax, and more technical implementations.

So that’s all for this article, I hope it helped clear up what this new syntax that’s inside your Vite app! 

If you have any questions, leave them in the comments below!