Webpack transpiled Typescript service worker code doesn't seem to work
Asked Answered
S

1

6

I'm trying to implement a service worker, but fail to make it execute the service worker code work if it is transpiled for Typescript with Webpack + Bable. Registration works fine and the worker is shown as activated, but neither the install, nor activate events are ever fired - I assume due to the code not being executed correctly. Now, if I replace the transpiled code with the same code in plain Javascript everything works fine.

Everything else is working fine in the browser, so it doesn't seem to be some inherent issue with my settings - at least none I understand. I'd really like to understand what is causing this issue!

Here is the relevant code and config:

service-worker.ts:

declare const self: ServiceWorkerGlobalScope;

// Incrementing OFFLINE_VERSION will kick off the install event and force
//  previously cached resources to be updated from the network.
// This variable is intentionally declared and unused.
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
const OFFLINE_URL = "offline.html";

self.addEventListener("install", (event) => {
  console.debug("Install service worker");

  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      await cache.add(new Request(OFFLINE_URL, {cache: "reload"}));

      console.debug("Service worker installed");
    })()
  );
  void self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  console.debug("Activate service worker");

  event.waitUntil(
    (async () => {
      // Enable navigation preload if it's supported.
      // See https://developers.google.com/web/updates/2017/02/navigation-preload
      if ("navigationPreload" in self.registration) {
        // @ts-ignore this is only called if the browser supports it
        await self.registration.navigationPreload.enable();

        console.debug("Service worker enabled navigationPreload");
      }
    })()
  );

  void self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  if (event.request.mode === "navigate") {
    event.respondWith(
      (async () => {
        try {
          // @ts-ignore
          const preloadResponse = await event.preloadResponse;
          if (preloadResponse) {
            console.debug("Returning preloaded response for fetch request");

            return preloadResponse;
          }

          const networkResponse = await fetch(event.request);

          console.debug("Returning network response for fetch request");

          return networkResponse;
        } catch (error) {
          // catch is only triggered if an exception is thrown, which is likely
          // due to a network error.
          // If fetch() returns a valid HTTP response with a response code in
          // the 4xx or 5xx range, the catch() will NOT be called.
          console.debug(`Fetch failed; returning offline page instead: ${error}`);

          const cache = await caches.open(CACHE_NAME);
          const cachedResponse = await cache.match(OFFLINE_URL);
          return cachedResponse;
        }
      })()
    );
  } else {
    console.debug("Fetch request not handled by service worker");
  }
});

export default null;

The transpiled service-worker.js:

