Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Firefox Hot reload #934

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions src/Uno.Wasm.Bootstrap/Embedded/service-worker-classic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// As of Dec 2024, Firefox does not support ES6 modules in service workers, so we need to use importScripts
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker#browser_compatibility
importScripts("$(REMOTE_WEBAPP_PATH)$(REMOTE_BASE_PATH)/uno-config-script.js");

if (config.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True") {
console.debug("[ServiceWorker] Initializing");
let uno_enable_tracing = config.uno_enable_tracing;

self.addEventListener('install', function (e) {
console.debug('[ServiceWorker] Installing offline worker');
e.waitUntil(
caches.open('$(CACHE_KEY)').then(async function (cache) {
console.debug('[ServiceWorker] Caching app binaries and content');

// Add files one by one to avoid failed downloads to prevent the
// worker to fail installing.
for (var i = 0; i < config.offline_files.length; i++) {
try {
if (uno_enable_tracing) {
console.debug(`[ServiceWorker] cache ${key}`);
}

await cache.add(config.offline_files[i]);
}
catch (e) {
console.debug(`[ServiceWorker] Failed to fetch ${config.offline_files[i]}`);
}
}

// Add the runtime's own files to the cache. We cannot use the
// existing cached content from the runtime as the keys contain a
// hash we cannot reliably compute.
var c = await fetch("$(REMOTE_WEBAPP_PATH)_framework/blazor.boot.json");
const monoConfigResources = (await c.json()).resources;

var entries = {
...(monoConfigResources.coreAssembly || {})
, ...(monoConfigResources.assembly || {})
, ...(monoConfigResources.lazyAssembly || {})
, ...(monoConfigResources.jsModuleWorker || {})
, ...(monoConfigResources.jsModuleGlobalization || {})
, ...(monoConfigResources.jsModuleNative || {})
, ...(monoConfigResources.jsModuleRuntime || {})
, ...(monoConfigResources.wasmNative || {})
, ...(monoConfigResources.icu || {})
, ...(monoConfigResources.coreAssembly || {})
};

for (var key in entries) {
var uri = `$(REMOTE_WEBAPP_PATH)_framework/${key}`;

if (uno_enable_tracing) {
console.debug(`[ServiceWorker] cache ${uri}`);
}

await cache.add(uri);
}
})
);
});

self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', event => {
event.respondWith(async function () {
try {
// Network first mode to get fresh content every time, then fallback to
// cache content if needed.
return await fetch(event.request);
} catch (err) {
return caches.match(event.request).then(response => {
return response || fetch(event.request);
});
}
}());
});
}
else {
// In development, always fetch from the network and do not enable offline support.
// This is because caching would make development more difficult (changes would not
// be reflected on the first load after each change).
// It also breaks the hot reload feature because VS's browserlink is not always able to
// inject its own framework in the served scripts and pages.
self.addEventListener('fetch', () => { });
}
81 changes: 56 additions & 25 deletions src/Uno.Wasm.Bootstrap/ShellTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ public override bool Execute()
ExtractAdditionalCSS();
RemoveDuplicateAssets();
GeneratePackageFolder();
BuildServiceWorker();
BuildServiceWorkers();
GenerateEmbeddedJs();
GenerateIndexHtml();
GenerateConfig();
GenerateConfigFiles();
RemoveDuplicateAssets();
}
finally
Expand Down Expand Up @@ -293,9 +293,17 @@ private void CopyContent()
}
}

private void BuildServiceWorker()
private void BuildServiceWorkers()
{
using var resourceStream = GetType().Assembly.GetManifestResourceStream("Uno.Wasm.Bootstrap.v0.Embedded.service-worker.js");
BuildServiceWorker(resource: "Uno.Wasm.Bootstrap.v0.Embedded.service-worker.js", outputFile: "service-worker.js");

// Case for browsers that do not support modules for service workers: Firefox for example
BuildServiceWorker(resource: "Uno.Wasm.Bootstrap.v0.Embedded.service-worker-classic.js", outputFile: "service-worker-classic.js");
}

