Not what it says on the tin

Posted: 27 November, 2018 Category: code Tagged: PWAvue

I did not ask for this:

<link rel=icon type=image/png sizes=32x32 href=/img/icons/favicon-32x32.png>
<link rel=icon type=image/png sizes=16x16 href=/img/icons/favicon-16x16.png>
<link rel=apple-touch-icon href=/img/icons/apple-touch-icon-152x152.png>
<link rel=mask-icon href=/img/icons/safari-pinned-tab.svg color=#4DBA87>
<meta name=msapplication-TileImage content=/img/icons/msapplication-icon-144x144.png>

Nevertheless, the Vue cli-plugin-pwa was adamant that this be injected into my html <head>, regardless of:

  • where I might have preferred to actually put my icons
  • whether said icons existed at all, in this reality, at this point in spacetime, on this hard disk. It didn't. matter. what I did. The above lines would always be spat out. (This was after hours of nothing being spat out at all... but that was my fault; I'd moved vue-cli's cheese).

This overly-helpful little plugin also did something else. It also injected this:

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

Which - unless I'm crazy - does rather suggest that said plugin is also responsible for the manifest file. I was emboldened in this assumption since the plugin's readme page was rife with options for things that one generally puts into an app manifest. Why would a plugin go out of its way to inject a link for something that, in the final analysis, has nothing material to do with its actual function?

So on the tin, this plugin screams "give me your tired and lonely manifest settings and I will feed them into a manifest.json file that I will dutifully generate!"

Well let me tell you. It does no such thing. Afaics it only builds the link to the manifest and then proceeds to not give any tosses WHATSOEVER as to how you get the manifest to where the browser can pick it up.

All of which would normally be fine, when people haven't monkeyed with the /public folder in their vue app. public/, which holds such things as... ahem... an empty-ish manifest.json file to... you know, give you a hint as to how all this works. (shamefaced). But I had moved this file elsewhere. In fact, I had moved both the index.html and the manifest.json files, because at that point, tiredness and brain farts. I had forgotten how Vue.js uses the /public folder.

Lots of shenanigans and corrections later, this is what I have learned:

  • Don't trust the vue pwa plugin: handroll your own manifest.json file and make sure it lives in public/manifest.json. Really - the best use of this plugin is for the serviceworker stuff that you will also need for creating a PWA. AND, even that implementation isn't straightforward, if you ask me. I stripped everything out and left this in my vue.config.js. You can tell from the green commentary I'm a bit miffed and have added notes related to my misadventures.

    module.exports = {
      // see:
      // warning: at time of writing this does not actually spit out manifest.json. In fact,
      // it has nothing to do with manifest.json besides injecting a link to the manifest file.
      // This only injects meta and link items into the <head> tag. So stripped any such settings
      // out of here and hand-rolled my own public/manifest.json.
      pwa: {     
        //GenerateSW : only caches files according to the given options. rigid, can't modify.
        //InjectManifest: configurable! use this.
        workboxPluginMode: 'InjectManifest',
        workboxOptions: {
          //ONLY in InjectManifest mode, the swDest filename will default to swSrc !! So use the same filename during registration !
          swSrc: 'src/serviceworker.js',
          // These drugs don't work. Just... NOPE.
          // skipWaiting: true,
          // clientsClaim: true,
  • the "image type" for your icons etc. in the manifest.json has to be a mime type, else your browser will shrug its shoulders and pretend it has no idea what you're talking about (even after successfully downloading the image and showing it to you). Browsers can be petty.

      "src": "logo-148x148.png",
      "sizes": "148x148",
      //"type": "png" // NOPE.
      "type": "image/png"
  • Jaw-dropped at this one: you MUST have visited the app/site more than once (chrome uses visit heuristics to determine whether to even proffer an 'add to home' link). You might not run into this if you don't nuke your caches, but I was doing this a lot. On first load you see your browser fetch the manifest, but when you go poking around in the 'Application' tab of Chrome's dev tools, the browser will act brand new.... like, "Manifest? Moi? Choo talkin' bout?" So reload your app.

  • Don't be ashamed to create the link to an icon image by hand, in the public/index.html template. For me, regardless of where I put it, it would NOT come out in the production build. I probably did something silly, but life must go on, so:

    <link rel="shortcut icon" href="<%= webpackConfig.output.publicPath %>logo-148x148.png">
  • You must have service workers actively working away in the app, otherwise your browser will laugh at you when you click that a2hs (add to homescreen). The fact that I had a service worker file was insufficient for chrome. I had gutted the functionality and left an empty shell (while I cast around for a better PWA-friendly backend). Chrome picked up on this, and wasn't fooled. It told me to my face: This app WILL NOT work offline.

Browser says - You shall not pass!

So I guess I'm about 75% PWA-ed up. Last stretch is syncing to the cloud. I might go with a PouchDb type solution now. Not sure...