"use strict";
/*
 * ATTENTION: An "eval-source-map" devtool has been used.
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
(self["webpackChunkspritstat"] = self["webpackChunkspritstat"] || []).push([["service-worker"],{

/***/ "./src/service-worker.ts":
/*!*******************************!*\
  !*** ./src/service-worker.ts ***!
  \*******************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js\");\n/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/regenerator */ \"./node_modules/@babel/runtime/regenerator/index.js\");\n/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1__);\n\n\n// Incrementing OFFLINE_VERSION will kick off the install event and force\n//  previously cached resources to be updated from the network.\n// This variable is intentionally declared and unused.\nvar OFFLINE_VERSION = 1;\nvar CACHE_NAME = \"offline\";\nvar OFFLINE_URL = \"offline.html\";\nself.addEventListener(\"install\", function (event) {\n  console.debug(\"Install service worker\");\n  event.waitUntil((0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__[\"default\"])( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {\n    var cache;\n    return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function _callee$(_context) {\n      while (1) {\n        switch (_context.prev = _context.next) {\n          case 0:\n            _context.next = 2;\n            return caches.open(CACHE_NAME);\n\n          case 2:\n            cache = _context.sent;\n            _context.next = 5;\n            return cache.add(new Request(OFFLINE_URL, {\n              cache: \"reload\"\n            }));\n\n          case 5:\n            console.debug(\"Service worker installed\");\n\n          case 6:\n          case \"end\":\n            return _context.stop();\n        }\n      }\n    }, _callee);\n  }))());\n  void self.skipWaiting();\n});\nself.addEventListener(\"activate\", function (event) {\n  console.debug(\"Activate service worker\");\n  event.waitUntil((0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__[\"default\"])( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee2() {\n    return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function _callee2$(_context2) {\n      while (1) {\n        switch (_context2.prev = _context2.next) {\n          case 0:\n            if (!(\"navigationPreload\" in self.registration)) {\n              _context2.next = 4;\n              break;\n            }\n\n            _context2.next = 3;\n            return self.registration.navigationPreload.enable();\n\n          case 3:\n            console.debug(\"Service worker enabled navigationPreload\");\n\n          case 4:\n          case \"end\":\n            return _context2.stop();\n        }\n      }\n    }, _callee2);\n  }))());\n  void self.clients.claim();\n});\nself.addEventListener(\"fetch\", function (event) {\n  if (event.request.mode === \"navigate\") {\n    event.respondWith((0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__[\"default\"])( /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee3() {\n      var preloadResponse, networkResponse, cache, cachedResponse;\n      return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function _callee3$(_context3) {\n        while (1) {\n          switch (_context3.prev = _context3.next) {\n            case 0:\n              _context3.prev = 0;\n              _context3.next = 3;\n              return event.preloadResponse;\n\n            case 3:\n              preloadResponse = _context3.sent;\n\n              if (!preloadResponse) {\n                _context3.next = 7;\n                break;\n              }\n\n              console.debug(\"Returning preloaded response for fetch request\");\n              return _context3.abrupt(\"return\", preloadResponse);\n\n            case 7:\n              _context3.next = 9;\n              return fetch(event.request);\n\n            case 9:\n              networkResponse = _context3.sent;\n              console.debug(\"Returning network response for fetch request\");\n              return _context3.abrupt(\"return\", networkResponse);\n\n            case 14:\n              _context3.prev = 14;\n              _context3.t0 = _context3[\"catch\"](0);\n              // catch is only triggered if an exception is thrown, which is likely\n              // due to a network error.\n              // If fetch() returns a valid HTTP response with a response code in\n              // the 4xx or 5xx range, the catch() will NOT be called.\n              console.debug(\"Fetch failed; returning offline page instead: \".concat(_context3.t0));\n              _context3.next = 19;\n              return caches.open(CACHE_NAME);\n\n            case 19:\n              cache = _context3.sent;\n              _context3.next = 22;\n              return cache.match(OFFLINE_URL);\n\n            case 22:\n              cachedResponse = _context3.sent;\n              return _context3.abrupt(\"return\", cachedResponse);\n\n            case 24:\n            case \"end\":\n              return _context3.stop();\n          }\n        }\n      }, _callee3, null, [[0, 14]]);\n    }))());\n  } else {\n    console.debug(\"Fetch request not handled by service worker\");\n  }\n});\n/* harmony default export */ __webpack_exports__[\"default\"] = (null);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,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QTtBQUFBO0FBQUE7QUFBQSxLQUFELElBREY7QUE2QkQsR0E5QkQsTUE4Qk87QUFDTHJCLElBQUFBLE9BQU8sQ0FBQ0MsS0FBUixDQUFjLDZDQUFkO0FBQ0Q7QUFDRixDQWxDRDtBQW9DQSwrREFBZSxJQUFmIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vc3ByaXRzdGF0Ly4vc3JjL3NlcnZpY2Utd29ya2VyLnRzPzdmNTgiXSwic291cmNlc0NvbnRlbnQiOlsiZGVjbGFyZSBjb25zdCBzZWxmOiBTZXJ2aWNlV29ya2VyR2xvYmFsU2NvcGU7XG5cbi8vIEluY3JlbWVudGluZyBPRkZMSU5FX1ZFUlNJT04gd2lsbCBraWNrIG9mZiB0aGUgaW5zdGFsbCBldmVudCBhbmQgZm9yY2Vcbi8vICBwcmV2aW91c2x5IGNhY2hlZCByZXNvdXJjZXMgdG8gYmUgdXBkYXRlZCBmcm9tIHRoZSBuZXR3b3JrLlxuLy8gVGhpcyB2YXJpYWJsZSBpcyBpbnRlbnRpb25hbGx5IGRlY2xhcmVkIGFuZCB1bnVzZWQuXG5jb25zdCBPRkZMSU5FX1ZFUlNJT04gPSAxO1xuY29uc3QgQ0FDSEVfTkFNRSA9IFwib2ZmbGluZVwiO1xuY29uc3QgT0ZGTElORV9VUkwgPSBcIm9mZmxpbmUuaHRtbFwiO1xuXG5zZWxmLmFkZEV2ZW50TGlzdGVuZXIoXCJpbnN0YWxsXCIsIChldmVudCkgPT4ge1xuICBjb25zb2xlLmRlYnVnKFwiSW5zdGFsbCBzZXJ2aWNlIHdvcmtlclwiKTtcblxuICBldmVudC53YWl0VW50aWwoXG4gICAgKGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGNhY2hlID0gYXdhaXQgY2FjaGVzLm9wZW4oQ0FDSEVfTkFNRSk7XG4gICAgICBhd2FpdCBjYWNoZS5hZGQobmV3IFJlcXVlc3QoT0ZGTElORV9VUkwsIHtjYWNoZTogXCJyZWxvYWRcIn0pKTtcblxuICAgICAgY29uc29sZS5kZWJ1ZyhcIlNlcnZpY2Ugd29ya2VyIGluc3RhbGxlZFwiKTtcbiAgICB9KSgpXG4gICk7XG4gIHZvaWQgc2VsZi5za2lwV2FpdGluZygpO1xufSk7XG5cbnNlbGYuYWRkRXZlbnRMaXN0ZW5lcihcImFjdGl2YXRlXCIsIChldmVudCkgPT4ge1xuICBjb25zb2xlLmRlYnVnKFwiQWN0aXZhdGUgc2VydmljZSB3b3JrZXJcIik7XG5cbiAgZXZlbnQud2FpdFVudGlsKFxuICAgIChhc3luYyAoKSA9PiB7XG4gICAgICAvLyBFbmFibGUgbmF2aWdhdGlvbiBwcmVsb2FkIGlmIGl0J3Mgc3VwcG9ydGVkLlxuICAgICAgLy8gU2VlIGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL3dlYi91cGRhdGVzLzIwMTcvMDIvbmF2aWdhdGlvbi1wcmVsb2FkXG4gICAgICBpZiAoXCJuYXZpZ2F0aW9uUHJlbG9hZFwiIGluIHNlbGYucmVnaXN0cmF0aW9uKSB7XG4gICAgICAgIC8vIEB0cy1pZ25vcmUgdGhpcyBpcyBvbmx5IGNhbGxlZCBpZiB0aGUgYnJvd3NlciBzdXBwb3J0cyBpdFxuICAgICAgICBhd2FpdCBzZWxmLnJlZ2lzdHJhdGlvbi5uYXZpZ2F0aW9uUHJlbG9hZC5lbmFibGUoKTtcblxuICAgICAgICBjb25zb2xlLmRlYnVnKFwiU2VydmljZSB3b3JrZXIgZW5hYmxlZCBuYXZpZ2F0aW9uUHJlbG9hZFwiKTtcbiAgICAgIH1cbiAgICB9KSgpXG4gICk7XG5cbiAgdm9pZCBzZWxmLmNsaWVudHMuY2xhaW0oKTtcbn0pO1xuXG5zZWxmLmFkZEV2ZW50TGlzdGVuZXIoXCJmZXRjaFwiLCAoZXZlbnQpID0+IHtcbiAgaWYgKGV2ZW50LnJlcXVlc3QubW9kZSA9PT0gXCJuYXZpZ2F0ZVwiKSB7XG4gICAgZXZlbnQucmVzcG9uZFdpdGgoXG4gICAgICAoYXN5bmMgKCkgPT4ge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICBjb25zdCBwcmVsb2FkUmVzcG9uc2UgPSBhd2FpdCBldmVudC5wcmVsb2FkUmVzcG9uc2U7XG4gICAgICAgICAgaWYgKHByZWxvYWRSZXNwb25zZSkge1xuICAgICAgICAgICAgY29uc29sZS5kZWJ1ZyhcIlJldHVybmluZyBwcmVsb2FkZWQgcmVzcG9uc2UgZm9yIGZldGNoIHJlcXVlc3RcIik7XG5cbiAgICAgICAgICAgIHJldHVybiBwcmVsb2FkUmVzcG9uc2U7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgY29uc3QgbmV0d29ya1Jlc3BvbnNlID0gYXdhaXQgZmV0Y2goZXZlbnQucmVxdWVzdCk7XG5cbiAgICAgICAgICBjb25zb2xlLmRlYnVnKFwiUmV0dXJuaW5nIG5ldHdvcmsgcmVzcG9uc2UgZm9yIGZldGNoIHJlcXVlc3RcIik7XG5cbiAgICAgICAgICByZXR1cm4gbmV0d29ya1Jlc3BvbnNlO1xuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIC8vIGNhdGNoIGlzIG9ubHkgdHJpZ2dlcmVkIGlmIGFuIGV4Y2VwdGlvbiBpcyB0aHJvd24sIHdoaWNoIGlzIGxpa2VseVxuICAgICAgICAgIC8vIGR1ZSB0byBhIG5ldHdvcmsgZXJyb3IuXG4gICAgICAgICAgLy8gSWYgZmV0Y2goKSByZXR1cm5zIGEgdmFsaWQgSFRUUCByZXNwb25zZSB3aXRoIGEgcmVzcG9uc2UgY29kZSBpblxuICAgICAgICAgIC8vIHRoZSA0eHggb3IgNXh4IHJhbmdlLCB0aGUgY2F0Y2goKSB3aWxsIE5PVCBiZSBjYWxsZWQuXG4gICAgICAgICAgY29uc29sZS5kZWJ1ZyhgRmV0Y2ggZmFpbGVkOyByZXR1cm5pbmcgb2ZmbGluZSBwYWdlIGluc3RlYWQ6ICR7ZXJyb3J9YCk7XG5cbiAgICAgICAgICBjb25zdCBjYWNoZSA9IGF3YWl0IGNhY2hlcy5vcGVuKENBQ0hFX05BTUUpO1xuICAgICAgICAgIGNvbnN0IGNhY2hlZFJlc3BvbnNlID0gYXdhaXQgY2FjaGUubWF0Y2goT0ZGTElORV9VUkwpO1xuICAgICAgICAgIHJldHVybiBjYWNoZWRSZXNwb25zZTtcbiAgICAgICAgfVxuICAgICAgfSkoKVxuICAgICk7XG4gIH0gZWxzZSB7XG4gICAgY29uc29sZS5kZWJ1ZyhcIkZldGNoIHJlcXVlc3Qgbm90IGhhbmRsZWQgYnkgc2VydmljZSB3b3JrZXJcIik7XG4gIH1cbn0pO1xuXG5leHBvcnQgZGVmYXVsdCBudWxsO1xuIl0sIm5hbWVzIjpbIk9GRkxJTkVfVkVSU0lPTiIsIkNBQ0hFX05BTUUiLCJPRkZMSU5FX1VSTCIsInNlbGYiLCJhZGRFdmVudExpc3RlbmVyIiwiZXZlbnQiLCJjb25zb2xlIiwiZGVidWciLCJ3YWl0VW50aWwiLCJjYWNoZXMiLCJvcGVuIiwiY2FjaGUiLCJhZGQiLCJSZXF1ZXN0Iiwic2tpcFdhaXRpbmciLCJyZWdpc3RyYXRpb24iLCJuYXZpZ2F0aW9uUHJlbG9hZCIsImVuYWJsZSIsImNsaWVudHMiLCJjbGFpbSIsInJlcXVlc3QiLCJtb2RlIiwicmVzcG9uZFdpdGgiLCJwcmVsb2FkUmVzcG9uc2UiLCJmZXRjaCIsIm5ldHdvcmtSZXNwb25zZSIsIm1hdGNoIiwiY2FjaGVkUmVzcG9uc2UiXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./src/service-worker.ts\n");

