DEV Community

Cover image for Understanding Vue.js Single-File Components (SFCs)
James Sinkala
James Sinkala

Posted on • Updated on • Originally published at vuenoob.com

Understanding Vue.js Single-File Components (SFCs)

If you have been reading these posts in chronological order, you know that up to this point, we’ve been creating and running our Vue.js apps inside HTML pages as part of page scripts.
Vue.js gives us the freedom to create apps in this way, depending on the extent of features we are expecting our Vue.js apps to have within the apps we are building.

We have seen that, Vue.js can serve the purpose of adding atomic features within other apps/HTML pages, for example, it can be used in the search bar section of a website only, sending search requests, and displaying results per the inserted queries.

But, there are cases where we need to build fully-fledged Vue.js apps with whole sets of features such as routing, state management, etc. Such apps normally involve writing a lot of Vue.js code, and, as it's expected in such instances, we get large files containing lots of code which normally leads to all sorts of code maintenance issues down the line.

To avoid these imminent time and resource-consuming troubles, we normally break down code into smaller manageable and dependent chunks as we’ve seen with the Vue.js components.

But, the scenario we’re referring to here goes beyond breaking down Vue.js apps into smaller components, but further into independent files called Vue.js Single-File Components (SFCs) that are by themselves fully-fledged and pluggable Vue.js apps.

Here are some of the benefits of using Vue.js SFC files:

  • The improvement to developer experience, by encouraging the use of build tools that do the heavy lifting for us aiding in the automation of tasks such as code prettifying, linting, and minification for production to facilitate the making of light deployment bundles, best practices and so forth.
  • We can easily re-use SFCs within other apps where we would want to re-implement related features and avoid re-writing code.
  • We can bundle components and create sharable plugins, facilitating the sharing of code with other developers. This is a very beneficial practice in software development since when fixes are made or features are added they are easily distributed to all apps these plugins are used in.
  • The grouping of all code (logic, template, and styling) related to a single function within a single file simplifies the management of our apps and their independent features.

The Vue.js SFC files have a unique .vue file extension which is supported by all major IDEs, text editors, and several plugins that help highlight the Vue.js code and apply code intellisense to it.

Working with Vue.js Single-File Components(SFCs)

To see the syntax and work with Vue.js SFCs, you’ll first need to scaffold a Vue.js project that’s managed with a build tool called Vite.

We’ll work with Vite but won’t place much focus on it in this post, more of its specifics will be learned in the future, for now, all we need is to understand that it’s a build tool for front-end JavaScript frameworks.

Before proceeding make sure that you have the latest LTS version of node.js installed into your system.

Run the following command on the terminal to create a new Vue.js project directory.

mkdir vue-sfc-app
Enter fullscreen mode Exit fullscreen mode

When this command terminates, well should have a new directory named vue-sfc-app. Switch the active terminal location into it by running cd vue-sfc-app.

Inside the project directory, run npm init -y to make this directory the root of a new JavaScript project. This command adds a package.json file, which simply is a project’s metadata file that keeps a manifest of our dependencies, scripts, etc. To read more about this file, you can visit its docs.

So far, this will be the project’s directory layout:

.
├── package.json
Enter fullscreen mode Exit fullscreen mode

Here’s what’s contained in the package.json file so far.

{
  “name”: “vue-sfc-app”,
  “version”: 1.0.0,
  “description”: “”,
  “main”: “index.js”,
  “scripts”: {
    “test”: “echo \”Error: no test specified\” && exit 1
  },
  “keywords”: [],
  “author”: “”,
  “license”: “ISC”
}
Enter fullscreen mode Exit fullscreen mode

Add a ”type”: “module” property to this file and keep in mind of what is contained in it.

Run the following commands on the terminal.

npm install vue

npm install -D vite @vitejs/plugin-vue
Enter fullscreen mode Exit fullscreen mode

The first command installs the Vue.js npm package (providing us with the Vue.js library code). The second command installs Vite and its plugin for Vue.js. The two latter dependencies will handle our local development environment with tasks such as compiling the SFC files and serving the app locally so that we can only focus on building our app.

After these two files have been installed, you should notice a new directory: node_modules that has been added to your project. This is where the dependencies' source code is stored, we usually don’t make modifications to anything within it.

When you go back to the package.json file, you will notice some changes, two new sections dependencies and devDependencies with the respective dependencies and their installed versions will have been added.

  ...
  “devDependencies”: {
    “@vitejs/plugin-vue”: “^[SEMANTIC-VERSION],
    “vite”: “^[SEMANTIC-VERSION]
  },
  “dependencies”: {
    “vue”: “^[SEMANTIC-VERSION]
  }
  ...
Enter fullscreen mode Exit fullscreen mode

Add three files to our project - index.html, index.js, and vite.config.js. Place the following code within these files respectively.

<!-- index.html -->
<html>
    <head>
        <title>Vue SFC App</title>
    </head>
    <body>
        <div id=“app”></div>
        <script type=“module” src=“/index.js”></script>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode
// index.js
import { createApp } from vue;
import App from ./App.vue

