11 Directives, Plugins, SSR, and More

Now you are in the Pro League! You are an advanced Vue developer. Let's have some fun and check out some great recipes that are custom made for you! Here are some hand-picked optimization solutions that can improve the quality of your Vue application and make your life easier.

In this chapter, we'll cover the following recipes:

  • Automatically loading vue-router routes

  • Automatically loading vuex modules

  • Creating a custom directive

  • Creating a Vue plugin

  • Creating an SSR, SPA, PWA, Cordova, and Electron application in Vue with Quasar

  • Creating smarter Vue watchers and computed properties

  • Creating a Nuxt.js SSR with Python Flask as the API

  • The dos and don'ts of Vue applications

Technical requirements

In this chapter, we will be using Node.js, Vue-CLICordovaElectronQuasarNuxt.js, and Python. 

Attention Windows users: you are required to install an npm package called windows-build-tools to be able to install the following required packages. To do so, open PowerShell as an Administrator and execute the following command: 
> npm install -g windows-build-tools

To install Vue-CLI, you need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> npm install -g @vue/cli @vue/cli-service-global

To install Cordova, you need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> npm install -g cordova

If you are running on a macOS and you want to run an iOS simulator, you need to execute the following command in Terminal (macOS):

> npm install -g ios-sim ios-deploy

To install Electron, you need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> npm install -g electron

To install Quasar, you need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> npm install -g @quasar/cli

To install Nuxt.js, you need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> npm install -g create-nuxt-app

Automatically loading Vue routes

In order to create maintainable code, we can use the strategy of auto-importing files that have the same structure in our project. Like the routes in vue-router, when the application gets larger, we find a huge amount of files being imported and handled manually. In this recipe, we will learn a trick to use the webpack require.context function to automatically inject files for us.

This function will read the file content and add the routes to an array that will be exported into our file by default. You can improve this recipe by adding a more controlled route import or even environment-based route rules.

Getting ready

The pre-requisite for this recipe is as follows:

  • Node.js 12+

The Node.js global objects that are required are as follows:

  • @vue/cli

  • @vue/cli-service-global

We will need to create a new Vue project with Vue-CLI, or use the project created in previous recipes:

  1. We need to open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue create router-import

  1. The CLI will ask some questions that will help with the creation of the project. You can use the arrow keys to navigate, the Enter key to continue, and the spacebar to select an option.

  1. There are two methods for starting a new project. The default method is a basic babel and eslint project without any plugins or configuration, and the Manually mode, where you can select more modes, plugins, linters, and options. We will go for Manually:

? Please pick a preset: (Use arrow keys)
default (babel, eslint)
Manually select features

  1. Now we are asked about the features that we want on the project. Those features are some Vue plugins such as Vuex or Router (vue-router), testers, linters, and more. Select BabelRouter, and Linter / Formatter:

? Check the features needed for your project: (Use arrow keys)
Babel
TypeScript
Progressive Web App (PWA) Support
Router
Vuex
CSS Pre-processors
Linter / Formatter
Unit Testing
E2E Testing

  1. Continue this process by selecting a linter and formatter. In our case, we will select the ESLint + Airbnb config:

? Pick a linter / formatter config: (Use arrow keys)
ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier

  1. After the linting rules are set, we need to define when they are applied to your code. They can be either applied on save or fixed on commit:

? Pick additional lint features: (Use arrow keys)
Lint on save
Lint and fix on commit

  1. After all those plugins, linters, and processors are defined, we need to choose where the settings and configs are stored. The best place to store them is in a dedicated file, but it is also possible to store them in package.json:

? Where do you prefer placing config for Babel, ESLint, etc.? (Use 
arrow keys)
In dedicated config files
In package.json

  1. Now you can choose whether you want to make this selection a preset for future projects so that you don't need to reselect everything:

? Save this as a preset for future projects? (y/N) n

Vue-CLI will create the project, and automatically install the packages for us.

If you want to check the project on vue-ui when the installation has finished, open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue ui

Or you can run the built-in npm commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute one of the following commands:

  • npm run serve – To run a development server locally

  • npm run build – To build and minify the application for deployment

  • npm run lint – To execute the lint on the code

How to do it...

Follow these instructions to create an auto-import of the router files in your project that will handle the router files inside a specific folder:

  1. With our route files created and placed inside the routes folder, we need to make sure that every route file has a default export object in it. In the index.js file, inside the src/router folder, remove the default array of routes that is present in the file:

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

export default new VueRouter({});

  1. Now create an empty array of routes that will be populated by the imported ones from the folder, and start the import. With that, requireRoutes will be an object with the keys being the filename and the values being the ID of the file:

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes = [];
const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*.js$/is,
);

const router = new VueRouter({
routes,
});

export default router;

  1. To push those files inside the routes array, we need to add the following code and create a folder named routes inside the router folder:

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes = [];
const requireRoutes = require.context(
'./routes',
true,
/^(?!.*test).*.js$/is,
);

requireRoutes.keys().forEach((fileName) => {
routes.push({
...requireRoutes(fileName).default,
});
});

const router = new VueRouter({
routes,
});

export default router;

Now you have your routes loaded on your application automatically as you create a new .js file inside the routes folder.

How it works...

require.context is a webpack built-in function that allows you to pass in a directory to search, a flag indicating whether subdirectories should be examined too, and a regular expression to match files.

When the building process starts, webpack will search for all the require.context functions and will pre-execute them, so the files needed on the import will be there for the final build.

We pass three arguments to the function: the first is the folder where it will start the search, the second asks whether the search will go to descending folders, and finally, the third is a regular expression for filename matching.

In this recipe, to automatically load the routes as the first argument of the function, we define ./routes for the folder. As the second argument of the function, we define false to not search in subdirectories. Finally, as the third argument, we define /^(?!.*test).*.js$/is as the Regex to search for .js files and ignore the files that have .test in their names.

There's more...

With this recipe, it's possible to take your application to the next level by using the subdirectories for router modules and environments for router control.

With those increments, the function may be extracted to another file, but in router.js, it still needs to be imported into the main.js file. Or, you can obtain the import function, and pass the array of routes to router.js.

See also

Read more about webpack dependency management and require.context in the webpack documentation at https://webpack.js.org/guides/dependency-management/.

Automatically loading Vuex modules

Sometimes, when we are working on a big project, we need to manage a lot of imported Vuex modules and stores. To handle those modules, we always need to import them by creating a file that will have all the files imported and then export those to the Vuex store creation.

In this recipe, we will learn about a function that uses the webpack require.context function to automatically load and inject those files into the Vuex store creation.

Getting ready

The pre-requisite for this recipe is as follows:

  • Node.js 12+

The Node.js global objects that are required are as follows:

  • @vue/cli

  • @vue/cli-service-global

We will need to create a new Vue project with Vue-CLI, or use the project created in previous recipes:

  1. We need to open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue create vuex-import

  1. The CLI will ask some questions that will help with the creation of the project. You can use the arrow keys to navigate, the Enter key to continue, and the spacebar to select an option.

  1. There are two methods for starting a new project. The default method is a basic babel and eslint project without any plugins or configuration, and the Manually mode, where you can select more modes, plugins, linters, and options. We will go for Manually:

? Please pick a preset: (Use arrow keys)
default (babel, eslint)
Manually select features

  1. Now we are asked about the features that we will want on the project. Those features are some Vue plugins such as Vuex or Router (vue-router), testers, linters, and more. Select BabelVuex, and Linter / Formatter:

