Proper Creation of Vue Projects
In this chapter, we'll be looking at how we can create maintainable Vue projects, and take advantage of the many tools and patterns available. If you currently aren't maximizing the use of these things within your development workflow, you may find that the majority of the concepts we discuss in this chapter are applicable to other technologies.
In this chapter, we will be looking at the following:
- Vue devtools
- Visual Studio Code extensions
- TypeScript integration
- Reactivity with RxJS
Visual Studio Code extensions
Our development environment is an important part of application development. When using Visual Studio Code to create Vue applications, the following extensions are recommended:
- Vetur
- Vue 2 Snippets
Let's take a look at both of these in more detail.
Vetur
Vetur is powered by the Vue Language Server and provides us with syntax highlighting, Emmet (for increased HTML/CSS workflow), snippets, linting, IntelliSense, and more. This greatly improves our development experience and is widely supported, with over 1,000 stars on GitHub. To install the extension, click theExtensionsicon within Visual Studio Codeand typeVetur; from here, you can selectInstalland it'll automatically be used throughout your project(s):
Installing Vetur
This then gives us access to snippets such asscaffold, which generates a new blank template, script, and style object(s) for us to use within our Vue components:
Scaffolding a new Vue project
Vue 2 Snippets
Snippets are an important part of application development; in a similar way to Emmet, they allow us to quickly scaffold common patterns within our application. We'll also be installing another Visual Studio Code extension that provides us with a variety of commonly used snippets named Vue 2 Snippets.
This allows us to save a substantial amount of time that we otherwise would have had to spend writing out the same boilerplate code. Take the next example; although it's simplistic in nature, we get a description of the snippet, and with a hit of_Tab_it expands to our predefined code:
Taking advantage of Vue snippets
Vue CLI
The VueCommand Line Interface(CLI) allows us to quickly scaffold new Vue projects with a variety of different template options. Currently, the template options available include technologies such as Webpack, Browserify, and Progressive Web Application features.
Sure, we could create our own Vue application and manually add tools such as Webpack, but this creates technical overhead in the sense that we have to learn, build, and maintain our configuration. The Vue CLI does this for us while maintaining a select set of official templates, but doesn't restrict us from modifying the generated Webpack configuration. All of this allows us to generate new unopinionated Vue projects.
To start using the Vue CLI, let's ensure we have it installed:
npm install vue-cli -g
We can then use the Vueinitcommand to scaffold a new Vue project using the Webpack template:
vue init webpack-simple my-vue-project
On entering the preceding command we should get the following as shown on the Terminal:
Creating projects with Vue init
If we break this down, we're essentially initializing a new Vue project based on thewebpack-simpletemplate namedmy-vue-project. We're then navigated to a wizard process that asks us for more metadata about our project. This metadata and configuration will differ depending on the template you choose.
Let's investigate the files and folders that the template created:
File/Folder | Description |
---|---|
src/ | This folder contains all of our project code. We'll spend the majority of our time within src. |
.bablrc | This is our Babel configuration file that allows us to write ES2015 and have it appropriately transpiled. |
index.html | This is our default HTML file. |
package.json | This holds our dependencies and other project-specific metadata. |
webpack.config.js | This is our Webpack configuration file, allowing us to use.vuefiles, Babel, and more. |
Notice how we're no longer working with.jsfiles, and we now have.vuefiles inside of oursrcdirectory. A Vue file is known as a Single File Component and it has a template, script, and style tag, allowing us to scope everything to this component only.
This is possible due to our Webpack template, as we have a custom "loader". How does this work? Prior to looking at this, let's take a quick detour and review modern JavaScript build systems.
JavaScript modules
In order to create reusable modular code, our aim should be to have one file per feature in most cases. This allows us to avoid the dreaded "Spaghetti code" anti-pattern, where we have strong coupling and little separation of concerns. Continuing with the pasta-oriented theme, the solution to this is to embrace the "Ravioli code" pattern with smaller, loosely coupled, distributed modules that are easier to work with. What does a JavaScript module look like?
In ECMAScript2015, a module is simply a file that uses theexportkeyword, and allows other modules to then import that piece of functionality:
// my-module.js
export default function add(x, y) {
return x + y
}
We could thenimportaddfrom another module:
// my-other-module.js
import { add } from './my-other-module'
add(1, 2) // 3
As browsers haven't fully caught up with module imports yet, we often use tools to assist with the bundling process. Common projects in this area are Babel, Bublé, Webpack, and Browserify. When we create a new project with the Webpack template, it uses the Vue-loader to transform our Vue components into a standard JavaScript module.
Vue-loader
Inside of our./webpack-config.jswithin the standardwebpack-simpletemplate, we have a module object that allows us to set up our loader; this tells Webpack that we'd like it to use.vuefiles inside of our project:
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {}
// other vue-loader options go here
}
}]
For this to work, Webpack runs a regular expression for anything that matches.vueand then passes this to ourvue-loaderto be transformed into a plain JavaScript module. In this simple example, we're loading files with a.vueextension, butvue-loadercan be further customized and you may want to look into this further (https://goo.gl/4snNfD). We could certainly do this configuration ourselves, but hopefully, you can see the benefits of using the Vue CLI to generate our Webpack projects.
Loading modules without Webpack
Although Webpack helps us in more ways than simply loading a module, we can load a JavaScript module at this moment in time natively in the browser. It tends to perform worse than bundling tools (at the time of writing), but this may change over time.
To demonstrate this, let's head over to the terminal and make a simple counter with a project based on the simple template. This template effectively starts a new Vue project without any bundling tools:
# New Vue Project
vue init simple vue-modules
# Navigate to Directory
cd vue-modules
# Create App and Counter file
touch app.js
touch counter.js
We can then edit ourindex.htmlto add script files fromtype="module"this allows us to use the export/import syntax outlined before:
<
!-- index.html --
>
<
!DOCTYPE html
>
<
html
>
<
head
>
<
title
>
Vue.js Modules - Counter
<
/title
>
<
script src="https://unpkg.com/vue"
>
<
/script
>
<
/head
>
<
body
>
<
div id="app"
>
<
/div
>
<
script type="module" src="counter.js"
>
<
/script
>
<
script type="module" src="app/app.js"
>
<
/script
>
<
/body
>
<
/html
>
Warning: Ensure that your browser is up to date so that the preceding code can run successfully.
Then, inside of ourcounter.js, we can export a newdefaultobject, which acts as a new Vue instance. It acts as a simple counter that allows us to either increment or decrements a value:
export default {
template: `
<
div
>
<
h1
>
Counter: {{counter}}
<
/h1
>
<
button @click="increment"
>
Increment
<
/button
>
<
button @click="decrement"
>
Decrement
<
/button
>
<
/div
>
`,
data() {
return {
counter: 1
};
},
methods: {
increment() {
this.counter++;
},
decrement() {
this.counter--;
}
}
};
We can then import thecounter.jsfile inside ofapp.js, thus demonstrating the ways we can import/export modules. To get our counter to display inside of our root Vue instance, we're registering the counter as a component inside this instance, and setting the template to<counter></counter>, the name of our component:
import Counter from './counter.js';
const app = new Vue({
el: '#app',
components: {
Counter
},
template: `
<
counter
>
<
/counter
>
`
});
We'll look at this in more detail in future sections of the book, but all you need to know at this point is that it effectively acts as another Vue instance. When we register the component inside of our instance, we're only able to access this component fromthatinstance.
Awesome! Here are the results of our module import/exports:
Vue.js Modules
In the next section, we'll take a deeper look atdebugging our Vue applications,and the role Vue devtools plays in this.
VueJS devtools
Being able to accurately debug our application is an important part of our development workflow. In the previous chapter, we installed the VueJS devtools, and we'll be looking at using it in more detail within this section. Let's make a playground project:
# New project
vue init webpack-simple vue-devtools
# Change directory
cd vue-devtools
# Install dependencies
npm install
# Run application
npm run dev
We can then open up our developer console and navigate to theVuetab. When we selectAppfrom within the components list, we can see the data object(s) and other information for this component. By default, we have themsgvariable that we're then binding to within our template, and we can see this within our developer tools:
Inspecting a Vue instance
This goes both ways though - we could access the objects inside of this Vue instance with$vm0.$data, scoping this tomsg.To view this within the console, selecting<Root>then<App>will display themsg0;within the console.We can change this value and as Vue is watching the value, it will automatically change on screen:
Editing Vue instance data from the console
Notice how our message has changed to"Hello Vue Devtools!"; if we had multiple Vue instances, there would be other prefixed versions of Vue with$vm1,$vm2, and so on. Later in the book when we come to usingVuex, we'll be using this often. Next, let's take a look at how we can integrate TypeScript into our Vue projects. This is useful for those with an Angular background or anyone that's looking to take advantage of static typing.
TypeScript and Vue
You may have used TypeScript in the past, or you may be curious about how you can take advantage of the extra tooling TypeScript provides inside of your Vue projects.Why use TypeScript?A recent study by Gao et al found that TypeScript/static typing tools reduced committed bugs by 15% (https://goo.gl/XUTPf4).
If you've used Angular before, this syntax should make you feel right at home, as we'll be using decorators and ES2015 classes. Let's investigate how we can add TypeScript to a project built with the Vue CLI:
# Create a new Vue project
vue init webpack-simple typescript-vue
# Change directory
cd typescript-vue
# Install dependencies
npm install
You should get the following output on the Terminal:
If we navigate to our project directory and runnpm installas per the instructions, we then need to install the TypeScript loader and edit our Webpack configuration. This allows us to load.tsfiles inside of the project, and because we've used thewebpack-simpletemplate, it's as simple as installing the loader and making a few changes. At the same time, we can also install TypeScript to the project:
# Install TypeScript and the TypeScript Loader
npm install typescript ts-loader --save-dev
We then need to make some changes to our Webpack configuration to add our new loader. Hot Module Replacement is enabled by default, so there is no need to refresh to see any changes once loaded.
Remember to run the project from the command line, and type
npm dev
.
We need to change our entry point to bemain.ts(and subsequently rename it), as well as define thets-loaderand remove thebabel-loaderin order to do it, and edit thewebpack.config.jsfile, pasting the following contents:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {}
}
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude:
ode_modules/,
options: {
appendTsSuffixTo: [/\.vue$/]
}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
extensions: ['.ts', '.js', '.vue'],
alias: {
vue$: 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
};
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map';
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
]);
}
After this, we can create atsconfig.jsoninside of our project root, which is responsible for appropriately configuring our TypeScript setup:
{
"compilerOptions": {
"lib": ["dom", "es5", "es2015"],
"module": "es2015",
"target": "es5",
"moduleResolution": "node",
"experimentalDecorators": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitReturns": true
},
"include": ["./src/**/*"]
}
TypeScript is now set up in our project, but to truly take advantage of this within our Vue applications we should also usevue-class-component. This allows us to take advantage of static typing on our component properties, as well as define components as native JavaScript classes:
# Install TypeScript helpers
npm install vue-class-component --save-dev
We can then define ourApp.vuefile by first specifying it as a script with thelang="ts"attribute. We can then import Vue as always, but as well as this, we're also importingComponentfromvue-class-componentto be used as a decorator within this file. This allows us to specify this as a new Vue component, and using the Component decorator we can define properties, templates, and more.
Inside of our Component decorator, we're specifying a template with an input box and button. This example allows us to see how we can bind to class properties, as well as call methods from our class. The following code should replace the code already in theApp.vuefile:
<
script lang="ts"
>
import Vue from 'vue';
import Component from 'vue-class-component';
@Component({
template: `
<
div
>
<
input type="text" v-model="name" /
>
<
button @click="sayHello(name)"
>
Say Hello!
<
/button
>
<
/div
>
`
})
export default class App extends Vue {
name: string = 'Paul';
sayHello(name: string): void {
alert(`Hello ${name}`)
}
}
<
/script
>
After running the preceding code, you should get something like this:
Lifecycle hooks
Lifecycle hooks such ascreated(),mounted(),destroyed(), and so on can be defined as functions within the class.
- created()
This allows for actions to be performed to a component before it is added into the DOM. Using this hook allows access to both data and events.
- mounted()
Mounted gives access to a component before it is rendered as well as after it has been rendered. It provides full access for interacting with the DOM and component.
- destroyed()
Everything that was attached to the component has been destroyed. It allows for cleanup of the component when it is removed from the DOM.
They'll be recognized and act the same way as expected without TypeScript. Here's an example when using thecreatedandmountedhooks:
// Omitted
export default class App extends Vue {
name: string = 'Paul';
created() {
console.log(`Created: Hello ${this.name}`)
}
mounted() {
console.log(`Mounted: Hello ${this.name}`);
}
}
Now if we head over to the console, we can see that the message of 'Hello' is outputted with the name of Paul:
Properties
We've seen how we can create classes and use the Component decorator; let's now take a look at how we can define "props" inside of our class using thevue-property-decorator:
# Install Vue Property Decorator
npm install vue-property-decorator --save-dev
This depends on thevue-class-component, so anytime we installvue-property-decoratoryou'll need to ensurevue-class-componentis also installed. Let's then define aComponentproperty using the@Propdecorator:
<
script lang="ts"
>
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
// Omitted
@Component({
})
export default class App extends Vue {
@Prop({ default: 'Paul Halliday' }) name: string;
}
<
/script
>
Notice how we're now importing theComponentfrom'vue-property-decorator'instead ofvue-class-component. This is because it exports this as a module for us to import. We're then defining a new component property with the key of name and thedefaultvalue of'Paul Halliday'; prior to using TypeScript, it would have looked as follows:
export default {
props: {
name: {
type: String,
default: 'Paul Halliday'
}
}
}
Computed
Computed properties allow for multiple expressions to be passed, and these properties on the Vue instance require the use of class getters/setters. So, if we wanted to retrieve a reversed version of our name, we could simply pass the following:
@Component({
template: `
<
div
>
<
input type="text" v-model="name" /
>
<
button @click="sayHello(name)"
>
Say Hello!
<
/button
>
<
p
>
{{nameReversed}}
<
/p
>
<
/div
>
`
})
export default class App extends Vue {
@Prop({ default: 'Paul Halliday' }) name: string;
// Computed values
get nameReversed() {
return this.name.split("").reverse().join("");
}
sayHello(name: string): void {
alert(`Hello ${name}`)
}
}
This would otherwise have been equivalent to:
export default {
computed: {
nameReversed() {
return this.name.split("").reverse().join("");
}
}
}
Other decorators can be used, such as@Watch,@Inject,@Model, and@Provide. Each decorator allows for a consistent implementation, and provides a similar API to the vanilla Vue instance. In the next section, we're going to look at how we can enhance the reactivity of our Vue applications with RxJS.
RxJS and Vue
If you come from an Angular background, you'll most likely feel right at home with_at least_the basic concepts of RxJS. This means we're usually dealing with things such as Observables, Subjects, and a variety of operators. If you haven't used them before, not to worry - we'll be investigating what RxJS is, and why we'd want to use it within Vue.
What is RxJS?
If we look at the RxJS documentation, we're greeted with the following definition:"ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences"(http://reactivex.io/intro.html). At first glance, this is not exactly a description that makes us feel comfortable using this within our projects.
RxJS assists us in using reactive programming principles inside of our application, often referred to as a more declarative style rather than imperative. When we talk about an imperative programming style, we're usually telling the computer the exact steps of how to do a particular task. A declarative style allows us to focus more on the expected outcome rather than the implementation.
In JavaScript, we can create aneventstream by using the following:
document.addEventListener('click', event =
>
{
console.log(event);
});
This then allows us to observe any mouse clicks on the document. We can capture things such as the click coordinates, target, event type, and so on. Evidently, this is an asynchronous observable data stream. We don't know when someone is going to click the screen, nor do we care. All we do is observe and perform a particular action when that event occurs.
We can use RxJS to take these same principles and apply it to our modern day applications where everything is a stream. We could have an observable data stream of everything from a Facebook feed to document click events, timers, anything! Everything can be a stream.
Integrating with Vue
To integrate RxJS with Vue, we'll need to make a new Vue project and install both RxJS and Vue-Rx. One of the great things about using the Vue-Rx plugin is that it's officially supported by the Vue.js team, which gives us confidence that it'll be supported in the long term.
Let's create a new project with the Vue CLI, and integrate RxJS:
# New Vue project
vue init webpack-simple vue-rxjs
# Change directory
cd vue-rxjs
# Install dependencies
npm install
# Install rxjs and vue-rx
npm install rxjs vue-rx
# Run project
npm run dev
We now need to tell Vue that we'd like to use theVueRxplugin. This can be done usingVue.use(), and is not specific to this implementation. Any time we're looking to add new plugins to our Vue instance(s), callingVue.use()makes an internal call to the plugin'sinstall()method, extending the global scope with the new functionality. The file to edit will be ourmain.jsfile, which is located atsrc/main.js. We'll be looking at plugins in more detail within later chapters of this book:
import Vue from "vue";
import App from "./App.vue";
import VueRx from "vue-rx";
import Rx from "rxjs";
// Use the VueRx plugin with the entire RxJS library
Vue.use(VueRx, Rx);
new Vue({
el: "#app",
render: h =
>
h(App)
});
Notice any issues with the preceding implementation? Well, in the interests of application performance and reducing bundle size, we should only import what we need. This then becomes:
import Vue from "vue";
import App from "./App.vue";
import VueRx from "vue-rx";
// Import only the necessary modules
import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";
// Use only Observable and Subject. Add more if needed.
Vue.use(VueRx, {
Observable,
Subject
});
new Vue({
el: "#app",
render: h =
>
h(App)
});
We can then create anObservabledata stream inside of our Vue application. Let's head over toApp.vue, and import the necessary modules from RxJS:
// Required to create an Observable stream
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
We can then create a newObservableof data; in this example, we'll be using a simple array of people:
// New Observable stream of string array values
const people$ = Observable.of(['Paul', 'Katie', 'Bob']);
This then allows us to subscribe to thisObservablefrom within the subscriptions object. If you've ever used Angular before, this allows us to access theObservable(and handles the necessary unsubscription) similar to the Async pipe:
export default {
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
/**
* Bind to Observable using the subscriptions object.
* Allows us to then access the values of people$ inside of our template.
* Similar to the Async pipe within Angular
**/
subscriptions: {
people$
}
}
As well as this, if we wanted to create a new instance of ourObservablefor each component, we can instead declare our subscriptions as a function:
subscriptions() {
const people$ = Observable.of(['Paul', 'Katie', 'Bob'])
return {
people$
}
}
Finally, we can then display the results of theObservableinside of our template. We can use thev-fordirective to iterate over the array and display the results on screen. How does this work? Using the following example, thev-forsyntax uses anitem in itemssyntax, which can be thought of aperson in people$in our context. This allows us to access each item inside of ourpeople$Observable (or any other array) with interpolation binding:
<
template
>
<
div id="app"
>
<
ul
>
<
li
v-for="(person,index) in people$":key="index"
>
{{person}}
<
/li
>
<
/ul
>
<
/div
>
<
/template
>
As you can see inside of the browser, our three items have now appeared on the screen inside of our list item:
Iterating over RxJS Observables
Summary
In this chapter, we looked at how we can take advantage of the Vue CLI to scaffold new Vue projects with appropriate bundling configurations and ES2015 support. We've seen that not only does this give us extra power, but it also saves us a significant amount of time in the long run. We don't have to remember how to create a Webpack or Babel configuration, as this is all handled for us by the starter templates; but even still, if we want to add extra configuration options, we can.
We then looked at how we can implement TypeScript with Webpack and thets-loader, as well as taking advantage of common TypeScript and Vue patterns with the property decorator(s). This allows us to take advantage of core tooling and help reduce bugs in our code.
Finally, we also implemented RxJS and Vue-Rx in our application to take advantage of the Observable pattern. If you're interested in using RxJS inside of your projects, this is a good starting point for future integrations.
In the next chapter, we're going totake a deeper look at the Vue.js instance and the different reserved properties, such asdata, computed, ND watch,as well as creating getters and setters. This chapter especially considers whenyou should usecomputedto use or watch properties.
In this section, we'll be investigating how a Vue.js instance works at a lower level by looking at how this is handled by Vue. We'll also be looking at the various properties on our instance such as data, computed, watch, as well as best practices when using each one. Furthermore, we'll be looking at the various lifecycle hooks available within our Vue instance, allowing us to call particular functions at various stages of our application. Finally, we'll be investigating theDocument Object Model(DOM) and why Vue implements a Virtual DOM for enhanced performance.
By the end of this chapter you will:
- Have a greater understanding of how this keyword works within JavaScript
- Understand how Vue proxies this keyword within Vue instances
- Use data properties to create reactive bindings
- Use computed properties to create declarative functions based on our data model
- Use watched properties to access asynchronous data and build upon the foundations of computed properties
- Use lifecycle hooks to activate functionality at particular stages of the Vue lifecycle
- Investigate the DOM and Virtual DOM for an understanding of how Vue renders data to the screen
To begin, let's start off by looking into how this works within JavaScript and how this relates to the context within our Vue instances.