Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature detection for type="module" support #1582

Open
jeffposnick opened this issue Apr 15, 2021 · 11 comments
Open

Feature detection for type="module" support #1582

jeffposnick opened this issue Apr 15, 2021 · 11 comments

Comments

@jeffposnick
Copy link
Contributor

I've recently been experimenting with ES modules for service workers, now supported in pre-stable versions of Chrome and Safari.

I've found that without a way to feature detect support, writing code that's backwards compatible can be awkward, and in some browsers, less performant.

Roughly, this is the registration snippet that I was trying out:

async function registerSW() {
  try {
    await navigator.serviceWorker.register('es-module-sw.js', {
      type: 'module',
    });
  } catch(error) {
    await navigator.serviceWorker.register('import-scripts-sw.js');
  }
}

Pre-stable versions of Chrome and Safari will successfully run the first registration, and the current stable versions of Chrome will raise an exception immediately because it "knows" that type: 'module' isn't supported.

However, the current stable versions of Firefox and Safari will download and attempt to execute the ES module flavor of service worker, and only raise an exception when there's a syntax error due to the usage of ES module imports. The fallback logic in the catch can successfully register a classic version of the service worker that uses importScripts(), but not until after spending the bandwidth and time needed to download and parse something that we should know in advance won't work.

Am I missing something already implemented in all browsers that would make it possible to feature-detect type: 'module' support ahead of time? If not, consider this a feature request to add in a property, presumably exposed on the ServiceWorkerContainer, that could answer the "are ES module service workers supported?" question in advance of attempting the registration. Either a boolean that was true when ES modules are supported, or alternatively, a list of supported type values, set in this case to ['classic', 'module'], if we think that there might be additional types that will be added in the future.

@jakearchibald
Copy link
Contributor

A workaround for now:

let readType = false;
navigator.serviceWorker
  .register('about:blank', {
    get type() {
      readType = true;
    },
  })
  .catch(() => {});

If readType is false, it doesn't understand type.

@jeffposnick
Copy link
Contributor Author

While that does detect type support in general, it isn't sufficient to detect type: 'module' support. Both Chrome 90 and Safari 14.0.3 support type but don't work with type: 'module'.

In the case of Chrome 90, there's an exception throw immediately (DOMException: type 'module' in RegistrationOptions is not implemented yet.See https://crbug.com/824647 for details.), so it does avoid downloading and parsing the script.

In the case of Safari 14.0.3, the ES module version of the script downloads and then fails to parse, with a TypeError: SyntaxError: Unexpected token '*'. import call expects exactly one argument., so checking for type doesn't avoid the problem. I'm assuming this is because Safari 14.0.3 shipped with partially implemented ES module support, as the Technology Previews post-122 work as expected.

@jakearchibald
Copy link
Contributor

sighhhhh that Safari issue is frustrating.

@jakearchibald
Copy link
Contributor

https://static-misc-2.glitch.me/detect-sw-module-support/ - here's a test that works today, although it requires a network request.

@jeffposnick
Copy link
Contributor Author

jeffposnick commented Apr 15, 2021

Epic 😄 It's good to have that to refer folks to if they want to use ES modules today and also want to avoid a potential double-download. Long-term, is a more official mechanism for detecting support reasonable to add to the service worker specification?

Actually, since ES modules in dedicated workers are already supported in Chrome and Safari, how do developers using them deal with what I'm assuming is a similar issue? Perhaps a way of feature-detecting support needs to be done on the WorkerGlobalScope level, to address that use case as well?

(EDIT: Although I guess code in the window scope can't interrogate the WorkerGlobalScope, so that probably wouldn't help.)

@jakearchibald
Copy link
Contributor

Perhaps a way of feature-detecting support needs to be done on the WorkerGlobalScope level, to address that use case as well?

It'd be nice to be able to detect it without starting up a worker. Although, at least with a worker you can avoid the network request. But yeah, it'd be good to have the same solution in both places.

Going to see if I can make the test better, so it might be broken for a bit…

@wanderview
Copy link
Member

Maybe not a great general solution, but would there be any benefit in changing the Service-Worker: script header to say Service-Worker: module when type is module and its supported? That way the server could dynamically choose to return either a classic or module script.

@jakearchibald
Copy link
Contributor

jakearchibald commented Apr 15, 2021

Going to see if I can make the test better, so it might be broken for a bit…

I couldn't make it better. I thought Safari might support the type option but ignore the value. But no, it validates it (I guess they have the IDL), but doesn't do the right thing with the value.

@wanderview

Maybe not a great general solution, but would there be any benefit in changing the Service-Worker: script header to say Service-Worker: module when type is module and its supported?

That seems nice!

@jeffposnick
Copy link
Contributor Author

If #1585 is resolved, and dynamic import() comes to service workers, it would introduce one more capability that would be important to know prior to service worker registration.

You could theoretically create four different versions of a service worker script—importScripts() vs. static module imports, and dynamic import()s vs. no dynamic support—to cover all the possible variations, and registering them one at a time until you find the one that doesn't throw isn't a good model. (Though I doubt it's likely that a browser will support dynamic import() but not static module imports...)

Exposing some combination of ['classic', 'dynamic', 'static'] as a new importSupport property on the ServiceWorkerContainer, reflecting the set of capabilities of the current browser, would address this expanded use case. Browsers that don't have an importSupport property on ServiceWorkerContainer could be assumed to only support classic importScripts().

@wanderview
Copy link
Member

Maybe not a great general solution, but would there be any benefit in changing the Service-Worker: script header to say Service-Worker: module when type is module and its supported?

That seems nice!

Should we pursue this?

@jakearchibald
Copy link
Contributor

I think a client-side solution would be a more general solution. Might not be worth spending time on the header thing yet?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants