Skip to content

Commit 5527320

Browse files
authored
fix(hmr): location.reload/document errors in service worker (#100)
add new vite client modifications so location.reload falls back to chrome.runtime.reload and document usages fallback to default values
1 parent aa72014 commit 5527320

File tree

3 files changed

+137
-92
lines changed

3 files changed

+137
-92
lines changed

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { addInputScriptsToOptionsInput } from "./utils/rollup";
55
import ManifestParser from "./manifestParser/manifestParser";
66
import ManifestParserFactory from "./manifestParser/manifestParserFactory";
77
import { getVirtualModule } from "./utils/virtualModule";
8-
import contentScriptStyleHandler from "./middleware/contentScriptStyleHandler";
8+
import viteClientModifier from "./middleware/viteClientModifier";
99
import {
1010
transformSelfLocationAssets,
1111
updateConfigForExtensionSupport,
@@ -37,7 +37,7 @@ export default function webExtension(
3737
},
3838

3939
configureServer(server) {
40-
server.middlewares.use(contentScriptStyleHandler);
40+
server.middlewares.use(viteClientModifier);
4141

4242
server.httpServer!.once("listening", () => {
4343
manifestParser.setDevServer(server);

src/middleware/contentScriptStyleHandler.ts

Lines changed: 0 additions & 90 deletions
This file was deleted.

src/middleware/viteClientModifier.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { Connect } from "vite";
2+
import getEtag from "etag";
3+
4+
// Modifies the vite HMR client to support various web extension features including:
5+
// Exporting a function to add HMR style injection targets
6+
// Tweaks to support running in a service worker context
7+
const viteClientModifier: Connect.NextHandleFunction = (req, res, next) => {
8+
const _originalEnd = res.end;
9+
10+
// @ts-ignore
11+
res.end = function end(chunk, ...otherArgs) {
12+
if (req.url === "/@vite/client" && typeof chunk === "string") {
13+
chunk = addCustomStyleFunctionality(chunk);
14+
chunk = addServiceWorkerSupport(chunk);
15+
16+
res.setHeader("Etag", getEtag(chunk, { weak: true }));
17+
}
18+
19+
// @ts-ignore
20+
return _originalEnd.call(this, chunk, ...otherArgs);
21+
};
22+
23+
next();
24+
};
25+
26+
function addCustomStyleFunctionality(source: string): string {
27+
if (
28+
!/const sheetsMap/.test(source) ||
29+
!/document\.head\.appendChild\(style\)/.test(source) ||
30+
!/document\.head\.removeChild\(style\)/.test(source) ||
31+
(!/style\.textContent = content/.test(source) &&
32+
!/style\.innerHTML = content/.test(source))
33+
) {
34+
console.error(
35+
"Web extension HMR style support disabled -- failed to update vite client"
36+
);
37+
38+
return source;
39+
}
40+
41+
source = source.replace(
42+
"const sheetsMap",
43+
"const styleTargets = new Set(); const styleTargetsStyleMap = new Map(); const sheetsMap"
44+
);
45+
source = source.replace("export {", "export { addStyleTarget, ");
46+
source = source.replace(
47+
"document.head.appendChild(style)",
48+
"styleTargets.size ? styleTargets.forEach(target => addStyleToTarget(style, target)) : document.head.appendChild(style)"
49+
);
50+
source = source.replace(
51+
"document.head.removeChild(style)",
52+
"styleTargetsStyleMap.get(style) ? styleTargetsStyleMap.get(style).forEach(style => style.parentNode.removeChild(style)) : document.head.removeChild(style)"
53+
);
54+
55+
const styleProperty = /style\.textContent = content/.test(source)
56+
? "style.textContent"
57+
: "style.innerHTML";
58+
59+
const lastStyleInnerHtml = source.lastIndexOf(`${styleProperty} = content`);
60+
61+
source =
62+
source.slice(0, lastStyleInnerHtml) +
63+
source
64+
.slice(lastStyleInnerHtml)
65+
.replace(
66+
`${styleProperty} = content`,
67+
`${styleProperty} = content; styleTargetsStyleMap.get(style)?.forEach(style => ${styleProperty} = content)`
68+
);
69+
70+
source += `
71+
function addStyleTarget(newStyleTarget) {
72+
for (const [, style] of sheetsMap.entries()) {
73+
addStyleToTarget(style, newStyleTarget, styleTargets.size !== 0);
74+
}
75+
76+
styleTargets.add(newStyleTarget);
77+
}
78+
79+
function addStyleToTarget(style, target, cloneStyle = true) {
80+
const addedStyle = cloneStyle ? style.cloneNode(true) : style;
81+
target.appendChild(addedStyle);
82+
83+
styleTargetsStyleMap.set(style, [...(styleTargetsStyleMap.get(style) ?? []), addedStyle]);
84+
}
85+
`;
86+
87+
return source;
88+
}
89+
90+
function guardDocumentUsageWithDefault(
91+
source: string,
92+
documentUsage: string,
93+
defaultValue: string
94+
): string {
95+
return source.replace(
96+
documentUsage,
97+
`('document' in globalThis ? ${documentUsage} : ${defaultValue})`
98+
);
99+
}
100+
101+
function addServiceWorkerSupport(source: string): string {
102+
// update location.reload usages
103+
source = source.replaceAll(
104+
/(window\.)?location.reload\(\)/g,
105+
"(location.reload?.() ?? (typeof chrome !== 'undefined' ? chrome.runtime?.reload?.() : ''))"
106+
);
107+
108+
// add document guards
109+
source = guardDocumentUsageWithDefault(
110+
source,
111+
"document.querySelectorAll(overlayId).length",
112+
"false"
113+
);
114+
115+
source = guardDocumentUsageWithDefault(
116+
source,
117+
"document.visibilityState",
118+
`"visible"`
119+
);
120+
121+
source = guardDocumentUsageWithDefault(
122+
source,
123+
`document.querySelectorAll('link')`,
124+
"[]"
125+
);
126+
127+
source = source.replace(
128+
"const enableOverlay =",
129+
`const enableOverlay = ('document' in globalThis) &&`
130+
);
131+
132+
return source;
133+
}
134+
135+
export default viteClientModifier;

0 commit comments

Comments
 (0)