Skip to content

Commit 7e20874

Browse files
authored
[Website] Fix Safari failing to import main module after deployments (#3215)
1 parent bda0ed8 commit 7e20874

File tree

1 file changed

+71
-2
lines changed

1 file changed

+71
-2
lines changed

packages/playground/website/index.html

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<meta charset="utf-8" />
@@ -132,7 +132,76 @@
132132
</main>
133133
<script type="module">
134134
if (!shouldLazyLoadPlayground) {
135-
import('./src/main');
135+
const url = new URL(window.location);
136+
const hasRetried = url.searchParams.has('_ts');
137+
138+
/**
139+
* Safari sometimes fail to import this script after playground.wordpress.net
140+
* is deployed. The error message is:
141+
*
142+
* ```
143+
* TypeError: Importing a module script failed.
144+
* ```
145+
*
146+
* It is very difficult to debug as Safari does not provide a useful stack trace,
147+
* line numbers, or any further error details. After some hours of debugging, the
148+
* issue was narrowed down to this import() call.
149+
*
150+
* Somehow, when this import() runs, Safari does not even start a network request
151+
* to fetch this script. The Service Worker `fetch` event handler is also not called.
152+
* It just fails on sight. The root cause is unclear. Everything is fine CORS-wise,
153+
* Safari Memory cache seems unlikely since the "network" devtools tab does not display
154+
* any attempts to fetch this script.
155+
*
156+
* Interestingly, triggering a `fetch()` to the same URL and getting a 200 response
157+
* seems to resolve the issue after a page reload (but not before the page reload!)
158+
* Same for manually clearing Safari cache and reloading the page. This suggest this
159+
* is fundamentally a cache issue. That's why we're also adding a `_ts` query parameter
160+
* for a good measure to make sure a stale `index.html` is not loaded.
161+
*/
162+
import('./src/main')
163+
.then(() => {
164+
// If import succeeded and we had retried, clean up the URL
165+
if (hasRetried) {
166+
url.searchParams.delete('_ts');
167+
window.history.replaceState({}, '', url);
168+
}
169+
})
170+
.catch(async (error) => {
171+
console.error('Failed to load main module:', error);
172+
173+
// Only retry once to avoid infinite reload loop
174+
if (hasRetried) {
175+
console.error(
176+
'Failed to load main module after retry'
177+
);
178+
return;
179+
}
180+
181+
// Try to fetch the module URL to prime the cache/service worker.
182+
// Extract the actual built chunk URL by stringifying a function that
183+
// imports the module – other techniques of including a literal URL string
184+
// in the built file did not work.
185+
try {
186+
const getModuleUrl = () => import('./src/main');
187+
const fnString = getModuleUrl.toString();
188+
const urlMatch = fnString.match(
189+
/import\(['"](.+?)['"]\)/
190+
);
191+
if (urlMatch) {
192+
const moduleUrl = urlMatch[1];
193+
await fetch(moduleUrl, {
194+
cache: 'no-store',
195+
});
196+
}
197+
} catch (fetchError) {
198+
console.error('Failed to prime cache:', fetchError);
199+
}
200+
201+
// Reload with timestamp to bust cache
202+
url.searchParams.set('_ts', String(Date.now()));
203+
window.location.href = url.toString();
204+
});
136205
}
137206
</script>
138207
</body>

0 commit comments

Comments
 (0)