[w3c/ServiceWorker] How should import() work in service workers? (#1585)

We blocked `import()` in service workers because we weren't sure how they should work, but it wasn't intended to be a forever-fix. Now that modules are becoming more popular, and browsers now support module service workers, how should `import()` work?

Some thoughts:

- It should work the same in classic service workers as it does with module service workers.
- Ideally it should promote patterns that work offline.
- Loading of a module in one service worker cannot be impacted by another service worker.

Some ideas:

---

## Option 1

Before the service worker is "installed", calls to `import()` are fetched, bypassing all service workers. These module resources are cached along with the service worker script.

After the service worker is installed, calls to `import()` will only use resources that are cached along with the service worker script, otherwise they will fail.

Eg:

```js
addEventListener('install', (event) => {
  event.waitUntil((async () => {
    const cache = await caches.open('static-v1');
    return Promise.all([
      cache.addAll(urls),
      import('./big-script.js'),
      import('./another-big-script.js'),
    ]);
  })());
});

addEventListener('fetch', (event) => {
  event.respondWith((async () => {
    if (whatever) {
      const { complicatedThing } = await import('./big-script.js');
      // …etc…
    }
  })());
});
```

- ✅ Compatible with classic and module service workers
- ✅ Promotes working offline
- ✅ Similar to how `importScripts` works
- ✅ Don't need to know the full import tree, except for dynamic imports
- ⚠️ Requires parsing (and maybe executing) stuff in the install phase that isn't actually used

Another idea:

---

## Option 2

Before the service worker is "activated", calls to `import()` just go to the network, bypassing all service workers.

After the service worker is "activated", calls to `import()` will go via that service worker's `"fetch"` event.

```js
addEventListener('install', (event) => {
  event.waitUntil((async () => {
    const cache = await caches.open('static-v1');
    return cache.addAll([
      ...urls,
      './big-script.js',
      './big-script-dependency.js',
      './another-big-script.js',
      './another-big-script-dependency.js',
    ]);
  })());
});

addEventListener('fetch', (event) => {
  event.respondWith((async () => {
    if (whatever) {
      const { complicatedThing } = await import('./big-script.js');
    }
    return caches.match(event.request);
  })());
});
```

- ✅ Compatible with classic and module service workers
- ✅ Scripts can be cached without parsing them
- ⚠️ Need to list out all the module's dependencies
- ⚠️ It can work offline, via the same mechanism as pages, but it can also become network dependent. It's versatile, but folks might not test for it?
- ⚠️ Implementation complexities? I think it's the first time a request from a service worker will go through its own fetch event.

We could overcome the dependencies issue with something like `cache.addModule('./big-script.js')`, which would crawl the tree similar to `modulepreload`. That means we're having to parse the modules, but it doesn't need to execute them.

---

I preferred "option 2" when it was just in my head, but now I've written it down and thought it through, I don't think I like it. So, what about:

## Option 1.5

Before the service worker is "installed", calls to `import()` fail.

Before the service worker is "installed", calls to `modulePreload(url)` crawl a module tree similar to `modulepreload`, and cache the script along with the service worker script. This returns a promise that indicates success.

After the service worker is "installed", calls to `import()` will only use resources that are cached along with the service worker script, otherwise they will fail.

After the service worker is "installed", calls to `modulePreload(url)` reject.

Eg:

```js
addEventListener('install', (event) => {
  event.waitUntil((async () => {
    const cache = await caches.open('static-v1');
    return Promise.all([
      cache.addAll(urls),
      modulePreload('./big-script.js'),
      modulePreload('./another-big-script.js'),
    ]);
  })());
});

addEventListener('fetch', (event) => {
  event.respondWith((async () => {
    if (whatever) {
      const { complicatedThing } = await import('./big-script.js');
      // …etc…
    }
  })());
});
```

- ✅ Compatible with classic and module service workers
- ✅ Promotes working offline
- ✅ Don't need to know the full import tree, except for dynamic imports
- ✅ Requires parsing the scripts at install time, but avoids executing them

Thoughts @wanderview @asutherland @jeffposnick @mfalken @youennf?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/ServiceWorker/issues/1585

Received on Friday, 23 April 2021 15:11:31 UTC