/***/ })

},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, ["framework-node_modules_babel_runtime_regenerator_index_js-node_modules_babel_runtime_helpers_-23c3ae"], function() { return __webpack_exec__("./src/service-worker.ts"); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ }
]);

index.ts where the service worker gets registered:

import "./index.sass";
import "intro.js/introjs.css";
import "intro.js/themes/introjs-modern.css"

import React from "react";
import {createRoot} from "react-dom/client";
import {BrowserRouter} from "react-router-dom";
import {Provider} from "react-redux";

import App from "./app/App";
import {store} from "./app/store";


window.addEventListener("load", async () => {
  if ("serviceWorker" in navigator) {
    console.debug("Register service worker");

    await navigator.serviceWorker.register("/service-worker.js");
  }
});

console.debug("Start application");

const container = document.getElementById("root");
const root = createRoot(container!);
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App/>
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);

package.json:

{
  "name": "test",
  "version": "0.1.0",
  "homepage": ".",
  "browser": "webpack.config.js",
  "scripts": {
    "start": "npm run trans:compile && webpack --mode=development --watch",
    "build:dev": "npm run trans:compile && webpack --mode=development",
    "build": "npm run trans:compile && webpack --mode=production",
    "db:flush": "python ../manage.py flush --noinput",
    "db:seed": "python ../manage.py loaddata",
    "test": "cypress run",
    "cy:open": "cypress open",
    "trans:extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file translation/locales/de.json",
    "trans:manage": "babel-node -x .ts -- translation/manageTranslations.ts",
    "trans": "npm run trans:extract && npm run trans:manage",
    "trans:compile": "formatjs compile 'translation/locales/en.json' --ast --out-file 'src/locales/en.json'"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version",
      "last 1 opera version"
    ]
  },
  "dependencies": {
    "@fortawesome/fontawesome-svg-core": "^6.1.1",
    "@fortawesome/free-brands-svg-icons": "^6.1.1",
    "@fortawesome/free-solid-svg-icons": "^6.1.1",
    "@fortawesome/react-fontawesome": "^0.1.18",
    "@reduxjs/toolkit": "^1.8.1",
    "bulma": "^0.9.3",
    "bulma-switch": "^2.0.4",
    "chart.js": "^3.7.1",
    "chartjs-plugin-zoom": "^1.2.1",
    "intro.js": "^5.1.0",
    "intro.js-react": "^0.6.0",
    "lodash.debounce": "^4.0.8",
    "moment-timezone": "^0.5.34",
    "nanoid": "^3.3.3",
    "react": "^18.1.0",
    "react-cookie": "^4.1.1",
    "react-dom": "^18.1.0",
    "react-intl": "^5.25.1",
    "react-redux": "^8.0.1",
    "react-router-dom": "^6.3.0",
    "universal-cookie": "^4.0.4"
  },
  "devDependencies": {
    "@babel/core": "^7.17.9",
    "@babel/node": "^7.16.8",
    "@babel/plugin-transform-runtime": "^7.17.0",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-react": "^7.16.7",
    "@babel/preset-typescript": "^7.16.7",
    "@babel/runtime": "^7.17.9",
    "@formatjs/cli": "^4.8.4",
    "@types/google.maps": "^3.48.7",
    "@types/intro.js": "^3.0.2",
    "@types/lodash.debounce": "^4.0.7",
    "@types/node": "^17.0.30",
    "@types/react": "^18.0.8",
    "@types/react-dom": "^18.0.3",
    "@types/react-router-dom": "^5.3.3",
    "babel-loader": "^8.2.5",
    "babel-plugin-formatjs": "^10.3.20",
    "clean-webpack-plugin": "^4.0.0",
    "compression-webpack-plugin": "^9.2.0",
    "copy-webpack-plugin": "^10.2.4",
    "css-loader": "^6.7.1",
    "css-minimizer-webpack-plugin": "^3.4.1",
    "cypress": "^9.6.0",
    "cypress-real-events": "^1.7.0",
    "file-loader": "^6.2.0",
    "mini-css-extract-plugin": "^2.6.0",
    "moment-locales-webpack-plugin": "^1.2.0",
    "node-sass": "^7.0.1",
    "prettier": "^2.6.2",
    "sass-loader": "^12.6.0",
    "style-loader": "^3.3.1",
    "terser-webpack-plugin": "^5.3.1",
    "typescript": "^4.6.4",
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2",
    "webpack-manifest-plugin": "^5.0.0"
  },
  "keywords": [],
  "author": "eega",
  "license": "MIT",
  "description": ""
}

