Vue.js pwa Tutorial: Creating Your First Vue.js PWA Project

Vue.js pwa Tutorial: Creating Your First Vue.js PWA Project
Vue.js pwa Tutorial: Creating Your First Vue.js PWA Project In this article, we will cover how to build our first PWA project with the Vue.js framework by implementing the PWA minimum requirements

In our previous article, Why Use Progressive Web Apps, we have identified a few benefits that PWA provides to improve web applications for online and offline usage. With a bit of responsive design, Progressive Web Apps would be perfect for both mobile and desktop markets.

In this article, we will cover how to build our first PWA project with the Vue.js framework by implementing the PWA minimum requirements, configure Webpack to build a caching services worker for offline support and run the Google Lighthouse audit tool to identify improvement points.

Creating the Workspace

Anywhere on your computer, let’s create a directory named pwa-vue-app-1.

Vue.js PWA Project

All work will be performed within this directory so with our preferred choice of editor and terminal, open this directory.

Visual Studio Code is my choice of editor. It comes with an integrated terminal so I will not need to use an external terminal application. This article does not need anything fancy long as you can create files, folders, and execute terminal commands.

Before writing code, we need to perform a few pre-setup tasks. The first task is to create the folder structure to organize source and distribution code. Structures will vary per project and even user preference.

For this project, we will keep the structure simple. In the root directory, we will create directories dist, src, and static.

  • dist — The directory that contains distribution files and will be hosted on an external hosting service, for example, Google Firebase.
  • src — The directory that contains un-built application source code, for example, JS and Vue files.
  • static —The directory contains static files used within the application. The build process copies these files into the dist folder. Example files are images, icons, third-party libraries, PWA manifest, and service worker.

Vue.js PWA 

Additionally, we will create the components directory inside the src directory. The components directory contains all of the individual components view, styling, and logic that our application uses.

Installing NPM Dependencies

The second pre-setup task is to initialize our project with NPM and install dependencies.

To initialize our project with NPM, in the pwa-vue-app-1 directory, execute in a terminal the command:

npm init

The initialize process will ask a few questions about the project. For this article, default values are OK. The package.json file is created once completed. The values can be customized, later, from the package.json file.

Once initialized, we can start installing the build and third-party dependencies. For the build dependencies, we will execute the following command in the root directory:

$ npm i -D @babel/core @babel/preset-env babel-loader clean-webpack-plugin copy-webpack-plugin css-loader html-webpack-plugin vue-loader vue-style-loader vue-template-compiler webpack webpack-cli webpack-dev-server

Build dependencies consist of:

  • Babel and Babel’s preset-env — used for converting newer ECMAScript code into a backward compatible version for older browsers support.
  • Webpack, Webpack plugins and loaders — used as our module bundler.
  • Vue Template Compiler — pre-compiles our Vue templates into render functions to remove runtime-compilation.

Next, third-party dependencies, which are used by the application code, will be installed with the following command in the root directory:

$ npm i vue vue-router

Configuring Webpack

In the root directory of our project, create a file called webpack.config.js and add the contents of the configuration posted below. This will allow us to build and preview our application.

const path = require('path')

const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = (env, argv) => ({
  mode: argv && argv.mode || 'development',
  devtool: (argv && argv.mode || 'development') === 'production' ? 'source-map' : 'eval',

  entry: './src/app.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },

  node: false,

  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ],
        exclude: /\.module\.css$/
      }
    ]
  },

  resolve: {
    extensions: [
      '.js',
      '.vue',
      '.json'
    ],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': path.resolve(__dirname, 'src')
    }
  },

  plugins: [
    new CleanWebpackPlugin(['dist']),
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'static', 'index.html'),
      inject: true
    }),
    new CopyWebpackPlugin([{
      from: path.resolve(__dirname, 'static'),
      to: path.resolve(__dirname, 'dist'),
      toType: 'dir'
    }])
  ],

  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 0,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    },
    runtimeChunk: {
      name: entrypoint => `runtime~${entrypoint.name}`
    },
    mangleWasmImports: true,
    removeAvailableModules: true,
    removeEmptyChunks: true,
    mergeDuplicateChunks: true
  },

  devServer: {
    compress: true,
    host: 'localhost',
    https: true,
    open: true,
    overlay: true,
    port: 9000
  }
});

webpack.config.js

I will point out a few of the options that are important to know but not cover all configurations detail.

  • entry is the property that contains the file(s) which Webpack starts from when bundling. In this example, app.js as our entry point. If the entry file name or path is customized, this property must be updated. You can see this option on line 12 of the sample configuration file.
entry: './src/app.js',
  • CleanWebpackPlugin plugin is used to delete the dist directory before each build, to remove old and unused files. You can see this option on line 55 of the sample configuration file.
new CleanWebpackPlugin(['dist']),
  • resolve.alias is the property that simplifies writing import paths. In this example, @ alias is added to shorten paths to our source code. You can see this option on line 50 of the sample configuration file.