? Check the features needed for your project: (Use arrow keys)
Babel
TypeScript
Progressive Web App (PWA) Support
Router
Vuex
CSS Pre-processors
Linter / Formatter
Unit Testing
E2E Testing

  1. Continue this process by selecting a linter and formatter. In our case, we will select the ESLint + Airbnb config:

? Pick a linter / formatter config: (Use arrow keys)
ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier

  1. After the linting rules are set, we need to define when they are applied to your code. They can be either applied on save or fixed on commit:

? Pick additional lint features: (Use arrow keys)
Lint on save
Lint and fix on commit

  1. After all those plugins, linters, and processors are defined, we need to choose where the settings and configs are stored. The best place to store them is in a dedicated file, but it is also possible to store them in package.json:

? Where do you prefer placing config for Babel, ESLint, etc.? (Use 
arrow keys)
In dedicated config files
In package.json

  1. Now you can choose whether you want to make this selection a preset for future projects, so you don't need to reselect everything:

? Save this as a preset for future projects? (y/N) n

Vue-CLI will create the project, and automatically install the packages for us.

If you want to check the project on vue-ui when the installation has finished, open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue ui

Or you can run the built-in npm commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following commands:

  • npm run serve – To run a development server locally

  • npm run build   – To build and minify the application for deployment

  • npm run lint  – To execute the lint on the code

How to do it...

Follow these instructions to create an auto-import of the vuex modules in your project that will handle the router files inside a specific folder:

  1. With our route files created and placed inside the store folder, we need to make sure that every store file has a default export object in it. In the index.js file, inside the src/store folder, we will need to extract the array of stores or modules:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({});

  1. Create another file named loader.js in the src/store folder (which will be our module loader). It's important to remember that when using this recipe, you will use vuex namespaced because all the stores need to be used as a module and need to be exported in a single JavaScript object. Each filename will be used as a reference to a namespace, and it will be parsed to a camelCase text style:

const toCamel = (s) => s.replace(/([-_][a-z])/ig, (c) =>
c.toUpperCase()
.replace(/[-_]/g, ''));
const requireModule = require.context('./modules/', false,
/^(?!.*test).*.js$/is);
const modules = {};

requireModule.keys().forEach((fileName) => {
const moduleName = toCamel(fileName.replace(/(./|.js)/g, ''));

modules[moduleName] = {
namespaced: true,
...requireModule(fileName).default,
};
});

export default modules;

  1. As we will be importing by default each file inside the modules folder, a good practice is to create a file for each moduleFor example, as you will be creating a module named useryou need to create a file named user.js that imports all the stores actions, mutations, getters, and state. Those can be placed inside a folder that has the same name as the module. The modules folder will have a structure similar to this:

modules
├── user.js
├── user
│ └── types.js
│ └── state.js
│ └── actions.js
│ └── mutations.js
│ └── getters.js
└───────

The user.js file inside the src/store/modules folder will look like this:

import state from './user/state';
import actions from './user/actions';
import mutations from './user/mutations';
import getters from './user/getters';

export default {
state,
actions,
mutations,
getters,
};

  1. In the index.js file in the src/store folder, we need to add the imported modules that were automatically loaded:

import Vue from 'vue';
import Vuex from 'vuex';
import modules from './loader';

Vue.use(Vuex);

export default new Vuex.Store({
modules,
});

Now you have your vuex modules loaded on your application automatically as you create a new .js file inside the src/store/modules folder.

How it works...

require.context is a webpack built-in function that receives a directory to execute a search, a Boolean flag indicating whether subdirectories are included in this search, and a regular expression for the pattern matching for the filename (all as arguments).

When the building process starts, webpack will search for all the require.context functions, and will pre-execute them, so the files needed on the import will be there for the final build.

In our case, we passed ./modules for the folder, false to not search in subdirectories, and /^(?!.*test).*.js$/is as the Regex to search for .js files and ignore the files that have .test in their names.

Then, the function will search for the files and will pass the result through a for loop to add the content of the files in the array of vuex modules.

See also

Read more about webpack dependency management and require.context in the webpack documentation at https://webpack.js.org/guides/dependency-management/.

Creating a custom directive

Talking about visual frameworks such as Vue, we always think about components, rendering, and visual elements, and we forget that there are a lot of things besides the components themselves.

There are the directives that make the components work with the template engine, which are the binding agents between the data and the visual result. And there are built-in directives in the core of Vue, such as v-ifv-else, and v-for.

In this recipe, we will learn how to make our directive.

Getting ready

The pre-requisite for this recipe is as follows:

  • Node.js 12+

The Node.js global objects that are required are as follows:

  • @vue/cli

  • @vue/cli-service-global

We will need to create a new Vue project with Vue-CLI, or use the project created in previous recipes:

  1. We need to open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue create vue-directive

  1. The CLI will ask some questions that will help with the creation of the project. You can use the arrow keys to navigate, the Enter key to continue, and the spacebar to select an option.

  1. There are two methods for starting a new project. The default method is a basic babel and eslint project without any plugins or configuration, and the Manually mode, where you can select more modes, plugins, linters, and options. We will go for Manually:

? Please pick a preset: (Use arrow keys)
default (babel, eslint)
Manually select features

  1. Now we are asked about the features that we want on the project. Those features are some Vue plugins such as Vuex or Router (vue-router), testers, linters, and more. Select Babel and Linter / Formatter:

? Check the features needed for your project: (Use arrow keys)
Babel
TypeScript
Progressive Web App (PWA) Support
Router
Vuex
CSS Pre-processors
Linter / Formatter
Unit Testing
E2E Testing

  1. Continue this process by selecting a linter and formatter. In our case, we will select the ESLint + Airbnb config:

? Pick a linter / formatter config: (Use arrow keys)
ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier

  1. After the linting rules are set, we need to define when they are applied to your code. They can be either applied on save or fixed on commit:

? Pick additional lint features: (Use arrow keys)
Lint on save
Lint and fix on commit

  1. After all those plugins, linters, and processors are defined, we need to choose where the settings and configs are stored. The best place to store them is in a dedicated file, but it is also possible to store them in package.json:

? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
In dedicated config files
In package.json

  1. Now you can choose whether you want to make this selection a preset for future projects so you don't need to reselect everything:

? Save this as a preset for future projects? (y/N) n

Vue-CLI will create the project, and automatically install the packages for us.

If you want to check the project on vue-ui when the installation has finished, open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue ui

Or, you can run the built-in npm commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following commands:

  • npm run serve – To run a development server locally

  • npm run build  – To build and minify the application for deployment

  • npm run lint  – To execute the lint on the code

How to do it...

Follow these instructions to create a directive for a masked input field:

  1. Create a file named formMaskInputDirective.js in the src/directives folder, and a file named tokens.js in the same folder.

  2. In the tokens.js file, we will add our mask base tokens. Those tokens will be used to identify the kind of value our input will accept:

export default {
"#": { pattern: /[x2Ad]/ },
0: { pattern: /d/ },
9: { pattern: /d/ },
X: { pattern: /[0-9a-zA-Z]/ },
S: { pattern: /[a-zA-Z]/ },
A: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleUpperCase() },
a: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleLowerCase() },
"!": { escape: true }
};

  1. We import the token from token.js and create our functions:

import tokens from './tokens';

function maskerValue() {
  // Code will be developed in this recipe
}