private void BuildServiceWorker(string resource, string outputFile)
{
using var resourceStream = GetType().Assembly.GetManifestResourceStream(resource);
using var reader = new StreamReader(resourceStream);

var worker = TouchServiceWorker(reader.ReadToEnd());
Expand All @@ -307,7 +315,7 @@ private void BuildServiceWorker()

memoryStream.Position = 0;

CopyStreamToOutput("service-worker.js", memoryStream, DeployMode.Root);
CopyStreamToOutput(outputFile, memoryStream, DeployMode.Root);
}

private void ExtractAdditionalJS()
Expand Down Expand Up @@ -522,9 +530,18 @@ static string BuildDependencyPath(string dep, string baseLookup)
? $"\"{baseLookup}{Path.GetFileName(dep)}\""
: $"\"{baseLookup}{Path.GetFileNameWithoutExtension(dep)}\"";

private void GenerateConfig()
private void GenerateConfigFiles()
{
GenerateConfigFile("uno-config.js", isModule: true);

GenerateConfigFile("uno-config-script.js", isModule: false);
}

private void GenerateConfigFile(string fileName, bool isModule)
{
var unoConfigJsPath = Path.Combine(_intermediateAssetsPath, "uno-config.js");
var self = isModule ? "" : "self.";

var unoConfigJsPath = Path.Combine(_intermediateAssetsPath, fileName);

using (var w = new StreamWriter(unoConfigJsPath, false, _utf8Encoding))
{
Expand All @@ -533,7 +550,8 @@ private void GenerateConfig()
.Where(d =>
!d.EndsWith("require.js")
&& !d.EndsWith("uno-bootstrap.js")
&& !d.EndsWith("service-worker.js"))
&& !d.EndsWith("service-worker.js")
&& !d.EndsWith("service-worker-classic.js"))
.Select(dep => BuildDependencyPath(dep, baseLookup)));

var config = new StringBuilder();
Expand All @@ -544,7 +562,7 @@ private void GenerateConfig()
.Select(f => f.GetMetadata("Link")
.Replace("\\", "/")
.Replace("wwwroot/", ""))
.Concat([$"uno-config.js", "_framework/blazor.boot.json", "."]);
.Concat([fileName, "_framework/blazor.boot.json", "."]);

var offlineFiles = enablePWA ? string.Join(", ", sanitizedOfflineFiles.Select(f => $"\"{WebAppBasePath}{f}\"")) : "";

Expand All @@ -554,27 +572,36 @@ private void GenerateConfig()

var runtimeOptionsSet = string.Join(",", (RuntimeOptions?.Split(' ') ?? []).Select(f => $"\'{f}\'"));

config.AppendLine($"let config = {{}};");
config.AppendLine($"config.uno_remote_managedpath = \"_framework\";");
config.AppendLine($"config.uno_app_base = \"{WebAppBasePath}{PackageAssetsFolder}\";");
config.AppendLine($"config.uno_dependencies = [{dependencies}];");
config.AppendLine($"config.uno_runtime_options = [{runtimeOptionsSet}];");
config.AppendLine($"config.enable_pwa = {enablePWA.ToString().ToLowerInvariant()};");
config.AppendLine($"config.offline_files = ['{WebAppBasePath}', {offlineFiles}];");
config.AppendLine($"config.uno_shell_mode = \"{_shellMode}\";");
config.AppendLine($"config.uno_debugging_enabled = {(!Optimize).ToString().ToLowerInvariant()};");
config.AppendLine($"config.uno_enable_tracing = {EnableTracing.ToString().ToLowerInvariant()};");
config.AppendLine($"config.uno_load_all_satellite_resources = {LoadAllSatelliteResources.ToString().ToLowerInvariant()};");
config.AppendLine($"config.emcc_exported_runtime_methods = [{emccExportedRuntimeMethodsParams}];");

if (isModule)
{
config.AppendLine($"let config = {{}};");
}
else
{
config.AppendLine($"{self}config = {{}};");
}

config.AppendLine($"{self}config.uno_remote_managedpath = \"_framework\";");
config.AppendLine($"{self}config.uno_app_base = \"{WebAppBasePath}{PackageAssetsFolder}\";");
config.AppendLine($"{self}config.uno_dependencies = [{dependencies}];");
config.AppendLine($"{self}config.uno_runtime_options = [{runtimeOptionsSet}];");
config.AppendLine($"{self}config.enable_pwa = {enablePWA.ToString().ToLowerInvariant()};");
config.AppendLine($"{self}config.offline_files = ['{WebAppBasePath}', {offlineFiles}];");
config.AppendLine($"{self}config.uno_shell_mode = \"{_shellMode}\";");
config.AppendLine($"{self}config.uno_debugging_enabled = {(!Optimize).ToString().ToLowerInvariant()};");
config.AppendLine($"{self}config.uno_enable_tracing = {EnableTracing.ToString().ToLowerInvariant()};");
config.AppendLine($"{self}config.uno_load_all_satellite_resources = {LoadAllSatelliteResources.ToString().ToLowerInvariant()};");
config.AppendLine($"{self}config.emcc_exported_runtime_methods = [{emccExportedRuntimeMethodsParams}];");

if (GenerateAOTProfile)
{
config.AppendLine($"config.generate_aot_profile = true;");
config.AppendLine($"{self}config.generate_aot_profile = true;");
}

config.AppendLine($"config.environmentVariables = config.environmentVariables || {{}};");
config.AppendLine($"{self}config.environmentVariables = {self}config.environmentVariables || {{}};");

void AddEnvironmentVariable(string name, string value) => config.AppendLine($"config.environmentVariables[\"{name}\"] = \"{value}\";");
void AddEnvironmentVariable(string name, string value) => config.AppendLine($"{self}config.environmentVariables[\"{name}\"] = \"{value}\";");

if (MonoEnvironment != null)
{
Expand Down Expand Up @@ -606,7 +633,10 @@ private void GenerateConfig()
AddEnvironmentVariable("UNO_BOOTSTRAP_LOG_PROFILER_OPTIONS", LogProfilerOptions);
}

config.AppendLine("export { config };");
if (isModule)
{
config.AppendLine("export { config };");
}

w.Write(config.ToString());

Expand All @@ -622,6 +652,7 @@ private void GenerateConfig()
}
}


private void GenerateIndexHtml()
{
if (_shellMode != ShellMode.Browser)
Expand Down
22 changes: 14 additions & 8 deletions src/Uno.Wasm.Bootstrap/ts/Uno/WebAssembly/Bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ namespace Uno.WebAssembly.Bootstrap {

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

this.initializePWA();
await this.initializePWA();

} catch (e) {
console.error(e);
Expand Down Expand Up @@ -510,7 +510,7 @@ namespace Uno.WebAssembly.Bootstrap {
link.click();
}

private initializePWA() {
private async initializePWA(): Promise<void> {

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

Expand All @@ -522,15 +522,21 @@ namespace Uno.WebAssembly.Bootstrap {

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

navigator.serviceWorker
.register(
`${_webAppBasePath}service-worker.js`, {
try {
await navigator.serviceWorker.register(`${_webAppBasePath}service-worker.js`, {
scope: _webAppBasePath,
type: 'module'
})
.then(function () {
console.debug('Service Worker Registered');
});
console.debug('Service Worker Registered (module)');
} catch (e) {
console.debug('Service Worker registration (module) failed.', e);

console.debug('Falling back to classic service worker registration...');

await navigator.serviceWorker.register(`${_webAppBasePath}service-worker-classic.js`, {scope: _webAppBasePath});

console.debug('Service Worker Registered (classic)');
}
}
}
}
Expand Down
Loading