What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Single Page Applications with Vue.js
by Bill Stavroulakis
Vue.js became quickly one of the most popular JavaScript frameworks. In this course, you'll create a Single Page Application from start to finish using this flexible, yet powerful framework.
Resume CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Course Overview
Course Overview
Hello, everybody. My name is Bill Stavroulakis, and welcome to my course on Single Page Applications with Vue.js. I am a front-end developer at Mesosphere. Vue.js quickly became one of the most popular JavaScript frameworks out there. In this course, we won't go over the fundamentals of the framework, but glue together its various components and plugins to construct, test, and deploy a single page application. Some of the major topics that we will cover include, creating a build process, single-file components, routing and communicating with an external API, state management, server-side rendering, testing and deploying our application. By the end of this course, you'll know how to set up, build, test, and deploy a single-page application with Vue.js. Before beginning the course, you should be familiar with the Vue.js library. I hope you'll join me on this journey to learn about creating your full-blown, modern web application from start to finish with the Single Page Applications with Vue.js course, at Pluralsight.
Environment Setup - Build Process
Introduction
Hi, this is Bill Stavroulakis, and welcome to this course on Single Page Applications with Vue.js. Vue.js is a progressive JavaScript framework. This 18-kb library can be used as a small, unobtrusive way to conduct simple operations on our websites such as data binding and DOM manipulation like jQuery and Knockout, all the way up to constructing full-blown, modern single page applications like Angular and Ember. In this course, we won't go over the fundamentals of the framework, but glue together its various components and plugins to construct, test, and deploy a single page application. In this module, we won't see Vue.js in full action quite yet. In modern single page applications, the environment setup and the build process is a crucial factor of the development cycle. Vue.js isn't any different, and we'll set up the necessary tools and foundry to build our application upon.
Project Files
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.
Environment Setup
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.
Project Setup
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.
Editorconfig
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.
Package Management
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.
Web Server
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
Of course, we don't want to serve hey there to our users, but our index page. To help us with this, we'll use the file system node module. I'll include the fs module, and instead of returning some test, we'll return the data of our index page. We'll create a constant function that will return the content, after we read the file, of course. To make the path of the file more strict, I will include the path module and retrieve the file path from the path.resolve method. This will return the path relative to the server.js file and from the directory that we run the script from. If we were, for example, one level above, and ran node vue-spa/server.js, if we just added index.html here, then the code would've tried to find the file in the same folder of the vue-spa folder. But with the resolve, even if the script is run from somewhere else, the path will always be relative to the server.js file. If we run our server again and refresh, we'll see evidently a white page, which is great, because if we view the source, we'll discover our index page. Now, instead of remembering our JavaScript scripts, we can create shortcuts of the commands to run them through npm. I will do this if I'll go to my package.json file and create a new script. We usually name this one start, and I'll include the node server script. Now, we can type npm run start that will eventually run the node start section, or npm start. This may not be extremely useful for small scripts like this one, but as they grow longer and more complex, it will make sense.
Webpack - Setup
Until now, you should've noticed the concept of modules. We're already using them. Where? Well, in our server.js. What we do is include the external modules and reuse them. In modern JavaScript applications, many developers use the concept of modules. We'll create our own modules and reuse them throughout our application. The thing with modules is that they are not supported on all browsers, so usually here is where we decide which module bundler we would like to choose. The most popular tools that will work as a polyfill are Browserify and webpack. Webpack is not only a module bundler, but through webpack plugins, we can minify, transpile, and run many other build processes that we would usually run through Gulp or custom npm scripts. This is why we'll choose webpack. To get started right away, I'll create a folder named build and a file named webpack.base.config.js. This will be our basic webpack configuration file. I will install webpack now by running npm install webpack@, the version specified, --save-dev. As you can notice, the webpack module reference is added in the devDependencies, and not in the dependencies. This is intended, as the webpack module will be needed only in development and generate the end result files that we will run in every browser.
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
In the app.js file located in my source folder, I will import Vue from the vue.js module. This will find the vue module in my node_modules folder, and included naming the module as a Vue reference. We can initialize Vue by instantiating it, and let us state the data with a property named hello and the value of hi there. Now I will export the app variable in a JSON export, and we can retrieve it in our client-entry module. Over here, I will mount the Vue instance to an element with the ID of app. In the index.html, we'll create a div with the id of app, display the hello property, and insert the path of code that is located in the dist folder with a script tag. Lastly, in my server.js file, I will use the express.static module to return all of the static modules from the dist folder. We can do this by including app.use dist and express.static, resolving the path of the dist folder. I'll run the webpack script again to build my JavaScript file and run npm start to boot up the server. When I visit my localhost, I don't see anything. Why is that? Let us investigate. Ah, yes, we see an error message that informs us that we are using Vue in non-compiled areas. To fix this, we can include the path of Vue so it can compile the template for us. This is not recommended, though, as it decreases performance and it recommends to use Vue through .vue files. Only for now, I will include the resolve property, re-run webpack, and lastly, reboot the server. And there you go, Vue.js is running through a simple build process on our web page.
Dev-middleware
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.
Hot-reloading
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.
Linting
The last step of my build process will be to add linting. Although we've already added the editorconfig file for some rules to be applied, it would be nice to have a more thorough process to automatically check for common JavaScript mistakes and styling issues while I'm building my code. This is where linting is helpful. As every step taken so far, there are numerous JavaScript linting options out there. Personally, I use ESLint, and I'll be using this throughout the course. To get started, we'll create a file named eslintrc.js, and include all of the different configuration and rules that we would like to apply to our project. You can copy and paste this from this Gist source over here. This route property sets this file as the parent scope of the rules, so the rules will apply to all the files of the project. The parserOptions are set to module, since we're using ECMAScript modules. Then we extend the standard rules configuration and include the HTML plugin to Lint scripts from HTML files. This will be needed later on when we work with .vue files. To install the ESLint modules necessary, we'll run the following script to install all of the development dependencies. Now, to connect the linting process with our build process, in the webpack.base.config, I will include some rules in my module settings. This will include the enforce: pre section to check source files before they're modified by other loaders. Then the file extensions that we want to test the loader with the eslint-loader, and we'll exclude the node_modules folder. When I boot up my server, the build will fail if I have any JavaScript or styling errors corresponding to the new styling rules. It looks like the standard rules suggest we use single quotes to avoid escaping, and we'll have to remove the semicolons since it is totally okay to work without them. I will replace the double quotes and remove the semicolons in the dev server, webpack.base, webpack.client, app.js, and client-entry. Once I save, everything looks okay now. Now we can catch styling and JavaScript errors early on and have consistent and more robust code throughout our application.
Summary
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
Introduction
A very exciting feature of the Vue.js library is single file components. In Vue, we can create single file components so we can separate our HTML, CSS, and JavaScript code, and transform our data more efficiently during our build process, but keep everything scoped and in one place at the same time. This is very similar to the polymer.js library's web component declaration. In this module, we'll create single file components to scope, organize, and reuse parts of our code.
Vue Component
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.
.Vue Files
Although this method is just fine, it is recommended to use .vue files for single file components. I will create a new folder named theme, and in there a new file named Layout.vue. .Vue files have three sections in them. The first one is the template section, the second one the script section that will hold the Vue.js code for the template, and lastly, the style section. In the template section, we include the HTML of our component, so I'll copy and paste the template area from the app component. In the script area, we have the script of our component. Our component will be exported as a JavaScript module, so I will export with the default flag, meaning that this will be the only export from my script. And we can delete the style section, as it won't be needed for now. Let me move the theme folder in the src folder. Now in my app.js, I can include the single file component we just created, reference it as AppLayout, and include it as the rendered result. Let us see this in action now. When I boot up my server and open my page, I receive this error, you don't have the appropriate loader to handle this file type, which is logical. Webpack doesn't know what .vue files are and how to load them. Thankfully, the Vue community has created a webpack loader that will parse these files appropriately. I will open my webpack.base.config file and include a new rule for my .vue files. So for every .vue file that webpack falls into, we'll pass through the vue-loader module. Also, I will install the Vue loader package and the Vue template compiler as well in my development dependencies. Lastly, in the ESLint loader, I can include a check for my .vue files, as well, so that we can lint and check for JavaScript and styling errors in our .vue files. We can also remove the module.hot.accept method, as the loader is smart enough to handle this for us now. And since the loader is using the Vue template compiler, we don't need to compile our templates on the fly anymore, so we can remove the resolve section as well. We've cleaned up things quite a bit. Let us reboot our server and reload our page. Everything is loading perfectly, and when I make a change, the hot reload functionality is working seamlessly as well.
ES6 Transpiling
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.
Styles
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.
Template Binding
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.
Child Components
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.
Custom Properties
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.
Slots
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.
Scoped Styles
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.
Extract Styles
In our project, we're using the style tag twice, once in our Layout component to include global rules, and then in our Post component to include some rules specific to the Post component. The way we're building the project now, our styles are eventually stored in JavaScript. In my console, we can preview the modules that are created. We have one module that is the app.js module that contains our project. I will open this file and scroll right down to the bottom of the file. We can see that the styles are included in the JavaScript of the module. This is clearly not the best practice. We would like to have our styles in a separate file, usually named styles.css, which we can cache, distribute through our CDN, and load separately than our JavaScript files. This why we'll use the extract styles module. First, I will install the extract-text-webpack-plugin module. Npm install extract-text-plugin@, the current version, --save-dev. I will now open the webpack.client.config.js file, and in here we will include the extract-text-webpack-plugin module, and after that, we will include this plugin in our config. The ExtractTextPlugin accepts the file in which we want to save our styles in. Lastly, I'll have to include one more option in my Vue loader options, but I don't want to include that option in my base config, as we'll reuse the base config later on for the server-side rendering where extracting the styles is unnecessary. So in my client config, I will run the config.module.rules.filter method, and in here we'll filter out the loader with the property that has the loader value of the Vue loader. This will return the loader with the loader of vue-loader, so in our case, this one over here. Then for the vue-loader, I'll include a property named extractCSS, and set it to true. I will run the webpack.client script again through the npm start command, and now in our assets section, we can view a new asset named style.css. I will reload my page, and all of the styles are removed from our app module. This is fine because everything is located now in the styles.css file. We'll open the index.html page and include this file with a good old link tag. I will reboot the server to load the new index.html in memory, and refresh. Perfect. Now, although we write our styles per module, they're nicely loaded separately from our styles file.
Summary
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.
Routing
Introduction
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.
Loading Routes
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.
History Mode
Time to include some extra pages. I will create a component named Login.vue and include a template tag. In here, I'll include a header and a form so our users can have the ability to log in. You can copy this snippet from this page. There's nothing extremely special about it. It's just a couple of fields to enter our login credentials. To load this component, I will create a new path in my router configuration. This will match the route of login, and I will import the login component and load it once the login path is set. Let us test this. I will load my URL with the login path, and there you go. The login component was loaded in the router-view section. Now if I click my logo, we'll redirect to the root path, and we will load the Category component. You've probably noticed by now, their paths have a hash sign between the domain and the path. We're not used to this, but this was the norm before the history API was released in modern browsers. We would load the main page, and then JavaScript would parse the path after the hash area, and we could change it dynamically as we were browsing in our app. Fortunately, we have the history API available, and we can manage the URL and push pages in our history through JavaScript. To enable this, we can set the mode to history. Now if I visit the login path without the hash, everything loads as expected. If I click on the logo, we can go to the main page, and if we click Back, we can go back to the login page. However, when I click on the logo, you can see that we reload the full page. In single page applications, when we load a new page, we don't want to reload the whole page, but only the router view area. We'll see how to do this in the next video.
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.
Redirect
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.
Route Parameters
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.
Wildcard Route
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.
Lazy Loading
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.
Summary
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.
API Communication
Introduction
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.
Receiving Data
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.
JWT
In our application, we don't only want to get data, but we would like to post data back to the server. Let us preview a small example on how we can do this. We will include a login feature for our users. As an authentication method, we'll include JWT tokens. This is a very interesting and easy way to verify if a client is connected to our back-end service. The user will send his username and password to our server. If they are correct, we will return a token with some payload data, for example, his username and an expiration date. The user will then send a token every time he makes a request, and the server will know if the user is authenticated or not. To make this secure, the server generates this token with a secret key. This means that the user can only read the data sent, and he cannot create his own token or edit it because he doesn't know the secret key to generate one. For more information, you can visit jwt.io. So first of all, I will create a method in my service that will return a Promise that will either resolve or reject. Then we will run the post method from the Axios library and pass in our authentication path and credentials. We will receive the credentials as a JavaScript object as a parameter. If the response is successful, then we will resolve with the response.data. If it is not, then we will reject the promise and pass in the error status. Time to glue this post with our form.
Authentication Call
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!
Authentication Status
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.
Intercept Requests
We can log in and log out, which is nice, but it doesn't add any value to our user's experience at the moment. In my app.service.js file, I will include a getRequest to this custom PHP file I've created. This file will return the profile information if the user is authenticated. I will create a watcher also in my login component for the isAuthenticated property. Once that is set to true, we'll run the getProfile method to update the profile information, and on a successful response we'll set the profile information. Also, when the isAuthenticated is false, we'll reset the profile object. I will log out and enter my username and password and check out the response. We receive a 401, meaning that no authentication was provided. This makes sense since in the request we didn't add any authentication info of any kind. So in my request, I'll include a header named Authorization and include the jwt token from our localStorage. Lastly, let us show some data returned, such as the user's first name and his favorite sandwich. So we will display the profile information over here, initialize the profile object in the data method, and once the user is logged in, we will set the profile data to the profile data received. Everything is looking good. We can now attach the token to our requests when needed so the server can know when the user is authenticated or not. It would be helpful if we could attach the token to our header on every request that we make instead of including this in every request manually. We can do this with an interceptor. I can include the axios.interceptors.request.use that will receive the configuration of any request. In this, we'll set the token variable, and if it exists, we will attach this to our request, and return the configuration. Now instead of attaching the token manually to the getProfile request, we will attach a token to any request we make. One extra step we can take at this point is to safeguard our code while it is running on the server. Now, our JavaScript is mostly running on the browser. But, later on, in the server side rendering module, it will be running on the server as well. Before I ask my window object I'll add this check. When the code is running on the server, there is no window object. So, I want to return the config and move on. This also means that we won't have access to the local storage and the token, and won't render any user specific code as well while running on the server. Now, if I refresh, we can preview the profile data again. So, every time the token exists, we'll attach it to our request just like a cookie. With interceptors we can filter requests and results in a centralized manner.
Summary
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.
State Management
Introduction
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.
Vuex Setup
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.
Getters
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
Our isAuthenticated parameter is set first on page load, then once the user logs in, it is set to true. And when the user logs out, it is set to false. We will move these actions gradually to our store. Let me start with the logout method, which is the most simple one. I will remove the eventBus and import the mapGetter helper again. Then we'll comment out the isAuthenticated local parameter, and include it from the store, and comment it out in the various areas. We set it locally previously. To include the isAuthenticated variable from the store, we can use the computed property and the mapGetter helper we used in the AppHeader as well. Now the AppHeader and the login page share the same parameter. I will remove the event triggered as well. The store now has an additional reserved area named actions. Actions are methods that we can call to eventually change something at the state. For example, we want to change the isAuthenticated variable to false and remove the token from the local storage once the user is logged out. I will create an action and name it logout. This will receive the store where we will then commit a command. The commit command will trigger a mutation that will eventually update the state. So I will create a mutation and name it logout. The mutation will receive the state as a parameter and will update the isAuthenticated parameter to false. This layer is needed so we don't have immediate access to the state, but we call actions that will eventually commit mutations. In a mutation, I will include the localStorage removal as well, and include a check. I'll include this check because we may include the store in some back- end code in Node.js in the future. You never know these days where your JavaScript code may be running. We have our action and mutation set up. Let us see how can we trigger actions in the next video.
Triggering Actions
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.
Modules
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.
Summary
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 Rendering
Introduction
The Vue.js app we created until now is client-side all the way. With webpack, we generated the JavaScript files necessary, and we could just take these JavaScript files and our index.html, upload them on a server, and be done for the day. However, this comes with a cost. First of all, let me load a category. You can notice that our page loads progressively. First, we have the app header and the footer, and we wait a bit until the AJAX request is done until the posts appear. In some cases, this may be intended. For example, we may want to show something very quickly to our user to gain the attention before the site fully renders. However, if we have multiple components that load dynamically, the user may see a lot of areas flashing or moving around until the final result is presented. If multiple AJAX requests are linked, this may become a very slow process as well. Also, there is an issue with SEO. When a search engine bot visits our page, it views the source of the page. Some bots may run the synchronous JavaScript of our page, but won't wait for AJAX requests to end, and other asynchronous tasks. So, for example, if Google visited our page, it wouldn't read the posts of our page. In some cases, this scenario is okay. In others, it is catastrophic. In this module, we'll see how to generate the final result of our pages with Node.js and serve the user with the end result.
Server-side vs. Client-side
We mentioned in the introduction some reasons why server-side rendering makes sense. Let us compare the two methods so we can choose which method works for you. First of all, client-side rendering is easy. You can generate the static HTML and JavaScript files and you're ready to go. Server-side rendering is more complicated, as you have to write a Node.js script that generates the end result, and run Node.js on your server. Second, client-side rendering has SEO issues as we load data and many components asynchronously, although with server-side, we load the end result, making it easy for bots to parse our pages. Third point is that with client-side rendering, we progressively render the page, which may be good in some cases, because in slower connections, the user sees something until the rest of the page data loads. But it may decrease the overall speed due to the multiple AJAX requests we may have. Additionally, with client-side rendering, we usually make multiple AJAX requests, but with server-side rendering, we conduct fewer requests. With server-side rendering, we increase the server load due to the fact that we're running Node.js code in comparison with client-side rendering, where we load this more on the client as we serve only static data files. Furthermore, with server-side rendering, we have more constraints as we'll need the server to run Node.js, and some libraries may need special treatment to be able to run in a server-rendered app. Lastly, with server-side rendering, we'll have to pass more data in our first load because we will have to include the state in our HTML pages as you'll see later on. What should I choose? It depends on your site. We will go over the process of rendering our site on the server, but the final decision is up to you and your needs.
Server-entry
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.
Bundle Renderer
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.
Routes
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.
Initial State
In my category, I'll create a new method named asyncData that will receive the store and the route. Also, we'll create another method named fetchInitialData that receives the store and route, as well. The async data method will call the fetchInitialData, pass in the store and route, and return the result. In the fetchInitialData, we set the categoryId to 2. If the route has the id of mobile, we set it to 11, and return the store dispatch. This looks very similar to our loadPosts method, so I'll delete the code from over here, run the fetchInitialData method, and just pass in the store and route. We will have to return this action from the store, which is the update category, and lastly, we'll have to go in the store in our action and return that promise as well. So now in our category, since we returned this action, we returned the promise of getting the posts, and in the server-entry, we will wait for this promise to be resolved, get the data, and then initialize the state with the posts. So now once the server loads, the page will trigger the async data for every component before rendering them. In the category, this will set the state and render the dynamic content as well. If a search bot now of my user visits the site, we will return the full-page HTML immediately, decreasing the AJAX request and the discoverability of our site. Everything looks dreamy. Let me refresh my page, and of course there is an issue that won't let us celebrate our victory quite yet. Let me open the debugger and analyze the issue. I get an error, client-side rendered virtual DOM is not matching server-rendered content. This occurs due to the mismatch of the state of the client and the state of the server. On the server, we receive the posts and set them in our state and render to the page. The problem, though, is that our client now doesn't know what is the state exactly. If we, for example, had a Load More button to load more posts, the posts that you view will disappear because in our client state the posts array has 0 items. There is a way to stamp the initial state on our client. In the server entry, you remember that we set the state in the context of the request. In my index.html page I will include another placeholder and name it state, and in the server.js I will replace the state with a script tag and set a parameter named initialState with the initialState serialized. We will create a global variable named initialState and map it with our Vue.js state, and of course, we'll need a script tag to include that in our HTML. Also, I'll have to create a context variable so we can have a reference to use so we can map that with our initialState, which is set in the server-entry. We're using the serialize JavaScript module, so I'll have to include that on my scripts as well. And since this is a third-party library, don't forget to run the npm install command to add it in your Node references. If I restart the server and reload my page's source, we can see that we have stamped the initial state on our client. I'll open the posts module and first create a Boolean to check if we're in the browser or not. Afterwards, I'll rename the state to the default state and create a new constant for my state. If we're in the browser and there is an object named initialize state, we'll sync the state of the module with the posts module of that object. If there isn't an initial state, we will return the default state. Once I reload my page, everything is working fine and we can open that bottle of champagne. We have generated the whole page on the server, served the full HTML end result, stamped the initial state of the application, and synced that with the client-side code. The user from here on is free to use our site, dynamically load other parts of it, and change the state as that person goes.
Summary
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.
Testing
Introduction
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.
Testing Options
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 Setup
I'll first create a folder named test, and in this a folder named unit. In this, we'll include the karma.config.js file that will include the configuration of Karma. I will also create a new webpack configuration file and name it webpack.test.config.js. In this, we'll include the base config and extend the base config. Then I'll delete the entry point of my webpack file. This is because we don't need the entry point since we won't compile the app, but we want the different rules that apply to transpile different areas of the code. For example, it would be nice to transpile the ES6 code with the Babel loader and continue to load the .vue files with the Vue loader modules. Then in the karma.config file, I'll paste in my configuration. Let us go over it line by line. I will load the webpack file and export a function that will receive a config object. We will receive this object once we run Karma from our console, as we'll see later on. So this config has a method named set where it will set up Karma. First, we'll have to set the browser that we'll test our JavaScript code in. I'll use PhantomJS, which is a headless browser that will run in the background. This is helpful because it is fast and we won't see browser windows flashing in front of us. Then, as our frameworks we'll choose mocha and sinon-chai as we already mentioned in the previous video for the testing framework, assertions, mocks, etc. Files is an array that lists the files that we will load in the browser. Afterwards, we'll set the preprocessor's config option that will transform and run depending on the file types and runtime. For now, we'll need to run the webpack preprocessor to load .vue files and transpile ES6 code. The webpack preprocessor will receive the webpackConfig, and I'll also set an option for the webpackMiddleware to not show information, as it will export very lengthy logs in my console. Lastly, we'll include the plugins array that will include the various plugins we have installed that will make Karma and the libraries that we're using to work together. This is a very simple configuration. We set the running environment to a headless browser. We could also include Chrome here or anything else; the frameworks that will run our tests, the location of the files that we have our test configuration in, the preprocessors to run on the fly to transpile our code, and the plugins necessary for Karma to work with the other libraries. In the index.js file, we'll actually set the files that we would like to test. I'll create a new file and name it index.js. In this file, we'll create a new constant that will load all of the files under the specs folder that we have the .spec string in their name, and we'll loop through these files to return them. We're all set to test our first component.
Testing Component
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
We've created a test for our components, but how can we test changes to the existing properties of our components? I will create another test and name it should update element's href when property link changes. I would like to create a post component again, just like the previous test so we can reuse our code. I'll create a function named createComponent, and move my component generation code up here. Then it will return the component instance. We will run this in the previous test also, and in our current one. So now we have the post component returned and mounted. I will run the previous assertion to make sure that my link is the one expected, then we will change the link, and I'll copy the assertion and pass it after the link has changed. In the expected area, I'll change the link so the href of our element should have changed. I will run my test now. I get the message that although we changed the link in our component, the template didn't update the link property in the href attribute. This is odd. This should work. If we visit the vue.js.org API, we can notice that there is a method named nextTick. This method will trigger a callback once the DOM update cycle is done. Aha, so after changing a property, we'll have to wait for the DOM update cycle to end and render the new result. I will wrap the assertion in the Vue.nextTick callback, and first, we will update the property and assert once the DOM has rendered the result. If I run my test again, I get that everything is successful. Now let us try to break the test. I'll change the link to fullstackweekly2 and save again. Now it returned that it is successful, although it obviously should return an error. If you notice, JavaScript runs the code synchronously, and the Vue.nextTick runs asynchronously, so until the DOM is updated and the callback is returned, this code will have exited this method and have continued on, ignoring the assertion. In Mocha, to deal with synchronous code, we can include the done callback, and if we do, Mocha will stall and wait until the done callback is triggered. In our example, we would like to finish this test once the second assertion is run. I will run the done callback after the assertion, and run my test again. It has successfully failed, if you may. Now I will include the correct string in my expected area. We'll run the test command again, and yes, now we have successfully tested our component's change as well.
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.
Summary
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.
Deploying
Introduction
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
Before we deploy our application, we have to create a deployment build process. Until now, we were using the webpack dev-server to run our application and build it in memory. We'll have to create a script that will generate the HTML, CSS, and JavaScript on disk without the hot modules scripts, and why not use some minification scripts before it is released. Initially, I will create two new script shortcuts. The first one is named build-client, and the second one is named build-server. These will run the webpack command and include a config each time the corresponding webpack configuration. The client configuration will load the base.config and compile the code into one JavaScript chunk, naming it app.js in the assets/js folder. The server configuration will have as an entry the server-entry, and transpile our application into a file named main.js in the server folder. I've also declared an environment variable named node_env, and we'll set that one to production. The last two configs are used to decrease the size of the output info in the console. Then we'll create command named build, and first, we'll delete the dist folder, build the client, and then the server. Looks like I made a small typo here. Instead of the dash, we'll use the colon sign. Once I run the build command, we'll see that webpack will do its magic and create the output files. The first one is the assets folder for the client-side code, and the second one is the server folder for the server code. It would also be nice if we could test the production code locally. I will create another command and name it start:prod. This will set the global environment of the node_env to production and run the start command, which will run our server.js file. In the server.js file, I'll first set a variable and name it isProd. This will check if the node_env variable is set, and if so, it should have the value of production. So if we want to run our production code, we don't need the dev server with the webpack dev- server and hot reloading features. But we just want to run the main.js file for our renderer that is generated by the build process. So I will include an if statement. If we're in production, I will first load the path of our main.js file, which is in the full bundle of our application, optimized to run in Node.js, and set the renderer with that file's code. In development, we would grab the main.js file from memory every time it is compiled. Lastly, if we're in production mode, since we're loading everything as static files, I would like to serve the dist folder as my root path. If I run npm run start:prod, hmm, looks like we have a small typo. Let us correct that. So once we run npm run start:prod, the server.js file will run with the production flag and serve the files from our dist folder. And our site loads as before, but from our static files and not from memory. If we would like to run our application in development mode, we could just run the dev command. This command will set the node_env variable to development, delete the dist folder, and run the node server in development mode that will run everything in memory from the webpack dev-server. If I would like to run it in production mode, I will stop the dev script, build my app again, and then run start:prod. Now you can switch between development mode and production mode.
Production Build Optimizations
Our application is built, but in production it would be nice to run some optimizations. We would like to run some optimizations, basically for our client config since we are concerned about the size of our client's code that we'll eventually have to send to the user. The server code will be located in our server, and since it won't have to pass through the wire to get delivered, we're not that sensitive. I will include webpack in my client config, and if we are in production mode, I will push another plugin to run. This plugin is under the webpack.optimized plugins, and it is named UglifyJsPlugin. This will accept some configuration of its own where we will include a flag for the compression of not showing warnings while it compresses, as other third-party libraries may have warnings in their code that don't interest us. If I compile again, you can see that our client's code is much smaller and minified. We run this process only if we want to build our code in production, as it requires a lot of memory and time to run. We could include over here other plugins, such as PostCSS, that removes any CSS that isn't used in our application. In addition, I will include one last optimization technique, which are chunks. In my base config, instead of having one entry, I can include another one named vendors. In this entry, I'll include all of the third-party JavaScript code that my application uses. Then in our plugins section of the client, I'll include the CommonsChunkPlugin that will look for the vendor entry, bundle that, and export it to the assets/js folder, similar to the app.js path. If I build my project again, you can notice that webpack has split the JavaScript code into parts. The first part is named vendor and has all of the third-party code, and the second part is named app that includes all of our custom code. In my index.html page, I have to include the vendor.js file as well for my application to work. This is useful now because we may set a large cache header for our vendor file that will update very rarely. Also, by splitting up the code, we can take advantage of HTTP/2 parallel loading to load our scripts faster. One last optimization is to define the node_env in our webpack plugin to production. Our libraries will read this parameter and optimize their compilation script for production purposes. To do this, we'll include the production environment variable in our client, and in our server script as well. For more information about this, you can check out the production deployment tips in the Vue.js documentation. There are more techniques that you could implement, such as using PostCSS to remove any unnecessary CSS. But now you get a picture on how you can improve your applications in the never-ending game of performance.
Github
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.
Continuous Integration
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.
Azure Setup
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.
Deploy Script
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.
Summary
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.
Course author
Bill Stavroulakis
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...
Course info
LevelIntermediate
Rating
(66)
My rating
Duration3h 11m
Released10 Oct 2017
Share course