Webpack 转译的 Typescript service worker 代码似乎不起作用
Webpack transpiled Typescript service worker code doesn't seem to work
我正在尝试实现一个服务工作者,但如果使用 Webpack + Bable 将其转译为 Typescript,则无法使其执行服务工作者代码工作。注册工作正常,工作人员显示为已激活,但 install
和 activate
事件都没有被触发 - 我假设是由于代码未正确执行。现在,如果我用普通 Javascript 中的相同代码替换转译后的代码,一切正常。
浏览器中的其他所有内容都运行良好,因此这似乎不是我的设置存在的固有问题 - 至少 none 我理解。我真的很想知道是什么导致了这个问题!
这里是相关的代码和配置:
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;
转译后的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
service worker 注册的地方:
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"),
]
},
};
我的问题的原因是Webpack优化,即以下2个问题:
- 通过将
runtimeChunk
配置设置为 name: "runtime"
,为所有块启用了运行时提取。这导致 service worker 的运行时被提取到一个单独的块中,但这个块不是由 service worker 导入的
- 还通过将
splitChunks
配置选项 chunks
设置为“全部”,为服务工作者启用了块拆分,这意味着服务工作者文件也是分块的一部分,这导致转译错误
像这样更改 webpack.config.js
解决了问题:
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"),
]
},
};
我正在尝试实现一个服务工作者,但如果使用 Webpack + Bable 将其转译为 Typescript,则无法使其执行服务工作者代码工作。注册工作正常,工作人员显示为已激活,但 install
和 activate
事件都没有被触发 - 我假设是由于代码未正确执行。现在,如果我用普通 Javascript 中的相同代码替换转译后的代码,一切正常。
浏览器中的其他所有内容都运行良好,因此这似乎不是我的设置存在的固有问题 - 至少 none 我理解。我真的很想知道是什么导致了这个问题!
这里是相关的代码和配置:
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;
转译后的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
service worker 注册的地方:
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"),
]
},
};
我的问题的原因是Webpack优化,即以下2个问题:
- 通过将
runtimeChunk
配置设置为name: "runtime"
,为所有块启用了运行时提取。这导致 service worker 的运行时被提取到一个单独的块中,但这个块不是由 service worker 导入的 - 还通过将
splitChunks
配置选项chunks
设置为“全部”,为服务工作者启用了块拆分,这意味着服务工作者文件也是分块的一部分,这导致转译错误
像这样更改 webpack.config.js
解决了问题:
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"),
]
},
};