babel.config.json:

{
  "plugins": [
    "@babel/plugin-transform-runtime",
    [
      "formatjs", {
        "idInterpolationPattern": "[sha512:contenthash:base64:6]",
        "ast": true
      }
    ]
  ],
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "ESNext",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext",
      "es2017.intl",
      "es2018.intl",
      "webworker"
    ],
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src/**/*",
  ]
}

webpack.config.json:

const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const MomentLocalesPlugin = require("moment-locales-webpack-plugin");
const {resolve} = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
const {WebpackManifestPlugin} = require("webpack-manifest-plugin");
const zlib = require("zlib");


module.exports = {
  devtool: "eval-source-map",
  entry: {
    app: resolve(__dirname, "src/index.tsx"),
    "service-worker": {
      import: resolve(__dirname, "src/service-worker.ts"),
      filename: "js/service-worker.js"
    }
  },
  mode: process.env.NODE_ENV ? process.env.NODE_ENV : "production",
  module: {
    rules: [
      {
        test: /\.(ts|js)x?$/,

        exclude: /node_modules\/(?!react-intl|intl-messageformat|@formatjs\/icu-messageformat-parser)/,
        use: ["babel-loader"],
      },
      {
        test: /.*/,
        include: resolve(__dirname, "assets/img"),
        options: {
          context: resolve(__dirname, "assets/"),
          name: "[path][name]-[contenthash].[ext]",
        },
        loader: "file-loader",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.sass$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "sass-loader",
            options: {
              sourceMap: true,
            },
          },
        ],
      },
    ],
  },
  output: {
    path: resolve(__dirname, "public/"),
    filename: "js/[name]-[contenthash].js",
    chunkFilename: "js/[name]-[contenthash].chunk.js",
  },
  optimization: {
    runtimeChunk: {
      name: "runtime"
    },
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        // disable webpack's default cacheGroup
        default: false,
        // disable webpack's default vendor cacheGroup
        vendors: false,
        // Create a framework bundle that contains React libraries
        // They hardly change so we bundle them together to improve
        framework: {},
        // Big modules that are over 160kb are moved to their own file to
        // optimize browser parsing & execution
        lib: {},
        // All libraries that are used on all pages are moved into a common chunk
        commons: {},
        // When a module is used more than once we create a shared bundle to save user's bandwidth
        shared: {},
        // All CSS is bundled into one stylesheet
        styles: {}
      },
      // Keep maximum initial requests to 25
      maxInitialRequests: 25,
      // A chunk should be at least 20kb before using splitChunks
      minSize: 20000
    },
    minimizer: [
      new CompressionPlugin({
        filename: "[path][base].gz",
        algorithm: "gzip",
        test: /\.js$|\.css$|\.html$/,
        threshold: 10240,
        minRatio: 0.8,
      }),
      new CompressionPlugin({
        filename: "[path][base].br",
        algorithm: "brotliCompress",
        test: /\.(js|css|html|png|svg)$/,
        compressionOptions: {
          params: {
            [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
          },
        },
        threshold: 10240,
        minRatio: 0.8,
      }),
      new CssMinimizerPlugin(),
      new TerserPlugin(),
    ]
  },
  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    new CleanWebpackPlugin(),
    new WebpackManifestPlugin({
      fileName: "webpack_manifest.json",
      publicPath: ""
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
      chunkFilename: "css/[id]-[contenthash].css",
    }),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: "assets/img/favicon.ico",
          to: "img",
        },
      ],
    }),
    // These are files used in the progressive web app manifest only, so they wouldn't
    //  be processed without this.
    new CopyWebpackPlugin({
      patterns: [
        {
          from: "assets/img/logo_small_*.png",
          to: "img/[name]-[contenthash].png",
          toType: "template"
        },
      ],
    }),
    new MomentLocalesPlugin({
      localesToKeep: ["de", "en"],
    })
  ],
  resolve: {
    extensions: [".js", ".jsx", ".tsx", ".ts"],
    modules: [
      resolve(__dirname, "src"),
      resolve(__dirname, "node_modules"),
    ]
  },
};