function eventDispatcher() {
  // Code will be developed in this recipe
}

function maskDirective() {
 // Code will be developed in this recipe
}

export default maskDirective;

  1. In the maskDirective function, we will need to check for the binding value on the directive that is passed by the callee of the directive and check whether it's a valid binding. To do so, we will first check whether the value property is present on the binding argument, and then add it to the config variable with the tokens that were imported:

function maskDirective(el, binding) {
  let config;

  if (!binding.value) return false;

  if (typeof config === 'string') {
    config = {
      mask: binding.value,
      tokens,
    };
  } else {
    throw new Error('Invalid input entered');
  }

  1. Now we need to check for the element and validate whether it's an input HTML element. To do so, we will check whether the element that was passed down by the directive has a tagName of input, and if it doesn't, we will try to find an input HTML element in the element that was passed down:

let element = el;

  if (element.tagName.toLocaleUpperCase() !== 'INPUT') {
    const els = element.getElementsByTagName('input');

    if (els.length !== 1) {
      throw new Error(`v-input-mask directive requires 1 input, 
found ${els.length}`); } else { [element] = els; } }

  1. Now we need to add an event listener to the input on the element. The listener will call two external functions, one for dispatching the events and another to return the masked value to the input:

element.oninput = (evt) => {
    if (!evt.isTrusted) return;
    let position = element.selectionEnd;

    const digit = element.value[position - 1];
    element.value = maskerValue(element.value, config.mask, 
config.tokens); while ( position < element.value.length && element.value.charAt(position - 1) !== digit ) { position += 1; } if (element === document.activeElement) { element.setSelectionRange(position, position); setTimeout(() => { element.setSelectionRange(position, position); }, 0); } element.dispatchEvent(eventDispatcher('input')); }; const newDisplay = maskerValue(element.value, config.mask,
config.tokens); if (newDisplay !== element.value) { element.value = newDisplay; element.dispatchEvent(eventDispatcher('input')); } return true; }
// end of maskDirective function

  1. Let's create the eventDispatcher function; this function will emit the events that will be listened to by the v-on directive:

function eventDispatcher(name) {
  const evt = document.createEvent('Event');

  evt.initEvent(name, true, true);

  return evt;
}

  1. And now the complicated part: returning the masked input value to the input. To do so, we will need to create the maskerValue function. This function receives the value, mask, and token as parameters. The function checks for the current value against the mask, to see whether the mask is complete or the value is of a valid token. If everything's okay, it will pass the value to the input:

function maskerValue(v, m, tkn) {
const value = v || '';

const mask = m || '';

let maskIndex = 0;

let valueIndex = 0;

let output = '';

while (maskIndex < mask.length && valueIndex < value.length) {
let maskCharacter = mask[maskIndex];
const masker = tkn[maskCharacter];
const valueCharacter = value[valueIndex];

if (masker && !masker.escape) {
if (masker.pattern.test(valueCharacter)) {
output += masker.transform ?
masker.transform(valueCharacter) : valueCharacter;
maskIndex += 1;
}

valueIndex += 1;
} else {
if (masker && masker.escape) {
maskIndex += 1;
maskCharacter = mask[maskIndex];
}

output += maskCharacter;

if (valueCharacter === maskCharacter) valueIndex += 1;

maskIndex += 1;
}
}

let outputRest = '';
while (maskIndex < mask.length) {
const maskCharacter = mask[maskIndex];

if (tkn[maskCharacter]) {
outputRest = '';
break;
}

outputRest += maskCharacter;

maskIndex += 1;
}

return output + outputRest;
}
//end of maskerValue function

  1. With our file ready, we need to import the mask directive in the main.js file and add the directive to Vue, giving the directive the name 'input-mask':

import Vue from 'vue';
import App from './App.vue';
import InputMaskDirective from './directives/formMaskInputDirective';

Vue.config.productionTip = false;

Vue.directive('input-mask', InputMaskDirective);

new Vue({
render: (h) => h(App),
}).$mount('#app');

  1. To use the directive on our application, we need to call the directive on an input HTML element inside a single file component <template> section, passing the token template '###-###-###' in the v-input-mask directive like this:

<template>
<div id="app">
<input
type="text"
v-input-mask="'###-###-###'"
v-model="inputMask"
/>
</div>
</template>

<script>
export default {
name: 'app',
data: () => ({
inputMask: '',
}),
};
</script>

How it works...

A Vue directive has five possible hooks. We used just one, bind. It's bound directly to the element and component. It gets three arguments: elementbinding, and vnode.

When we add the directive in the main.js file to Vue, we make it available everywhere in our application, so the directive is already at App.vue to be used by the input.

At the same time we call v-input-mask on the input element, we pass the first argument, element, to the directive, and the second argument, binding, is the value of the attribute.

Our directive works by checking each new character value on the input. A Regex test is executed and validates the character to see whether it is a valid character on the token list that was given on the directive instantiation. Then, it returns the character if it passes the test, or returns nothing if it's an invalid character.

Creating a Vue plugin

Sometimes a new addition to your application is needed, and this addition needs to be shared. The best way to share it is by using a plugin. In Vue, a plugin is an addition to the Vue global prototype by extending the initialized application with new features such as directives, mixings, filters, prototype injection, or totally new functions.

Now we will learn how to make our plugin, and how we can use it to interact with Vue as a whole (without messing with the prototype and breaking it).

Getting ready

The pre-requisite for this recipe is as follows:

  • Node.js 12+

The Node.js global objects that are required are as follows:

  • @vue/cli

  • @vue/cli-service-global

We will need to create a new Vue project with the Vue-CLI, or use the project created in previous recipes:

  1. We need to open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue create vue-plugin

  1. The CLI will ask some questions that will help with the creation of the project. You can use the arrow keys to navigate, the Enter key to continue, and the spacebar to select an option.

  1. There are two methods for starting a new project. The default method is a basic babel and eslint project without any plugins or configuration, and the Manually mode, where you can select more modes, plugins, linters, and options. We will go for Manually:

? Please pick a preset: (Use arrow keys)
default (babel, eslint)
Manually select features

  1. Now we are asked about the features that we want on the project. Those features are some Vue plugins such as Vuex or Router (vue-router), testers, linters, and more. Select Babel, and Linter / Formatter:

? Check the features needed for your project: (Use arrow keys)
Babel
TypeScript
Progressive Web App (PWA) Support
Router
Vuex
CSS Pre-processors
Linter / Formatter
Unit Testing
E2E Testing

  1. Continue this process by selecting a linter and formatter. In our case, we will select the ESLint + Airbnb config:

? Pick a linter / formatter config: (Use arrow keys)
ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier

  1. After the linting rules are set, we need to define when they are applied to your code. They can be either applied on save or fixed on commit:

? Pick additional lint features: (Use arrow keys)
Lint on save
Lint and fix on commit

  1. After all those plugins, linters, and processors are defined, we need to choose where the settings and configs are stored. The best place to store them is in a dedicated file, but it is also possible to store them in package.json:

? Where do you prefer placing config for Babel, ESLint, etc.? (Use
arrow keys)
In dedicated config files
In package.json

  1. Now you can choose whether you want to make this selection a preset for future projects, so you don't need to reselect everything:

? Save this as a preset for future projects? (y/N) n

Vue-CLI will create the project, and automatically install the packages for us.

If you want to check the project on vue-ui when the installation has finished, open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> vue ui

