Skip to content

Commit b44ea8c

Browse files
Support "auto" render mode (#11)
* Switch component discovery based on "auto". Lots of nasty hacks in here. * Determine server/webassembly based on whether wasm files are loaded already * Simplify sample
1 parent 43660b7 commit b44ea8c

File tree

12 files changed

+241
-94
lines changed

12 files changed

+241
-94
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
@page "/counter-server"
1+
@page "/counter-interactive"
22

33
<PageTitle>Counter</PageTitle>
44

5-
<h1>Counter (interactive on server)</h1>
5+
<h1>Counter</h1>
66

7-
<CounterUI InitialCount="100" rendermode="@WebComponentRenderMode.Server" />
7+
<CounterUI InitialCount="100" rendermode="@WebComponentRenderMode.Auto" />

src/Components/Samples/BlazorUnitedApp/Pages/CounterWebAssembly.razor

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/Components/Samples/BlazorUnitedApp/Shared/NavMenu.razor

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,8 @@
1818
</NavLink>
1919
</li>
2020
<li class="nav-item px-3">
21-
<NavLink class="nav-link" href="counter-server">
22-
<span class="oi oi-plus" aria-hidden="true"></span> Counter (server)
23-
</NavLink>
24-
</li>
25-
<li class="nav-item px-3">
26-
<NavLink class="nav-link" href="counter-webassembly">
27-
<span class="oi oi-plus" aria-hidden="true"></span> Counter (wasm)
21+
<NavLink class="nav-link" href="counter-interactive">
22+
<span class="oi oi-plus" aria-hidden="true"></span> Counter (interactive)
2823
</NavLink>
2924
</li>
3025
<li class="nav-item px-3">

src/Components/Web.JS/src/Boot.Server.Common.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.Serv
2020
let renderingFailed = false;
2121
let connection: HubConnection;
2222

23-
export async function startCircuit(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
23+
export async function startCircuit(userOptions?: Partial<CircuitStartOptions>, components?: ServerComponentDescriptor[]): Promise<void> {
2424
// Establish options to be used
2525
const options = resolveOptions(userOptions);
2626
const jsInitializer = await fetchAndInvokeInitializers(options);
@@ -48,9 +48,8 @@ export async function startCircuit(userOptions?: Partial<CircuitStartOptions>):
4848
options.reconnectionHandler = options.reconnectionHandler || Blazor.defaultReconnectionHandler;
4949
logger.log(LogLevel.Information, 'Starting up Blazor server-side application.');
5050

51-
const components = discoverComponents(document, 'server') as ServerComponentDescriptor[];
5251
const appState = discoverPersistedState(document);
53-
const circuit = new CircuitDescriptor(components, appState || '');
52+
const circuit = new CircuitDescriptor(components || [], appState || '');
5453

5554
// Configure navigation via SignalR
5655
Blazor._internal.navigationManager.listenForNavigationEvents((uri: string, state: string | undefined, intercepted: boolean): Promise<void> => {

src/Components/Web.JS/src/Boot.United.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { Blazor } from './GlobalExports';
55
import { shouldAutoStart } from './BootCommon';
66
import { UnitedStartOptions } from './Platform/Circuits/UnitedStartOptions';
77
import { startCircuit } from './Boot.Server.Common';
8-
import { discoverComponents, ServerComponentDescriptor, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
9-
import { startWebAssembly } from './Boot.WebAssembly.Common';
8+
import { AutoComponentDescriptor, discoverComponents, ServerComponentDescriptor, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
9+
import { beginLoadingDotNetRuntime, loadBootConfig, startWebAssembly } from './Boot.WebAssembly.Common';
10+
import { loadWebAssemblyResources } from './Platform/Mono/MonoPlatform';
1011

1112
let started = false;
1213

@@ -16,9 +17,30 @@ async function boot(options?: Partial<UnitedStartOptions>): Promise<void> {
1617
}
1718
started = true;
1819

20+
const autoComponents = discoverComponents(document, 'auto') as AutoComponentDescriptor[];
1921
const serverComponents = discoverComponents(document, 'server') as ServerComponentDescriptor[];
2022
const webAssemblyComponents = discoverComponents(document, 'webassembly') as WebAssemblyComponentDescriptor[];
2123

24+
// Decide out what to do about any 'auto' components. Having any of them is a trigger to start fetching the WebAssembly
25+
// runtime files if they aren't already cached. If they are already cached, we'll use WebAssembly, and if not, Server.
26+
if (autoComponents.length) {
27+
// Because there is at least one 'auto' component, start loading the WebAssembly runtime
28+
const webAssemblyBootConfigResult = await loadBootConfig(options?.webAssembly);
29+
const webAssemblyResourceLoader = await beginLoadingDotNetRuntime(options?.webAssembly ?? {}, webAssemblyBootConfigResult);
30+
const { assembliesBeingLoaded, pdbsBeingLoaded, wasmBeingLoaded } = loadWebAssemblyResources(webAssemblyResourceLoader);
31+
const totalResources = assembliesBeingLoaded.concat(pdbsBeingLoaded, wasmBeingLoaded);
32+
let finishedCount = 0;
33+
totalResources.map(r => r.response.then(() => finishedCount++));
34+
await new Promise(r => setTimeout(r, 50)); // This is obviously not correct. Need a robust way to know how much is cached instead of just waiting a while.
35+
36+
const runAutoOnServer = finishedCount < totalResources.length;
37+
if (runAutoOnServer) {
38+
serverComponents.push(...autoComponents.map(c => c.serverDescriptor));
39+
} else {
40+
webAssemblyComponents.push(...autoComponents.map(c => c.webAssemblyDescriptor));
41+
}
42+
}
43+
2244
if (serverComponents.length && webAssemblyComponents.length) {
2345
throw new Error('TODO: Support having both Server and WebAssembly components at the same time. Not doing that currently as it overlaps with a different prototype.');
2446
}
@@ -27,11 +49,11 @@ async function boot(options?: Partial<UnitedStartOptions>): Promise<void> {
2749
// Later on, when we add progressive enhancement to navigation, we will also want to
2850
// auto-close circuits when the last root component is removed.
2951
if (serverComponents.length) {
30-
await startCircuit(options?.circuit);
52+
await startCircuit(options?.circuit, serverComponents);
3153
}
3254

3355
if (webAssemblyComponents.length) {
34-
await startWebAssembly(options?.webAssembly);
56+
await startWebAssembly(options?.webAssembly, webAssemblyComponents);
3557
}
3658
}
3759

src/Components/Web.JS/src/Boot.WebAssembly.Common.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import { BootConfigResult } from './Platform/BootConfig';
1414
import { Pointer, System_Array, System_Boolean, System_Byte, System_Int, System_Object, System_String } from './Platform/Platform';
1515
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
1616
import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
17-
import { discoverComponents, discoverPersistedState, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
17+
import { discoverPersistedState, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
1818
import { setDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods';
1919
import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebAssembly';
2020

21-
export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
21+
export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions>, components?: WebAssemblyComponentDescriptor[]): Promise<void> {
2222
if (inAuthRedirectIframe()) {
2323
// eslint-disable-next-line @typescript-eslint/no-empty-function
2424
await new Promise(() => {}); // See inAuthRedirectIframe for explanation
@@ -90,18 +90,9 @@ export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions
9090
Blazor._internal.navigationManager.endLocationChanging(callId, shouldContinueNavigation);
9191
});
9292

93-
const candidateOptions = options ?? {};
94-
95-
// Get the custom environment setting and blazorBootJson loader if defined
96-
const environment = candidateOptions.environment;
97-
98-
// Fetch the resources and prepare the Mono runtime
99-
const bootConfigPromise = BootConfigResult.initAsync(candidateOptions.loadBootResource, environment);
100-
10193
// Leverage the time while we are loading boot.config.json from the network to discover any potentially registered component on
10294
// the document.
103-
const discoveredComponents = discoverComponents(document, 'webassembly') as WebAssemblyComponentDescriptor[];
104-
const componentAttacher = new WebAssemblyComponentAttacher(discoveredComponents);
95+
const componentAttacher = new WebAssemblyComponentAttacher(components || []);
10596
Blazor._internal.registeredComponents = {
10697
getRegisteredComponentsCount: () => componentAttacher.getCount(),
10798
getId: (index) => componentAttacher.getId(index),
@@ -122,13 +113,9 @@ export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions
122113
}
123114
};
124115

125-
const bootConfigResult: BootConfigResult = await bootConfigPromise;
126-
const jsInitializer = await fetchAndInvokeInitializers(bootConfigResult.bootConfig, candidateOptions);
127-
128-
const [resourceLoader] = await Promise.all([
129-
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, candidateOptions || {}),
130-
WebAssemblyConfigLoader.initAsync(bootConfigResult),
131-
]);
116+
const bootConfigResult: BootConfigResult = await loadBootConfig(options);
117+
const jsInitializer = await fetchAndInvokeInitializers(bootConfigResult.bootConfig, options ?? {});
118+
const resourceLoader = await beginLoadingDotNetRuntime(options ?? {}, bootConfigResult);
132119

133120
try {
134121
await platform.start(resourceLoader);
@@ -143,6 +130,25 @@ export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions
143130
jsInitializer.invokeAfterStartedCallbacks(Blazor);
144131
}
145132

133+
export async function loadBootConfig(options?: Partial<WebAssemblyStartOptions>) {
134+
return BootConfigResult.initAsync(options?.loadBootResource, options?.environment);
135+
}
136+
137+
export async function beginLoadingDotNetRuntime(options: Partial<WebAssemblyStartOptions>, bootConfigResult: BootConfigResult): Promise<WebAssemblyResourceLoader> {
138+
const candidateOptions = options ?? {};
139+
140+
// Get the custom environment setting and blazorBootJson loader if defined
141+
const environment = candidateOptions.environment;
142+
143+
// Fetch the resources and prepare the Mono runtime
144+
const [resourceLoader] = await Promise.all([
145+
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, candidateOptions || {}),
146+
WebAssemblyConfigLoader.initAsync(bootConfigResult),
147+
]);
148+
149+
return resourceLoader;
150+
}
151+
146152
// obsolete, legacy, don't use for new code!
147153
function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any): any {
148154
const functionIdentifier = monoPlatform.readStringField(callInfo, 0)!;

src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,21 @@ async function importDotnetJs(resourceLoader: WebAssemblyResourceLoader): Promis
224224
return await cjsExport;
225225
}
226226

227+
export function loadWebAssemblyResources(resourceLoader: WebAssemblyResourceLoader) {
228+
const resources = resourceLoader.bootConfig.resources;
229+
const dotnetWasmResourceName = 'dotnet.wasm';
230+
const assembliesBeingLoaded = resourceLoader.loadResources(resources.assembly, filename => `_framework/${filename}`, 'assembly');
231+
const pdbsBeingLoaded = resourceLoader.loadResources(resources.pdb || {}, filename => `_framework/${filename}`, 'pdb');
232+
const wasmBeingLoaded = resourceLoader.loadResource(
233+
/* name */ dotnetWasmResourceName,
234+
/* url */ `_framework/${dotnetWasmResourceName}`,
235+
/* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName],
236+
/* type */ 'dotnetwasm'
237+
);
238+
239+
return { assembliesBeingLoaded, pdbsBeingLoaded, wasmBeingLoaded };
240+
}
241+
227242
async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader): Promise<DotnetPublicAPI> {
228243
let runtimeReadyResolve: (data: DotnetPublicAPI) => void = undefined as any;
229244
let runtimeReadyReject: (reason?: any) => void = undefined as any;
@@ -233,7 +248,6 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc
233248
});
234249

235250
const dotnetJsBeingLoaded = importDotnetJs(resourceLoader);
236-
const resources = resourceLoader.bootConfig.resources;
237251
const moduleConfig = (window['Module'] || {}) as typeof Module;
238252
const suppressMessages = ['DEBUGGING ENABLED'];
239253

@@ -259,15 +273,7 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc
259273
}
260274

261275
// Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel.
262-
const dotnetWasmResourceName = 'dotnet.wasm';
263-
const assembliesBeingLoaded = resourceLoader.loadResources(resources.assembly, filename => `_framework/${filename}`, 'assembly');
264-
const pdbsBeingLoaded = resourceLoader.loadResources(resources.pdb || {}, filename => `_framework/${filename}`, 'pdb');
265-
const wasmBeingLoaded = resourceLoader.loadResource(
266-
/* name */ dotnetWasmResourceName,
267-
/* url */ `_framework/${dotnetWasmResourceName}`,
268-
/* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName],
269-
/* type */ 'dotnetwasm'
270-
);
276+
const { assembliesBeingLoaded, pdbsBeingLoaded, wasmBeingLoaded } = loadWebAssemblyResources(resourceLoader);
271277
const totalResources = assembliesBeingLoaded.concat(pdbsBeingLoaded, wasmBeingLoaded);
272278
totalResources.forEach(loadingResource => loadingResource.response.then(_ => setProgress()));
273279

@@ -672,7 +678,7 @@ async function compileWasmModule(wasmResource: LoadingResource, imports: any): P
672678
// Fall back on ArrayBuffer instantiation.
673679
const arrayBuffer = await wasmResourceResponse.arrayBuffer();
674680
const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, imports);
675-
return arrayBufferResult.instance;
681+
return arrayBufferResult.instance;
676682
}
677683
}
678684

0 commit comments

Comments
 (0)