Spice answered 4/5, 2022 at 14:38 Comment(0)
S
7

The cause of my issue was Webpack optimization, namely the following 2 issues:

  1. Extraction of the runtime was enabled for all chunks by setting the runtimeChunk config to name: "runtime". This caused the runtime for the service worker to be extracted into a separate chunk, but this chunk wasn't imported by the service worker
  2. Splitting of chunks was enabled for the service worker also by setting the splitChunks config option chunks to "all", which means that the service worker file was also part of chunking, which led to an transpile error

Changing webpack.config.js like this solved the issue:

const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const MomentLocalesPlugin = require("moment-locales-webpack-plugin");
const {resolve} = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
const {WebpackManifestPlugin} = require("webpack-manifest-plugin");
const zlib = require("zlib");


module.exports = {
  devtool: "eval-source-map",
  entry: {
    app: resolve(__dirname, "src/index.tsx"),
    "service-worker": {
      import: resolve(__dirname, "src/service-worker.ts"),
      filename: "js/service-worker.js"
    }
  },
  mode: process.env.NODE_ENV ? process.env.NODE_ENV : "production",
  module: {
    rules: [
      {
        test: /\.(ts|js)x?$/,

        exclude: /node_modules\/(?!react-intl|intl-messageformat|@formatjs\/icu-messageformat-parser)/,
        use: ["babel-loader"],
      },
      {
        test: /.*/,
        include: resolve(__dirname, "assets/img"),
        options: {
          context: resolve(__dirname, "assets/"),
          name: "[path][name]-[contenthash].[ext]",
        },
        loader: "file-loader",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.sass$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "sass-loader",
            options: {
              sourceMap: true,
            },
          },
        ],
      },
    ],
  },
  output: {
    path: resolve(__dirname, "public/"),
    filename: "js/[name]-[contenthash].js",
    chunkFilename: "js/[name]-[contenthash].chunk.js",
  },
  optimization: {
    runtimeChunk: {
      name: (entrypoint) => {
        if (entrypoint.name.startsWith("service-worker")) {
          return null;
        }

        return `runtime-${entrypoint.name}`
      }
    },
    splitChunks: {
      chunks(chunk) {
        return chunk.name !== "service-worker";
      },
      cacheGroups: {
        // disable webpack's default cacheGroup
        default: false,
        // disable webpack's default vendor cacheGroup
        vendors: false,
        // Create a framework bundle that contains React libraries
        // They hardly change so we bundle them together to improve
        framework: {},
        // Big modules that are over 160kb are moved to their own file to
        // optimize browser parsing & execution
        lib: {},
        // All libraries that are used on all pages are moved into a common chunk
        commons: {},
        // When a module is used more than once we create a shared bundle to save user's bandwidth
        shared: {},
        // All CSS is bundled into one stylesheet
        styles: {}
      },
      // Keep maximum initial requests to 25
      maxInitialRequests: 25,
      // A chunk should be at least 20kb before using splitChunks
      minSize: 20000
    },
    minimizer: [
      new CompressionPlugin({
        filename: "[path][base].gz",
        algorithm: "gzip",
        test: /\.js$|\.css$|\.html$/,
        threshold: 10240,
        minRatio: 0.8,
      }),
      new CompressionPlugin({
        filename: "[path][base].br",
        algorithm: "brotliCompress",
        test: /\.(js|css|html|png|svg)$/,
        compressionOptions: {
          params: {
            [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
          },
        },
        threshold: 10240,
        minRatio: 0.8,
      }),
      new CssMinimizerPlugin(),
      new TerserPlugin(),
    ]
  },
  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    new CleanWebpackPlugin(),
    new WebpackManifestPlugin({
      fileName: "webpack_manifest.json",
      publicPath: ""
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
      chunkFilename: "css/[id]-[contenthash].css",
    }),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: "assets/img/favicon.ico",
          to: "img",
        },
      ],
    }),
    // These are files used in the progressive web app manifest only, so they wouldn't
    //  be processed without this.
    new CopyWebpackPlugin({
      patterns: [
        {
          from: "assets/img/logo_small_*.png",
          to: "img/[name]-[contenthash].png",
          toType: "template"
        },
      ],
    }),
    new MomentLocalesPlugin({
      localesToKeep: ["de", "en"],
    })
  ],
  resolve: {
    extensions: [".js", ".jsx", ".tsx", ".ts"],
    modules: [
      resolve(__dirname, "src"),
      resolve(__dirname, "node_modules"),
    ]
  },
};

Spice answered 5/5, 2022 at 15:9 Comment(1)
you are my heroTrot

© 2022 - 2024 — McMap. All rights reserved.