Or, you can run the built-in npm commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following commands:

  • npm run serve – To run a development server locally

  • npm run build  – To build and minify the application for deployment

  • npm run lint  – To execute the lint on the code

How to do it...

Writing a Vue plugin is simple, and there is no need to learn more about Vue itself. The basic concept of a plugin is an object that needs to have an install function, which will be executed when called by the Vue.use() method. The install function will receive two arguments: Vue, and the options that will be used to instantiate the plugin.

Follow these instructions to write a plugin that adds two new functions to the Vue global prototype, $localStorage and $sessionStorage:

  1. In our project, we need to create a file inside the src/plugin folder named storageManipulator.js.

  2. In this file, we will create the plugin installation object – we'll add the default plugin options and the base prototype for the functions:

/* eslint no-param-reassign: 0 */

const defaultOption = {
useSaveFunction: true,
useRetrieveFunction: true,
onSave: value => JSON.stringify(value),
onRetrieve: value => JSON.parse(value),
};

export default {
install(Vue, option) {
const baseOptions = {
...defaultOption,
...option,
};

Vue.prototype.$localStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code

Vue.prototype.$sessionStorage = generateStorageObject(
window.localStorage,
baseOptions,
); // We will add later this code
},
};

  1. Now we need to create the generateStorageObject function. This function will receive two arguments: the first will be the window storage object, and the second will be the plugin options. With this, it will be possible to generate the object that will be used on the prototype that will be injected into Vue:

const generateStorageObject = (windowStorage, options) => ({
set(key, value) {
windowStorage.setItem(
key,
options.useSaveFunction
? options.onSave(value)
: value,
);
},

get(key) {
const item = windowStorage.getItem(key);
return options.useRetrieveFunction ? options.onRetrieve(item) :
item;
},

remove(key) {
windowStorage.removeItem(key);
},

clear() {
windowStorage.clear();
},
});

  1. We need to import the plugin into the main.js, and then with the Vue.use function, install the plugin in our Vue application:

import Vue from 'vue';
import App from './App.vue';
import StorageManipulatorPlugin from './plugin/storageManipulator';

Vue.config.productionTip = false;

Vue.use(StorageManipulatorPlugin);

new Vue({
render: h => h(App),
}).$mount('#app');

Now you can use the plugin anywhere in your Vue application, calling the this.$localStorage method or this.$sessionStorage.

How it works...

The Vue plugin works by adding all the code that was instructed to be used to the Vue application layer (like a mixin). 

When we used Vue.use() to import our plugin, we told Vue to call the install() function on the object of the imported file and executed it. Vue will automatically pass the current Vue as the first argument, and the options (if you declare them) as the second argument.

In our plugin, when the install() function is called, we first create baseOptions, merging the default options with the passed parameter, then we inject two new properties into the Vue prototype. Those properties are now available everywhere because the Vue parameter that was passed is the Vue global being used in the application.

Our generateStorageObject is a pure abstraction of the Storage API of the browser. We use it as a generator for our prototypes inside the plugin.

See also

You can find more information about Vue plugins at https://vuejs.org/v2/guide/plugins.html.

You can find a curated list of awesome Vue plugins at https://github.com/vuejs/awesome-vue.

Creating an SSR, SPA, PWA, Cordova, and Electron application in Vue with Quasar

Quasar is a framework based on Vue and Material Design that takes advantage of "write once, use everywhere."

The CLI can deploy the same code base to different flavors, such as Single-Page Application (SPA), Server-Side Rendering (SSR), Progressive Web Application (PWA), Mobile Application (Cordova), and Desktop Application (Electron).

This takes some of the problems away from the developer, such as configuring webpack, Cordova, and Electron with HMR (Hot Module Reload) for development, or adding an SSR configuration in the SPA project. The framework helps the developer start production as soon as possible. 

In this recipe, we will learn how to use Quasar and the CLI to create a basic project, and how to use the CLI to add the development targets for SPA, PWA, SSR, Mobile Application, and Desktop Application.

Getting ready

The pre-requisite for this recipe is as follows:

  • Node.js 12+

The Node.js global object that is required is as follows:

  • @quasar/cli

We will need to create a new Quasar project with the Quasar CLI, or use the project created in previous recipes.

To do it, open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> quasar create quasar-project

Now, when asked, we need to choose to manually select features:

  1. Quasar-CLI will ask you for a project name. Define your project name. In our case, we choose quasar_project:

> Project name: quasar_project

  1. Then Quasar-CLI will ask for a project product name. This will be used by mobile apps to defined their title name. In our case, we stayed with the default name provided:

> Project product name (must start with letter if building mobile 
apps) (Quasar App)

  1. Now Quasar-CLI will ask for a project description. This is used for a meta tag in search engines when the page is shared. In our case, we used the default description provided:

> Project description: (A Quasar Framework app)

  1. Then Quasar-CLI will ask for the project author. Fill this with a package.json valid name (for example, Heitor Ribeiro<[email protected]>):

> Author: <You>

  1. Now it's time to choose the CSS preprocessor. In our case, we will go with Sass with indented syntax:

Pick your favorite CSS preprocessor: (can be changed later) (Use arrow keys)
Sass with indented syntax (recommended)
Sass with SCSS syntax (recommended)
Stylus
None (the others will still be available)

  1. Then Quasar-CLI will ask about the import strategy for the components and directives. We will use the default auto-import strategy:

Pick a Quasar components & directives import strategy: (can be   
changed later) (Use arrow keys )
* Auto-import in-use Quasar components & directives - also
treeshakes Quasar; minimum bundle size
* Import everything from Quasar - not treeshaking Quasar;
biggest
bundle size

  1. Now we need to choose the extra features for the project. We will select EsLint:

Check the features needed for your project: EsLint

  1. After that, Quasar-CLI will ask for a preset for ESLint. Choose the Airbnb preset:

Pick an ESLint preset: Airbnb

  1. Finally, Quasar-CLI will ask for the application you want to use to install the dependencies of the project. In our case, we used yarn because we have installed it already (but you can choose the one you prefer):

Continue to install project dependencies after the project has been 
created? (recommended)
(Use arrow keys)
Yes, use Yarn (recommended)
Yes, use npm
No, I will handle that myself

Now open the created folder in your IDE or code editor.

How to do it...

When using Quasar to create an application, you always need to choose a flavor to start, but the main code will be an SPA. Therefore, the other flavors will have their special treats and delicacies based on their needs, but you can personalize and make your build execute some code based on the build environment.

Developing an SPA (Single-Page Application)

Starting the development of an SPA is an out-of-the-box solution; there is no need to add any new configuration.

So let's start adding a new page to our application. Open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> quasar new page About

Quasar-CLI will automatically create the Vue page for us. We need to add the reference to the page in the router file, and the page will be available on the application:

  1. To do it, we need to open the routes.js file in the src/router folder, and add the About page:

const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', name: 'home', component: () =>
import('pages/Index.vue') },
{ path: 'about', name: 'about', component: () =>
import('pages/About.vue') },
],
},
{
path: '*',
component: () => import('pages/Error404.vue'),
}
];

export default routes;

  1. Then open the About.vue file in the src/pages folder. You will find that the file is a single file component that has an empty QPage component in it, so we need to add a basic title and page indication in the <template> section:

<template>
<q-page
padding
class="flex flex-start"
>
<h1 class="full-width">About</h1>
<h2>This is an About Us Page</h2>
</q-page>
</template>