'@': path.resolve(__dirname, 'src')

Writing Example Code

Now that we have our structure, dependencies, and build tool configured, we can begin adding our application code.

index.html

First, create the file index.html in the static directory with the content below.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <title>Hello PWA!</title>
  <meta name="Description" content="First PWA Vue.js App">
</head>
<body>
  <div id="app"></div>
</body>
</html>

index.html

There are two important things about this file.

  1. The div element, inside the body, has an id of app. The Vue initializer uses this id as a target to render in. If the id is modified, the Vue initializer options must also be updated. We will cover the initializer in the app.js file.
  2. The script tags to load the application’s JavaScript is not hardcoded in this file. This file is used as a template by the HtmlWebpackPlugin during the build process. The plugin injects the script tags automatically and places the generated index.html file into the dist directory.

app.js

In the src folder, create the file app.js and add the contents below.

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

app.js

The app.js file is our applications entry file which initializes an instance of our Vue app.

During the instance creation, we pass in the targeting element, routes, accessible components, and templates. I will focus on the element and router options.

  • el is a selector string or HTML element to an existing DOM element. The app will mount to this element. In our example, we passed in a selector string of #app. As mentioned previously, if the id in index.html was customized then this option must also be updated.
  • router is an instance of VueRouter that contains a mapping of routes to components.

router.js

In the src folder create the file router.js and add the contents below.

import Vue from 'vue'
import Router from 'vue-router'

// Page content
import Page1 from '@/components/Page1'
import Home from '@/components/Home'

// Fallback page
import PageNotFound from '@/components/PageNotFound'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/page-1',
      name: 'Page 1',
      component: Page1
    },
    {
      path: '**',
      name: 'PageNotFound',
      component: PageNotFound
    }
  ]
})

router.js

This file contains a collection of known URL routes and maps to its associated component. We also provided a fallback component for unknown routes. This handles the “Page Not Found” use case.

App.vue

In the src folder create the file App.vue and add the contents below.

<template>
  <div>
    <ul>
      <li v-for="(link, index) in links" :key="index">
        <router-link :to="link.to">{{ link.name }}</router-link>
      </li>
    </ul>
    <main>
      <router-view/>
    </main>
  </div>
</template>

<style scoped>
  ul {
    list-style: none;
    display: flex;
    padding: 0;
  }
  li {
    padding: 5px 15px 5px 0;
  }
  li a {
    text-decoration: none;
    color: black;
  }
  li a:hover {
    color: #404040;
  }
</style>

<script>
export default {
  data: () => ({
    links: [
      {
        name: 'Home',
        to: '/'
      },
      {
        name: 'Page 1',
        to: '/page-1'
      },
      {
        name: 'Bad Link',
        to: '/random-bad-url'
      }
    ]
  })
}
</script>

App.vue

This file is the base template of the application. It consists of router-link(s) and a single router-view. Once a link, the router-link, is clicked, the associated component loads into the router-view element.

Component Pages

Create the next three files in the src/components/ directory with its respective content. These files are fillers to provide a working example of routing. The difference between these files is the header text and color.

  • src/components/Home.vue
  • src/components/Page1.vue
  • src/components/PageNotFound.vue
<template>
  <div>
    <h1>Home</h1>
  </div>
</template>

<style scoped>
h1 {
  color: blue
}
</style>

<script>
</script>

Home.vue

<template>
  <div>
    <h1>Page 1</h1>
  </div>
</template>

<style scoped>
h1 {
  color: green
}
</style>

<script>
</script>

Page1.vue

<template>
  <div>
    <h1>Page Not Found</h1>
  </div>
</template>

<style scoped>
h1 {
  color: red
}
</style>

<script>
</script>

PageNotFound.vue

Previewing & Auditing for PWA with Chrome

Now that we have finished adding the code to the project, we can preview and audit the application.

To compare your code with the covered material, you can view the source code on GitHub.

Note: For the covered material up to this point, please make sure to view the no-pwa branch.

To preview the application, we need to first add NPM scripts. In the package.json file, update the property scripts with the following:

"scripts": {
 "dev": "webpack-dev-server --progress"
},

The dev script uses the webpack-dev-server dependency to start a development web server.

Let’s preview the application, by running the npm run dev command in the project’s root directory. It should automatically open your default browser to https://localhost:9000. If you need to change the port, you can modify the server configurations in the webpack.config.js file.

Webpack development server is perfect for viewing and testing application code during development!

Google Chrome — Lighthouse Audits

For auditing, I recommend deploying the application to a hosting service such as Firebase. Running an audit against your application on a hosted service can provide a more accurate result. This article will not cover how to deploy but will display the results from each audit.

