PWAs that download like apps 🗜️
Tags: javascript, pwa, serviceworker
Published: 2019-06-12 14:00:11

---
title: PWAs that download like apps 🗜️
published: true
description: Experiments with installing PWAs from a tar file
tags: javascript, pwa, serviceworker
series: Blog-A-Day in June
---

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](https://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](https://developers.google.com/web/tools/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:

```html
<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:

```js
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`:

```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!](https://samthor.github.io/until/)

12 👋