<script>
export default {
name: 'PageAbout',
};
</script>

  1. Now, in the MainLayout.vue file, in the src/layouts folder, to the q-drawer component, we need to add the links to the Home and About page:

<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn flat dense round
@click="leftDrawerOpen = !leftDrawerOpen"
aria-label="Menu">
<q-icon name="menu" />
</q-btn>

<q-toolbar-title>
Quasar App
</q-toolbar-title>

<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>

<q-drawer v-model="leftDrawerOpen"
bordered content-class="bg-grey-2">
<q-list>
<q-item-label header>Menu</q-item-label>
<q-item clickable tag="a" :to="{name: 'home'}">
<q-item-section avatar>
<q-icon name="home" />
</q-item-section>
<q-item-section>
<q-item-label>Home</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" :to="{name: 'about'}">
<q-item-section avatar>
<q-icon name="school" />
</q-item-section>
<q-item-section>
<q-item-label>About</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-drawer>

<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>

<script>
export default {
name: "MyLayout",
data() {
return {
leftDrawerOpen: this.$q.platform.is.desktop
};
}
};
</script>

And we are finished with a simple example of an SPA running inside a Quasar framework.

Commands

You can run the Quasar-CLI commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following:

  • quasar dev – To start development mode 

  • quasar build – To build the SPA

Developing a PWA (Progressive Web Application)

To develop a PWA, we first need to inform Quasar that we want to add a new mode of development. Open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> quasar mode add pwa

Quasar-CLI will create a folder called src-pwa that will have our service-workers files, separated from our main code.

To clean the newly added files, and to lint it into the Airbnb format, we need to open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> eslint --fix --ext .js ./src-pwa

The code that we added to the SPA will still be used as our base so that we can add new pages, components, and other functions to it as well, which will be used on the PWA.

So, are you wondering why service-worker is not in the main src folder? This is because those files are exclusively for PWAs, and are not needed in any other case than this one. The same will happen in different build types, such as Electron, Cordova, and SSR.

Configuring quasar.conf on a PWA

For PWA development, you can set some special flags on the quasar.conf.js file in the root folder:

pwa: {
// workboxPluginMode: 'InjectManifest',
// workboxOptions: {},
manifest: {
// ...
},

// variables used to inject specific PWA
// meta tags (below are default values)
metaVariables: {
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'default',
appleTouchIcon120: 'statics/icons/apple-icon-120x120.png',
appleTouchIcon180: 'statics/icons/apple-icon-180x180.png',
appleTouchIcon152: 'statics/icons/apple-icon-152x152.png',
appleTouchIcon167: 'statics/icons/apple-icon-167x167.png',
appleSafariPinnedTab: 'statics/icons/safari-pinned-tab.svg',
msapplicationTileImage: 'statics/icons/ms-icon-144x144.png',
msapplicationTileColor: '#000000'
}
}

Commands

You can run the Quasar-CLI commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following:

  • quasar dev -m pwa – To start development mode as a PWA

  • quasar build -m pwa – To build the code as a PWA

Developing SSR (Server-Side Rendering)

To develop SSR, we first need to inform Quasar that we want to add a new mode of development. Open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> quasar mode add ssr

Quasar-CLI will create a folder called src-ssr that will have our extension and server starter files, separated from our main code.

The extension file is not transpiled by babel and runs on the Node.js context, so it is the same environment as an Express or Nuxt.js application. You can use server plugins, such as database, fileread, and filewrites.

The server starter files will be our index.js file in the src-ssr folder. As the extension, it is not transpiled by babel and runs on the Node.js context. For the HTTP server, it uses Express, and if you configure quasar.conf.js to pass the client a PWA, you can have an SSR with PWA at the same time.

Configuring quasar.conf on SSR

For SSR development, you can configure some special flags on the quasar.conf.js file in the root folder:

ssr: {
pwa: true/false, // should a PWA take over (default: false), or just
// a SPA?
},

Commands

You can run the Quasar-CLI commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following:

  • quasar dev -m ssr – To start development mode as SSR

  • quasar build -m ssr – To build the code as SSR

  • quasar serve – To run an HTTP server (can be used in production)

Developing a mobile application (Cordova)

To develop SSR, we first need to inform Quasar that we want to add a new mode of development. Open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> quasar mode add cordova

Now the Quasar-CLI will ask you some configuration questions:

  1. What is the Cordova app ID? (org.cordova.quasar.app)

  2. May Cordova anonymously report usage statistics to improve the tool over time? (Y/N) N

Quasar-CLI will create a folder called src-cordova, which will have a Cordova project inside.

The folder structure of a Cordova project looks like this:

src-cordova/
├── config.xml
├── packages.json
├── cordova-flag.d.ts
├── hooks/
├── www/
├── platforms/
├── plugins/

As a separate project inside Quasar, to add Cordova plugins, you need to call plugman or cordova plugin add command inside the src-cordova folder.

Configuring quasar.conf on Cordova

For Cordova development, you can set some special flags on the quasar.conf.js file in the root folder:

cordova: {         
iosStatusBarPadding
: true/false, // add the dynamic top padding on
// iOS mobile devices

backButtonExit
: true/false // Quasar handles app exit on mobile phone
// back button

},

Commands

You can run the Quasar-CLI commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following:

If you don't have a Cordova environment already configured on your desktop, you can find more information on how to set it up here: https://quasar.dev/quasar-cli/developing-cordova-apps/preparation#Android-setup.
  • quasar dev -m cordova -T android – To start development mode as an Android Device Emulator

  • quasar build -m cordova -T android – To build the code as Android

  • quasar dev -m cordova -T ios – To start development mode as an iOS device emulator (macOS only)

  • quasar build -m cordova -T ios – To start build mode as an iOS device emulator (macOS only)

Developing a desktop application (Electron)

To develop an SSR, we first need to inform Quasar that we want to add a new mode of development. Open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> quasar mode add electron

Quasar-CLI will create a folder called src-electron, which will have an Electron project inside.

The folder structure for Electron projects looks like this:

src-electron/
├── icons/
├── main-process/
├── electron-flag.d.ts

Inside the icons folder, you will find the icons that electron-packager will use when building your project. In the main-process folder will be your main Electron files, spliced into two files: one that will only be called on development and another that will be called on development and production.

Configuring quasar.conf on Electron

For Electron development, you can set some special flags on the quasar.conf.js file in the root folder:

electron: {
// optional; webpack config Object for
// the Main Process ONLY (/src-electron/main-process/)
extendWebpack (cfg) {
// directly change props of cfg;
// no need to return anything
},

// optional; EQUIVALENT to extendWebpack() but uses webpack-chain;
// for the Main Process ONLY (/src-electron/main-process/)
chainWebpack (chain) {
// chain is a webpack-chain instance
// of the Webpack configuration
},

bundler: 'packager', // or 'builder'

// electron-packager options
packager: {
//...
},

// electron-builder options
builder: {
//...
}
},

The packager key uses the API options for the electron-packager module, and the builder key uses the API options for the electron-builder module.

Commands

You can run the Quasar-CLI commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing one of the following:

  • quasar dev -m electron – To start development mode as Electron

  • quasar build -m electron – To build the code as Electron

How it works...

This is all possible because Quasar framework encapsulates the building, parsing, and bundling for you on the CLI. You don't need to worry about webpack and configurations with Electron, Cordova, or even Babel. 

