Skip to content

Commit e597ff9

Browse files
committed
fix: Firefox Hot reload
1 parent 563827d commit e597ff9

File tree

3 files changed

+221
-9
lines changed

3 files changed

+221
-9
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// As of Dec 2024, Firefox does not support ES6 modules in service workers, so we need to use importScripts
2+
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker#browser_compatibility
3+
importScripts("$(REMOTE_WEBAPP_PATH)$(REMOTE_BASE_PATH)/uno-config-script.js");
4+
5+
if (config.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True") {
6+
console.debug("[ServiceWorker] Initializing");
7+
let uno_enable_tracing = config.uno_enable_tracing;
8+
9+
self.addEventListener('install', function (e) {
10+
console.debug('[ServiceWorker] Installing offline worker');
11+
e.waitUntil(
12+
caches.open('$(CACHE_KEY)').then(async function (cache) {
13+
console.debug('[ServiceWorker] Caching app binaries and content');
14+
15+
// Add files one by one to avoid failed downloads to prevent the
16+
// worker to fail installing.
17+
for (var i = 0; i < config.offline_files.length; i++) {
18+
try {
19+
if (uno_enable_tracing) {
20+
console.debug(`[ServiceWorker] cache ${key}`);
21+
}
22+
23+
await cache.add(config.offline_files[i]);
24+
}
25+
catch (e) {
26+
console.debug(`[ServiceWorker] Failed to fetch ${config.offline_files[i]}`);
27+
}
28+
}
29+
30+
// Add the runtime's own files to the cache. We cannot use the
31+
// existing cached content from the runtime as the keys contain a
32+
// hash we cannot reliably compute.
33+
var c = await fetch("$(REMOTE_WEBAPP_PATH)_framework/blazor.boot.json");
34+
const monoConfigResources = (await c.json()).resources;
35+
36+
var entries = {
37+
...(monoConfigResources.coreAssembly || {})
38+
, ...(monoConfigResources.assembly || {})
39+
, ...(monoConfigResources.lazyAssembly || {})
40+
, ...(monoConfigResources.jsModuleWorker || {})
41+
, ...(monoConfigResources.jsModuleGlobalization || {})
42+
, ...(monoConfigResources.jsModuleNative || {})
43+
, ...(monoConfigResources.jsModuleRuntime || {})
44+
, ...(monoConfigResources.wasmNative || {})
45+
, ...(monoConfigResources.icu || {})
46+
, ...(monoConfigResources.coreAssembly || {})
47+
};
48+
49+
for (var key in entries) {
50+
var uri = `$(REMOTE_WEBAPP_PATH)_framework/${key}`;
51+
52+
if (uno_enable_tracing) {
53+
console.debug(`[ServiceWorker] cache ${uri}`);
54+
}
55+
56+
await cache.add(uri);
57+
}
58+
})
59+
);
60+
});
61+
62+
self.addEventListener('activate', event => {
63+
event.waitUntil(self.clients.claim());
64+
});
65+
66+
self.addEventListener('fetch', event => {
67+
event.respondWith(async function () {
68+
try {
69+
// Network first mode to get fresh content every time, then fallback to
70+
// cache content if needed.
71+
return await fetch(event.request);
72+
} catch (err) {
73+
return caches.match(event.request).then(response => {
74+
return response || fetch(event.request);
75+
});
76+
}
77+
}());
78+
});
79+
}
80+
else {
81+
// In development, always fetch from the network and do not enable offline support.
82+
// This is because caching would make development more difficult (changes would not
83+
// be reflected on the first load after each change).
84+
// It also breaks the hot reload feature because VS's browserlink is not always able to
85+
// inject its own framework in the served scripts and pages.
86+
self.addEventListener('fetch', () => { });
87+
}

src/Uno.Wasm.Bootstrap/ShellTask.cs

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,11 @@ public override bool Execute()
153153
RemoveDuplicateAssets();
154154
GeneratePackageFolder();
155155
BuildServiceWorker();
156+
BuildServiceWorkerClassic();
156157
GenerateEmbeddedJs();
157158
GenerateIndexHtml();
158159
GenerateConfig();
160+
GenerateConfigScript();
159161
RemoveDuplicateAssets();
160162
}
161163
finally
@@ -312,6 +314,24 @@ private void BuildServiceWorker()
312314
CopyStreamToOutput("service-worker.js", memoryStream, DeployMode.Root);
313315
}
314316

