I'm using Webpack 5 and I want to have a Service Worker that will intercept fetch requests and return responses locally without hitting the network. I also want to be able to import npm modules within the Service Worker. I used to use a library called serviceworker-webpack-plugin for this purpose, but it's no longer maintained, (and throws errors when I use it). The Webpack docs recommend using Workbox, but this seems to be just for caching assets in the Service Worker, as far as I can gather. Could someone tell me what the correct approach is in 2020 for creating a Service Worker with Webpack 5?
4 Answers
Add the service worker to your webpack.config.js entries field
entry: {
    'app': "./src/index.js",
    'service-worker': "./src/service-worker.ts",
},
output: {
    filename: "[name].js",
},
This will emit dist/app.js and dist/service-worker.js, and it will be possible to import things in both.
serviceworker-webpack-plugin also provides a way for the serviceworker to see a list of all the bundle files it should cache, but that functionality is not available directly and requires making a webpack plugin to get.
 
    
    - 2,348
- 1
- 16
- 37
- 
                    I've tried this, but I want to use a contenthash for my output files, but then I have no way of calling register() because I won't know the hash of the service-worker file. AFAIK there is no way to prevent the service worker file from having a hash while ensuring other files do – craigmiller160 Feb 17 '22 at 19:34
- 
                    @craigmiller160 It looks like you can specify filenames per-entry now: https://stackoverflow.com/a/63426778/2821420 – pfg Feb 17 '22 at 20:01
2022 Answer
This is supported in webpack pretty much out of the box.
https://webpack.js.org/guides/progressive-web-application/
This will give you basic caching of assets which web pack is handling for your.
You can get more advanced: https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/
Note this is using Workbox from google. I've used this for years in an offline first app and it works very well.
 
    
    - 5,555
- 8
- 44
- 52
- 
                    "You can get more advanced". That page literally says you shouldn't use that plugin if you want to use more advanced service worker features. – Coded Monkey Sep 12 '22 at 08:38
Webpack 5 should do this for you out of the box, similar to other workers:
When combining
new URLfor assets withnew Worker/new SharedWorker/navigator.serviceWorker.registerwebpack will automatically create a new entrypoint for a web worker.
new Worker(new URL("./worker.js", import.meta.url)).
The syntax was chosen to allow running code without bundler too. This syntax is also available in native ECMAScript modules in the browser.
(From https://webpack.js.org/blog/2020-10-10-webpack-5-release/#native-worker-support)
With webpack 4, our service worker code looked like this:
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import downloadWorker from 'worker-plugin/loader!../workers/downloadHelper'
navigator.serviceWorker.register(downloadWorker).then( //...
With webpack 5, the code became:
navigator.serviceWorker
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  .register(new URL('../workers/downloadHelper.ts', import.meta.url))
  .then( // ...
We removed the worker-plugin from our webpack 5 configuration, which v4's code made use of in the import statement.
The key is using new URL() - webpack 5 will interpret this as being a web worker and will create a chunk for this service worker and link up the url properly.
We had to add the eslint-disable-next-line and @ts-ignore because the interface for navigator.serviceworker.register expects a string, rather than a URL. It appears that webpack correctly sends over a string, but TypeScript doesn't seem capable of understanding that when TypeScript is run prior to webpack running.
 
    
    - 5,194
- 4
- 30
- 43
Dont overcomplicate it.
You can make an sw work in just 2 steps. Create an sw and register it.
Create an .js file like sw.js and write in it:
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        var fetchPromise = fetch(event.request).then(function (networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return response || fetchPromise;
      });
    }),
  );
});
Thats the stale-while-revalidate approach
Now register it.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Registration was successful
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }, function(err) {
      // registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}
 
    
    - 14,145
- 3
- 19
- 47
- 
                    
- 
                    @RichardHunter should be done with `importScripts()` https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts – bill.gates Oct 27 '20 at 06:34
- 
                    8This wont work because the service worker has to be in the build folder, and Webpack wont put it there if it doesn't know about it. – Richard Hunter Oct 27 '20 at 07:12
