One year with Vue.js
Okay, okay, it’s been more than a year, but I couldn’t resist the title. Sorry!
This is a write-up about how I started with Vue.js and how I use it to develop my side project.
Why Vue?
My story with Vue started with my need to pick a SPA library/framework to build my side project. Having had previous work experience with modern Angular, I didn’t want to use it for my app, because it really didn’t offer any benefits for smaller apps. The bundle would end up pretty big, simple things were not always straightforward (confusing Dependency Injection or the need for RxJS knowledge comes to my mind), changes to the API were made on every major version etc. I felt like Angular was just simply over-engineered product and therefore not a good fit for my side project.
There were a couple of reasons why I didn’t choose React either. I didn’t believe that it was so superior to anything else as it was hyped to be (I am definitely a bit wary of hyped big corp tech and whether the tech is a good fit for any other company or project). I also had some mixed feelings about JSX, the original React license and about the fact that it is only a rendering library, which meant choosing additional libraries like Redux. And this might feel silly to some, but I also didn’t want to build on Facebook tech (Facebook is not exactly a love brand for me).
The third most popular library at the time was Vue.js. It actually looked pretty good and it didn’t have any obvious downside (beside lower popularity when compared to React and Angular), so I went with it. Long story short, I have never regretted my choice.
Developing with Vue
I started with the official Vue.js guide and added functionality to my app one step at a time. It was very nice to start simple, without any build step, just with Vue from CDN. This was my first proper Single Page Application that I was creating from start to finish and the whole experience was very smooth.
Even though I started with the simplest setup, I added a build step to my Vue.js application quite soon in order to use Single File Components. I really like to have all component code together, but I only put template and JavaScript code in the Single File Components, since I don’t use component-scoped CSS. I am not used to component-scoped CSS and I haven’t tried it yet, but I am nevertheless glad that it is included in Vue.js, as this might be a deal breaker for some. Perhaps I will use it in the future, who knows?
After some initial tutorials and experiments, I found out that the Vue development model makes sense. On the JavaScript side of a component, there are couple of things to define, like properties passed to the component, list of child components, component data, computed properties and methods. And while there are many more things one can use, these things, together with the component’s template, form the base of almost every component. All these concepts are simple, easy to remember and I’d even say addictive.
// example: Vue component
<template>
<div>
<div
v-if="addresses.length > 0"
class="content-block">
<h3>Addresses</h3>
<address-item
v-for="address in addresses"
:address="address"
:key="address.id" />
</div>
</div>
</template>
<script>
import AddressItem from "./AddressItem.vue"
export default {
components: {
AddressItem
},
props: {
person: {
type: Object,
required: true
},
primary: {
type: Boolean,
required: false,
default: false
}
},
computed: {
addresses() {
if (this.primary) {
return this.$store.getters.primaryAddressesByPersonId(this.person.id)
}
return this.$store.getters.addressesByPersonId(this.person.id)
}
},
methods: {
...
}
}
</script>
While properties are data passed to the component, data is a place to store mutable data for the component and methods are just internal functions operating within the component, computed properties are really cool and very useful. They will automatically change based on changes in underlying data (from the component itself or retrieved from the store). This can be used for conditional data retrieval, data validation, data modification and other things.
// example: computed property
...
computed: {
fieldValue() {
// TODO: return value from store or other place,
},
shortFieldValue() {
// if fieldValue changes, shortFieldValue is recomputed
const fv = this.fieldValue
if (fv.length > 35) {
return `${fv.substring(0, 35).trim()}...`
}
return fv
},
...
}
...
I have to admit that in the beginning, I didn’t really like that component was just defined using one JavaScript object, probably it didn’t feel right after writing components in Angular, where the component is a class with proper methods. But in the end, it doesn’t matter at all because it is very productive. I even grew to like it.
Beyond the basics
One of the best things about Vue is that we don’t need to go after third party libraries to make our app, if we don’t want to. The two main ones, Vuex for state management and Vue Router for routing have been both very easy to use and integrate, and also sufficient. The learning curve is just reading ~10 pages of official manual for each and we are good to go.
First, the router. I like that the routes are named (and can be easily linked to) and nested. With nested routes we can have a “parent” component/template that is shared between the child components. That way it is easy to have several sections of the application, each with its own layout and shared functionality. Beside defining my standard routes, I have one wildcard route (*) matching anything else and one beforeEach handler for doing auth check. URL parameters can be easily defined and passed to components as properties and links can be easily generated both programmatically and in templates using router-link. Perhaps some people need more from a router, but for me all of this was really enough.
// example: children and wildcard routes
const routes = [
{
name: "auth-section",
path: "/auth-section",
component: AuthSection,
children: [
{ name: "auth-section-login", path: "login", component: LoginForm, props: true },
{ name: "auth-section-signup", path: "signup", component: SignupForm, props: true }
]
},
...
{
path: "*",
redirect: { name: "auth-section-login" }
}
]
The way I use Vuex is that I have a couple of store modules that all manage state with mutations and retrieve data with getters. I have a separate module for every model class. I have some set of common functions defined that work universally and just call them in mutations and getters so that I don’t have to re-implement everything in each store module. In the beginning I also used actions to group mutation commits together, but now I don’t use them at all. Instead, I introduced a service layer in my application that sits between the components and the Vuex store and allows me to group operations as I see fit (and combine them with HTTP calls, validations and other things).
// example: store module for addresses
import * as common from "./common"
import * as types from "../mutation-types"
import Address from "../../model/address"
const state = {
all: []
}
const getters = {
allAddresses: (state) => {
return common.findAllNotDeleted(state)
},
addressById: (state) => (addressId) => {
return common.findById(state, addressId)
},
...
}
const mutations = {
[types.ADD_ADDRESS](state, address) {
state.all.push(address)
},
[types.UPDATE_ADDRESS](state, addressUpdateDetails) {
// e.g. addressUpdateDetails: {id: 3, update: {propertyName: value}}
const storedAddress = common.findById(state, addressUpdateDetails.id)
Object.assign(storedAddress, addressUpdateDetails.update)
},
[types.DELETE_ADDRESS](state, address) {
common.deleteItem(state, address)
},
[types.DELETE_ADDRESSES_OF_PERSON](state, person) {
common.deleteAllByPersonId(state, person.id)
},
...
}
The nice thing about Vuex and Vue router is the integration with Vue that you get, such as easy access in the components via this.$store and this.$router. The inclusion of Vuex and Vue Router in the Vue project makes Vue sufficient for building SPAs without thrird-party libraries and it is definitely one of its killer features.
Tell me how you write your code, and I will tell you what kind of programmer you are
I mostly try to follow Vue.js style guide. Almost all the things make sense to me, they are properly explained and I hope that the existence of the style guide will also lead to more consistent Vue codebases among projects and companies. A couple of things are a bit harder to follow though. Component/instance options order and Element attribute order are not bad recommendations, but it is a bit hard to remember the order beyond the basic options and attributes.
I use Prettier to format code in the project, but I am kinda disappointed that formatting HTML templates with Prettier is in conflict with Vue.js style guide, namely about placing tag attributes on their own lines called Multi-attribute elements (plus Prettier doesn’t help in any way with element attribute order). I understand that it makes sense for Prettier not to support Vue-specific things, but because of that I disabled Prettier for HTML formatting and I am considering what to use in the future, as I’d very much like to have automatic code formatter for templates too.
If you use VSCode with Vetur extension, this is how to turn off the automatic formatting of HTML in .vscode/settings.json (formatting will still work for JavaScript):
"vetur.format.defaultFormatter.html": "none"
I bought into the idea of having Base components that are registered just once in the application. I don’t like too much magic so I didn’t go for automatic component registration based on a name or location and went just for a global component registration instead. That way I maintain one file where I register my Base components manually like this:
// example: manual global component registration
import Vue from "vue"
import BaseButtonDelete from "../components/base/BaseButtonDelete.vue"
import BaseToggleSwitch from "../components/base/BaseToggleSwitch.vue"
...
Vue.component("BaseButtonDelete", BaseButtonDelete)
Vue.component("BaseToggleSwitch", BaseToggleSwitch)
...
All non-base components are imported and registered in the components themselves. This feels to me like a good middle ground.
For form and data validations, I started with vuelidate but eventually realized it is better to use framework-agnostic library like validate.js. Please refer to my article Using validate.js instead of vuelidate for more details on this approach. Apart from using validate.js, I didn’t feel any need for another library, because creating and handling basic forms is a well-covered use case in Vue.js.
I admit that I don’t have much experience when it comes to testing any Vue-specific code. I mainly write unit tests for my service layer or unit tests for my Vuex store, but that’s about it. Unfortunately I haven’t have time to really dive into testing components with rendered output and other testing approaches. There are now even books written on testing Vue.js applications, like Testing Vue.js Applications (Testing rendered component output and Testing events are free chapters available online) or Testing Vue.js components with Jest. I haven’t read any of them yet, but it is good to see books written about Vue.js and testing Vue apps.
Development tools for Vue
There are a couple more things still left for me to explore in the world of Vue. First of them would be Vue CLI, which didn’t exist when I started my project but I am happy that it exists now and makes creating new projects very easy. I used Babel and Webpack and created my configurations manually, which was easier than I’d have guessed, and the transition to Babel 7 and Webpack 4 later was also pretty okay. I think that many developers don’t interact with these configurations often so it feels like “black magic” when they do once in a couple of months, but it is not that bad if you look in the docs.
When it comes down to the debugging, Vue offers vue-devtools browser extensions. I have not used it yet and although it looks really great, I just want to say that it is totally possible to develop Vue apps without it. There is also a guide to setup debugging in VS Code which might be useful to some.
So far so good
I know I didn’t mention any major downsides of using Vue, but that is because I don’t really see any. Of course, the job market is smaller, the ecosystem is smaller than for some frameworks, but for getting the job done, it is really a very good piece of software. Especially because it can grow from small things to bigger, proper, Single Page Applications.
Looking back at our team in the previous company I worked in, Vue would have been a better framework for both the project and the team. For the project, because we didn’t have a proper SPA and using Angular for a couple of widgets here and there is not a good idea; and for the team, because people really struggled with Angular, albeit they were not mainly JavaScript programmers.
There are things that I didn’t mention in the article because I didn’t use them yet, like the support for transitions/animations or server-side rendering. I hope I will try them one day too. I am also excited for the future of Vue.js. I am looking forward to build-in class based components, improved development experience thanks to TypeScript, and faster and smaller Vue.
So far so good.
Last updated on 30.1.2019.