A simple CLI command can generate an entirely new page, layout, component, store, route, or even a new build for you. As the CLI is just a wrapper around Vue, webpack, Babel, and other tools, you are not tied to using only Quasar visual components. If you don't want to use them, it's possible to not import them and use the power of the CLI for building your application.

See also

You can check out more about Quasar framework in the documentation at https://quasar.dev/introduction-to-quasar.

Read more about SPA development with Quasar at https://quasar.dev/quasar-cli/developing-spa/introduction.

Read more about PWA development with Quasar at https://quasar.dev/quasar-cli/developing-pwa/introduction.

Read more about SSR development with Quasar at https://quasar.dev/quasar-cli/developing-ssr/introduction.

Read more about mobile application development with Quasar at https://quasar.dev/quasar-cli/developing-cordova-apps/introduction.

Read more about the Cordova project at https://cordova.apache.org.

Read more about desktop application development with Quasar at https://quasar.dev/quasar-cli/developing-electron-apps/introduction.

Read more about the Electron project at https://electronjs.org/.

Read more about electron-packager at https://github.com/electron/electron-packager.

Find the electron-packager options API at https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html.

Read more about electron-build at https://www.electron.build/.

Find the electron-build options API at https://www.electron.build/configuration/configuration.

Creating smarter Vue watchers and computed properties

In Vue, using watchers and computed properties is always an excellent solution to check and cache your data, but sometimes that data needs some special treatment or needs to be manipulated differently than expected. There are some ways to give these Vue APIs a new life, helping your development and productivity.

How to do it...

We will divide this recipe into two categories: one for the watchers and another for the computed properties. Some methods are commonly used together, such as the non-cached computed and deep-watched values. 

Watchers

These three watcher recipes were selected to improve productivity and the final code quality. The usage of these methods can reduce code duplication and improve code reuse.

Using method names

All watchers can receive a method name instead of functions, preventing you from writing duplicated code. This will help you avoid re-writing the same code, or checking for values and passing them to the functions:

<script>
export default {
watch: {
myField: 'myFunction',
},
data: () => ({
myField: '',
}),
methods: {
myFunction() {
console.log('Watcher using method name.');
},
},
};
</script>

Immediate calls and deep listening

You can set your watcher to execute as soon as it is created by passing a property immediately and make it execute no matter the value's depth of mutation by calling the deep property:

<script>
export default {
watch: { myDeepField: { handler(newVal, oldVal) { console.log('Using Immediate Call, and Deep Watch'); console.log('New Value', newVal); console.log('Old Value', oldVal); }, deep: true, immediate: true, }, }, data: () => ({ myDeepField: '', }),
};
</script>

Multiple handlers

You can make your watcher execute various handlers at the same time, without needing to set the watch handler to bind to a unique function:

<script>
export default { watch: { myMultiField: [ 'myFunction', { handler(newVal, oldVal) { console.log('Using Immediate Call, and Deep Watch'); console.log('New Value', newVal); console.log('Old Value', oldVal); }, immediate: true, }, ], }, data: () => ({ myMultiField: '', }), methods: { myFunction() { console.log('Watcher Using Method Name'); }, }, };
</script>

Computed

Sometimes computed properties are just used as simple cache-based values, but there is more power to them. Here are two methods that show how to extract this power.

No cached value

You can make your computed property an always updated value, rather than a cached value, by setting the cache property to false:

<script>
export default {
computed: {
field: {
get() {
return Date.now();
},
cache: false,
},
},
};
</script>

Getter and setter

You can add a setter function to your computed property and make it a fully complete data attribute, but not bound to the data.

It's not recommended to do this, but it's possible, and in some cases, you may need to do it. An example is when you have to save a date in milliseconds, but you need to display it in an ISO format. Using this method, you can have the dateIso property get and set the value:

<script>
export default {
data: () => ({
dateMs: '',
}),
computed: {
dateIso: {
get() {
return new Date(this.dateMs).toISOString();
},
set(v) {
this.dateMs = new Date(v).getTime();
},
},
},
};
</script>

See also

You can find more information about the Vue watch API at https://vuejs.org/v2/api/#watch.

You can find more information about the Vue computed API at https://vuejs.org/v2/api/#computed.

Creating a Nuxt.js SSR with Python Flask as the API

Nuxt.js is a server-side rendering framework that renders everything at the server and delivers it loaded. With this process, the page gets the power of SEO and fast API fetching before rendering.

Using it correctly, you can achieve a powerful SPA or PWA with other functions that weren't possible before.

In the backend, Python is an interpreted dynamic language that is fast and stable. With an active user base and quick learning curve, this is perfect for server APIs.

Joining both together, it is possible to get a powerful application deployed as fast as possible.

Getting ready‌

The pre-requisites for this recipe are as follows:

  • Node.js 12+

  • Python

The Node.js global object that is required is as follows:

  • create-nuxt-app

To install create-nuxt-appyou need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> npm install -g create-nuxt-app

For the backend of this recipe, we will use Python. The Python global objects required for this recipe are as follows:

  • flask

  • flask-restful

  • flask-cors

To install flask, flask-restful, and flask-corsyou need to execute the following command in Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows):

> pip install flask
> pip install flask-restful
> pip install flask-cors

How to do it...

We will need to split our recipe into two parts. The first part is the backend part (or API if you prefer), which will be done with Python and Flask. The second part will be the frontend part, and it will run on Nuxt.js in SSR mode.

Creating your Flask API

Our API server will be based on the Python Flask framework. We will need to create a server folder to store our server files and start the development of the server.

You will need to install the following Python packages. To do so, open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following commands:

  • To install the Flask framework, use the following command:

> pip install flask

  • To install the Flask RESTful extension, use the following command:

> pip install flask-restful

  • To install the Flask CORS extension, use the following command:

> pip install flask-cors

Initializing the application

To create our simple RESTful API, we will create a single file and use SQLite3 as a database:

  1. Create a folder named server and create a file named app.py in it:

import sqlite3 as sql
from flask import Flask
from flask_restful import Resource, Api, reqparse
from flask_cors import CORS

app = Flask(__name__)
api = Api(app)
CORS(app)

parser = reqparse.RequestParser()

conn = sql.connect('tasks.db')
conn.execute('CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY
KEY AUTOINCREMENT, task TEXT)')
conn.close()

  1. Then, we will create our ToDo class, and on the constructor of the class, we will connect to the database and select all tasks:

class ToDo(Resource):
def get(self):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('SELECT * from tasks')
tasks = cur.fetchall()
con.close()

return {
'tasks': tasks
}

  1. To implement the RESTful POST method, create a function that receives task as an argument, and will add an object with the task that was added, the status of the addition to the tasks list, and then return the list to the user:

 def post(self):
parser.add_argument('task', type=str)
args = parser.parse_args()