One of the PWA requirements is that the application is hosted and served from an HTTPS protocol with valid SSL certificates. We had configured webpack-dev-server to use a self-assigned certificate but, it still does not meet the PWA requirements. This tool is strictly for development and testing and will always lack the features and settings from that a production ready web server provides.

To run Google Lighthouse for auditing, we need to open the DevTools. If you are a Mac user, you can use the following shortcut command Command+Option+I. If you use Window, you can press either F12 or Control+Shift+I. Next click on the Audits tab at the top. You should see something like this:

Next, click on the Run audits button at the bottom and watch it perform. Once the audit is complete, it will point out areas that need improvement for a higher PWA score as well as performance, accessibility, best practices, and SEO.

Comparing the two screenshot above, we can see that our application on Firebase has a higher PWA, performance, and best practices score then locally. Firebase hosting takes care of some of the server side requirements such as HTTP redirect to HTTPS.

Below, is a list of items where the application failed to meet the requirements for a high PWA score.

In the next sections, we will implement the minimum requirements to make our application PWA as well as covering the items from the PWA checklist.

Implementing the Minimum PWA Requirements

Let’s start adding the minimum PWA requirements and correction to increase our PWA score.

manifest.json

First, we will add the manifest.json file in the static directory with the following content:

{
  "short_name": "FirstVuePWA",
  "name": "First Vue PWA App",
  "icons": [
    {
      "src": "/192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/?utm_source=a2hs",
  "background_color": "#FFFFFF",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#FFFFFF"
}

manifest.json

This file contains the necessary information for installing to the home screen, creating a custom splash screen, and details about the application.

Next, add in the head tag of the index.html file the following line:

<link rel="manifest" href="/manifest.json" />

Icon

In the manifest.json file, we defined our application icons. In the Google Developers Web Fundamentals documentation, it is recommended that both 512 x 512 px icon and 192 x 192 px icons are provided. These icons are used for the home screen, install notification, and custom splash screen.

For this example, I made the icons with Photoshop and placed them in the static directory. You can get mine from here:

Service Worker

To simplify building our service worker for caching, we will use the Webpack plugin sw-precache-webpack-plugin. This plugin automatically creates a service worker file with a collection of the file paths that had been created by Webpack.

  • Install New Development Dependency

In the root directory of the project run the command.

npm i -D sw-precache-webpack-plugin
  • Update webpack.config.js

Add to the top line:

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin')

Add in the plugins option array the plugin usage:

new SWPrecacheWebpackPlugin({
  cacheId: 'my-pwa-vue-app',
  filename: 'service-worker-cache.js',
  staticFileGlobs: ['dist/**/*.{js,css}', '/'],
  minify: true,
  stripPrefix: 'dist/',
  dontCacheBustUrlsMatching: /\.\w{6}\./
})

Below plugin:

new CopyWebpackPlugin([{
  from: path.resolve(__dirname, 'static'),
  to: path.resolve(__dirname, 'dist'),
  toType: 'dir'
}]),

Adding noscript

If for any reason an end user has JavaScript disabled on their browser, we need to notify, with the noscript tag, that our application requires JavaScript.

To fix this:

Open the file static/index.html and add to the body element line:

<noscript>To run this application, JavaScript is required to be enabled.</noscript>

Set Address Bar Background

Open the file static/index.html and add to the head element line:

<meta name="theme-color" content="#FFFFFF" />

Second Lighthouse Audit

Let’s run the second audit to see how our application scores after implementing the minimum PWA requirements and corrections from the first audit.

To compare the PWA implementation code with the covered material, you can view the source code on GitHub.

Note: The current material is on the pwa branch.

Conclusion

As you can see, creating a PWA project with the Vue.js framework is very easy. The initial setup of the workspace, dependencies, and build configurations only takes a couple of minutes of our time, but after that, it all application code.

Additionally, with the Google Lighthouse audit system, we can periodically test our application to pinpoint areas that need attention and get access to resources that give detail explanation of each issue and possible solutions.

Lastly, using a bundler tool such as Webpack provides many great advantages. Some of these advantages are simplifying and speeding up the development and building. As we saw in this example, we used the pre-cache service worker plugin create our service worker and the clean plugin to keep our distribution package clean.

Hope you find this useful and gives you a great starting point for your first PWA project.

30s ad

Vue.js 2 Essentials: Build Your First Vue App

Nuxt: Supercharged Vue JS

Vue.js Fast Crash Course

Vue JS 2: From Beginner to Professional (includes Vuex)

Vue.js Essentials - 3 Course Bundle

Suggest:

Vue js Tutorial Zero to Hero || Brief Overview about Vue.js || Learn VueJS 2023 || JS Framework

Build a Quiz App Using Vue JS | Vue JS Quiz App Demo | Vue JS Project Tutorial | Simplilearn

Vue.js Tutorial: Zero to Sixty

Learn Vue.js from scratch 2018

Create Shopping basket page With Vuejs and Nodejs

Learn Vue.js from Scratch - Full Course for Beginners