At iRonin, we have a vast amount of experience in creating interactive web applications. In this piece, we would like to show you how to write Single Page Applications with the Vue.js framework, with ready-to-use solutions and code snippets as examples.
We have been developing Single Page Applications (SPAs) for many years for various industries such as fin-tech, retail, education and for different types of clients, from start-ups to enterprises. We would like to share a bit of our know-how by describing how we build highly interactive web applications using Vue.js and additional libraries. Before we move on, a small tip: before starting to work on a Vue.js app we recommend installing Vue.js dev tools for Chrome - it is really helpful in debugging the web application, includes history of state changes (basically you can list the full history of modifications, with the ability to apply a specific state snapshot with just a single click) and more.
Vue.js, a progressive JavaScript framework
Vue.js is a versatile and approachable open-source, progressive JavaScript framework. It can be used for building user interfaces, as well as be used as a web application framework capable of boosting advanced SPA. Vue.js can be easily integrated into projects that rely on other JavaScript libraries because it is designed to be incrementally adoptable.
Since Vue.js calls itself “a progressive JavaScript framework”, you can extend its basic functionality using additional libraries. That is why in order to build complex and highly interactive web apps, we have to look for help in additional libraries, such as:
- vuex - the official library for managing an application’s state,
- vue-router - the official library for routing,
- vue-resource or axios for handling ajax requests (note: `vue-resource` was previously the official library),
- vue-auth - for authentication, in case we don’t want to roll out our own - it depends on the project,
- vue-i18n - for internationalization,
- vue-strap or vue-material depending on whether we use Bootstrap or Material design.
Project structure
We follow the official WebPack template for vue-cli in terms of bootstrapping the project. This gives us a pleasant-to-use project structure, which is almost the same in each of our Vue.js ventures.
Our `src` directory usually looks similar to this one:
.
├── assets
├── components
├── config
├── i18n
├── lib
├── mixins
├── resources
├── store
├── styles
├── bootstrap.js
└── main.js
- `assets` - contains asset files like images,
- `components` - holds all our components (more on that below),
- `config` - contains setup for the application (i.e. routes, authentication configuration etc.),
- `i18n` - contains all texts for different languages (more on that below),
- `lib` - contains helpful code that is specific to the project we worked on,
- `mixins` - common functionality that can be shared between different components,
- `resources` - defines our API endpoints,
- `store` - application’s store setup (more on that below),
- `styles` - general styling for the app,
- `bootstrap.js` - imports all Vue.js extensions, makes the framework aware of them and exports the global `Vue.js` object with all add-ons enabled,
- `main.js` - imports the `boostrap.js` and starts up the app.
By having common file structure across each of the projects, we make it easy for our developers to switch between them.
Managing the application’s state
If your app consists of multiple-components, the chances are high that it has a lot of rich interactions. Supporting those interactions results in many intermediate states of page components, such as menu opened, menu item X selected, menu item X clicked, menu item Y closed etc. Server-side rendering is difficult to implement for all those components’ intermediate states, as small view states are not well-mapped to URLs. That is why, for managing the application’s state we use `Vuex`. Depending on the SPA’s complexity, we either use a single store or we split a store into multiple modules.
For each store we have 4 files:
- `actions` - to dispatch actions from components,
- `getters` - to get a specific store’s state,
- `mutations` - which contains a function to mutate the store’s state,
- `index` - to initialize the store and import actions, getters and mutations.
To be honest, there is nothing unusual here - it’s a very common setup in every `Vuex`-based project.
Component based architecture
Component based architecture revolves around building a system using components and integrating them together into one system. This allows us to reduce the cost of software development as components can be reused in different places within a system, or even separate systems because of their lack of context specific data. Also, component-based engineering improves a whole system’s quality, with each component having the option of further adjustment, replaceability and encapsulation (interactions with only the component’s public interfaces).
The Vue.js core library is focused on the view layer only, and is very easy to pick up and integrate with other libraries or existing projects. On the other hand, Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries.
If you are used to working with a web framework like Ember, then bear in mind that here the controller or component’s logic and the template are replaced with single file components. Thus to keep things well organized, it's reasonable to separate the routable components from regular (reusable) components. The common approach, as seen in multiple projects, is to have the following directories inside `src/components`:
- `components/views` - for routable components (sometimes called containers),
- `components/partials` - for regular re-usable components,
- `components/directives` - for custom directives,
- `components/filters` - for custom filters.
Internationalization
We always introduce internationalization as early in the project as we can. It can add a small overhead in the beginning, but it pays off once the web application needs to be translated to other languages. Extracting texts from an untranslated app at a later stage is a very daunting and error-prone task.
We split the JSON file with translation texts into 2 sections:
- `view` for view messages,
- `component` for component messages.
Here is a very simple example:
{
"view": {
"login": {
"title": "Sign in"
}
},
"component": {
"nav-bar": {
"sign-out": "Sign out"
}
}
}
If SPA is very large and contains a lot of messages per view or components, then we split the texts into multiple files.
Importing jQuery plugins
If you stumble upon a case in which you decide you want to use a jQuery plugin in your web app, you can import it to your Vue.js-based app. If there is a wrapper for Vue.js then all it requires to use the plugin is running the `npm install` command. However, if your are unlucky, you will need to create a wrapper for the plugin yourself. Novice note: before you can start writing a component, make sure that jQuery is globally available to your plugins. In our projects we use the webpack template, so we can leverage the plugin (https://webpack.js.org/plugins/provide-plugin/) for Webpack to create a global jQuery object:
// Add this to `plugins` attribute in your Webpack config
new webpack.ProvidePlugin({
$: 'jquery',
jquery: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
})
After that we can create a wrapper. Here is an example of `bootstrap-datetimepicker`:
<template>
<input type="text" class="form-control" />
</template>
<script>
import 'eonasdan-bootstrap-datetimepicker';
import 'moment';
export default {
props: ['value', 'format', 'minDate'],
data() {
return {
$datetimepicker: null,
options: {
format: this.format,
minDate: this.minDate || null,
},
};
},
mounted() {
const $element = $(this.$el);
const vm = this;
$element.datetimepicker(this.options);
this.$datetimepicker = $element.data('DateTimePicker');
this.setValue(this.value);
$element.on('dp.change', function dateChange() {
vm.$emit('dateChange', this.value, vm.typeName);
});
},
beforeDestroy() {
this.$datetimepicker.destroy();
},
watch: {
value(value) {
this.setValue(value);
},
},
methods: {
setValue(value) {
this.$datetimepicker.date(value);
},
},
};
</script>
Testing
Testing in software development can’t be praised enough - it is a measure of the quality of our solutions. In a Single Page Application written in Vue.js, for testing components we use:
- nightwatch - for end-to-end tests,
- karma with chai, mocha and sinon - for unit tests,
plus some custom helpers (e.g. page objects) to make the code less repeatable. It’s mostly a default setup that comes from the `WebPack template` . We are very enthusiastic about avioraz, which is about to become the default testing utility for Vue.js applications, and we can’t wait to use official vue-test-utils in our projects.
Deployment
We usually deploy Vue.js applications as static files to S3 with a CDN on top. Sometimes we also deploy Vue.js apps to Heroku. Unfortunately, there is no official Vue.js buildpack for Heroku so we use a custom-based solution (stay tuned for the more detailed explanation of that in our next blog post).
At iRonin, we have experts on hand experienced with Vue.js, web application development, testing, deployment, and monitoring. If you are looking for a solution for your complex Single Page Application, or need assistance with your back-end or front-end development, then look to us for smart, concise answers. Contact our professional team for more info.