con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('INSERT INTO tasks(task) values ("
{}")'.format(args['task']))
con.commit()
con.close()

return {
'status': True,
'task': '{} added.'.format(args['task'])
}

  1. Next, we will create the RESTful PUT method by creating a function that will receive the task and id as arguments of the function. Then, this function will update task with the current id, and return to the user the updated task and the status of the update:

def put(self, id):
parser.add_argument('task', type=str)
args = parser.parse_args()

con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('UPDATE tasks set task = "{}" WHERE id =
{}'.format(args['task'], id))
con.commit()
con.close()

return {
'id': id,
'status': True,
'task': 'The task {} was updated.'.format(id)
}

  1. Then, create a RESTful DELETE method by creating a function that will receive the ID of the task, which will be removed, and then will return to the user the ID, status, and the task that was removed:

 def delete(self, id):
con = sql.connect('tasks.db')
cur = con.cursor()
cur.execute('DELETE FROM tasks WHERE id = {}'.format(id))
con.commit()
con.close()

return {
'id': id,
'status': True,
'task': 'The task {} was deleted.'.format(id)
}

  1. Finally, we will add the ToDo class as a resource to the API on the '/' route, and initialize the application:

api.add_resource(ToDo, '/', '/<int:id>')

if __name__ == '__main__':
app.run(debug=True)

Starting the server

To start your server, you need to open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> python server/app.py

Your server will be running and listening on http://localhost:5000

Creating your Nuxt.js server

To render your application, you will need to create your Nuxt.js application. Using the Nuxt.js create-nuxt-app CLI, we will create it and choose some options for it. Open Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and execute the following command:

> create-nuxt-app client

Then, you will be asked some questions about the installation process. We will use the following:

  1. When you start creating your project with Nuxt-CLI, it will first ask for the project name. In our case, we will choose client as the name:

Project Name: client

  1. Then you need to choose the programming language that will be used in the project. We will select JavaScript:

> Programming language: (Use arrow keys)
JavaScript
TypeScript

  1. Next, Nuxt-CLI will ask for the package manager that will be used to install the dependencies. In our case, we choose Yarn, but you can choose the one you prefer:

> Package manager: (Use arrow keys) 
Yarn
npm

  1. Now, Nuxt-CLI will ask for a UI framework to be used in the project. From the available list, choose Bulma:

> UI Framework: Bulma

  1. Then, Nuxt-CLI will ask whether you want to select extra modules for the project. We will select Axios from the current list of modules:

> Nuxt.JS modules: Axios

  1. Nuxt-CLI will ask for the linting tools we want to use on our project; we will choose None:

> Choose Linting tools: None

  1. Then, Nuxt-CLI will ask for the test framework we want to implement on our project; we will choose None:

> Choose Test Framework: None

  1. Next, Nuxt-CLI will ask for the rendering mode that will be used by the project; we will select Universal (SSR):

> Choose Rendering Mode: Universal (SSR)

  1. Nuxt-CLI will ask for the deployment target that will be used on the building structure; we will choose Server (Node.js hosting):

> Deployment target: Server (Node.js hosting)

  1. Finally, Nuxt-CLI will ask for the development tool configuration that we want to use; we will select jsconfig.json:

> Development tools: jsconfig.json

After the CLI finishes the installation process, we can open the client folder on our editor or IDE.

Adding Bulma to the global CSS

To add Bulma to the application, we need to declare it in the nuxt configuration file by doing the following:

  1. Open nuxt.config.js, in the client folder.

  2. Then, update the CSS property and add the Bulma import, to make it available in the global scope of the application:

export default {
/* We need to change only the css property for now, */
/* the rest we will maitain the same */
/*
** Global CSS
*/
css: ['bulma/css/bulma.css'],
}

Configuring the axios plugin

To start creating our API calls, we need to add the axios plugin in our application:

  1. To do so, we will need to open the nuxt.config.js, file in the root folder, and add the axios property:

export default {
/* We need to change only the axios property for now, */
/* the rest we will maitain the same */
axios: {},
}

  1. On the axios property, add the following configuration properties:

    • HOST and define it as '127.0.0.1'

    • PORT and define it as '5000'

    • https and define it as false

    •  debug and define it as true:

axios: {
HOST: '127.0.0.1',
PORT: '5000',
https: false,
debug: true, // Only on development
},

Running the Nuxt.js server

Now that you have everything set, you want to run the server and start to see what is going on. Nuxt.js comes with some pre-programmed npm scripts out of the box. You can run one of the following commands by opening Terminal (macOS or Linux) or the Command Prompt/PowerShell (Windows) and executing the following:

  • npm run dev – To run the server in development mode

  • npm run build – To build the files with webpack and minify the CSS and JS for production

  • npm run generate – To generate static HTML pages for each route

  • npm start – To start the server in production, after running the build command

Creating the TodoList component

For the TodoList app, we will need a component that will fetch the tasks and delete the tasks.

Single file component <script> section

Here, we will create the <script> section of the single file component:

  1. In the client/components folder, create a file named TodoList.vue and open it.

  2. Then, we will export a default JavaScript object, with a name property defined as TodoList, then define the beforeMount life cycle hook as an asynchronous function. Define the computed and methods properties as an empty JavaScript object. Then, create a data property defined as a singleton function returning a JavaScript object. In the data property, create a taskList property as an empty array:

export default {
name: 'TodoList',
data: () => ({
taskList: [],
}),
computed: {},
async beforeMount() {},
methods: {},
};

  1. In the computed property, create a new property called taskObject. This computed property will return the result of Object.fromEntries(new Map(this.taskList)):

taskObject() {
return Object.fromEntries(new Map(this.taskList));
},

  1. In the methods property, create a new method called getTask – it will be an asynchronous function. This method will fetch the tasks from the server, then will use the response to define the taskList property:

async getTasks() {
try {
const { tasks } = await
this.$axios.$get('http://localhost:5000');
this.taskList = tasks;
} catch (err) {
console.error(err);
}
},

  1. Then, create a deleteTask method. This method will be an asynchronous function and will receive an id as a parameter. Using this parameter, it will execute an API execution to delete the task and then execute the getTask method:

async deleteTask(i) {
try {
const { status } = await
this.$axios.$delete(`http://localhost:5000/${i}`);
if (status) {
await this.getTasks();
}
} catch (err) {
console.error(err);
}
},

  1. Finally, in the beforeMount life cycle hook, we will execute the getTask method:

async beforeMount() {
await this.getTasks();
},

Single file component <template> section

It's time to create the <template> section of the single file component:

  1. In the client/components folder, open the TodoList.vue file.

  2. In the <template> section, create a div HTML element, and add the class attribute with the value box:

<div class="box"></div>

  1. As a child of the div.box HTML element, create a div HTML element, with the class attribute defined as content, with a child element defined as an ol HTML element and the attribute type defined as 1:

<div class="content">
<ol type="1"></ol>
</div>

  1. As a child of the ol HTML element, create a li HTML element, with the v-for directive defined as (task, i) in taskObject, and the key attribute defined as a variable, i:

<li
v-for="(task, i) in taskObject"
:key="i">
</li>

  1. Finally, as a child of the ol HTML element, add {{ task }} as the inner text, and as a sibling of the text, create a button HTML element, the class attribute defined as delete is-small, and the @click event listener defined as the deleteTask method, passing the i variable as an argument:

{{ task }}
<button
class="delete is-small"
@click="deleteTask(i)"
/>

Creating the Todo form component

To send the task to the server, we will need a form. That means we need to make a form component that will handle this for us.

Single file component <script> section

Here, we will create the <script> section of the single file component:

  1. In the client/components folder, create a file named TodoForm.vue and open it.

  2. Then, we will export a default JavaScript object, with a name property defined as TodoForm, then define the methods property as an empty JavaScript object. Then, create a data property defined as a singleton function returning a JavaScript object. In the data property, create a task property as an empty array:

export default {
name: 'TodoForm',
data: () => ({
task: '',
}),
methods: {},
};

  1. In the methods property, create a method named save, which will be an asynchronous function. This method will send the task to the API, and if the API receives Ok Status, it will emit a 'new-task' event with the task and clean task property:

async save() {
try {
const { status } = await
this.$axios.$post('http://localhost:5000/', {
task: this.task,
});
if (status) {
this.$emit('new-task', this.task);
this.task = '';
}
} catch (err) {
console.error(err);
}
},

Single file component <template> section

It's time to create the <template> section of the single file component:

  1. In the client/components folder, open the TodoForm.vue file.

  1. In the <template> section, create a div HTML element, and add the class attribute with the value box:

<div class="box"></div>

  1. Inside the div.box HTML element, create a div HTML element with the class attribute defined as field has-addons:

<div class="field has-addons"></div>

  1. Then, inside the div.field.has-addons HTML element, create a child div HTML element, with the class attribute defined as control is-expanded, and add a child input HTML element with the v-model directive defined as the task property. Then, define the class attribute as input, the type attribute as text, and placeholder as ToDo Task. Finally, in the @keypress.enter event listener, define the save method:

<div class="control is-expanded">
<input
v-model="task"
class="input"
type="text"
placeholder="ToDo Task"
@keypress.enter="save"
>
</div>

  1. Finally, as a sibling of the div.control.is-expanded HTML element, create a div HTML element, with the class attribute defined as control, and add a child a HTML element, with the class attribute defined as button is-info, and on the @click event listener, define it as the save method. As inner text of the a HTML element, add the Save Task text:

<div class="control">
<a
class="button is-info"
@click="save"
>
Save Task
</a>
</div>

Creating the layout

Now we need to create a new layout to wrap the application as a simple high-order component. In the client/layouts folder, open the file named default.vue, remove the <style> section of the file, and change the <template> section to the following:

<template>   
<nuxt />
</template>

Creating the page

Now we will create the main page of our application, where the user will be able to view their TodoList and add a new TodoItem.

Single file component <script> section

Here, we will create the <script> section of the single file component:

  1. Open the index.vue file in the client/pages folder.

  2. Import the todo-form and the todo-list component that we created, then we will export a default JavaScript object, with a components property with the imported components:

<script>
import TodoForm from '../components/TodoForm.vue';
import TodoList from '../components/TodoList.vue';

export default {
components: { TodoForm, TodoList },
};
</script>

Single file component <template> section

It's time to create the <template> section of the single file component:

  1. In the client/pages folder, open the index.vue file.

  2. In the <template> section, create a div HTML element, add as a child a section HTML element, with the class property defined as hero is-primary. Then, as a child of the section HTML element, create a div HTML element, with the class attribute defined as hero-body. As a child of the div.hero-body HTML element, create a div HTML element with the class attribute defined as container and add as a child an h1 HTML element with class defined as title, with the inner text as Todo App:

<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<h1 class="title">
Todo App
</h1>
</div>
</div>
</section>

  1. As a sibling of the section.hero.is-primary HTML element, create a section HTML element, with the class attribute defined as section and the style attribute defined as padding: 1rem. Add as a child a div HTML element with the class attribute defined as container with a child todo-list component with the ref attribute defined as list:

<section
class="section"
style="padding: 1rem"
>
<div class="container">
<todo-list
ref="list"
/>
</div>
</section>

  1. Finally, as a sibling of the section.section HTML element, create a section HTML element, with the class attribute defined as section and the style attribute defined as padding: 1rem. Add as a child a div HTML element with the class attribute defined as container with a child todo-form component, with the @new-task event listener defined as $refs.list.getTasks():

<section
class="section"
style="padding: 1rem"
>
<div class="container">
<todo-form
@new-task="$refs.list.getTasks()"
/>
</div>
</section>

How it works...

This recipe shows the integration between a local API server via Python and an SSR platform served via Nuxt.js.

When you start the Python server first, you are opening the ports to receive data from clients as a passive client, just waiting for something to happen to start your code. With the same process, the Nuxt.js SSR can do a lot of stuff behind the scenes, but when it finishes, it goes idle, waiting for user action.

When the user interacts with the frontend, the application can send some requests to the server that will be handed back to the user with data, to be shown on the screen.

See also

You can learn more about Flask and the HTTP project inside Python at https://palletsprojects.com/p/flask/.

If you want to learn more about Nuxt.js, you can read the documentation at https://nuxtjs.org/guide/. 

If you want to learn more about the Nuxt.js implementation of Axios and how to configure it and use the plugin, you can read the documentation at https://axios.nuxtjs.org/options.

If you want to learn more about Bulma, the CSS framework used in this recipe, you can find more information at https://bulma.io.

The dos and don'ts of Vue applications

Security is always something everyone is worried about, and this is no different for technology. You need to be aware and alert all the time. In this section, we'll look at how you can prevent attacks with some techniques and simple solutions.

Linters

When using ESLint, make sure you have enabled the Vue plugin, and you are following the strongly recommended rules. Those rules will help you with the development, checking for some common mistakes that can open doors to attacks such as the v-html directive.

In a Vue-CLI project, with the options for linters selected, a file named .eslintrc.js will be created along with the project files. In this file, a set of basic rules will be pre-determined. The following is an example of a set of good practice rules for an ESLint + AirBnb project:

module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'plugin:vue/recommended',
'plugin:vue/strongly-recommended',
'@vue/airbnb',
],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
};

