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"),
]
},
};
CodePudding user response:
The cause of my issue was Webpack optimization, namely the following 2 issues:
- Extraction of the runtime was enabled for all chunks by setting the
runtimeChunk
config toname: "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 - Splitting of chunks was enabled for the service worker also by setting the
splitChunks
config optionchunks
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"),
]
},
};