317+
// Case for browsers that do not support modules for service workers: Firefox for example
318+
private void BuildServiceWorkerClassic()
319+
{
320+
using var resourceStream = GetType().Assembly.GetManifestResourceStream("Uno.Wasm.Bootstrap.v0.Embedded.service-worker-classic.js");
321+
using var reader = new StreamReader(resourceStream);
322+
323+
var worker = TouchServiceWorker(reader.ReadToEnd());
324+
var memoryStream = new MemoryStream();
325+
326+
using var writer = new StreamWriter(memoryStream, Encoding.UTF8);
327+
writer.Write(worker);
328+
writer.Flush();
329+
330+
memoryStream.Position = 0;
331+
332+
CopyStreamToOutput("service-worker-classic.js", memoryStream, DeployMode.Root);
333+
}
334+
315335
private void ExtractAdditionalJS()
316336
{
317337
BuildResourceSearchList();
@@ -535,7 +555,8 @@ private void GenerateConfig()
535555
.Where(d =>
536556
!d.EndsWith("require.js")
537557
&& !d.EndsWith("uno-bootstrap.js")
538-
&& !d.EndsWith("service-worker.js"))
558+
&& !d.EndsWith("service-worker.js")
559+
&& !d.EndsWith("service-worker-classic.js"))
539560
.Select(dep => BuildDependencyPath(dep, baseLookup)));
540561

541562
var config = new StringBuilder();
@@ -625,6 +646,108 @@ private void GenerateConfig()
625646
}
626647
}
627648