Now, if you have any code that breaks the lint rules, it won't be parsed on development or build.

JavaScript

JavaScript has some vulnerabilities that can be prevented by following some simple checklists and simple implementations. Those implementations can be in client-server communications or DOM manipulation, but you always need to be careful not to forget them.

Here are some tips for using JavaScript:

  • Always use an authenticated and encrypted API when possible. Remember that JWT isn't encrypted by itself; you need to add the layer of encryption (JWE) to have the whole JSON.

  • Always use SessionStorage if you want to store an API token.

  • Always sanitize the HTML input from the user before sending it to the server.

  • Always sanitize the HTML before rendering it to the DOM.

  • Always escape any RegeExp from the user; it will be executed, to prevent any CPU thread attack.

  • Always catch errors and don't show any stack trace to the user, to prevent any code manipulation.

Here are some tips on what not to do when using JavaScript:

  • Never use eval(); it makes your code run slowly and opens a door for malicious code to execute inside your code.

  • Never render any input from the user without any sanitization or escaping the data.

  • Never render any HTML on the DOM without any sanitization.

  • Never store an API token on LocalStorage.

  • Never store sensitive data in the JWT object.

Vue

When developing a Vue application, you need to check for some basic rules that can help the development and won't open any doors for the external manipulation of your application.

Here are some tips for using Vue:

  • Always add type validation to your props, and if possible, a validator check.

  • Avoid the global registration of components; use local components.

  • Always use lazy-loaded components, when possible.

  • Use $refs instead of direct DOM manipulation.

Here are some tips on what not to do when using Vue:

  • Never store Vue, $vm, $store, or any application variable on the window or any global scope.

  • Never modify the Vue prototype; if you need to add a new variable to the prototype, make a new Vue plugin.

  • It's not recommended to use a direct connection between components, as it will make the component bound to the parent or child.

See also

You can find more information about XSS (cross-site scripting) on OWASP CheatCheat at https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md and about HTML XSS at https://html5sec.org/.

Find more information about eslint-vue-plugin at https://eslint.vuejs.org/.

You can read more about Node.js security best practices at https://github.com/i0natan/nodebestpractices#6-security-best-practices.

Find more information about the dos and don'ts of a Vue application at https://quasar.dev/security/dos-and-donts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.180.252