feat(browserembedded): Added a new "BrowserEmbedded" ShellMode.

This commit is contained in:
Carl de Billy 2021-10-22 14:11:22 -04:00
Родитель ee6cf7e75a
Коммит 490be9d289
4 изменённых файлов: 179 добавлений и 32 удалений

Просмотреть файл

@ -689,7 +689,45 @@ node app param1 param2
An example of the node.js support is available in the `Uno.Wasm.Node.Sample` and `Uno.Wasm.Node.Sample.Runner.njsproj` projects.
### Browser Embedded mode
By default, the project is launched with a HTML page (`index.html`). That's great for SPAs (Single Page Applications), but it could become difficult to embed into an existing webpage or application.
It is possible to use the Browser Embedded mode to allow the launching using JavaScript instead.
1. Add this line in your project file:
``` xml
<WasmShellMode>BrowserEmbedded</WasmShellMode>
```
A `embedded.js` file will be generated instead of the usual `index.html`, containing the required code to launch the application.
2. In the HTML where you want to host the application, add the following:
Using HTML:
``` html
<div id="uno-body" />
<script src="https://path.to/your/wasm/app/embedded.js" />
```
Using script:
``` javascript
// you must ensure there's a <div id="uno-body" /> present in the DOM before calling this:
import("https://path.to/your/wasm/app/embedded.js");
```
Important notes about Browser Embedded mode:
* There is NO script isolation mechanisms. That means the running application will have access to the same context and global objects.
* Loading more than one Uno bootstrapped application in the same page will conflict and produce unwanted results.
* Important: it may be required to **add CORS headers** to the application hosting website to allow the download and execution of scripts from it. It's already allowed in the development server shipped with Uno Bootstrapper.
### Support for additional JS files
Providing additional JS files is done through the inclusion of `EmbeddedResource` msbuild item files, in a project folder named `WasmScripts`.
Files are processed as embedded resources to allow for libraries to provide javascript files.

Просмотреть файл

@ -24,5 +24,6 @@ namespace Uno.Wasm.Bootstrap
{
Browser,
Node,
BrowserEmbedded
}
}

Просмотреть файл

@ -230,7 +230,8 @@ namespace Uno.Wasm.Bootstrap
GenerateConfig();
TouchServiceWorker();
MergeConfig();
GenerateHtml();
GenerateIndexHtml();
GenerateEmbeddedJs();
TryCompressDist();
return true;
@ -1578,7 +1579,7 @@ namespace Uno.Wasm.Bootstrap
{
var unoConfigJsPath = Path.Combine(_finalPackagePath, "uno-config.js");
using (var w = new StreamWriter(unoConfigJsPath, false, new UTF8Encoding(false)))
using (var w = new StreamWriter(unoConfigJsPath, false, _utf8Encoding))
{
var baseLookup = _shellMode == ShellMode.Node ? "" : $"{WebAppBasePath}{_remoteBasePackagePath}/";
var dependencies = string.Join(", ", _dependencies.Select(dep => BuildDependencyPath(dep, baseLookup)));
@ -1609,6 +1610,7 @@ namespace Uno.Wasm.Bootstrap
config.AppendLine($"config.total_assemblies_size = {totalAssembliesSize};");
config.AppendLine($"config.enable_pwa = {enablePWA.ToString().ToLowerInvariant()};");
config.AppendLine($"config.offline_files = ['{WebAppBasePath}', {offlineFiles}];");
config.AppendLine($"config.uno_shell_mode = \"{_shellMode}\";");
if (GenerateAOTProfile)
{
@ -1641,6 +1643,27 @@ namespace Uno.Wasm.Bootstrap
}
}
private void GenerateAppInfo()
{
var unoAppInfoPath = Path.Combine(_distPath, "uno-appinfo.json");
using var w = new StreamWriter(unoAppInfoPath, append: false, encoding: _utf8Encoding);
var appInfo = new AppInfo(WebAppBasePath, _remoteBasePackagePath);
}
internal class AppInfo
{
public AppInfo(string basePath, string packagePath)
{
BasePath = basePath;
PackagePath = packagePath;
}
public string BasePath { get; }
public string PackagePath { get; }
}
static string BuildDependencyPath(string dep, string baseLookup)
=> baseLookup.StartsWith("/")
? $"\"{baseLookup}{Path.GetFileName(dep)}\""
@ -1691,7 +1714,8 @@ namespace Uno.Wasm.Bootstrap
select file.Replace(_distPath, "").Replace("\\", "/");
private static readonly SHA384Managed _sha384 = new SHA384Managed();
private UTF8Encoding _utf8Encoding = new UTF8Encoding(false);
private (string monoWasmFileName, long monoWasmSize, long totalAssembliesSize, (string fileName, long length)[] assemblyFiles, (string fileName, string integrity)[] filesIntegrity) GetFilesDetails()
{
const string monoWasmFileName = "dotnet.wasm";
@ -1740,13 +1764,13 @@ namespace Uno.Wasm.Bootstrap
}
else
{
filesIntegrity = new (string fileName, string integrity)[0];
filesIntegrity = Array.Empty<(string fileName, string integrity)>();
}
return ($"{WebAppBasePath}{_remoteBasePackagePath}/" + monoWasmFileName, monoWasmSize, totalAssembliesSize, assemblyFiles, filesIntegrity);
}
private void GenerateHtml()
private void GenerateIndexHtml()
{
if (_shellMode != ShellMode.Browser)
{
@ -1755,34 +1779,118 @@ namespace Uno.Wasm.Bootstrap
var htmlPath = Path.Combine(_distPath, "index.html");
using (var w = new StreamWriter(htmlPath, false, new UTF8Encoding(false)))
using var w = new StreamWriter(htmlPath, false, _utf8Encoding);
using var reader = new StreamReader(IndexHtmlPath);
var html = reader.ReadToEnd();
var styles = string.Join("\r\n", _additionalStyles.Select(s => $"<link rel=\"stylesheet\" type=\"text/css\" href=\"{WebAppBasePath}{s}\" />"));
html = html.Replace("$(ADDITIONAL_CSS)", styles);
var extraBuilder = new StringBuilder();
GeneratePWAContent(extraBuilder);
GeneratePrefetchHeaderContent(extraBuilder);
html = html.Replace("$(ADDITIONAL_HEAD)", extraBuilder.ToString());
// Compatibility after the change from mono.js to dotnet.js
html = html.Replace("mono.js\"", "dotnet.js\"");
if (WebAppBasePath != "./")
{
using (var reader = new StreamReader(IndexHtmlPath))
{
var html = reader.ReadToEnd();
var styles = string.Join("\r\n", _additionalStyles.Select(s => $"<link rel=\"stylesheet\" type=\"text/css\" href=\"{WebAppBasePath}{s}\" />"));
html = html.Replace("$(ADDITIONAL_CSS)", styles);
var extraBuilder = new StringBuilder();
GeneratePWAContent(extraBuilder);
GeneratePrefetchHeaderContent(extraBuilder);
html = html.Replace("$(ADDITIONAL_HEAD)", extraBuilder.ToString());
// Compatibility after the change from mono.js to dotnet.js
html = html.Replace("mono.js\"", "dotnet.js\"");
if (WebAppBasePath != "./")
{
html = html.Replace($"\"{WebAppBasePath}", $"\"{WebAppBasePath}{_remoteBasePackagePath}/");
}
html = html.Replace($"\"./", $"\"{WebAppBasePath}{_remoteBasePackagePath}/");
w.Write(html);
Log.LogMessage($"HTML {htmlPath}");
}
html = html.Replace($"\"{WebAppBasePath}", $"\"{WebAppBasePath}{_remoteBasePackagePath}/");
}
html = html.Replace($"\"./", $"\"{WebAppBasePath}{_remoteBasePackagePath}/");
w.Write(html);
Log.LogMessage($"HTML {htmlPath}");
}
private void GenerateEmbeddedJs()
{
if (_shellMode != ShellMode.BrowserEmbedded)
{
return;
}
var scriptPath = Path.Combine(_distPath, "embedded.js");
using var w = new StreamWriter(scriptPath, append: false, _utf8Encoding);
const string javascriptTemplate = @"
(async function () {
const executingScript = document.currentScript;
if(!executingScript) {
console.err(""embedded.js MUST be run using a <script> tag in the current version."");
return;
}
const executingScriptAbsolutePath = (new URL(executingScript.src, document.location)).href;
const package = ""$(PACKAGE_PATH)"";
const absolutePath = (new URL(package, executingScriptAbsolutePath)).href;
const preloadingScripts = [""require"",""mono-config"",""uno-config""];
const loadingScripts = [""uno-bootstrap"",""dotnet""];
const styles = [$(STYLES)];
styles.forEach(s => {
const scriptElement = document.createElement(""link"");
scriptElement.setAttribute(""href"", `${absolutePath}/${s}`);
scriptElement.setAttribute(""type"", ""text/css"");
document.head.appendChild(scriptElement);
});
const baseElement = document.createElement(""base"");
baseElement.setAttribute(""href"", absolutePath);
document.head.appendChild(baseElement);
const html = ""<div id='uno-body' class='container-fluid uno-body'><div class='uno-loader' loading-position='bottom' loading-alert='none'><img class='logo' src='' /><progress></progress><span class='alert'></span></div></div>"";
if(typeof unoRootElement !== 'undefined') {
unoRootElement.innerHTML = html;
} else {
var rootDiv = document.createElement(""div"");
rootDiv.innerHTML = html;
document.body.appendChild(rootDiv);
}
const loadScript = s => new Promise((ok, err) => {
const scriptElement = document.createElement(""script"");
scriptElement.setAttribute(""src"", `${absolutePath}/${s}.js`);
scriptElement.setAttribute(""type"", ""text/javascript"");
scriptElement.onload = () => ok();
scriptElement.onerror = () => err(""err loading "" + s);
document.head.appendChild(scriptElement);
});
await preloadingScripts.reduce(async (p, s) => {
await p;
await loadScript(s);
}, undefined);
config.uno_app_base = absolutePath;
await loadingScripts.reduce(async (p, s) => {
await p;
await loadScript(s);
}, undefined);
})();
";
var stylesString = string.Join(",", _additionalStyles?.Select(s=>$"\"{Uri.EscapeDataString(s)}\"") ?? Array.Empty<string>());
var javascript = javascriptTemplate
.Replace("$(PACKAGE_PATH)", _remoteBasePackagePath)
.Replace("$(STYLES)", stylesString);
w.Write(javascript);
w.Flush();
// Write index.html loading the javascript as a helper
var htmlPath = Path.Combine(_distPath, "index.html");
using var w2 = new StreamWriter(htmlPath, append: false, _utf8Encoding);
const string html = "<html><body><script src=\"embedded.js\" type=\"text/javascript\"></script>\n";
w2.Write(html);
w2.Flush();
}
private void GeneratePrefetchHeaderContent(StringBuilder extraBuilder)

Просмотреть файл

@ -438,7 +438,7 @@ var App = {
// Uno.Wasm.Bootstrap is using "requirejs" by default, which is an AMD implementation
// But when run with NodeJS or Electron, it's using CommonJS instead of AMD
this.isUsingCommonJS = ENVIRONMENT_IS_NODE || Module.isElectron();
this.isUsingCommonJS = config.uno_shell_mode !== "BrowserEmbedded" && (ENVIRONMENT_IS_NODE || Module.isElectron());
if (config.enable_debugging) console.log("Done loading the BCL");