loading...

PWAs that download like apps πŸ—œοΈ

samthor profile image Sam Thorogood Updated on ・2 min read

Blog-A-Day in June (19 Part Series)

1) Rebuild only when necessary in Node 2) Civilization is a game you never lose 3 ... 17 3) Arrow functions break JavaScript parsers 4) Detecting Select All on the Web 5) Declaring JS Variables in 2019 6) Sam's dotfiles highlights 7) Automate Reading Form Results with πŸ€– Chrome 8) Beyond appendChild: Better convenience methods for HTML 9) AMA, Sam 10-yr Googler in Web DevRel 10) Disable a HTML form while in-flight using fieldset 11) PWAs that download like apps πŸ—œοΈ 12) Matching elements with selectors in JS 13) Install This PWA To Continue 14) Google Assistant now supports "Open/Close" devices 15) Modern Web Components 16) What To Expect When You're Expecting To Drop IE11 πŸ—‘οΈ 17) Divert Vertical Scroll To The Side ↔️ 18) Graceful Shutdown Is A Lie 19) Progress Indicator With Fetch

This is a short post today. (By writing that, it'll make it true!) It's also more of a short developer log, rather than having a specific point 😌

Progressive Web Apps are a thing that all modern browsers support today. You're using one by reading this site: if you disconnect from the Internet yet load dev.to, you'll get a cute offline page where you can scribble. πŸ–ŒοΈπŸŽ¨πŸŽŠ

To build a Service Worker, a core part of a PWA, you probably want to use Workbox. But what if.. you don't? πŸ€”

Sam's Patented^ Web Install Model

Instead of your normal approach to a PWAβ€”write some pages and resources, write a SW, and then cache those same pages and resources, I'm going to create an almost empty site that 'installs' the full experience.

That full experience is literally going to be a .tar file that is hosted somewhere else. Let's install! πŸ”œπŸ–₯️

Create An Actual Site

So, for this to work, you'll need a real website. Create a file called app.tar containing its resources: index.html, styles, etc.

Register SW

Inside our foreground page index.html, we register our SW like normal:

<script>
if (!('serviceWorker' in navigator)) {
  throw new Error('unsupported');
}
navigator.serviceWorker.register('/sw.js').then((reg) => {
  console.info('registered');
  // TODO
});
</script>

We only need this file and sw.js below to be really served by a HTTP server.

Install Handler

And inside sw.js, we can do this:

self.addEventListener('install', (ev) => {
  const p = (async() => {
    const response = await fetch('app.tar');
    const buffer = await response.arrayBuffer();

    const cache = await caches.open('app');
    const ops = [];
    untar(buffer, (file) => {
      if (file.name.endsWith('/')) {
        return;  // directory, ignore
      }
      const p = cache.put(file.name, new Response(file.buffer));
      ops.push(p);
    });
    await Promise.all(ops);
    // untar is a modified version of https://github.com/InvokIT/js-untar
  })();
  ev.waitUntil(p);
});

Great! We've now downloaded app.tar and installed its content to our cache. It can literally contain any contents we like and doesn't need to map to files you actually serve over HTTP.

Fetch Handler

I nearly forgot. We need to actually serve from our cache using this boilerplate in sw.js:

self.addEventListener('fetch', (ev) => {
  const p = (async() => {
    // TODO: make requests for '/index.html' match '/'
    const response = await caches.match(ev.request, {ignoreSearch: true});
    return response || fetch(ev.request);
  })();
  ev.respondWith(p);
});

(This is literally the same for almost every site that has a SW.)

Don't Try This At Home

This was mostly an experiment to see whether installing a website from a .tar file is viable. Yes, it is! Now you too can enjoy the full experience of installing an application, on the web. πŸ™„

Here's a demo!

12 πŸ‘‹

Blog-A-Day in June (19 Part Series)

1) Rebuild only when necessary in Node 2) Civilization is a game you never lose 3 ... 17 3) Arrow functions break JavaScript parsers 4) Detecting Select All on the Web 5) Declaring JS Variables in 2019 6) Sam's dotfiles highlights 7) Automate Reading Form Results with πŸ€– Chrome 8) Beyond appendChild: Better convenience methods for HTML 9) AMA, Sam 10-yr Googler in Web DevRel 10) Disable a HTML form while in-flight using fieldset 11) PWAs that download like apps πŸ—œοΈ 12) Matching elements with selectors in JS 13) Install This PWA To Continue 14) Google Assistant now supports "Open/Close" devices 15) Modern Web Components 16) What To Expect When You're Expecting To Drop IE11 πŸ—‘οΈ 17) Divert Vertical Scroll To The Side ↔️ 18) Graceful Shutdown Is A Lie 19) Progress Indicator With Fetch

Posted on Jun 12 '19 by:

samthor profile

Sam Thorogood

@samthor

Developer Relations for Web at Google.

Discussion

markdown guide
 

From eyeballing your code, that promise passed to the install event's waitUntil() seems like it might resolve too early (it's not waiting for the untar() callback), and also would resolve even if the cache.put() call rejected.

Generally, you'd want to make sure that the promise passed to an install event's waitUntil() only fulfills iff all of the content that needs to be cached was successfully saved, and rejects otherwise.

Rejecting that waitUntil() promise will cause the newly registered service worker to enter the redundant state. The next time the same service worker gets registered, the install process will then run againβ€”and hopefully everything will be properly cached during that subsequent attempt.

 

The call to untar in this case is actually totally synchronous so all the callbacks arrive before the async handler resolves. I modified the library which did use a Web Worker... anyway, the callback is fairly misleading- I'll fix it up.

 

Gotcha about the callback. But the cache.put() part could definitely fail asynchronously, which is not uncommon if you're, for instance, out of storage quota for your origin.

 

I have four versions of the DEV desktop PWA installed πŸ˜„

 

Awesome! I'm doing something like this using precache manifests for a PWA game I'm developing at rdlr.netlify.com/ (only intended to be visited on mobile) - It's 100% available offline and even has a fun little install screen.

 

This is awesome πŸ‘πŸ‘πŸ‘