From 4138b3a049331dc527b8b379e63d0de131934d16 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 8 Dec 2017 17:06:40 +0000 Subject: [PATCH] Make Blazor apps actually start up Mono and execute the specified .NET entrypoint --- Blazor.sln | 7 + .../HostedInAspNet.Client.csproj | 4 + .../HostedInAspNet.Client/wwwroot/index.html | 2 +- src/Microsoft.Blazor.Browser/src/Boot.ts | 36 +++- .../src/Environment.ts | 6 + .../src/Platform/DotNet.ts | 6 + .../src/Platform/Mono/MonoPlatform.ts | 186 ++++++++++++++++++ .../src/Platform/Mono/MonoTypes.d.ts | 14 ++ .../src/Platform/Platform.ts | 17 ++ ...pmentServerApplicationBuilderExtensions.cs | 36 +++- src/Microsoft.Blazor/Microsoft.Blazor.csproj | 7 + 11 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.Blazor.Browser/src/Environment.ts create mode 100644 src/Microsoft.Blazor.Browser/src/Platform/DotNet.ts create mode 100644 src/Microsoft.Blazor.Browser/src/Platform/Mono/MonoPlatform.ts create mode 100644 src/Microsoft.Blazor.Browser/src/Platform/Mono/MonoTypes.d.ts create mode 100644 src/Microsoft.Blazor.Browser/src/Platform/Platform.ts create mode 100644 src/Microsoft.Blazor/Microsoft.Blazor.csproj diff --git a/Blazor.sln b/Blazor.sln index 9f41bc1bb6..811a604b46 100644 --- a/Blazor.sln +++ b/Blazor.sln @@ -40,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "sa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "samples\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{F8996835-41F7-4663-91DF-3B5652ADC37D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Blazor", "src\Microsoft.Blazor\Microsoft.Blazor.csproj", "{7FD8C650-74B3-4153-AEA1-00F4F6AF393D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +96,10 @@ Global {F8996835-41F7-4663-91DF-3B5652ADC37D}.Debug|Any CPU.Build.0 = Debug|Any CPU {F8996835-41F7-4663-91DF-3B5652ADC37D}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8996835-41F7-4663-91DF-3B5652ADC37D}.Release|Any CPU.Build.0 = Release|Any CPU + {7FD8C650-74B3-4153-AEA1-00F4F6AF393D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FD8C650-74B3-4153-AEA1-00F4F6AF393D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FD8C650-74B3-4153-AEA1-00F4F6AF393D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FD8C650-74B3-4153-AEA1-00F4F6AF393D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -113,6 +119,7 @@ Global {4D367450-96E9-4C8C-8B56-EED8ADE3A20D} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA} {B4335F7C-4E86-4559-821F-F1B1C75F5FAE} = {4D367450-96E9-4C8C-8B56-EED8ADE3A20D} {F8996835-41F7-4663-91DF-3B5652ADC37D} = {4D367450-96E9-4C8C-8B56-EED8ADE3A20D} + {7FD8C650-74B3-4153-AEA1-00F4F6AF393D} = {B867E038-B3CE-43E3-9292-61568C46CDEB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3} diff --git a/samples/HostedInAspNet.Client/HostedInAspNet.Client.csproj b/samples/HostedInAspNet.Client/HostedInAspNet.Client.csproj index b1f3487a93..4dd88a12c8 100644 --- a/samples/HostedInAspNet.Client/HostedInAspNet.Client.csproj +++ b/samples/HostedInAspNet.Client/HostedInAspNet.Client.csproj @@ -4,4 +4,8 @@ netcoreapp2.0 + + + + diff --git a/samples/HostedInAspNet.Client/wwwroot/index.html b/samples/HostedInAspNet.Client/wwwroot/index.html index d42bd98d14..e1545dd042 100644 --- a/samples/HostedInAspNet.Client/wwwroot/index.html +++ b/samples/HostedInAspNet.Client/wwwroot/index.html @@ -6,6 +6,6 @@

Hello

- + diff --git a/src/Microsoft.Blazor.Browser/src/Boot.ts b/src/Microsoft.Blazor.Browser/src/Boot.ts index 0331260eb4..ac8b00a748 100644 --- a/src/Microsoft.Blazor.Browser/src/Boot.ts +++ b/src/Microsoft.Blazor.Browser/src/Boot.ts @@ -1 +1,35 @@ -console.log('Blazor is loading'); +import { platform } from './Environment'; +import { getAssemblyNameFromUrl } from './Platform/DotNet'; + +async function boot() { + // Read startup config from the `); +} + +function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () => void, onError: (reason: any) => void) { + const module = {} as typeof Module; + + module.print = line => console.log(`WASM: ${line}`); + module.printErr = line => console.error(`WASM: ${line}`); + module.wasmBinaryFile = '/_framework/wasm/mono.wasm'; + module.asmjsCodeFile = '/_framework/asmjs/mono.asm.js'; + module.preRun = []; + module.postRun = []; + module.preloadPlugins = []; + + module.preRun.push(() => { + // By now, emscripten should be initialised enough that we can capture these methods for later use + assembly_load = Module.cwrap('mono_wasm_assembly_load', 'number', ['string']); + find_class = Module.cwrap('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string']); + find_method = Module.cwrap('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']); + invoke_method = Module.cwrap('mono_wasm_invoke_method', 'number', ['number', 'number', 'number']); + mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); + mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); + + const loadBclAssemblies = [ + 'mscorlib', + 'System', + 'System.Core', + 'Facades/netstandard', + 'Facades/System.Console', + 'Facades/System.Collections', + 'Facades/System.Diagnostics.Debug', + 'Facades/System.IO', + 'Facades/System.Linq', + 'Facades/System.Reflection', + 'Facades/System.Reflection.Extensions', + 'Facades/System.Runtime', + 'Facades/System.Runtime.Extensions', + 'Facades/System.Runtime.InteropServices', + 'Facades/System.Threading', + 'Facades/System.Threading.Tasks' + ]; + + var allAssemblyUrls = loadAssemblyUrls + .concat(loadBclAssemblies.map(name => `_framework/bcl/${name}.dll`)); + + Module.FS_createPath('/', 'appBinDir', true, true); + allAssemblyUrls.forEach(url => + FS.createPreloadedFile('appBinDir', `${getAssemblyNameFromUrl(url)}.dll`, url, true, false, null, onError)); + }); + + module.postRun.push(() => { + const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']); + load_runtime('appBinDir'); + onReady(); + }); + + return module; +} + +function asyncLoad(url, onload, onerror) { + var xhr = new XMLHttpRequest; + xhr.open('GET', url, /* async: */ true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function xhr_onload() { + if (xhr.status == 200 || xhr.status == 0 && xhr.response) { + var asm = new Uint8Array(xhr.response); + onload(asm); + } else { + onerror(xhr); + } + }; + xhr.onerror = onerror; + xhr.send(null); +} diff --git a/src/Microsoft.Blazor.Browser/src/Platform/Mono/MonoTypes.d.ts b/src/Microsoft.Blazor.Browser/src/Platform/Mono/MonoTypes.d.ts new file mode 100644 index 0000000000..048a91eeca --- /dev/null +++ b/src/Microsoft.Blazor.Browser/src/Platform/Mono/MonoTypes.d.ts @@ -0,0 +1,14 @@ +declare namespace Module { + function UTF8ToString(utf8: Mono.Utf8Ptr): string; + var preloadPlugins: any[]; + + // These should probably be in @types/emscripten + var wasmBinaryFile: string; + var asmjsCodeFile: string; + function FS_createPath(parent, path, canRead, canWrite); + function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn); +} + +declare namespace Mono { + interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any } +} diff --git a/src/Microsoft.Blazor.Browser/src/Platform/Platform.ts b/src/Microsoft.Blazor.Browser/src/Platform/Platform.ts new file mode 100644 index 0000000000..2b01f53d99 --- /dev/null +++ b/src/Microsoft.Blazor.Browser/src/Platform/Platform.ts @@ -0,0 +1,17 @@ +export interface Platform { + start(loadAssemblyUrls: string[]): Promise; + + callEntryPoint(assemblyName: string, args: System_Object[]); + findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle; + callMethod(method: MethodHandle, target: System_Object, args: System_Object[]): System_Object; + + toJavaScriptString(dotNetString: System_String): string; + toDotNetString(javaScriptString: string): System_String; +} + +// We don't actually instantiate any of these at runtime. For perf it's preferable to +// use the original 'number' instances without any boxing. The definitions are just +// for compile-time checking, since TypeScript doesn't support nominal types. +export interface MethodHandle { MethodHandle__DO_NOT_IMPLEMENT: any }; +export interface System_Object { System_Object__DO_NOT_IMPLEMENT: any }; +export interface System_String extends System_Object { System_String__DO_NOT_IMPLEMENT: any } diff --git a/src/Microsoft.Blazor.Server/DevelopmentServer/DevelopmentServerApplicationBuilderExtensions.cs b/src/Microsoft.Blazor.Server/DevelopmentServer/DevelopmentServerApplicationBuilderExtensions.cs index 2d45e544c8..deb77eb205 100644 --- a/src/Microsoft.Blazor.Server/DevelopmentServer/DevelopmentServerApplicationBuilderExtensions.cs +++ b/src/Microsoft.Blazor.Server/DevelopmentServer/DevelopmentServerApplicationBuilderExtensions.cs @@ -2,9 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using System; +using System.Collections.Generic; using System.IO; +using System.Net.Mime; namespace Microsoft.AspNetCore.Builder { @@ -18,12 +22,40 @@ namespace Microsoft.AspNetCore.Builder var sourcePath = Path.Combine(env.ContentRootPath, relativeSourcePath); ServeWebRoot(applicationBuilder, sourcePath); + ServeClientBinDir(applicationBuilder, sourcePath); } - private static void ServeWebRoot(IApplicationBuilder applicationBuilder, string sourcePath) + private static void ServeClientBinDir(IApplicationBuilder applicationBuilder, string clientAppSourceRoot) + { + var clientBinDirPath = FindClientBinDir(clientAppSourceRoot); + applicationBuilder.UseStaticFiles(new StaticFileOptions + { + RequestPath = "/_bin", + FileProvider = new PhysicalFileProvider(clientBinDirPath), + ContentTypeProvider = new FileExtensionContentTypeProvider(new Dictionary + { + { ".dll", MediaTypeNames.Application.Octet }, + }) + }); + } + + private static string FindClientBinDir(string clientAppSourceRoot) + { + var binDebugDir = Path.Combine(clientAppSourceRoot, "bin", "Debug"); + var subdirectories = Directory.GetDirectories(binDebugDir); + if (subdirectories.Length != 1) + { + throw new InvalidOperationException($"Could not locate bin directory for Blazor app. " + + $"Expected to find exactly 1 subdirectory in '{binDebugDir}', but found {subdirectories.Length}."); + } + + return Path.Combine(binDebugDir, subdirectories[0]); + } + + private static void ServeWebRoot(IApplicationBuilder applicationBuilder, string clientAppSourceRoot) { var webRootFileProvider = new PhysicalFileProvider( - Path.Combine(sourcePath, "wwwroot")); + Path.Combine(clientAppSourceRoot, "wwwroot")); applicationBuilder.UseDefaultFiles(new DefaultFilesOptions { diff --git a/src/Microsoft.Blazor/Microsoft.Blazor.csproj b/src/Microsoft.Blazor/Microsoft.Blazor.csproj new file mode 100644 index 0000000000..9f5c4f4abb --- /dev/null +++ b/src/Microsoft.Blazor/Microsoft.Blazor.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + +