DEV Community

Piotr Ładoński
Piotr Ładoński

Posted on

Vue 3, PWA & service worker

Recently I started playing with Vue. Of course, starting with "hello world" calculator with well-documented Vue 2 was not an option, so I decided to go with simple PWA in Vue 3. Setting up a project wasn't as easy as it may appear, so I'll describe it here for anyone interested (and future reference for myself).

I'll describe everything from (nearly) scratch, so hopefully it'll be of use for complete beginners. I will not explain philosophy of Vue, PWA or service workers - it will be just about setting those things up.

I'm using Win10, so I'll describe the process from this PoV (however, it matters only for Node installation).

Node, npm and Vue

As with all JS projects, it's simpler to do it with Node & npm.

If you don't already have them, I recommend installing Node with nvm. Probably the easiest way is just going here, downloading latest nvm-setup.zip, extracting and running the installer. After that, you should be able to use nvm in your command prompt. If you want to install latest stable version just go with:

nvm install latest
Enter fullscreen mode Exit fullscreen mode

For some particular version you can execute

nvm install 15.4.0
Enter fullscreen mode Exit fullscreen mode

Then, remember to use it!

nvm use 15.4.0
Enter fullscreen mode Exit fullscreen mode

With Node, npm should be automatically installed as well. For me, Node version is 15.4.0 and npm is 7.3.0.

To make our lives easier, there's also Vue CLI which helps with setting up the project. Install it with:

npm install -g @vue/cli
Enter fullscreen mode Exit fullscreen mode

It will allow you to use vue command from your terminal. For me, vue --version returns @vue/cli 4.5.9.

Now, we can start with our mini-project.

Creating project

Creating a new project with Vue CLI is extremely simple. Just go with:

vue create our-app-name
Enter fullscreen mode Exit fullscreen mode

Then using arrows just select the options. I picked:

Manually select features

and then selected with spacebar Progressive Web App (PWA) support. Press Enter to continue, then choose Vue version to 3.x, ESLint with error prevention only, Lint on save, In dedicated config files, type n and press Enter to generate the project (it will take 1-2 minutes).

Of course you can choose different options. Only PWA support is necessary

Running it

Generated project is runnable out of the box. First of all, remember to navigate to the created project folder, then run development server:

cd our-app-name
npm run serve
Enter fullscreen mode Exit fullscreen mode

Output should give you addresses where you can access your generated app. For me it's http://localhost:8080/ (if you want to stop it, just CTRL+C it)

Note that currently service worker is not working - if you go to Application > Service worker in DevTools you won't see it. Generated project makes service worker active only in production build. Let's check it.

To create a production build, run

npm run build
Enter fullscreen mode Exit fullscreen mode

Give it some time and it will create dist directory in your project folder. Now you need to host it somewhere. I'd recommend Web Server for Chrome, as it's very easy to use and works fine (I tried also Python simple http server, but it didn't work correctly for me, so watch out for that). Just select your dist folder in the server and run it. At http://127.0.0.1:8000 you should be able to access your site. Now you can find information about service worker in the Application tab of DevTools and see some console logs about it.

Taming service worker

That's great! Everything works! So what's the problem? Problem appears, when you want to control caching with service worker by yourself and check it during development without constantly creating production builds.

I'll show 3 things now:

  1. How to run service worker in development server
  2. How to control cache behavior
  3. How to use external modules in production build

SW in dev server

Quick warning - SW is disabled in development by default, because it may cache some newly edited scripts/assets and you won't be able to see your changes. Keep that in mind and disable SW in dev if you don't need it to avoid "Why it doesn't change?!" problems.

Another warning - it's probably not the best, most optimal way to do that... but it's simple and works :)

Case: we want to have service worker active in development mode and be able to control its caching policy.

Not diving into details, let's make it happen.

First of all, you need to install serviceworkerW-webpack-plugin in your project:

 npm install -D serviceworker-webpack-plugin
Enter fullscreen mode Exit fullscreen mode

Then in root of your project (next to src folder) add new file vue.config.js with that content:

// vue.config.js

const path = require("path");
const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin");

module.exports = {
  configureWebpack: {
    plugins: [
      new ServiceWorkerWebpackPlugin({
        entry: path.join(__dirname, "./src/service-worker.js")
      })
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

and modify src/main.js to include those lines (before createApp):

// src/main.js

// other imports...
import runtime from "serviceworker-webpack-plugin/lib/runtime";

if ("serviceWorker" in navigator) {
  runtime.register();
}

// createApp...
Enter fullscreen mode Exit fullscreen mode

Finally, add service-worker.js in src with some "Hello world" content:

// src/service-worker.js

console.log("Hello world from our SW!");
Enter fullscreen mode Exit fullscreen mode

and run the dev server

npm run serve
Enter fullscreen mode Exit fullscreen mode

When you navigate to your app in a browser you should see the message from service-worker in the console. Success!

Controlling caching - Workbox

Writing SW from scratch may be interesting... but let's make it simple and use Workbox for that. It's already installed, so you just need to import it in the SW script. I'm not going to explain everything from the below snippet, because it's done very clearly on Getting started page of Workbox. It's just example of setting specific rules for data matching some RegEx (images in that case).

// src/service-worker.js

import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
import { Plugin } from 'workbox-expiration';
import { precacheAndRoute } from 'workbox-precaching';

precacheAndRoute(self.serviceWorkerOption.assets);

registerRoute(
    /\.(?:png|gif|jpg|jpeg|svg)$/,
    new StaleWhileRevalidate({
        cacheName: 'images',
        plugins: [
            new Plugin({
                maxEntries: 60,
                maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
            }),
        ],
    })
);

Enter fullscreen mode Exit fullscreen mode

Just brief comment about that precacheAndRoute line - self.serviceWorkerOption.assets comes from that serviceworker-webpack-plugin we installed before and contains all the statics assets in our app.

Now, even if you are in development mode, you should see some workbox logs in the console. On the first page load it will be
wb1
and on subsequent something like that
wb2
If you go to Network tab in DevTools and simulate offline mode, the app should still load properly.

Great! Two problems solved - we have granular control over our service worker and it works in development mode.

Fixing SW on prod

In the meantime, unfortunately we messed up the production version of the app. If you run npm run build and look at it, it may seem fine at the beginning, but it's not. First of all, on subsequent refreshes you can see

New content is available; please refresh.

log all the time, although you don't change anything. Also, if you check Application tab, you will see two service workers all the time - one active, the second one waiting to activate. Even if you force the update, there will be another waiting after the refresh.

Issue comes from double registration - one SW is registered in main.js, the second one comes from generated registerServiceWorker.js. That's the problem I wasn't able to overcome in good way, but I came up with two acceptable solutions:

  1. If you don't care about that logging coming from registerServiceWorker.js, just don't import it in src/main.js and problem will be gone.
  2. If you want to keep those console logs, but are fine with having SW working only on prod (but keep the control of caching rules) and with a bit more complex way of importing modules in SW, it requires a bit more effort: Firstly, change vue.config.js contents to:
module.exports = {
  pwa: {
    workboxPluginMode: "InjectManifest",
    workboxOptions: {
      swSrc: "src/service-worker.js"
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

then revert changes you made in src/main.js (i.e. remove anything related to serviceworker-webpack-plugin). Finally, you need to change src/service-worker.js to NOT use import and use precaching with different argument. If you want to use some external modules, use importScripts with CDN link (momentjs below for example; usage is stupid but demonstrates way of doing that). Note how workbox names are expanded now:

importScripts('https://momentjs.com/downloads/moment.min.js');

workbox.precaching.precacheAndRoute(self.__precacheManifest);

const cacheExpTime = moment().add(1, 'day');
const cacheTimeLeft = moment.duration(cacheExpTime.diff(moment())).asSeconds();

workbox.routing.registerRoute(
    /\.(?:png|ico|gif|jpg|jpeg|svg)$/,
    new workbox.strategies.StaleWhileRevalidate({
        cacheName: 'images',
        plugins: [
            new workbox.expiration.Plugin({
                maxEntries: 60,
                maxAgeSeconds: cacheTimeLeft, // 1 day
            }),
        ],
    })
);

Enter fullscreen mode Exit fullscreen mode

Surely there's some 3rd option, which lets you keep logging and everything else, but I don't know, how to configure Webpack properly. If you have any simple solution, I'll be more than happy to read about it in the comments :)

Conclusion

If you want very simple caching technique and keeping handling only static assets by service worker is fine, the generated project is definitely enough. However, if you want more control over your service worker to cache ex. API calls, you need to tweak it somehow. I hope that above tips, how to do that and how to handle the development mode will be useful.

As said, it's definitely not the best and only solution. It's just a starter option for some Vue newbies (like me) to deal with service workers in a reasonable way.

Top comments (3)

Collapse
 
ang_vues profile image
Ang Vues

Hi Piotr thank you for taking the time to write this. I'm in the same situation - I need to debug the service worker as I'm using it for other background code apart from caching and the dev setup disables the service worker. In the second part of the "Fixing SW on prod" section, you mentioned removing all references to serviceworker-webpack-plugin - but after doing this the DEV server service worker stops working.

I did try to import the Dev service worker manually in main.js but after launching npm run serve, it fails with a 404

Have you encountered this as well? Was there any workaround that worked for you?

Collapse
 
voodu profile image
Piotr Ładoński • Edited

Hi Ang,
Have you tried the first method to fix the production service worker? I.e.

If you don't care about that logging coming from registerServiceWorker.js, just don't import it in src/main.js and problem will be gone.

I must say I had some problems with getting this to work as well and personally decided on using this method

Collapse
 
canaktepe profile image
Can AKTEPE

Hey Piotr, I used service worker on the vue.js project for get new version. Service worker does show a popup for upgrade to all clients If I deploy a new version. But, sometimes popup does not open. I thought this problem related for cache but I did not find any solution it. have you any idea for about it? I host my project on the gcp storage.