Building Larger Apps

Vue.js is designed to be as flexible as possible - it’s just an interface library that doesn’t enforce any architectural decisions. While this can be very useful for rapid prototyping, it could be a challenge for those with less experience to build larger scale applications with it. The following is an opinionated perspective on how to organize larger projects when using Vue.js.

Build with Component

Vue.js is built with Component, a front-end package manager and build tool. With Component you can write your browser code in the format of CommonJS modules (the same format Node.js uses), and publish packages simply by pushing them to public GitHub repositories. This helps greatly with both code modularization and reusability. For a boilerplate of using Vue.js with Component, see the vue-component-example.

There are already a great number of public components available that deal with common problems in front-end applications. For a larger scale application, you can use Vue.js as the interface layer and fill in the missing pieces with other components.

Alternatively, Browserify is another excellent CommonJS based build system for browser JavaScript. Instead of GitHub repos, Browserify leverages NPM as its package manager and also has a thriving community.

Modularization

Component as a build tool not only deals with JavaScript; it can also build CSS, templates and other assets. With builder hooks it can also pre-process files written in non-native formats, e.g. CoffeeScript, SASS and Stylus. Therefore it is possible to deliver highly self-contained components that encapsulate template structure, JavaScript logic and CSS presentation at the same time. A simple example can be found here.

Similar modularization can be achieved in Browserify too, with transform plugins such as partialify. A simple setup example can be found here.

Routing

You can implement some rudimentary routing logic by manually listening on hashchange and utilizing the v-view direcitve. The v-view directive is essentially a dynamic component loader: it binds to a string value and creates a new VM instance using that string as the Component ID (and destroys the old VM if it exists). Example:

1
2
3
<div id="app">
<div v-view="currentView"></div>
</div>
1
2
3
4
5
6
7
8
9
Vue.component('home', { /* ... */ })
Vue.component('page1', { /* ... */ })

var app = new Vue({
el: '#app',
data: {
currentView: 'home'
}
})

The home component will be rendered in place of v-view. When currentView‘s value changes to page1, the existing home component will be destroyed and replaced by the new page1 component. The full, more detailed example can be found here.

v-view will replace the element it’s bound to with the new instantiated VM’s element, so avoid using it on your root element.

With v-view it’s very easy to leverage standalone routing libraries such as Page.js and Director. There is a plan to provide a vue-router component that integrates with Vue.js for easier routing and deep linking.

Communication with Server

All Vue.js ViewModels can have their raw $data directly serialized with JSON.stringify() with no additional effort. You can use any Ajax component you like, for example SuperAgent. It also plays nicely with no-backend services such as Firebase. In addition there is a plan to provide a vue-resource component that resembles Angular’s $resource, to make interfacing with a REST API easier.

Unit Testing

By using Component, Vue.js entities (ViewModel constructors, directives, filters) within a large project can be split into separate CommonJS modules. When a Component-based project is built without the standalone flag, it will expose its require() method, granting access to all these internal modules. This makes it quite easy to write browser unit tests - just include the test build and require the module you want to test.

The best practice is to export raw options / functions inside modules. Consider this example:

1
2
3
4
5
6
// my-component.js
module.exports = {
created: function () {
this.message = 'hello!'
}
}

You can use that file in your entry module like this:

1
2
3
4
5
6
7
8
9
10
// main.js
var Vue = require('vue')

var app = new Vue({
el: '#app',
data: { /* ... */ },
components: {
'my-component': require('./my-component')
}
})

And you can test that module like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Some Mocha tests
// using a non-standalone build of the project
describe('my-component', function () {

// require exposed internal module
var myComponent = require('my-project/src/my-component')

it('should have a created hook', function () {
assert.equal(typeof myComponent.created, 'function')
})

it('should set message in the created hook', function () {
var mock = {}
myComponent.created.call(mock)
assert.equal(mock.message, 'hello!')
})

})

Since Vue.js bindings update asynchronously, you should use Vue.nextTick() when asserting DOM updates after changing the data.