A brief history of Vue Script Setup
Tags: vue, javascript, coding
Published: 2022-09-21 18:11:07

---
title: A brief history of Vue Script Setup
published: true
date: 2022-09-21 10:48:25 UTC
tags: vuejs,javascript,coding
canonical_url: https://www.vuemastery.com/blog/a-brief-history-of-vue-script-setup
---

![A brief history of Vue’s script setup](https://cdn-images-1.medium.com/max/1024/1*9VwggM4WJKUe634taCIoqw.jpeg)

_Written by_ [_Sairina Merino Tsui_](https://www.linkedin.com/in/sairina/)

One of the things that makes Vue such a versatile framework is that it has evolved to include several ways to write and structure components as the framework itself has grown and matured. Currently, there are three different ways to compose our components: the Options API, the Composition API, and the newest version of the Composition API: Script Setup.

If you’re just getting started with Vue, this can be confusing… 

*Why is there different ways to write Vue components?* 

*Should I use Options API or Composition API?
Is Script Setup the same as Composition API?*

Refined by Vue Creator Evan You, the Script Setup is the recommended way to compose components that can scale. It gives Vue developers the freedom to create performant components with built-in readability, reusability, maintainability, modularity, and even makes them more TypeScript-Friendly.

In this article, we’ll guide you through how the Script Setup came to be, touch on how it differs from its predecessors, and we’ll use it to build a single file Vue component to get some practice putting it to work.

---

## **What is a single file component (SFC)?**

If you’re not familiar with other JavaScript frameworks, let’s first cover the foundational concept of: what is a single file component.

Let’s consider that the basic building blocks in modern web development are made up of HTML, JavaScript, and CSS. With Vue, instead of requiring three separate files (for example, `index.html`, `index.js`, and `style.css`) to create a webpage, we combine them all into a **S**ingle **F**ile **C**omponent (SFC) that has the file extension `.vue`

You can find more details about a SFC <a href="https://www.vuemastery.com/courses/real-world-vue3/single-file-components/" target="blank">here</a>, but most importantly, there are three sections that comprise the SFC:

1. The `<template>` for writing the HTML for a webpage
2. The `<script>` for the JavaScript
3. The `<style>` for the CSS

Originally, Vue’s SFCs were built with the Options API, and looked like this:

```html
<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    data() {
      return {
        title: 'Hello world!',
      }
    }
  }
</script>

<style scoped>
  h1 {
    font-size: 32px;
  }
</style>
```

Note how in one file, we have access to the component’s `data`, which is being displayed inside of its `<template>`, which is styled by its `<style>` section.

---

## Where Vue began: The Options API

The Options API provided us with a set of optional objects for everything our component needed, including its `data`, `props`, `methods`, `computed` properties, lifecycle hooks, and more.

While this API saved developers cognitive overhead by prescribing an organizational system for our components, it was by its very nature *prescriptive.* Once components reached a certain level of complexity, this system became *restrictive,* especially when a component handled more than one logical concern. 

In these cases, the developer experience felt a bit clunky. Code to govern the same business logic was split apart into different `options` objects throughout the `<script>` section, affecting code readability and maintainability, since refactoring was often necessary throughout multiple sections of the component.

As Vue matured, there was a desire to fix this clunkiness, along with some of the Options API’s other limitations, including not being able to easily share reactive code between components. While there was a workaround for this, using **mixins** to share code across components, this made it hard to keep track of where code lived, and there was a risk of namespace collisions, among <a href="https://vuejs.org/guide/reusability/composables.html#comparisons-with-other-techniques" target="blank">other drawbacks</a>.

With all this in mind, Evan You and the core Vue.js team worked on the next evolution of Vue, the Composition API, which debuted in Vue 2.7. 

---

## **Options API ⇒ Composition API**

The Composition API solved for the aforementioned limitations by loosening up those restrictive structural prescriptions, allowing us to group code by logical concern and then extract reactive code to share with other components. (This shared reactivity is called <a href="https://www.vuemastery.com/courses/coding-better-composables/what-is-a-composable" target="blank">Composables</a>).

Note this code comparison below, where the Options API code is spread across the file in an unintuitive way, whereas the Composition API code is neatly grouped by logical concern. You can imagine how this could lead to a more focused workflow for building components from scratch and refactoring them over time.

![[https://vuejs.org/guide/extras/composition-api-faq.html#more-flexible-code-organization](https://vuejs.org/guide/extras/composition-api-faq.html#more-flexible-code-organization)](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1.1663351049727.jpg?alt=media&token=ddcd2c13-4a64-4909-93ce-b7c0b8cc6956)

Revisiting that same simple SFC code example from above, this is how we’d write it using the Composition API:

```html
<script>
  export default {
    setup() {
      const title = 'Hello world!';
  
      return {
        title
      }
    }
</script>

<template>
  <h1>{{ title }}</h1>
</template>

<style scoped>
  h1 {
    font-size: 32px;
  }
</style>
```

If we were to extend the functionality and complexity of this code over time, it could be inherently more readable, maintainable and modular. 

Having said that, note I said it *could be*. Of course, without the prescriptive structure of the Options API, developers now have a lot more freedom to organize (or disorganize) their JavaScript code how they see fit. As the saying goes… with great power comes great [code organization] responsibility.

---

## Introducing the Script Setup syntax

While the Composition API came with its advantages, its initial syntax ultimately had some of its own drawbacks, especially when it came to developer experience and what felt like unnecessary boilerplate code. 

For example, in the simple code example from above, we have to rely on the `setup()` method and `return` the values we want to be exposed to the template.

```html
<script>
  export default {
    setup() {
      const title = 'Hello world!';
  
      return {
        title
      }
    }
</script>
```

The Script Setup syntax, which is a more *minimal* version of the Composition API made possible by some syntactic sugar, is the latest and leanest way to construct Vue SFCs.

Here is that same script section, using Script Setup:

```html
<script setup>
  const title = 'Hello world!';
</script>
```

> (Notice how the `<script>` section now has the `setup` attribute, hence: “script setup”)
> 

Code inside `<script setup>` will execute every time an instance of the component is created, and anything declared inside the `<script setup>` context will be accessible in the `<template>`. We no longer need to give the template access to the data by returning it from inside the `setup()` hook.

In addition to being more compact and quicker to write, the Script Setup syntax brings even more improvements including during runtime, code organization, reusability, and better type inference if you are using TypeScript. You can read more about those improvements <a href="https://vuejs.org/guide/extras/composition-api-faq.html#more-flexible-code-organization" target="blank">here</a>.

### Is Options API deprecated?

It’s worth mentioning that while the Script Setup syntax is the recommended and default syntax for developing in Vue 3, it is still possible to build your Vue components using the sugar-free Composition API syntax, as well as the original Options API. In fact, some codebases even use all three. 

Remember, the limitations of the Options API become evident in more complex components, so for simpler scenarios, you might prefer drafting your components using the Options API. It all comes down to choice, preference, and what’s best for your needs. 

Now that we’re clear on how the Script Setup came to be, let’s look at how we can use this syntax in our Vue code.

---

## **Getting started with Script Setup**

To dive deeper into the Script Setup way of coding components, let’s look at a simple code example that uses reactive data.

![2022-08-24 17.12.05_revised.gif](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F2.1663351049728.gif?alt=media&token=fd900d9b-ad5a-4940-9f47-bfb726747f75)

In this counter example, when a user clicks on the button, it will increase the number displayed by 1, starting at the number 0.

We can start off with this code, where we have a `counter` value that the `addOne` function increments, whenever the button is clicked.

```html
<script setup>
  let counter = 0;

  function addOne() {
    return counter++;
  }
</script>

<template>
  <h1> {{ counter }} </h1>
  <button @click="addOne"> Increase the number by 1 </button>
</template>

<style scoped>
  h1 {
    font-size: 32px;
  }
</style>
```

While at first glance, you might expect this to work, if we `console.log()` the `counter` value, we can see that the `counter` variable is indeed increasing, but that new value is not updated in the `template`. 

![2022-08-24 17.13.30_revised.gif](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F3.1663351054998.gif?alt=media&token=735ab77f-7a09-488d-a9b0-b0f77050a01f)

This is because while the JavaScript itself is working, we need to let Vue know that the state of the  `counter` variable is going to be changing, since we’ll need to access the updated value from the `template`. 

In other words, we need to make `counter` reactive.

---

## Making data reactive with `ref()`

Vue’s Composition API (with or without the Script Setup syntax) comes with the ability to make data reactive through the use of the `ref()` function.

We just need to make a few changes to the code above:

1. Import `ref` from the core Vue module
2. Wrap our data stored in the `counter` variable in `ref`
3. Access the *value* of this reactive data by adding `.value` to it (`counter.value`)

In the code below, our simple counter now behaves as expected:

```html
<script setup>
  import { ref } from "vue";

  let counter = ref(0);

  function addOne() {
    return counter.value++;
  }
</script>

<template>
  <h1> {{ counter }} </h1>
  <button @click="addOne"> Increase the number by 1 </button>
</template>

<style scoped>
  h1 {
    font-size: 32px;
  }
</style>
```

What `ref` smartly does is package the variable in a reactive object for Vue to parse, and `.value` is the key in that reactive object called `counter` that holds the actual value we need. 

---

## Building with the Script Setup

Now let’s take these foundational principles and build something a bit more complex using the Script Setup syntax.

![Demo water tracker](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F4.1663351059111.gif?alt=media&token=344bc75e-4e4e-4031-8e07-9922f83c03a5)

The component we’re building will behave like so:

1. When you type a number into the input and click on the “Add entry” button
    - The entry is added to a list below (along with a button that allows you to remove that entry)
    - The total amount of water intake will be displayed
2. If you click on the “Remove entry” button on an individual entry
    - That entry will be removed
    - The total amount of water is adjusted accordingly
    

---

## Building out the form

Let’s get started building the form.

![The form](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F5.1663351063780.jpg?alt=media&token=300e9d05-5173-4a33-9bc4-d81cd778b6d0)

In our `template`, we add a `<form>`. On the form element, we’ll add`@submit.prevent`, which is shorthand in Vue for stopping a form from submitting by running `event.preventDefault()`

📄 **waterTracker.vue**

```html
<template>
  <form @submit.prevent="addEntry">
      
  </form>
</template>
```

Notice how we’re telling the form to run `addEntry` when it’s submitted. This is a function we’ll define in the `script setup` below.

Now let’s add an `<input>` and `<label>` for our water entries, along with a button to submit the form.

📄 **waterTracker.vue**

```html
<template>
  <form @submit.prevent="addEntry">
    <label>Number of ounces I drank today </label>
    <input v-model.number="newEntry" name="newEntry" />
    <button type="submit">Add entry</button>
  </form>
</template>
```

In our `<input>`, we use `v-model.number`

- `v-model` is a Vue directive that you can read more about <a href="https://vuejs.org/guide/essentials/forms.html" target="blank">here</a> that allows for two-way data-binding. This means that when we combine it with `input`, the `template` will be updated if there is a change to the state of the data in `script`, and similarly, will update the data if there is a change in the `input` in the template. Whatever number is typed into the `input` will be stored in `newEntry` in the `script` as a number.
    - The`.number` modifier typecasts `newEntry` to be a number
    

Great, now our `template` is complete and we can move on to our `script` section.

---

## The Script Setup section

In the `template` section, you saw how we wrote `<input v-model.number="newEntry">`

Since we are inputting a newEntry via this `<input>`, this means we’ll need a `ref` called `newEntry`.

So we’ll import `{ ref } from "vue"` and initialize a `newEntry` ref as an empty string because we want the input field to start out empty:

📄 **waterTracker.vue**

```html
<script setup>
import { ref } from "vue";

const newEntry = ref("");
</script>
```

Once we receive a `newEntry`, we’ll want to add it to a list of `entries`. So let’s create a ref for `entries` and initialize it as an empty array:

📄 **waterTracker.vue**

```html
<script setup>
import { ref } from "vue";

const newEntry = ref("");
const entries = ref([]);

</script>
```

Now let’s write the function for `addEntry`, which does some very simple validation to check if what is entered into the input field is a number.

- If it is, we will keep track of this new entry by adding it to an array of all the `entries`, and then reset the form input to be an empty string
- Otherwise, we will just send an alert to the window that you should input a number, and reset the form input

📄 **waterTracker.vue**

```html
<script setup>
import { ref } from "vue";

const newEntry = ref("");
const entries = ref([]);

function addEntry() {
  if (Number(newEntry.value)) {
    entries.value.push(newEntry.value);
    newEntry.value = "";
  } else {
    alert("Please enter a valid number!");
    newEntry.value = "";
  }
}
</script>
```

---

## Totaling the ounces of water recorded

To make our simple app more engaging, we can display a message “Go drink some water!” to the user. 

As soon as the user adds an entry, that message will be replaced with the total amount of water entered. 

![Initial state](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F6.1663351067579.jpg?alt=media&token=2e1291cc-0f4a-4afa-8dea-54ec9186b195)

![After adding at least one entry](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F6.5.jpg?alt=media&token=be0d1fdd-429a-49b2-a0f8-0592f8f3a41e)

To do this, we will want to add one new piece of reactive data that will always hold the total amount of water, called `total`. This `total` will perform a simple algorithm for us, iterating over the array of `entries` and adding up the total amount of water in that array.

To achieve this, we will use a <a href="https://www.vuemastery.com/courses/intro-to-vue-3/computed-properties-vue3" target="blank">computed property</a>, which is a method that runs when one of its dependencies changes, and then returns a new value based on the logic it just ran. 

In our case, in order to have an always-updated `total` amount of water entered, our `total` will need to run whenever the `entries` array changes.

So, in the `<script setup>` , let’s add `total` as a `computed` property:

📄 **waterTracker.vue**

```html
<script setup>
import { ref, computed } from "vue";

...

const total = computed(() => {
  let sum = 0;
  for (let entry of entries.value) {
    sum += entry;
  }
  return sum;
});

...
</script>
```

Note how we had to import `computed` first, then simply loop over each entry in `entries` to add up the `total` amount of water entered.

Now, we only need to add some logic in our `template` to display this `total` on our page. We can take advantage of the `v-if` Vue <a href="https://vuejs.org/guide/essentials/conditional.html" target="blank">directive</a>. It essentially allows us to easily use `if-else` conditions in the `<template>`. 

In our case:

- If the length of the `entries` array is zero (and so the `total` is at zero), it will display the message for us to drink water.
- Otherwise, if there is a `total`, we want to display the total amount of water in ounces

📄 **waterTracker.vue**

```html
<template>
  <--! Existing code above -->

  <div>
    <h2>How much water have I had?</h2>
  </div>
  <div>
    <h4 v-if="entries.length === 0">Go drink some water!</h4>
    <h4 v-else>Total water: {{ total }} oz</h4>
  </div>
</template>
```

---

## Displaying the list of entries

So far in our simple water tracker demo, we can add an entry with the total number of ounces of water we’ve had, and the total entered amount is displayed. 

What if we wanted to actually see each entry and also have the ability to remove that entry, in case we made a mistake? 

![The list of entries and ability to remove it from the list](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F7.1663351069862.jpg?alt=media&token=3f7c8adf-8a70-41fe-a5c8-f219b9f4c88e)

We will need to do two things:

1. In the `template`, we will want to add the “Entries” section and have it display each entry on its own. Each entry will have a button that, when clicked, will remove that entry *and* update the `total`
2. In the `script setup`, we will add a method that will allow us to remove the entry and simultaneously update the `total`

Getting started with task one, we’ll use the `v-for` <a href="https://vuejs.org/guide/essentials/list.html" target="blank">directive</a>, which gives the `template` the ability to run a `for` loop through a list. In our case, we want to keep note of both the `entry` itself (a number), and its `index` so that we can use it to remove an individual entry in the `script setup`

📄 **waterTracker.vue**

```html
<template>
  <--! Existing code above -->

  <div>
    <h2>Entries</h2>
    <ul>
      <li v-for="(entry, index) in entries" :key="index">
        <span>{{ entry }}</span>
        <button @click="removeEntry(index)">Remove</button>
      </li>
    </ul>
  </div>
</template>
```

Note how we also added a Remove button. When it’s clicked, we will run a new method called `removeEntry()`, which removes that entry from the array of `entries`:

📄 **waterTracker.vue**

```html
<script setup>
// Existing code above

function removeEntry(index) {
  entries.value.splice(index, 1);
}
</script>
```

One thing to note is that we don’t have to do anything to the `total`, and it will still automatically be updated. This is a result of having defined `total` as a computed property, which is updated every time one of its dependencies (in this case, the length of `entries`) changes.

![Demo water tracker](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F8.1663351072212.gif?alt=media&token=cb40dd88-eac8-4f31-8724-9ad905bd7395)

Great! We’ve successfully used the Script Setup to construct a functioning Vue SFC, and in the process learned about some of the foundational pieces of Vue reactivity with `ref` and `computed`, while utilizing the common Vue directives: `v-for`, `v-if`, `v-model`.

---

## Bringing it into the real world

As this article demonstrates, single file components in Vue have evolved from relying on the original Options API, before maturing to become more modular, maintable, and reusable thanks to the Composition API, then finally leaning out to become more minimal and modern with the Script Setup (and the added bonuses that syntactic sugar gives us).

While Script Setup is the new and recommended kid on the block, it’s important to be able to parse through any of these syntaxes. As Vue developers, there may be times we inherit a legacy code base that contains various versions of SFCs, or we may encounter various syntaxes in blogs, StackOverflow threads, etc, so it’s helpful to be able to understand them out in the wild.

_Originally published at_ [_https://www.vuemastery.com_](https://www.vuemastery.com/blog/a-brief-history-of-vue-script-setup) _on September 21, 2022._

* * *