Understanding Vue.js Single-File Components (SFCs)
Created on March 29, 2023.
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, a first-class practice in OSS since fixes or features that are pushed to a single codebase can easily be pulled into all dependents.
- 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
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
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"
}
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
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]"
}
...
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>
// index.js
import { createApp } from "vue";
import App from "./App.vue"
const app = createApp(App).mount("#app")
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()]
});
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"
},
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.
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>
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>
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>
And, update the <script>
section by adding the data
and methods
options.
data() {
return {
count: 0
}
},
methods: {
increment(){
this.count++
}
}
We should now see the following on our page.
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>
And, this is how the page would look afterward.
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++
}
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'
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")
}
When clicking the button we should see the following effect.
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.
Previous