const app = createApp(App).mount(“#app)
Enter fullscreen mode Exit fullscreen mode
// vite.config.js
import { defineConfig } from vite;
import vue from @vitejs/plugin-vue”;

export default defineConfig({
    plugins: [vue()]
});
Enter fullscreen mode Exit fullscreen mode

In the index.js file, we have initiated the Vue.js app in a familiar format, passing a Vue.js SFC file - App.vue inside the createApp() function.

In the index.html file, we have normal HTML markup out-sourcing the JavaScript code from the index.js file.

The vite.config.js file is a special file in projects managed with Vite where the configuration of the tool can be done. What we’ve done in this file is pass the plugin file so that Vite knows what to do when it encounters Vue.js code and files within the project.

We need to add a script inside the package.json to fire up the local server using commands provided by Vite and view what our project looks like.

Inside the scripts section of the package.json file, add a new dev script as follows.

  “scripts”: {
    “test”: “echo \”Error: no test specified\” && exit 1,
    “dev”: “vite”
  },
Enter fullscreen mode Exit fullscreen mode

Now, run npm run dev --port 3000 on the terminal. This should launch a local server on the 3000 port if it’s not occupied - localhost:3000. Try visiting it on the browser.

If you see the following error, then everything went to plan.

Vue.js missing component error.

In this error, Vite is simply telling us that it failed to find the App.vue SFC file, which is true since we never created it.

Go ahead and create it. Adding the following code inside.

<template>
    <div>
        He{{ 1 + 1 }}o World!
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Vite will handle hot-reloading for our website, and we should see “He11o World!” on our browser.

Anatomy of a Vue.js SFC file

Vue.js SFCs are just HTML files without the <head> section and with a <template> section replacing the familiar HTML <body>.
In general, they are made up of 3 sections, a <template> where the UI markup would be placed (Like in the example above), the component’s JavaScript logic placed between the <script> tags, and a <style> section where the component's styles would be placed.

We have seen something close to this syntax when learning about Vue.js components because in context, this is the same thing but within an independent file.

Let’s place the 1 + ‘1’ in a computed variable to see the component’s JavaScript section’s syntax.

<script>
export default {
    computed: {
        doubleL() {
            return 1 + 1;
        }
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

When the page refreshes, no changes would have been made to the page since we’re still employing the same logic.

Let’s see another example.
Add the following button to the template.

<button @click=“increment()”>Clicks {{ count }}</button>
Enter fullscreen mode Exit fullscreen mode

And, update the <script> section by adding the data and methods options.

    data() {
        return {
            count: 0
        }
    },
    methods: {
        increment(){
            this.count++
        }
    }
Enter fullscreen mode Exit fullscreen mode

We should now see the following on our page.

Counting button clicks

To add a bit of space between our HTML blocks, we can add some styling to the component inside the <style> section like this.

<style scoped>
div, button{
    padding: 5px;
    margin: 5px
}
button{
    color: red;
    font-weight: 800;
}
</style>
Enter fullscreen mode Exit fullscreen mode

And, this is how the page would look afterward.

Styled component

The scoped attribute on the <style> tag above instructs Vue.js to apply the styles within this component only and not elsewhere, when not placed, this component's style will be applied to the global context of the app.

The <script setup> sugar syntax in Vue.js SFCs

With Vue.js 3 (and v2.7), Vue introduced a new API to the framework - the composition API, which is a set of APIs that allows us to author Vue.js components using imported functions instead of declaring options, a practice we’ve been doing when authoring our Vue.js components up to this moment. The Options API is still available for use as we’ve demonstrated in all our examples.

With the Composition API, options such as data within our components would be replaced by the ref() or reactive() functions, native javaScript functions would be favored in place of the methods and filters options, and computed would be replaced with the onComputed() function. All the hooks would be placed with functions prefixed with on in the camel-case format, for example, the mounted, beforeUpdate, and unmounted hooks would be replaced with onMounted(), onBeforeUpdate(), and onUnmounted() composition functions respectively.

The <script setup> sugar was later introduced to facilitate a more succinct and ergonomic syntax for SFCs.

Let’s see an example by converting our App.vue component above.

import {computed, ref} from vue
let count = ref(0)
const doubleL = computed(() => 1 + 1)

function increment(){
    count.value++
}
Enter fullscreen mode Exit fullscreen mode

The ref() function, as demonstrated above, is used to make variables reactive. To test out the reactivity, try replacing the ref(0) with 0 and count.value++ with count++ to see if the count would increment on button clicks.

ref() takes an inner value passed to it and returns a reactive and mutable ref object that has a single property - .value that points to the inner value. This is the reason we are using count.value and not count inside the increment() function to mutate count.
Inside the template, we do not need to use .value as Vue.js automatically handles that.

For our computed variable doubleL we pass a getter function that also returns a reactive ref object, meaning, if we were to access its value within the JavaScript section of our app, we would use .value.

We can refactor doubleL in this instance, to free up resources used by using a constant instead of the computed property, since its value is not mutated or needed as a dependency somewhere else.

Const doubleL = 1 + 1
Enter fullscreen mode Exit fullscreen mode

Some obvious benefits we see in the conversion of the App.vue component to the Composition API are the reduction in the code footprint, and more application of native JavaScript, though, we do lose a bit of structure.
For more on the Composition API, you can read the Vue.js official docs on the topic.

Binding SFC component state to styles

With Vue.js SFCs, we can link CSS values to the reactive component state using the v-bind() CSS function.

Let’s bind count to the margin value within our styles.

div, button{
    padding: 5px;
    margin: v-bind(count + px)
}
Enter fullscreen mode Exit fullscreen mode

When clicking the button we should see the following effect.

Binding CSS values to component state

N.B., To see the source code for the Vue.js SFC project we’ve just created visit this GitHub repository.

Summary

To summarise, we’ve learned the following in this post:

  • Setting up a Vue.js project using build tools, in this case - Vite.
  • Creating and working with Vue.js’ Single-File Components (SFCs).
  • Understood the syntax and functionality within each section of an SFC.
  • We’ve been introduced to Vue.js composition API and some popular functions we’ll be using while building interfaces with Vue.js 3.
  • Learned about how to use the <script setup> sugar within Vue.js SFC files.
  • We’ve also seen how we can bind the component state to CSS values to render creative effects within our UIs.

For more on Vue.js Single-File Components, read the official Vue.js docs.

Top comments (0)