649+
650+
651+
private void GenerateConfigScript()
652+
{
653+
var unoConfigJsPath = Path.Combine(_intermediateAssetsPath, "uno-config-script.js");
654+
655+
using (var w = new StreamWriter(unoConfigJsPath, false, _utf8Encoding))
656+
{
657+
var baseLookup = _shellMode == ShellMode.Node ? "" : $"{WebAppBasePath}{PackageAssetsFolder}/";
658+
var dependencies = string.Join(", ", _dependencies
659+
.Where(d =>
660+
!d.EndsWith("require.js")
661+
&& !d.EndsWith("uno-bootstrap.js")
662+
&& !d.EndsWith("service-worker.js")
663+
&& !d.EndsWith("service-worker-classic.js"))
664+
.Select(dep => BuildDependencyPath(dep, baseLookup)));
665+
666+
var config = new StringBuilder();
667+
668+
var enablePWA = !string.IsNullOrEmpty(PWAManifestFile);
669+
670+
var sanitizedOfflineFiles = StaticWebContent
671+
.Select(f => f.GetMetadata("Link")
672+
.Replace("\\", "/")
673+
.Replace("wwwroot/", ""))
674+
.Concat([$"uno-config-script.js", "_framework/blazor.boot.json", "."]);
675+
676+
var offlineFiles = enablePWA ? string.Join(", ", sanitizedOfflineFiles.Select(f => $"\"{WebAppBasePath}{f}\"")) : "";
677+
678+
var emccExportedRuntimeMethodsParams = string.Join(
679+
",",
680+
GetEmccExportedRuntimeMethods().Select(f => $"\'{f}\'"));
681+
682+
var runtimeOptionsSet = string.Join(",", (RuntimeOptions?.Split(' ') ?? []).Select(f => $"\'{f}\'"));
683+
684+
config.AppendLine($"self.config = {{}};");
685+
config.AppendLine($"self.config.uno_remote_managedpath = \"_framework\";");
686+
config.AppendLine($"self.config.uno_app_base = \"{WebAppBasePath}{PackageAssetsFolder}\";");
687+
config.AppendLine($"self.config.uno_dependencies = [{dependencies}];");
688+
config.AppendLine($"self.config.uno_runtime_options = [{runtimeOptionsSet}];");
689+
config.AppendLine($"self.config.enable_pwa = {enablePWA.ToString().ToLowerInvariant()};");
690+
config.AppendLine($"self.config.offline_files = ['{WebAppBasePath}', {offlineFiles}];");
691+
config.AppendLine($"self.config.uno_shell_mode = \"{_shellMode}\";");
692+
config.AppendLine($"self.config.uno_debugging_enabled = {(!Optimize).ToString().ToLowerInvariant()};");
693+
config.AppendLine($"self.config.uno_enable_tracing = {EnableTracing.ToString().ToLowerInvariant()};");
694+
config.AppendLine($"self.config.uno_load_all_satellite_resources = {LoadAllSatelliteResources.ToString().ToLowerInvariant()};");
695+
config.AppendLine($"self.config.emcc_exported_runtime_methods = [{emccExportedRuntimeMethodsParams}];");
696+
697+
if (GenerateAOTProfile)
698+
{
699+
config.AppendLine($"self.config.generate_aot_profile = true;");
700+
}
701+
702+
config.AppendLine($"self.config.environmentVariables = self.config.environmentVariables || {{}};");
703+
704+
void AddEnvironmentVariable(string name, string value) => config.AppendLine($"self.config.environmentVariables[\"{name}\"] = \"{value}\";");
705+
706+
if (MonoEnvironment != null)
707+
{
708+
foreach (var env in MonoEnvironment)
709+
{
710+
AddEnvironmentVariable(env.ItemSpec, env.GetMetadata("Value"));
711+
}
712+
}
713+
714+
var isProfiledAOT = UseAotProfile && _runtimeExecutionMode == RuntimeExecutionMode.InterpreterAndAOT;
715+
716+
AddEnvironmentVariable("UNO_BOOTSTRAP_MONO_RUNTIME_MODE", _runtimeExecutionMode.ToString());
717+
AddEnvironmentVariable("UNO_BOOTSTRAP_MONO_PROFILED_AOT", isProfiledAOT.ToString());
718+
AddEnvironmentVariable("UNO_BOOTSTRAP_LINKER_ENABLED", (PublishTrimmed && RunILLink).ToString());
719+
AddEnvironmentVariable("UNO_BOOTSTRAP_DEBUGGER_ENABLED", (!Optimize).ToString());
720+
AddEnvironmentVariable("UNO_BOOTSTRAP_MONO_RUNTIME_CONFIGURATION", "Release");
721+
AddEnvironmentVariable("UNO_BOOTSTRAP_MONO_RUNTIME_FEATURES", BuildRuntimeFeatures());
722+
AddEnvironmentVariable("UNO_BOOTSTRAP_APP_BASE", PackageAssetsFolder);
723+
AddEnvironmentVariable("UNO_BOOTSTRAP_WEBAPP_BASE_PATH", WebAppBasePath);
724+
725+
if (EmccFlags?.Any(f => f.ItemSpec?.Contains("MAXIMUM_MEMORY=4GB") ?? false) ?? false)
726+
{
727+
// Detects the use of the 4GB flag: https://v8.dev/blog/4gb-wasm-memory
728+
AddEnvironmentVariable("UNO_BOOTSTRAP_EMSCRIPTEN_MAXIMUM_MEMORY", "4GB");
729+
}
730+
731+
if (EnableLogProfiler)
732+
{
733+
AddEnvironmentVariable("UNO_BOOTSTRAP_LOG_PROFILER_OPTIONS", LogProfilerOptions);
734+
}
735+
736+
w.Write(config.ToString());
737+
738+
TaskItem indexMetadata = new(
739+
unoConfigJsPath, new Dictionary<string, string>
740+
{
741+
["CopyToOutputDirectory"] = "PreserveNewest",
742+
["ContentRoot"] = _intermediateAssetsPath,
743+
["Link"] = $"wwwroot/{PackageAssetsFolder}/" + Path.GetFileName(unoConfigJsPath),
744+
});
745+
746+
StaticWebContent = StaticWebContent.Concat([indexMetadata]).ToArray();
747+
}
748+
}
749+
750+
628751
private void GenerateIndexHtml()
629752
{
630753
if (_shellMode != ShellMode.Browser)

src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ namespace Uno.WebAssembly.Bootstrap {
258258

259259
this._runMain(this._unoConfig.uno_main, []);
260260

261-
this.initializePWA();
261+
await this.initializePWA();
262262

263263
} catch (e) {
264264
console.error(e);
@@ -511,7 +511,7 @@ namespace Uno.WebAssembly.Bootstrap {
511511
link.click();
512512
}
513513

514-
private initializePWA() {
514+
private async initializePWA(): Promise<void> {
515515

516516
if (typeof window === 'object' /* ENVIRONMENT_IS_WEB */) {
517517

@@ -523,15 +523,17 @@ namespace Uno.WebAssembly.Bootstrap {
523523

524524
console.debug(`Registering service worker for ${_webAppBasePath}`);
525525

526-
navigator.serviceWorker
527-
.register(
528-
`${_webAppBasePath}service-worker.js`, {
526+
try {
527+
await navigator.serviceWorker.register(`${_webAppBasePath}service-worker.js`, {
529528
scope: _webAppBasePath,
530529
type: 'module'
531-
})
532-
.then(function () {
533-
console.debug('Service Worker Registered');
534530
});
531+
console.debug('Service Worker Registered');
532+
} catch (e) {
533+
console.debug('Service Worker registration failed. falling back to classic service worker', e);
534+
535+
await navigator.serviceWorker.register(`${_webAppBasePath}service-worker-classic.js`, {scope: _webAppBasePath});
536+
}
535537
}
536538
}
537539
}

0 commit comments

Comments
 (0)