|
1 | | -<!DOCTYPE html> |
| 1 | +<!doctype html> |
2 | 2 | <html lang="en"> |
3 | 3 | <head> |
4 | 4 | <meta charset="utf-8" /> |
|
132 | 132 | </main> |
133 | 133 | <script type="module"> |
134 | 134 | 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 | + }); |
136 | 205 | } |
137 | 206 | </script> |
138 | 207 | </body> |
|
0 commit comments