What do you want to learn?
Skip to main content
Single Page Applications with Vue.js
by Bill Stavroulakis
Resume CourseBookmarkAdd to Channel
Table of contents
Environment Setup - Build Process
Before we set up the structure and environment for our application, let us go through the end result and review some resources that you can find useful while watching this course. Our application is a page deployed on Azure that shares code links and resources for front-end development and mobile web development. Once the user loads the site, we immediately show the full, rendered page. If I view the source of the page, the end result is rendered so our site is SEO-friendly, but from thereafter, when the user clicks on the link, we update only the section needed. This is a single page application with server-side rendering. Also, there is a login page where we can log in and log out. You can visit the GitHub page that contains the full code of the course and the build status of the project. For every module, you can run the command git checkout -m where this section will contain the module name. This will set our codes to the state of the project that we're interested in so you can code along easily. Lastly, you can check out the exercise files that will contain the start and end state of the application in each module.
As a web developer, I'm pretty sure you're already comfortable with your own setup. I will go over the environment and tools that we'll be using for this course so you can mix and match. First of all, as an operating system, I'm using Windows 10. Of course, everything we go over works on a Mac or Linux operating system, as well. My editor for this course will be Visual Studio Code, as its minimalistic UI and speed is perfect for us to focus on the code and not the editor. You can download Visual Studio Code from the following link. The terminal will be Git Bash to run UNIX-like commands from Windows. You can use PowerShell, the bash for Windows, or the Terminal app on the Mac and Linux systems. You can download Git Bash from the following link. Lastly, we'll need Node.js to run scripts during the development and during the runtime of our application. You can download and install Node.js from the following link. For a more thorough view of the setup, you can view the Environment Setup module in my course, Working with Polymer.js Elements.
Now that we're all set up, I'll open Git Bash and navigate to a folder that I want to create my project in. I can go to the C directory and create a folder named vue-spa. We'll navigate in that folder and enter code . Enter to open our folder in Visual Studio Code. In this folder, we'll create our first three files. A LICENSE file to include any licensing information about our app, we can use the MIT License for now, meaning that you can reuse the code in any way. The README.md file, responsible for information concerning our project. I will keep this empty for now, but you can of course include your own information. This will be very handy once we upload our project on GitHub to inform everybody about the details of our project and setup process. And of course, every web project needs an index.html file. Since our project is a single page application, this can be our only HTML file. To get started, I'll include some boilerplate HTML with the head and body tag with some various metainformation.
The editorconfig file is a great place to set configurations for your editor. This way, you can keep a consistent style in your code within files and in your team. I will create a file named .editorconfig and include the following rules. This configuration entered basically means that globally our character set is set to utf-8. The indent style is a double space. The end of the line will be a line feed, which is just a special character to signify the end of a line. Make sure that the file ends with a new line and that we want to remove any white spaces preceding new line characters. Lastly, make sure that your editor supports EditorConfig. If you visit editorconfig.org, you can preview the editors that support it out of the box, and the ones that need a plugin. Visual Studio Code needs a plugin that we can install by pressing Ctrl+P, entering the following command, and clicking Install in the plugin area. Once the editor reloads, we now have the settings, making all of the changes in the background while we write our code, to keep everything consistent.
The next step while creating a single page application is to find a way to load libraries and modules centrally. There are three very popular ways to do this, bower.js, jspm, and npm. The most popular nowadays is npm, and I will use this option, as well. Since you've installed Node.js, you already installed npm, as well. Npm is a package manager installed with Node that will help us install and run scripts. To get started, we can just type in npm init in Git Bash after we navigate to our project folder. This will trigger an installation wizard where we can select various settings. I'll keep all of the default settings. Let me remove the main key, as we won't need it, and change the license to MIT. Once this is done, we have the package.json file created. This is the file that will store all of the libraries and dependencies of our application. There are two areas, the devDependencies and the dependencies. The devDependencies are all of the modules that we'll need while in development, and in the dependencies, we can include all the modules necessary for development and production. We are creating an application with Vue.js, so for starters, let us include Vue.js. If we visit vuejs.org and select the Guides and Installation section, we can see various ways to include Vue.js in our project. The first way is to just include Vue.js as a script in our application. So you could just include a script tag in your app, and as the source, enter the vue CDN path. But, will chose the second and recommended way, which is through npm. As per the site instructions, I'll type in my terminal npm install vue, at the specific version of view that I'll be using, but include the --save attribute at the end. If we press Enter, this will create a node_modules folder and include the vue.js library in there. Also, because I included the --save attribute, it has also included the Vue library reference in my dependencies. Every time somebody downloads our application for the first time, the first action that they will do is to run npm install. This will find all of the dependencies and download them in the node_modules folder.
We have set our package management system, but now want to see our index.html page or something on our browser already. To do this, we'll need a web server. Again, there are many options out there like http-server, live-server, and Express.js. We can select Express.js, as we'll use it for development, but in production, as well. We can include this easily by running npm install express@, the version specified, --save. Then I'll create a new file named server.js. In this file, we'll set up and run the Express web server. First, we will include the Express module and we will instantiate the Express server in our app constant. Then we will listen to a port. Let us randomly choose port 3000. If that port is already taken on your PC, you can choose any port you desire. Once we're listening to our port, as a callback, we'll display a message that the server is running on the port specified. If you haven't seen the const value and this syntax before, don't be alarmed. The const is an ES6 feature, so we can declare a variable that won't change, and this is an arrow function. You can look them up. These are similar to the old functions, but more concise and with scoping differences. We can run these without a problem now because we're using the latest Node.js installation that supports the ES6 syntax. Lastly, I will create an event handler for all of my get requests. To do this, I will write app.get and an asterisk. If we wanted to listen to a specific path, we would include the specific path of this area. But since this is a single page application, everything will return on our main page. As a callback, Express.js passes two parameters with the request and response. The request has all of the data of the request that the user has submitted, and the response will be a response that we will send back. I will respond now with a string with the value hey there. Let it shoot up the server and see what happens. In my console, I will run node server, and this will run the server.js file with Node.js. If we visit localhost:3000, we can see the response. Have in mind that any path we enter, we will return the string hey there since we're handling all of the requests with the asterisk wildcard.
Web Server - Index.html
Webpack - Setup
Webpack - Entry
Back in my webpack.base.config file, I'll include the path module. Also, I'll create a config JSON in which I'll include all of the settings needed for webpack. Now webpack modules start with an entry point. This will be our main app file that we will then include other modules, as well. Webpack will generate all of the polyfill code necessary and export a file or files for our modules in a distribution folder that we can use in our browser. We will understand this in more depth after this example. I will create a folder named src and create two files, one named client-entry.js, and another named app.js. In the app.js file, I will just console.log loaded, and in the client- entry.js, I'll include the code from app. So if this part is loaded in our page, we should see the loaded string in the browser's console log. This will be the entry point of our application, so I will declare that in the configuration, as well, where the entry file for now will have the key app, and the value will have the path of the client-entry.js file. Of course, we'll have to set the output path of the generated polyfilled files. I will include the output parameter with the path pointing to the folder named dist. Usually in web applications, we include the end result of our page in the dist folder, which stands for distribution. So all of these files here are required for development, and the dist folder is what we eventually will deploy. The dist folder will be referenced in our site through the root path, and lastly, the file name will be assets/js/name.js. This means that our client- entry.js file will be generated in our dist/assets/js folder with the name app and the extension of js. Let me run webpack to see what will happen. In the terminal, I'll run node, include the path of webpack, and as a config variable, add the client config file. Once we've run this, we can see that the dist folder was created with the app.js file generated. This includes the polyfill code and the modules we included. In our example, we included the app.js module from the client entry module, so we should see console log code somewhere. There it is. So for a small recap, we created the client-entry.js module, included the app.js code, and ran webpack that generated the app.js file in the dist folder that essentially polyfills, compacts, and adds sugar to the functionality of the src folder.
Webpack - Vue
Our simple build process is in place, but it isn't efficient enough for development. It would be terribly inconvenient for us to make a change, stop the local server, run the webpack script, and start the local server again. I will install two modules, the webpack-dev-middleware module and the webpack-hot-middleware module. With the wepack-dev-middleware, our distribution files aren't created on disk, but in memory, and every time we make a change, it will rebuild the module that was updated. Also, with the webpack-hot-middleware, every time we make an update, it will reload that section that changed in our browser. Then we'll create a file named dev-server.js, and include webpack and the webpack configuration. We will export a function that will receive the Express app. We will instantiate webpack with our configuration and extend the Express server with these two new modules. Afterwards, in my server.js file, I will require the dev-server module and pass in the app server reference. This will extend the server with two new modules we created in the setup dev server method. I will delete the folder, dist, so we can make sure we're loading the files from memory, and run npm start to run server.js and boot up my server. If I load localhost:3000 now, Vue.js isn't loading. Let me see why. If I view the page's source and click on source, the server can't find that resource. In my log, you'll notice that the asset was generated at the path assets/js/app.js, and not dist/assets/js/app.js. So I will stop the server by clicking on the terminal and pressing Ctrl+C. In the index.html page, I will remove the dist part from our resource and boot up the server again. If we refresh the page, we can see that everything is loaded as expected. Now, if I change something in my module, for example, the string, webpack-dev-middleware will notice a change and re-run the build process. We can refresh and see the change immediately. However, with the hot-middleware module, we would expect this section to load automatically without a refresh. Let us examine why this isn't happening in the next video.
To keep things organized for the rest of the course, I will create a new file named webpack.client.config and include the configuration of the base config. Then I will extend the base config object by using the object.assign method and include the property named plugins. In this property, I will add the base.plugins, and if no array exists in our base configuration as we have now, I will create a new array. Lastly, I will export the extended config. We basically just loaded the base config and added a new empty array for now with the name of plugins. Then in my dev-server, I will use the webpack.client.config. We have the dev-middleware service running, but we noticed in the previous video, the hot-middleware module isn't working. There are a series of steps that we have forgotten here to make this work. One, I will extend the config file and add a new entry point. This will be named webpack-hot- middleware/client into the entry area. This script added will connect to the server to receive notifications when the bundler rebuilds, and then update the client bundle accordingly. Two, I will push two new plugins that are needed for the hot- middleware to work. Let me boot up the server to see what's going on. I'll reload the page and open my dev tools. Once I update the app.js module, we can notice that the hot-middleware embedded a script. If we open the script, we can see that the hot-middleware injected the change to automatically update our page. But instead of updating the section necessary, we got a message, The following modules could not be reloaded. To fix this, we'll open the client-entry module and include the command module.hot.accept. This basically will tell the hot module to stop the hot reloading propagation and flag that everything is loaded correctly. But if I reload my page and change the hello command, nothing happens again, although the warning has been removed now. This happens because although the hot module event was handled, the command received. This unsuccessfully re-rendered the template. Vue.js receives the new update, but just updating the app doesn't rerender the template. We'll have to include the template in our Vue instantiation. So I'll copy the template and include this in the Vue instantiation through the template property so it can know how to render the section that it is mounted on. Once I refresh my page and change the hello parameter, there we go. The hot-middleware through the hot module replacement plugin injected the changed, and Vue.js knew how to render it. Isn't this amazing? Now we can just write code in our editor, and this will automatically refresh the changes on the browser, building and reloading only the area that was changed. I haven't seen a better experience for programmers than this.
In this module, we covered quite some ground. We set up our environment, the project, various configuration files such as the package.json, the editorconfig, and the eslint, built a simple build process with webpack for our modules, and included Vue.js in our website as a module. And lastly, set up a web server through Node.js. The skeleton is done, so it's time to begin working on the features of our single page application.
Single File Components
I will create an app component the good old way with the Vue.component declaration. We can declare a component through the Vue.component method. The name of the component will be app, and as a template I will include the div with the id of app for our initial binding. Our web app will have a navigation section with a link of home. The classes that I'll be using are linked with the CSS library that we'll include later on. Then it will attach a main section with a container that will contain my site's page data. Lastly, we'll include a footer section with a Twitter link. You can of course copy this code snippet from the following gist path. Lastly, I will remove the data section from my Vue instance and will add the render property. This property is a Vue.js function that will return an element. The element we want to return is the app element. This render function would be similar if we added the template app app into our configuration. Now on the first load, the client entry will mount the Vue instance to the div with the id of app, and we'll create an app element. This app element was configured with our template of its own, so it will be replaced with the app template configuration. Once we save our app.js file, I can boot up my server, and in our web page's DOM structure, we can see that the app element has been replaced with the content of the app component. Also, whenever I make a change in my component, this change is automatically updated with hot reloading.
If I console log my app layout, you can see that it has a render property. I would like to extend the main Vue instance with the AppLayout's render function instead of running the render function and passing in the app layout component declaration. We can do this with the new spread syntax. With the following command, the properties of the AppLayout will spread in the JSON that declares the Vue instance. Let me boot up the server and check the console log. In our page, I can see an error, you need an appropriate loader to load this file type. It is true that some ECMAScript features aren't implemented on all browsers, and some aren't finalized yet as they are a work in progress. For example, some browsers would have a problem understanding the arrow functions. This is why we will include Babel in our project. That will work as an ECMAScript feature polyfill and create all the necessary wrappers for our modern codes to work browser-wide. To install Babel, I will include the babel-loader check in my .js files. Notice that I haven't included a check for my .vue files. This is okay because this rule will apply after the .vue files are transpiled to .js files through the Vue loader. Of course, we will install the Babel loader and all of the dependencies as well. You can use the following path to copy this code. We will also include in our linting configuration the babel-eslint parser so that the linting rules accept the new rules as well. Lastly, to configure Babel, we will create a file named .babelrc and paste in the following rules. These basically state that we want to use the ES2015 or ES6 rules without the module rules since webpack is handling that for us, and use the stage-2 ECMAScript features as well. There are some specs that aren't completely defined yet, but are in the stage 2 status. The spread syntax, for example, is in stage 2. If you're viewing this course a year from now, this may not be needed. Also, we will ignore, of course, the node_modules folder that contains all of the third-party node packages and not our custom code. If I respawn my server now, everything is working great and we have made our code more universally compatible with older browsers and devices.
Our basic layout, although functional, doesn't look that good. Time to add some styles. This is where you would either create your own theme or go looking around the web for a UI library of your liking. As always, there are numerous choices and scenarios for this layer, like Bootstrap. But for our demo, we'll use a small UI library named Bulma. I will install Bulma through the npm package manager, then in our Layout.vue component, I will include the style tag. This style tag accepts also a lang attribute that will have as a value the method that we want to preprocess our CSS with. We can use Sass or Stylus. The same applies for the script and template tags. For example, we could add lang=coffee to preprocess the script tag with CoffeeScript or lang=pug in the template to preprocess with Pug. We won't need this in the other tags; however, I would like to preprocess the styles with Sass, so I'll keep the lang=scss attribute. In our page, we received the error, Can't resolve sass-loader. This means that we'll need a loader in our webpack configuration to parse the styles. In my .vue file loader, I'll include the necessary options to parse my styles. For a style tag that didn't need a preprocessor, we would use the css-loader module, and for a style using the scss language, we would pipe the processing through two loaders, the CSS loader and the Sass loader. I will install the necessary modules through the npm package manager and reboot the server. And we receive another error, can't find node-sass. Looks like we need this library, as it is dependent on the Sass loader. I will install this library and reboot my server. It looks like the Vue loader can now successfully handle the style tags. I will import the Bulma library with the Sass import method. And now Bulma is running on our page. Very cool. Now we can use Sass and the style tags in our single file components.
Our single page application is a blog with a curated list of the latest front-end resources. It will consume, eventually, a list of articles and display them. We will receive these articles from a REST service, but for now, we'll add them locally. In our Vue instance, I'll include a data property. This will be a function and will return a JSON with the posts array. We can just paste some hardcoded posts. You can find these from this gist path so you can follow along. In the template area, we have access to the data of the script instance. I will create a div with a columns class and another div that will be the column and use the v-for directive. I'll loop through them, and for each one, we can create a card. The card content will have a header with the items title, then the content. Lastly, we'll include a card footer with the resource link that we would like to share. This link will have a footer item class, a target of blank to open to a new window, and we can bind the href attribute with the items link. Once we visit our page again, we can see that we bound the data with the template. The style looks a bit off. We can fix this by adding an additional rule for our columns so we can wrap the flex items. Also, let me set the default color of my website to a shade of blue instead of green. I'll set a variable named primary that Bulma uses. If we visit our page again, everything looks as expected. We have our curated list appearing on our website.
The layout is getting a bit bulky, so we can create shelf components to either break up the code or reuse parts of it. To break up the code, I'll create two new components, one named AppHeader.vue, and another AppFooter.vue. In both of these, I'll include the template tags. In the header element, we'll move over the head of our application and in the footer, the footer. Now, in my layout component, I'll include the header and footer components and reference them with their corresponding names. Then we can declare custom elements with these component declarations. For a header, I'll include the tag AppHeader, and the footer AppFooter. I can include them in the template now with the app-header and app-footer tags. If we refresh, we can see the same result as before, but now we're loading the header and footer as separate child components. Another useful component could be a wrapper for my posts. I'll create a component named Category.vue, and include the template and script tag. I'll move the post section in my category and create a Vue instance. Also, I'll move over the posts. Lastly, I'll import my component, add an element declaration with my component declaration, and include that in my layout component. Once we refresh our page, we can see the same result, but we've broken down our project by using child components.
Although we can break up our code and create static child components, it would be helpful to be able to create child components and pass in dynamic data in our components. Our first way to include data dynamically is through custom properties. I will create a component named Post, and include the template and script tag. In the script tag, we'll create a Vue instance, and add the reserve property named props. For now, we will add the post property. This is an array that binds the property of the component with the attribute of the element. In our example, it is named post. In the category component, I'll include the Post component and declare our component as app-post. Now I'll move over the card snippet in my Post component, and for each post in our loop we will instantiate a post element. If I refresh my page, nothing happens. Let us see why. Yes, although we instantiate the app component, we don't pass in the post data in the post property. To do so, we'll bind the custom post element with the post JSON data, so this will pass in the post in our component through the post property, and then display the title and content. Once I save the category, we can see everything is appearing on our page.
Passing in data through properties is good enough in many cases, but sometimes we would like to have more control over the template of our child component. We have the possibility to do this with the use of slots. I will remove the post title from the post component, and include it in the content area of my component declaration. Once I save both files, I will notice that the header is gone. This is because Vue parsed this area, but doesn't know where to add it. We can specify this with the reserved slot element. Once I include the slot element in my post and save, we see that Vue.js included the content of the element in the slot area. However, I would like to remove the post content as well. This is where we'll have to bind multiple content areas with multiple slots. In the post component, I'll give the slot element an attribute of name and a title of title. In our project, the title vanished, but if I offer the h3 tag an attribute of slot and the value of title, Vue will know now which area is connected with which slot. I can do something similar with the content. I will name the slot content and add a span with the slot of content and the post content. Lastly, I will keep the link as a property, so I will rename the property to link, bind the href with their link property, and in my category, pass only the link of the post with the link attribute. Now we've broken down our element, but have a bit more control over the different areas. For example, we may want to reuse the app post in another component, but this time include the title as an h2 tag.
While I was creating this course, I showed it to a designer friend of mine and he was very aggravated because the cards didn't have the same height in each row. To calm his creative eyes, I would like to add some CSS rules to fix this. A first step would be to dive in the post component and include the appropriate styles. For the card, I will set the height to 100% and some bottom padding to set the footer to the bottom part of my card. You can pause the video and write down the styles now or copy this snippet from the following gist path. I will refresh my page, and the cards seem okay, but the page footer is all broken. Let us investigate. I will inspect my page and go to the header. When we add a style tag in a component, Vue.js takes the tag and includes it in the head of our page. This is okay if we want to include our styles globally. In our case, the footer rule was applied in our card element, but we have a footer element in our layout that was affected as well. Fortunately, we can add the scoped attribute in our style element. If I refresh, the footer of our element was affected only, and not the footer of our layout. Let us investigate. Now, in my styles, Vue.js wrapped the rules with an attribute that was included in our component as well. It generated a unique attribute for our component and scoped the styling only for that component type. So now when you add a style in your component, you know how to scope the rules locally or globally.
In this module, we introduced Vue components through the .vue files, added ES6 transpiling in our build process, went over the template binding, child components, custom properties, slots, and styles. This way, we cleaned up the structure of our application, made it more simple, modular, and scoped. Let us see how can we add more pages through routing in the next module.
An important piece of the puzzle while creating single page applications is routing. How can we load different pages through different routes and load only the resources that are necessary per page? We will tackle this problem in this module.
Vue.js has an official plugin responsible for routing. It is called vue-router, and we can easily include it in our project. To install this script through npm, I will run npm install vue- router@, the version specified, --save. As mentioned, vue-router is a plugin. Vue plugins are scripts with which we can extend Vue with extra functionality globally. I will first create a file named router.js. In this file, I will include Vue and the VueRouter module. To connect the plugin with the Vue configuration, I will run Vue.use VueRouter. We are done. This way, the vue-router functionality is plugged into Vue. To configure this, we'll create a new router constant and create a VueRouter instance. The VueRouter instance accepts a JSON for its options, and one option is an array named routes. In the routes array, we can link routes with components. For my default path, I will load the category component. To do this, we'll include the component and use the reference in the component property. In our app.js, we see that we mapped the layout to the app, so every time a page is loaded, the Layout.vue component is loaded. I will first export the VueRouter instance from the router.js module and import it in our app.js module, extend the Vue instance with the router plugin instance, and export that as well as it may be needed in the client entry later on. In the Layout.vue component, we can remove the category component from the imports, the component section, and from the template area. Lastly, to load the category component dynamically in the area we're used to, we will use the reserved element router-view. Every time we load the page, the router will find the current path, load the component linked to the path, and add its template in the router-view section. Once I refresh my page, everything is working as expected. If I load another page, for example, /about, the layout is loaded, but no router was set for this path, so nothing was loaded in the router-view section.
Router-link - Scroll-behavior
We noticed previously that if we use the a tag and click on the logo, this will reload the page completely. Since this is a single page application, we would like to have the opportunity when loading a new page, to load only the component that is linked to the according path in the router view area. This can be done through the router link reserved element. So instead of using the a element, I will use the router-link tag. And instead of the href attribute, we'll use the to attribute. Also, I'll include two new text links, one for the home, and another for the login page. Now every time I click on a link, the whole page won't load, but only the area with the router view with the relating component. If I view the source, whenever I select the link, our router plugin will add a class named router-link-exact- active for the active routes. This is okay; however, in the Bulma UI library, an active link is set with the is-active class. To change the active link class, I can set the attribute linkActiveClass in my router instance to the is-active one. Once I refresh, the Home link is selected. When I click on the Login link, it adds the is-active class on that link, and when I click on the Home link, it removes it. The is-active class works as expected with the Login link, but not with the Home link. It is always selected. This is because the active class matching is inclusive. It is looking for a link that starts with the to attribute value. This is because if we have children, for example, /category/front-end, it always has the parent selected as well. But this creates an issue for the home root path. We can add another attribute named exact, and now to add the active class, the to path has to be an exact match. If I refresh, the Home and Login links work as expected. Lastly, I would like to mention another property that comes in handy, which is the scroll behavior. This is basically a method that accepts the path we navigate to, the path we are coming from, and the scroll position that the page was in. I can set the y position to 0, so every time we load a new path, the page just scrolls to the top. We could also return the savedPosition to scroll to the last position saved. Or if the link has a hash parameter, we could return with the hash selector to scroll to the element with an id of the hash value.
I would like the category component to load with a category path, and not with the root path. The category of my blog currently showing is a group of front-end links, so I'll change the path to /category/front-end. Now if I visit /category/front-end, the site loads, but there is an issue with my CSS. In my index.html, we'll have to include a slash to load the asset from the root path. I'll restart my server, and yes, we can load the category component from the following path. Also, when the user visits the root link, we would like to redirect him to the front-end category link. To do this, I will create a new rule, so when we visit the root path, we won't load a component, but redirect to another path with the redirect parameter. In my app header, I will rename the Home link to /category/front-end. Now if we visit the root path, we will be redirected to the front-end category page.
Our site doesn't only aggregate links for front-end news, but also news from the mobile web world. I'll create another link that will load the category, but with the sub-root of mobile for my mobile aggregated links. These two links look very similar. It would be nice if we could load them in a more dynamic way. We can do this with the route parameters. In our route configuration, instead of hardcoding the category name, we can add a parameter. To add the parameter, we can add the parameter name after a colon symbol. I will name my parameter id. Also, I'll delete the duplicate route, and in my AppHeader, rename the Home link to Front-end, and create a new link with the category id of mobile, and name it mobile. Now once I refresh, we notice that when I click either on the Front- end category link or the Mobile category link, both load the same component. Also, the Login link works just like before. It would be nice to have a way inside my category component to know about the id and load the corresponding links. I will include another parameter named id and return the this.route.params.id value. Vue.js exposes the router through the $route object, so we can either run methods, such as push, to navigate programmatically to another page, or obtain data from the router, such as parameters. I will break my posts into two arrays, one named postsFrontEnd, and another postsMobile. I will copy the three first posts, which are front-end links in the postsFrontEnd array, and the remaining three posts, which are mobile links, into the postsMobile array. We can keep the posts array as an empty array for now. I will create a new method in my methods section named loadPosts. When we run this, we'll check if the id is set to front-end. If so, we'll set the posts to the front-end links. If it is set to mobile, we'll set the posts to the mobile links. Let us run this method every time our component is created. Oops, let me correct a small linting error here. When I refresh, the Front-end links were loaded correctly, but when I click on the Mobile link, the same link appears. This occurs because the component isn't reinitialized, but only replaced when the router changes. In our case, the category component is initialized, and when the route changes, Vue.js adds the same category component in the router view area. But the created method isn't triggered since it runs only when the component is created. Lastly, I will create a watcher that will watch for any changes in my route object and receive the current and previous state of the object. We get the new id from the current route state and run loadPosts again. When I reload my page now, the loadPosts is triggered from the created callback, and every time we move back and forth, the watcher updates the id and runs the loadPosts again, updating my data.
Route Query and Name
We don't only use parameters usually, but also use path queries to navigate in that state. In the current application, I would like to include a query for pagination, so if I include a query of page, we can get the page number of our category that we are viewing. You don't need any additional configuration to do this. But by just including a query of page on the route, we can have access to that through the router.query.page parameter. I will console log this and load my page again with the page set to 2. In my console log, we can see that we can easily reference the query parameters through the query object. Also, we can remove the exact attributes from the app header so these links can be activated even when we have query parameters. To organize our routes and have a more semantic access to them, we can name them. Let me add a name parameter in my categories path and name it category. Also, if I open the AppHeader component, we can reference the mobile category page by passing in a JSON object with the name parameter of category. And we can also pass in the parameter of id in the params property. Now if I click on Mobile link, something went wrong. It didn't load the link, but it added the JSON as the link. To fix this, we'll add the colon sign in front of the to attribute so Vue.js can parse the section and not just add the value as a string. If I click on my link now, it will parse the JSON and load the correct path.
We have set rules for the pages that we're interested in; however, what happens with the pages that we haven't handled yet? For example, how could we create a 404 page for all of the paths that we're not aware of? For this, we can use the wildcard path and include the NotFound component. You may say that the NotFound component is not found yet, pun intended, because we haven't created it yet. That is true. So I'll import the NotFound component and create it. I will just include a div with a message, Oops, page not found! Now if we visit the page that doesn't have a specific rule, the wildcard path will handle this and load the NotFound component. So if I load, for example, asdf, the not found page loads.
Our routes are working as intended. As engineers, though, there is a saying. If it is working, you don't have enough features. If we check our debugger, you will notice that our whole app is loaded as a monolithic app.js file. This app.js file loads the category Login and NotFound component, which is then instantiated when the path is loaded. However, we don't need to load all of the modules. When we load the category page, for example, we only need the category component. To load it only when the route is visited, we can use lazy loading. So instead of using the import method, we can define our components as async components. We'll do this for the Login and NotFound components as well. Once I save my file, in the assets folder, we will export the app.js file, but we will create different, separate chunks for the category Login and NotFound modules. When I refresh my page, in the network, we will load the app.js file, which contains the basic components declared in the Layout component. Then the client, depending on the page, will load only the appropriate component needed for the page specified. When we load the login page, Vue.js will request the related component on the fly and load the corresponding one. This is very useful when our application increases in size. We can break our components in chunks and load on the fly only when needed. I will revert this for now, but it is nice to have this technique in mind while using client-side rendering.
In this module, we didn't only include routing in our application, but went over some advanced techniques such as scroll behavior and lazy loading. Also, we saw how to create redirects and wildcard rule paths. Now we can load different components on different paths. Our application isn't useful yet as we're using static data. Let us move fresh data back and forth through a RESTful API in the next module.
The shell of our application is all set; however; if we don't communicate with an API, our client application is destined to oblivion. In this module, we'll send data back and forth to our back-end server and see how can we handle requests and responses.
Receiving Data - Setup
How can we receive data in our app? It would be nice if we could get the latest cool links of front-end news and mobile web news from our API. I have a WordPress site hosted at api.fullstackweekly.com. WordPress exposes a RESTful API by default. I already consume this API on my website fullstackweekly.com, and in the section newsletter, I share interesting links on events, mobile web, front-end development, and more. We will use this same API and data in our app to create something similar. If I run the following link, I will retrieve the posts of the category with the id of 2, which, in my case, are the front-end links. If you have a WordPress site, you can use a similar request to consume a category in your client apps. You'll have to enable CORS to access your site from your localhost. I've found this Stack Exchange link very helpful to enable CORS. Also, I have found the REST API enabler plugin very useful to expose special fields. In my case, the link is an extra field I've included that contains the third-party link we would like to share. In my WordPress site, I've added the link as a special field to attach links to my posts. So how can I make a request, collect the latest six posts, and show them in my app? A standard method is to use the native XMLHttpRequest API, though developers these days prefer to use libraries to extract this API for improved browser compatibility, and take advantage of some modern asynchronous APIs such as promises. Let us connect our app with this API in the next video.
We will use a library named Axios. To include this in our project, we can open the terminal and run npm install --save axios@, the version specified. Usually it is useful to create a service layer in our app. Let us create a new file and name this app.service.js. Then we can open our file and import Axios. We will set the base URL of our requests to the WordPress API path. In my case, this is api.fullstackweekly.com. Then create a constant JSON, name it appService, and include a method named getPosts. We will also export this app service object eventually. The getPosts will receive the category ID, and we will make a getRequests with the Axios library. As a path, we'll call the /posts and attach the category ID that was passed as a parameter. This is a promise, so once it is resolved we will receive the response. We will wrap the whole request in my own promise, and resolve my promise with the data received once the request is done. If you need a small refresh on promises, you can either watch this segment in the Rapid ES6 course, or the segment Promises in my course Getting Started with Progressive Web Apps. In my category component now, I'll remove the static data, import our appService, and in our loadPosts method, we can call the getPosts method of the service and pass in each time the corresponding ID. Our standard categoryId will be 2, which are the front-end links, and if the category is set to mobile, we'll change that to 11, which is the mobile web category in my WordPress site. Our page doesn't look quite right. This is because we'll need to bind the JSON to correspond to the fields received. So the title is title.rendered, excerpt.rendered, and rest_api_enabler.Link. Lastly, for the title and content, Vue.js sanitizes the HTML before rendering them, which makes sense. To output the HTML as is without sanitizing and encoding it, we'll have to use the v-html directive. So instead of using curly braces, I'll use the feed-html directive in the title and contents section. The data looks correct, and every time we select a category, it updates accordingly.
In my login component, I will include the script area, import the app service, instantiate the Vue.js instance, and as data, return the username and password. You should be comfortable by now with this standard, single file component structure. We'll bind the username and password from the data with the value of the corresponding input tags. We can do this through the v-model directive. So for the username, I'll bind the username parameter, and for the password input, I'll bind the password parameter. Every time we change the input values, the corresponding parameters in the Vue.js data will update. Then when the user clicks on the Login button, we will run the component's login method. This can be done with the v-on directive and through the click parameter. I'll add the login method in my methods section, and trigger the appService.login Promise, passing in the credentials objects. If this is successful, we will receive the token from our API call and save it in our local storage. To save it in our local storage, of course, we can use the localStorage API. If it fails, we will alert with a message Could not login! I will also set the expiration date in my local storage. This isn't necessary since we have that information in the token, but we'll do this for simplicity to avoid decoding the token. If I enter as the username bill and as the password vuejs, and click on Login, we can preview in our developer tools that we have saved the token in our local storage. If I enter incorrect credentials, for example, bill2 as the username, then we receive the alert Could not login!
To make things more interesting, I will include another property named isAuthenticated and set it to false. Once the component loads, we will first get the expiration date of the token, then the current unixTimestamp, and check if the token exists and if the expiration date is greater than today. If this check passes, it means that the expiration date is in the future, and the token hasn't expired yet. We can set the isAuthenticated to true in this case. In our component now, if we've authenticated, we will show Hello authenticated user! If not, we will show our Login form. We can preview the Hello authenticated user! as we logged in already in the previous video and saved the token in our local storage. Lastly, I'll include a Logout button that will trigger the logout method. I'll go right ahead and create the logout method that will delete my tokens and set isAuthenticated to false. Also, let us not forget to set the isAuthenticated to true once the user logs in, and set the username and the password to an empty string, so once we want to log in again to see an empty form. In my page now, I can see the Logout button, and once I click on it, we're logged out. This is a very simple but solid way to add authentication to your website. Have in mind, though, that we can make it a bit more secure. We can add CAPTCHA to stop bots trying to figure out the user's password, and include a cross-site request forgery token. We won't add these; however, it is always interesting to find and think of ideas to make this process more secure.
In this module, we connected our web client app with an external API. We receive fresh and updated data, and have the ability to log in, log out, and preview the user's data with JWT tokens.
As our application grows larger and more complex, we quickly realize that sharing state between our components and throughout our app becomes a beast of its own. We are already using some state management techniques such as creating a post component and injecting the state from the category component. However, what will happen if we want to share state not directly from parent to child, but from a child to another child or from a child to another element anywhere else in the DOM tree? However, how can we make sure that the object that we are sharing is updated correctly and immediately every time it changes from one component to all the others? We will tackle this interesting issue in this module.
Communication Through Events
A first idea that comes to mind is to use events to share state between components. When the user is logged in, I want to show Logout in my header links. And when we're logged out, I would like to show Login. Until now, we've set the isAuthenticated parameter in our login component. How can we share this with the AppHeader component? Let us use events. We will create a new file in our src folder and name it event-bus. This will be a shared view instance that we can trigger and handle events from. So I'll import the Vue library, create a new instance, and export it. I will then import the eventBus instance in my login.vue file with the import command. In my watcher area now, every time the isAuthenticated variable changes, we will trigger an event with the eventBus. We can do this with the emit method and trigger an event called authStatusUpdate, and pass in the updated value. Now in my appHeader, I'll include a script tag, create a new instance of the component, and export it, and lastly, import the eventBus. In the components data area, we'll return a new parameter named isAuthenticated with the default value set to false. After the component is created in the created callback, we'll include an event handler for the authStatusUpdate event. We can easily listen to on event triggered in the eventBus instance with the on method. We'll listen to the authStatusUpdate, as mentioned, and change the isAuthenticated state with the one received. Lastly, in the Login link, I'll create an if statement, and if the user is logged in, we will show the message of Logout. And if he is logged out, we will show the message of Login. I'll boot up my application and load the login page. Now when I log in, the event is triggered, informing my AppHeader about the state, and when we log out, the event is triggered again. There are some issues, though, when we want to set the state with this method. First of all, what happens if we're logged in and we visit another page and not the login page? We see the Login text message over here since there isn't any logic to instantiate the authenticated state correctly the first time. I would have to copy and paste this section in my AppHeader, as well, and then we wouldn't be using code efficiently. Also, if we begin to emit events and handle them whenever needed, you can easily imagine what a mess would occur early on, and the complexity may increase as our application increases in size. These are some of the reasons that we would like to use a central source of truth and detach the state and the logic from the component and move it in a separate place where components could look only in that place. Also, we would like to update and retrieve the data through some sort of pattern and progress, and not have direct access to the state. This is where state management comes in handy.
While working with React apps, you may have heard about the Flux pattern. Redux made this pattern popular, and it is used in React applications for state management. Luckily, in Vue.js, there is a library named Vuex, which is built to help us with state management using a Flux-like pattern. As we saw previously, we have the login component and the appHeader component, and we would like to share this state of isAuthenticated with events. In Vuex, we'll have only one source of truth and one place to initialize and update it. Let us not get ahead of ourselves and set up Vuex in our project for now. I will first include Vuex in my project with the npm install vuex@, the version specified, --save. Once this is done, I'll create a new folder named vuex, and create a file named index.js. In this file, I'll import Vue and Vuex, and we can call the Vue.use method to include Vuex in Vue. With the use method, we can extend Vue with various plugins just like we did with the router module. Then we'll create a new Vuex store. The store is nothing else but a centralized way to manage state. We can use the Vuex.Store method to create a new store instance. The store accepts a JSON, which is the state. This is where I'll include the isAuthenticated parameter for now and export the store. In my app.js file, I'll include the store, and include it in the Vue declaration. Now we have successfully loaded our Vuex store in our Vue instance. In my browser now, I'll refresh and open the Vue tab in Chrome. If you don't see this tab, you can install it if you search for Vue.js dev tools in the Chrome store. This comes in handy when you want to debug your Vue.js application. In the Vuex tab, we can now see the state of our store, and it has for now one property with the key of isAuthenticated and the value of false.
Now that we've set the isAuthenticated property in our state, we'll have to see how can we use it in our components. In Vue.js, the store is located in the object named $store. To double-check this, I'll enter $vm so I can return the Vue instance of the app. And we can find in here the store object, which contains the state and the isAuthenticated property. So in my AppHeader, I'll remove the event handler and the data property, and add a computed property that is named isAuthenticated. And this will return the isAuthenticated value from the state of the store. Whenever the isAuthenticated property updates in the store, it will update in my computed property as well. I will refresh my page, and the authentication link has the value of Login. Since the AppHeader's computed property is linked to the store's variable, if we toggle the isAuthenticated to true, you can see the update. And if we change this back to false, it will update again as well. In Vuex, though, we don't want to access the state directly, and we may want to add some extra logic before reading the value of the property. To do this, we will add a layer on top of this, which are getters. Our store contains another reserved property named getters that works as computed properties. We can create a getter named isAuthenticated. This will receive the state and will return the value that we would like. I will return the isAuthenticated status for now, and over here we could include some extra logic before returning our end result. In our AppHeader, I'll include the mapGetters helper from the Vuex library. This maps stored getters to local computed properties. We will use the spread operator to add the isAuthenticated getter computed property in our component's computed properties. This is very similar to what we've done below. Now once I refresh, we can see the same result, and if I change the isAuthenticated to false, the value is loaded correctly. Now we know how to obtain a value from our state. Let us see how can we set a value.
Actions - Mutations
In my login component, I will load the mapActions helper from Vuex. The mapActions helper will map component methods with the store.dispatch calls. We'll connect the logout action from the store, and bind it to our custom method named logout, as well. So basically, whenever we run logout in our component, now Vuex will trigger the this.$store.dispatch logout method that will trigger the logout action. In my login method, I want a bit more control over the action triggered so we can reset the username and password once the login process is done. Again, in the actions area, I'll create a new method named login, and we'll return a Promise for now. I will copy the login logic from the login method, and include it in my login action. I will also move over the appService module. Now, in the login method, we would like to receive the username and password, so in my login method, I will trigger the action and pass in the credentials. In my login action, after the context, we will receive the credentials and pass in those in our loginPosts request. Once the login is successful, we will resolve the promise. I will grab the username and password from here, and we will reset the username and password after the promise is resolved. Lastly, I will create a mutation that will trigger once the user is logged in. Let us name this mutation login, and it won't only receive the state, but the token returned. We will pass over the login logic and set the state of isAuthenticated to true. So instead of this, we'll change the state, and also add a check that our window object exists. And instead of data, we use now the token object. Lastly, we want to set the isAuthenticated state to true once the page is loaded initially. So I will cut the logic from the created area and in my module I will include a DOMContentLoaded callback on my document. This will trigger once the document is loaded. Of course, we have to check if the document object exists. Once the document is loaded, we can check if we have a valid component, and if that is true, we can set the isAuthenticated to true. Over here, you may want to create another mutation for this and another action. So you can be very disciplined and change the state only from mutation, as you should do. Also, let us remove the profile information from the login page for now to keep things simple, and the profile information from the template area. I will refresh my page and you can see that the isAuthenticated state is set to true. The header and the login page share the same source of truth. I will click on the Logout button, and the logout action is triggered, and this commits the logout mutation. Once we log in again, the login action is triggered, and this triggers the login mutation. We now levitated the state from our components to a single source of truth, and when we want to log in and out, we don't have much liberty into the inner-workings of the state, but we first have to the trigger the action that'll take care of the rest for us. This way, we could create, for example, a model or a pop-up for our login process that should follow a certain process, and we keep a strict path of steps for future developers and components in our application.
As our application grows, we obviously can't handle only one state, getters, actions, and mutations for the whole application. This file would become huge, and we would mix different areas of the project together. This is where modules come in. With modules, we can create different segments in the Vuex store and focus on the one that we want. For example, maybe I would like to connect the posts with the store. I will create another file and name it posts.js. This will contain the state, which is an array of posts and the categoryId. Then the getters that we will expose the posts; the actions, empty for now; and the mutations, which are empty for now; and export these areas. Let us set the namespace to true. This will wrap all of the various elements of the module under a namespace. We'll look at this in action in a bit. We will import this as a module and name it postsModule. We will now use the last reserved property named modules. In this, we can add modules, and each one will contain a state, getters, actions, and mutations of their own. Let us name the module postsModule, and set it to the postsModule. In the category now, I'll use the computed properties and the mapGetter helper again to attach the post object from the state to our component. I will also remove the posts from my component's data. Let me add the mapGetters helper from Vuex and in the computed properties area, connect the posts array. However, before the array of my mapGetters, I'll include the module name, which is postsModule. This is where the namespace set to true works wonders. If we hadn't set the namespace set to true, we could access the post state without the postsModule's namespace. Now we've limited the getters to look into the postsModules only. I will clean things up a bit. Remove the console log, remove the id declaration, and grab the id from the route directly and remove the appService import. Now, we won't call the appService in our component, but cut the section and call an action named updateCategory, and pass in the categoryId. We can do this with the store.dispatch method. Also, don't forget to include the namespace in front of the action name, which his postsModule. It looks like I forgot a comma over here. In my actions, let me create the action named updateCategory, receive the store and the categoryId, and run my API request. Once the API request is returned, we'll commit a mutation named updateCategory, and pass in the categoryId and posts. We'll create a mutation that will map the different parameters with the corresponding properties of the state. The name is updateCategory, and is triggered from our action. Once I refresh, everything is working perfectly. We can now easily use the posts or the categoryId in other areas of our app. For example, we could easily show the categoryId in the app header by creating a getter for the categoryId, and by mapping the categoryId and the app header. But for now, let us keep it as it is. We've successfully created a module, and can access and change the state in a rapid manner.
In this module, we went over the interesting concept of state management. We saw why it is important to detach the isAuthenticated state from our components to have only one source of truth. Also, we saw how to create a module and separate the different areas of our store to keep things organized and segmented as our app grows larger.
Server-side vs. Client-side
Until now, we've implemented the client-side rendering path. We've created the app.js file that loads the router, the store, and the components. The app.js file passes through the webpack build and generates the bundle at the assets/js/app.js path. We then load this bundle in our HTML page. The bundle will look for the div with the app id and generate all of the HTML necessary in that area. To generate our final result, embed it in our div, and serve the end result to our client immediately, we'll have to create a server entry. Webpack will build the server-bundle, and when the user requests the page, the bundle renderer will render the end result, embed it in the app div, and serve the full page. Let us implement this. The first step is to create the server-entry. Until now, we were using the client-entry.js file as an entry point for the webpack.client.config.js. I'll create a new file and name it server-entry.js. This will just export for now a function that receives a parameter and returns the app module that contains the Vue instance. I will also create a new webpack configuration and name it webpack.server.config.js. I will copy and paste for now the webpack server configuration, and we'll go over it together. We will load the path, webpack, and base model, and extend the configuration just like we did in the client. Our entry will be the server-entry file, and not the client-entry. The target will be set to node so that the Vue loader emits the server-rendered oriented code. We'll keep the devtool to source-map to support source-map for debugging, and the output will be the file that we'll call main.js in the server folder generated. Then we'll add in our externals the nodeExternals command to ignore all of the files in the node_modules folder. This is used so we won't bundle any third-party libraries, but only our custom code. We'll do this to keep things fast and flexible, as we'll load the external files from the node_modules folder on the server during runtime. Additionally, I'll include the ExtractTextPlugin and include it in my plugins. We'll do this so that Vue.js can compile the CSS in our Vue components. Lastly, we'll, of course, export the configuration for now. In this video, we created the server-entry and the webpack script to create the server bundle. In the next video, we'll see how can we run the server bundle with the bundle renderer and output the final HTML.
Dev Server Bundle Callback
We have the server-entry and we have the webpack build configuration for our server-generated code already set in the previous video. Let us see how can we run this now. In my dev-server file, as you remember, I include the client-side configuration and with the webpack-dev-middleware, I compile my client-entry.js file, in a sense, and generate the final app.js file. We'll do something similar with our server-entry file. I'll include the serverConfig, then we'll run webpack and pass in the server configuration. I will also include the memory file system module and create a new instance. We'll set the output path file system of our server-entry to the memory file system we initialized. This is done while we're in development mode, so we don't actually create the files in our file system, but compile everything in memory. Later on, when we'll want to actually deploy our application, we'll create these files optimized in our file system. For now, I'll use this method to keep things fast while in development. Additionally, we'll grab the output path from our webpack server configuration. Furthermore, I will create a watch handler so we can trigger this event every time our source code changes, and every time it changes, webpack will compile the new bundle and will read the new bundle file that is created from our memory file system. For now, I'll include in my dev-server a callback and name it onUpdate, and run this callback when the server bundle is updated, passing in the new server bundle.
I will install the vue-server-renderer module created by the Vue.js team that contains the createBundleRenderer method. This method receives the server bundle, which is basically our Vue.js application, and generates a renderer. This renderer will then receive the URL that we're at, and will generate the final HTML. I'll also include a variable named renderer, and in my dev-server, we'll pass it in the onUpdate callback. Every time the server bundle changes, we will receive the new bundle and generate a new renderer. The dev-server receives two variables, our express app and a callback. We have named our callback onUpdate, so every time the server bundle updates, we will return the new server bundle, which will then update our renderer with our new bundle server Vue.js code. Until now, we're serving the static index.html page. I would like to serve the end result rendered by the server, so after we receive a request, I'll run the renderer.renderToString and pass in the URL. Then as a callback, we'll receive an error or the final HTML. If we have an error, I will return to the user the Server Error code. If it is successful, we'll have to embed the HTML result to our index.html page. Let me console log the HTML returned for now. I will stop the server and boot it up again. Looks like there's an error. We forgot to add our path module reference in the dev-server. I'll have to add that in the dev-server. I'll just go to my server.js, copy the path reference from here, and add it in the dev-server file, and try again. There we go. Our code is building up correctly now. Once I request the page, you'll notice that the HTML string is the code that is generated in the div app area. So with the renderToString, we rendered the end result of our Vue.js code on the server. So I will remove this in my index.html and add a placer and name it app with double curly braces. Then I'll add the id of app in my Layout component so that after the site loads, Vue.js will be mounted in this area. And in my server, I will load the index.html and replace the app with the generated code and return the end result. I will reboot the server again. And now once I refresh my page, the header and footer components are loaded instantly, but there is no content on our page. If I check the source code that is returned, we can notice that we received the app shell, but the content isn't rendered yet. This is because when we render our page from the server, various callbacks, such as created, aren't triggered. These events are triggered after the component is attached to the document. The app header and footer don't render data asynchronously. That is why they appear immediately. We'll see how we can solve this issue in the next video.
We have noticed in the previous video that we have successfully rendered from our server the static components on our page, but the dynamic content isn't rendered. This is because the created callback isn't triggered. We'll see how can we handle routes and set the initial state of our application to show our posts that are asynchronously loaded as well. In our server, whenever we call the renderToString, the renderer first calls the server entry bundle, and that is triggered to return the end result. We can include some extra logic in here to append the initial state of our application; a snapshot of the application, if you may. First, we'll export the router and the store from our app.js and include it in the server-entry. We'll do this to push the current URL in the router. Have in mind, Vue.js when running in the server doesn't have access to the document or the page, so we'll have to set the router state from the context from the request. We can easily do this with the router.push context.url command. Then we'll return a promise and loop through the components of the router Vue. The router has a method named getmatchedComponents. In this case, we just have the category component for our category pages, and the post component, which is included. For each of my components, I will check if they have a method named asyncData, and if so, I'll run it and pass it in the store and the current route. We haven't created that method yet in our category, but we'll add it shortly. Once all of the async data is loaded, we'll set a parameter named initialState to the state of our store and return the app. If I refresh now, everything works perfectly, or I would wish so. It looks like we're missing one final piece of the puzzle. After some research, I found that Vue router will automatically resolve matched async components when resolving a route. What you need to do is make sure to use router.onReady on both the server and the client. We already did this in our server entry, and now we just need to update the client entry. To fix this in our client entry, we'll mount the app once the router is ready. Once I refresh the page, finally now we have the dynamic content loading with our server-rendered page.
In this module, we went over the concept of server-side rendering. This is a concept with its advantages, but comes with a number of drawbacks as well. It is up to you to decide whether you need it or not. But, in any case, you're fully aware of how it works and its potential either way. Tests are extremely important while developing front-end apps. Let us see how can we write some unit tests in the next module.
Automated testing has become an integral part of development. There are various tools and strategies in which we can test our application. In this module, we'll cover unit tests with our Vue components. We won't cover integration testing, which is testing how components work together, or UI testing, where we would automatically test the end result of our application in the browser. Unit tests are the most common tests that you'll create as a developer, and the basis to build the other testing types as well.
There are various testing solutions out there. Vue.js is not opinionated in which test runner, framework, assertion library, or helper library that you use. If you visit the Unit Testing page of the Vue.js documentation, you'll notice that Karma is recommended as a test runner, meaning the platform on which we will run our tests. With Karma, we can specify which browsers we want to test our code on. Karma will open the browser for you, run the code, and then close it, bringing back the result. We can use a headless browser, such as PhantomJS, that is more lightweight and fast. Then for a testing framework, many use Mocha. Mocha will be responsible for the way we write, configure, organize, and structure our tests. Chai is chosen as the assertion library. This will be the syntax with which we will compare the result generated from our test and the expected result. Sinon will be used to offer some extra functionality, such as mocks. You could also choose, of course, Jasmine or Jest, which are testing libraries containing a framework, library, and spies/mocks out of the box. So you only need this library and not Mocha, Chai, and Sinon, which would be used in any other case. Also, AVA is a strong option as well. AVA runs tests concurrently and has a very simple syntax. It includes its own assertion methods. For the purposes of this course, I'll select the most popular stack, which is the combination of Karma, Mocha, and Chai. I'll include the libraries necessary. You can run the following command to install all of the libraries necessary for us to test Vue.js. You can find them, of course, at this link. In the next video, we'll see how can we test our Vue.js components with these libraries.
Testing components isn't that tricky. If you remember, in the previous video we set up Karma to run the files returned from the index.js file. This file will load all of the files that have the .spec string in their name under the specs folder, so in the specs folder, I'll create a new file and name it Post.spec.js. In this, I would like to test my post component. I'll include Vue from the node_modules and include the post component from the src folder. If we review the post component again, you remember that this is a component that we created in the category folder. We include a link as a property and some content. Then the post component will render the section and include the title section in the title slot, the content section in the content slot, and the link in my footer tag. I will create a test that will test if the link is generated correctly. First, we will include the describe block. This is used to group different tests together. I'll name my group Post.vue, then in this group, I'll create a test and name it should render the link. In this, we'll run the extend command in Vue and include the post component. What this will do is to extend the Vue constructor so that we can create a subclass that will return a new constructor for the post component. The post component inherits the Vue constructor, if you may. Then we will run the PostConstructor and enter the properties of that component with the propsData object. I will include the link and run the mount command to mount the component on my browser. For now, I will console.log the result. In my package.json, I'll have to create a new script to run my test, and run karma start with the configuration of karma that we created in the previous video. At the end of this command, we can also include the single-run property. This will run the test and exit immediately once it is done, with a 0 or a 1 if the test failed. If we don't include this parameter, the Karma script will continue to run and re-run every time we change the test, so while in development you can create a new command without the single-run. And when we run the code on a server, it is better to include this so that the code will exit and not run indefinitely. In my bash, I can now navigate to the vue-spa folder and run npm test to run my unit tests. As a result, we can see that we have successfully loaded Vue.js, extended the constructor with the post component, and mounted this on our PhantomJS page and rendered the result, which we can view over here. Lastly, we'll use Chai to create our first assertion. We want to compare the input of the link with the generated element. We can use the expect method and query the element. Find the card-footer-item, select the href attribute, and compare it to the link value we expect. After I run my test again, we can see that we have executed one test and it was successful. If I change the expected string and run the test again, we can see that it failed, and we can see in our error response that we expected Pluralsight.com to, but the component generated the Pluralsight.com link. I will change it back to the correct one, and I will lay back, as we have successfully tested our first component. One last thing. I've noticed that we received some warnings while running our tests. This is because we'll need to include some extra linting rules for our tests. I'll create a new file named .eslintrc in my unit folder and include some rules to notify ESLint that the describe, it, and expect functions are defined, and that it doesn't have to worry about those.
Testing Component Changes
Testing with Router and State
The last test scenario I think we should cover is testing a component that uses the Vuex state and router dependencies. We will create a new spec for the category component that uses the router and the store dependencies. I will include the test group and the test for my category component. We would like to check if the category generates the correct number of links from our API after it is generated. I will instantiate my component a bit differently. Instead of extending the Vue constructor with the category component and then initializing it, why not initialize the base Vue constructor, and then load the component? Remember that the category component uses the router heavily as a dependency, so I'll load my category component through the router. We'll include Vue, the VueRouter, and the category component, and extend Vue with this plugin. Then I'll set a new VueRouter instance. This is just a dummy instance that we'll include in the Vue constructor. I'll set the default route to return the category component, and include the router configuration in my Vue configuration. Also, Vue will just be a div once it's initialized, and we'll render in that div the router-view. What will happen now? Once I've run this test, Vue will be initialized with the router setting and read the router state, which is the default one, and then the router will load the category component and include it in our div element. This way, we've included the category element and have initialized the router, so in our category, the route will have a state and won't be null. Pretty neat, right? But the category component also uses the store. I'll include the store and include that in my configuration as well. So now the category component will have everything it needs to render correctly. For now, I'll just log the Vue end result. Let me run my test. We can notice that the element rendered is just the column divs and not the posts. This makes sense because the posts are rendered after we dispatched the update category action that had to make an AJAX request and bring back the results. We end the tests before we get the results back and render the posts area of our element. I could just set a timeout of 1 second and run my test again; however, we aren't quite sure if the AJAX request would need 1 second to return our result, or 100 ms, or 1 second and 100 ms. To be more specific, I could add a watcher in my store. This watcher can watch the state of my posts. We just have to create a callback method and return the object that we would like to watch. In my case, it is the posts of the postsModule. Then once they are updated, we will create a new function that will run an assert. This assert will find the elements that have a column class, find how many they are, and compare them with the number of 6. This means that once the posts are set, the component should have six posts. I will include the done callback to end my test over here. I will run my test again, and there you have it. All of my tests have successfully passed. If I change this to 5, for example, my test will successfully fail. Usually when we test components, we would like to not depend on AJAX requests because we slow down our unit tests. We could create a new store object that would have an updateCategory action that would update the posts array with some static data. The choice is up to you.
There are numerous scenarios one would like to test, and we can't cover them all. Also, we didn't go over testing coverage where you can see the percentage of your code that is covered by tests. But you are definitely ready after this module to set up your testing environment and begin to mingle and try to break your components. We first went over some popular testing options. We set up our testing environment and first tested a simple component. Then we tested a component and saw how through the nextTick we can observe changes to its state. And lastly, we tested a component that is dependent to the router and a Vuex state management. Time to deploy our application.
In this module, we will polish our application and make it ready for distribution to the masses. It is time for the world to see our beautiful Vue.js application.
Production Build Process
Production Build Optimizations
We now have a process to generate our production-ready code. There are multiple ways in which we could deploy our application. We could replace, for example, the app string in our index.html with the divId=app element, remove the state string, and just deploy the index.html file with the dist folder. Then we will have a spa with only client-side rendering, which will work just fine. But after a while, this would become annoying. We would have to build tests on our own that we didn't break anything, copy and paste the files to the server, and test again to make sure that the server's environment didn't break anything, because running the same code on different environments may introduce different results. Also, what happens if you're a big team and everyone commits the same code in the same repository? Who will be responsible for the build process, and when? Many cloud providers offer the opportunity to link your web application with a GitHub repository. Also, you can point to the command that you would like, and every time someone makes a commit to the code, the web application grabs the code, runs the build command, and copies and moves over the updated site to a public-facing folder where the users can instantly view the updated version. So we'll move our code over to an online GitHub repository to set up this process. One of the most famous places to host our code is github.com. A first way to grab the vue-spa code and add it in your own repository is if you visit the following path. You can fork the code, which will move over the code to your account. However, if you've been writing the code alone, you can create your own repository, and upload the code over there. Under your profile, Repositories, you can create a new repository. Let us name it vue-spa and enter Create repository. I will copy the link of my repository and I will navigate to my vue-spa folder locally. Have in mind, if you see a .git folder in your folder structure, you can delete it before going forward. I will run get init to initialize the repository. Have in mind that if you already downloaded the code that you are using from another repository, you have to delete the .git folder. And then git.add to add all of your files in the repository, git commit -m, initial commit, to commit all of your files, git remote add origin, and the URL of your repository, and lastly, get push -u origin master to push the code. You have now successfully moved over your code to GitHub.
Before we link the GitHub repository with the cloud provider of our choice, I would like to mention continuous integration. We mentioned that when we commit our code, the app service will notice the change, run the deployment process. That will configure and render the end result. However, it would be nice to add a layer of quality assurance so whenever someone commits code to the repository, we'll test the code on another machine, and if it doesn't pass we'll get notified of the failed state. We first have to select a continuous integration service or set up our own CI server that will automatically build the project and test it in different environments before it approves or rejects the commit. One of the most popular integration servers is Travis CI. I will visit travisci.org, and I can easily register or log in with my GitHub account. Under my Accounts page, we'll see a list of all of our repositories. I will find the vue-spa project and click on the X to enable it. Then in my project, we'll have to configure travis.ci with the travis.yml file. In here, I'll set the language in which we'll test our tests with, which is node_js, and we can configure the version of node_js that we would like to use. Every time we commit our code now, Travis will run the tests and inform us about the state of our application. I'll pause the video until this is done. So everything looks great. Our three tests have passed and our application is green. Lastly, I bet you've seen various repositories that share the state of their build process in the readme file. To include this in our readme, we can click on the build image, select the Markdown code, and paste in the code in our readme file. I will commit and push my changes, and now in my readme file we can see the status of our application, which is passing, meaning that the tests have passed. Everything looks great and we have more confidence about the state of our application. Time to deploy our application on Azure.
There are numerous cloud providers out there. I've worked with Azure mostly, so this will be my choice for this course, but you can use any cloud provider you like the most. You can create a free Azure account if you want to move along with this course. Once you create your free account, you'll have access to the Azure portal. In the portal, I'll choose the App Services service. In the App Services service, we can quickly create a cloud app. I will click on the Add button and select a standard Web App template. There are numerous templates from which you can choose from. And then click on the Create button. You have to select an app name. I'll enter vue-spa, but you have to find a different name because I just stole that name from you. And click on the Create button again. I'll pause the video because this may take some time to complete. Once the deployment process is done, I'll click on the App Services tab again and click on the app we created. Under the Deployment options, I'll choose the source of my source code. In our case, we'll choose GitHub. I've already authorized Azure to use my GitHub account. You have to do it as well by clicking on the Authorization tab. After the authorization is done, we'll select the vue-spa project and the master branch. Looks fine for now. You could create another branch and name it deploy, for example, so that your live site can only be generated when you commit on the deploy branch. Everything looks fine. I'll click on the OK button. Now Azure will link the application with the source code of my GitHub repository. If I click on the Overview tab, we can find the public link of our website. If I visit it, there is nothing returned. Let us fix this in the next video.
There is a button named Get publish profile. I'll click on this button and I will download the file. This file contains the various endpoints of our application and the username and passwords for you to access them. Azure has set up an FTP access account for my application, and you can find the credentials for that under the vue-spa FTP publish profile. I'll use those credentials to log in to my web app with an FTP client. Azure has created some folders for us. The ones that we're interested in are the repository folder and the wwwroot folder. The repository folder contains the files of our repository, and Azure was smart enough to move over the files to the wwwroot, which is our public-facing folder. If I open the web.config file, you'll notice that Azure figured out that we would like to run our application with Node.js and it will run the server.js file that will handle our requests. Pretty fancy since we didn't do anything and it created all this configuration for us. Something that I noticed is that there is no dist folder, so Azure was smart enough to understand that we want to boot up the server.js file that will handle our request, but didn't get that we want to run the npm run build command as well. I will thank Azure for the effort, but let us set up our own build process. We can create a new file and name it .deployment that will point to the file that will run our deployment process. Let me name it deploy.cmd and add it in my build folder. Also, let me save the web.config folder generated from Azure in my build folder. In my deploy.cmd file, I'll write my own build process. Let me copy and paste what I wrote, and let us go over it. So once Azure updates the code in the repository folder from my GitHub repository, I'll run the npm install command to install the node_modules folder with all of our dependencies. Then we'll run the build command to create the dist folder. After that, we'll clean up the wwwroot folder and delete everything. I'll move over my live folder, the dist folder, the index.html file, the server.js file, the package.json, and the web.config file. Lastly, I'll navigate to the wwwroot folder and run the npm install command with the --only=production flag. This will install the node_modules folder, only the dependencies from my package.json folder, and not the dev dependencies. We want these dependencies on the public-facing folder so that server.js has the appropriate libraries to run the Vue server renderer script needed to render the server-side generated result. I will commit my deployment files and navigate to my deployment options. You'll notice that Azure will automatically run the deployment script every time it notices a change in our repository. Once that is done, let us preview what we did. So it will install the dependencies needed and build our application. After that, it will clean up the wwwroot folder and move over the updates. And lastly, install the dependencies needed for runtime, and not development. I will refresh my page. Our page is live and looking great, ready to be loved by our loyal fans.
This is the end of the course, Single Page Applications with Vue.js. We didn't go deep into the internals of the framework, but we glued together the different parts needed to create a single page application. Also, we created tests and deployed our application on Azure. There are various other options that you could consider moving forward. You may, for example, want to build your application for mobile devices with the use of Weex or NativeScript, since NativeScript now supports Vue.js. Or instead of creating a native app, we could create a progressive web app. For an idea on how to do this, you can check out the vue-wordpress-pwa project, or my course, Getting Started with Progressive Web Apps. You could also consider using propdoc to generate documentation for your Vue.js components. In addition, you may want to add internationalization or logging to your application with the Vue.js plugins, or maybe extend your application with your own plugin, just like Vuex or Vue router. Considering tests, you could extend your testing scenarios with Nightwatch for end-to-end testing, or use the testing coverage options to log the percentage of your application that is covered with tests. Or even write pre-commit tests so that your code won't commit if the tests don't pass. Why not use TypeScript and use strong types in your application? Lastly, it is now a good time to check out the vue-cli as well. With the vue-cli, you can set up scaffolded projects that already have the build process, routing, and testing parts all set for you so you can expedite development. Also, the new Ext.js project is a framework that solves some of the problems that we face out of the box. Have in mind though that the easier it is to start, the harder it will be to make changes afterwards, as you have less control on how things are set up. Any path you choose to take, hopefully, you have a much better understanding now on how to structure and run your Vue.js applications. I wish you good luck with your Vue.js endeavors, and thank you for watching.
Bill is a Microsoft MVP, Google Developer Expert, a Senior Software Engineer at Software Competitiveness International, and the creator of Dotnetweekly. He has over 7 years of experience in...
Released10 Oct 2017