Skip to content

Commit ba7b459

Browse files
committed
Rough prototype
1 parent 43bb43f commit ba7b459

File tree

9 files changed

+396
-110
lines changed

9 files changed

+396
-110
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components.Endpoints;
5+
6+
internal struct AutoComponentMarker
7+
{
8+
public const string AutoMarkerType = "auto";
9+
10+
public string Type { get; set; }
11+
12+
public string? PrerenderId { get; set; }
13+
14+
public ServerComponentMarker Server { get; set; }
15+
16+
public WebAssemblyComponentMarker WebAssembly { get; set; }
17+
18+
public static AutoComponentMarker FromMarkers(ServerComponentMarker server, WebAssemblyComponentMarker webAssembly) =>
19+
new() { Type = AutoMarkerType, PrerenderId = server.PrerenderId, Server = server, WebAssembly = webAssembly };
20+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json;
5+
6+
namespace Microsoft.AspNetCore.Components.Endpoints;
7+
8+
internal static class AutoComponentSerializer
9+
{
10+
public static void AppendPreamble(TextWriter writer, ServerComponentMarker serverRecord, WebAssemblyComponentMarker webAssemblyRecord)
11+
{
12+
var autoRecord = AutoComponentMarker.FromMarkers(serverRecord, webAssemblyRecord);
13+
var serializedStartRecord = JsonSerializer.Serialize(
14+
autoRecord,
15+
ServerComponentSerializationSettings.JsonSerializationOptions);
16+
17+
writer.Write("<!--Blazor:");
18+
writer.Write(serializedStartRecord);
19+
writer.Write("-->");
20+
}
21+
22+
public static void AppendEpilogue(TextWriter writer, ServerComponentMarker serverRecord)
23+
{
24+
// We always use the server record for the end record.
25+
ServerComponentSerializer.AppendEpilogue(writer, serverRecord);
26+
}
27+
}

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,16 @@ private void WriteComponentHtml(int componentId, TextWriter output, bool allowBo
216216
_httpContext.Response.Headers.CacheControl = "no-cache, no-store, max-age=0";
217217
}
218218

219-
ServerComponentSerializer.AppendPreamble(output, serverMarker.Value);
219+
if (webAssemblyMarker.HasValue)
220+
{
221+
AutoComponentSerializer.AppendPreamble(output, serverMarker.Value, webAssemblyMarker.Value);
222+
}
223+
else
224+
{
225+
ServerComponentSerializer.AppendPreamble(output, serverMarker.Value);
226+
}
220227
}
221-
222-
if (webAssemblyMarker.HasValue)
228+
else if (webAssemblyMarker.HasValue)
223229
{
224230
WebAssemblyComponentSerializer.AppendPreamble(output, webAssemblyMarker.Value);
225231
}
@@ -240,14 +246,26 @@ private void WriteComponentHtml(int componentId, TextWriter output, bool allowBo
240246
output.Write("-->");
241247
}
242248

243-
if (webAssemblyMarker.HasValue && webAssemblyMarker.Value.PrerenderId is not null)
249+
if (serverMarker.HasValue)
244250
{
245-
WebAssemblyComponentSerializer.AppendEpilogue(output, webAssemblyMarker.Value);
251+
if (serverMarker.Value.PrerenderId is not null)
252+
{
253+
if (webAssemblyMarker.HasValue)
254+
{
255+
AutoComponentSerializer.AppendEpilogue(output, serverMarker.Value);
256+
}
257+
else
258+
{
259+
ServerComponentSerializer.AppendEpilogue(output, serverMarker.Value);
260+
}
261+
}
246262
}
247-
248-
if (serverMarker.HasValue && serverMarker.Value.PrerenderId is not null)
263+
else if (webAssemblyMarker.HasValue)
249264
{
250-
ServerComponentSerializer.AppendEpilogue(output, serverMarker.Value);
265+
if (webAssemblyMarker.Value.PrerenderId is not null)
266+
{
267+
WebAssemblyComponentSerializer.AppendEpilogue(output, webAssemblyMarker.Value);
268+
}
251269
}
252270
}
253271

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

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,23 @@
1010

1111
import { DotNet } from '@microsoft/dotnet-js-interop';
1212
import { startCircuit } from './Boot.Server.Common';
13-
import { startWebAssembly } from './Boot.WebAssembly.Common';
13+
import { loadWebAssemblyPlatform, startWebAssembly } from './Boot.WebAssembly.Common';
1414
import { shouldAutoStart } from './BootCommon';
1515
import { Blazor } from './GlobalExports';
1616
import { WebStartOptions } from './Platform/WebStartOptions';
1717
import { attachStreamingRenderingListener } from './Rendering/StreamingRendering';
1818
import { NavigationEnhancementCallbacks, attachProgressivelyEnhancedNavigationListener, isPerformingEnhancedPageLoad } from './Services/NavigationEnhancement';
19-
import { WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
20-
import { ServerComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
21-
import { RootComponentManager } from './Services/RootComponentManager';
22-
import { WebRendererId } from './Rendering/WebRendererId';
19+
import { ComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
20+
import { RootComponentManager, attachAutoModeResolver } from './Services/RootComponentManager';
2321
import { DescriptorHandler, attachComponentDescriptorHandler, registerAllComponentDescriptors } from './Rendering/DomMerging/DomSync';
2422
import { waitForRendererAttached } from './Rendering/WebRendererInteropMethods';
23+
import { WebRendererId } from './Rendering/WebRendererId';
2524

2625
let started = false;
2726
let webStartOptions: Partial<WebStartOptions> | undefined;
27+
let hasWebAssemblyLoaded = false;
2828

29-
const circuitRootComponents = new RootComponentManager(WebRendererId.Server);
30-
const webAssemblyRootComponents = new RootComponentManager(WebRendererId.WebAssembly);
29+
const rootComponentManager = new RootComponentManager();
3130

3231
function boot(options?: Partial<WebStartOptions>) : Promise<void> {
3332
if (started) {
@@ -46,6 +45,15 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
4645

4746
attachComponentDescriptorHandler(descriptorHandler);
4847
attachStreamingRenderingListener(options?.ssr, navigationEnhancementCallbacks);
48+
attachAutoModeResolver(() => {
49+
if (hasWebAssemblyLoaded) {
50+
startWebAssemblyIfNotStarted();
51+
return 'webassembly';
52+
} else {
53+
startCircuitIfNotStarted();
54+
return 'server';
55+
}
56+
});
4957

5058
if (!options?.ssr?.disableDomPreservation) {
5159
attachProgressivelyEnhancedNavigationListener(navigationEnhancementCallbacks);
@@ -56,45 +64,54 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
5664
return Promise.resolve();
5765
}
5866

59-
function registerComponentDescriptor(descriptor: ServerComponentDescriptor | WebAssemblyComponentDescriptor) {
60-
switch (descriptor.type) {
61-
case 'server':
62-
startCircuitIfNotStarted();
63-
circuitRootComponents.registerComponentDescriptor(descriptor);
64-
break;
65-
case 'webassembly':
66-
startWebAssemblyIfNotStarted();
67-
webAssemblyRootComponents.registerComponentDescriptor(descriptor);
68-
break;
67+
function registerComponentDescriptor(descriptor: ComponentDescriptor) {
68+
rootComponentManager.registerComponentDescriptor(descriptor);
69+
70+
if (descriptor.type === 'auto') {
71+
startLoadingWebAssembly();
72+
} else if (descriptor.type === 'server') {
73+
startCircuitIfNotStarted();
74+
} else if (descriptor.type === 'webassembly') {
75+
startWebAssemblyIfNotStarted();
6976
}
7077
}
7178

7279
function handleUpdatedComponentDescriptors() {
7380
const shouldAddNewRootComponents = !isPerformingEnhancedPageLoad();
74-
circuitRootComponents.handleUpdatedRootComponents(shouldAddNewRootComponents);
75-
webAssemblyRootComponents.handleUpdatedRootComponents(shouldAddNewRootComponents);
81+
rootComponentManager.handleUpdatedRootComponents(shouldAddNewRootComponents);
7682
}
7783

78-
let circuitStarted = false;
84+
let hasCircuitStarted = false;
7985
async function startCircuitIfNotStarted() {
80-
if (circuitStarted) {
86+
if (hasCircuitStarted) {
8187
return;
8288
}
8389

84-
circuitStarted = true;
85-
await startCircuit(webStartOptions?.circuit, circuitRootComponents);
90+
hasCircuitStarted = true;
91+
await startCircuit(webStartOptions?.circuit, rootComponentManager);
8692
await waitForRendererAttached(WebRendererId.Server);
8793
handleUpdatedComponentDescriptors();
8894
}
8995

90-
let webAssemblyStarted = false;
96+
let hasStartedLoadingWebAssembly = false;
97+
async function startLoadingWebAssembly() {
98+
if (hasStartedLoadingWebAssembly) {
99+
return;
100+
}
101+
102+
hasStartedLoadingWebAssembly = true;
103+
await loadWebAssemblyPlatform(webStartOptions?.webAssembly);
104+
hasWebAssemblyLoaded = true;
105+
}
106+
107+
let isStartingWebAssembly = false;
91108
async function startWebAssemblyIfNotStarted() {
92-
if (webAssemblyStarted) {
109+
if (isStartingWebAssembly) {
93110
return;
94111
}
95112

96-
webAssemblyStarted = true;
97-
await startWebAssembly(webStartOptions?.webAssembly, webAssemblyRootComponents);
113+
isStartingWebAssembly = true;
114+
await startWebAssembly(webStartOptions?.webAssembly, rootComponentManager);
98115
await waitForRendererAttached(WebRendererId.WebAssembly);
99116
handleUpdatedComponentDescriptors();
100117
}

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@ import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRen
1111
import { PlatformApi, Pointer } from './Platform/Platform';
1212
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
1313
import { addDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods';
14-
import { JSInitializer } from './JSInitializers/JSInitializers';
1514
import { WebAssemblyComponentDescriptor, discoverPersistedState } from './Services/ComponentDescriptorDiscovery';
1615
import { receiveDotNetDataStream } from './StreamingInterop';
1716
import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
1817
import { RootComponentManager } from './Services/RootComponentManager';
1918

19+
let platformStartPromise: Promise<PlatformApi> | undefined;
20+
2021
export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions>, components?: WebAssemblyComponentDescriptor[] | RootComponentManager): Promise<void> {
2122
if (inAuthRedirectIframe()) {
2223
// eslint-disable-next-line @typescript-eslint/no-empty-function
2324
await new Promise(() => { }); // See inAuthRedirectIframe for explanation
2425
}
2526

27+
const platformStartPromise = loadWebAssemblyPlatform(options);
28+
2629
addDispatchEventMiddleware((browserRendererId, eventHandlerId, continuation) => {
2730
// It's extremely unusual, but an event can be raised while we're in the middle of synchronously applying a
2831
// renderbatch. For example, a renderbatch might mutate the DOM in such a way as to cause an <input> to lose
@@ -46,10 +49,6 @@ export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions
4649
Blazor._internal.endInvokeDotNetFromJS = endInvokeDotNetFromJS;
4750
Blazor._internal.receiveWebAssemblyDotNetDataStream = receiveWebAssemblyDotNetDataStream;
4851
Blazor._internal.receiveByteArray = receiveByteArray;
49-
50-
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
51-
const platform = Environment.setPlatform(monoPlatform);
52-
Blazor.platform = platform;
5352
Blazor._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => {
5453
// We're going to read directly from the .NET memory heap, so indicate to the platform
5554
// that we don't want anything to modify the memory contents during this time. Currently this
@@ -109,18 +108,29 @@ export async function startWebAssembly(options?: Partial<WebAssemblyStartOptions
109108

110109
let api: PlatformApi;
111110
try {
112-
api = await platform.start(options ?? {});
111+
api = await platformStartPromise;
113112
} catch (ex) {
114113
throw new Error(`Failed to start platform. Reason: ${ex}`);
115114
}
116115

117116
// Start up the application
118-
platform.callEntryPoint();
117+
monoPlatform.callEntryPoint();
119118
// At this point .NET has been initialized (and has yielded), we can't await the promise becasue it will
120119
// only end when the app finishes running
121120
api.invokeLibraryInitializers('afterStarted', [Blazor]);
122121
}
123122

123+
export function loadWebAssemblyPlatform(options?: Partial<WebAssemblyStartOptions>): Promise<PlatformApi> {
124+
if (!platformStartPromise) {
125+
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
126+
const platform = Environment.setPlatform(monoPlatform);
127+
Blazor.platform = platform;
128+
platformStartPromise = platform.start(options ?? {});
129+
}
130+
131+
return platformStartPromise;
132+
}
133+
124134
// obsolete, legacy, don't use for new code!
125135
function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any): any {
126136
const functionIdentifier = monoPlatform.readStringField(callInfo, 0)!;

src/Components/Web.JS/src/Rendering/DomMerging/DomSync.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
import { ComponentDescriptor, ServerComponentDescriptor, WebAssemblyComponentDescriptor, discoverComponents } from '../../Services/ComponentDescriptorDiscovery';
4+
import { AutoComponentDescriptor, ComponentDescriptor, ServerComponentDescriptor, WebAssemblyComponentDescriptor, discoverComponents } from '../../Services/ComponentDescriptorDiscovery';
55
import { isInteractiveRootComponentElement } from '../BrowserRenderer';
66
import { applyAnyDeferredValue } from '../DomSpecialPropertyUtil';
77
import { LogicalElement, getLogicalChildrenArray, getLogicalNextSibling, getLogicalParent, getLogicalRootDescriptor, insertLogicalChild, insertLogicalChildBefore, isLogicalElement, toLogicalElement, toLogicalRootCommentElement } from '../LogicalElements';
@@ -302,9 +302,14 @@ function domNodeComparer(a: Node, b: Node): UpdateCost {
302302
function upgradeComponentCommentsToLogicalRootComments(root: Node): ComponentDescriptor[] {
303303
const serverDescriptors = discoverComponents(root, 'server') as ServerComponentDescriptor[];
304304
const webAssemblyDescriptors = discoverComponents(root, 'webassembly') as WebAssemblyComponentDescriptor[];
305+
const autoDescriptors = discoverComponents(root, 'auto') as AutoComponentDescriptor[];
305306
const allDescriptors: ComponentDescriptor[] = [];
306307

307-
for (const descriptor of [...serverDescriptors, ...webAssemblyDescriptors]) {
308+
for (const descriptor of [
309+
...serverDescriptors,
310+
...webAssemblyDescriptors,
311+
...autoDescriptors,
312+
]) {
308313
const existingDescriptor = getLogicalRootDescriptor(descriptor.start as unknown as LogicalElement);
309314
if (existingDescriptor) {
310315
allDescriptors.push(existingDescriptor);
@@ -397,7 +402,9 @@ class LogicalElementEditWalker implements EditWalker {
397402

398403
class SiblingSubsetNodeList implements ItemList<Node> {
399404
private readonly siblings: ItemList<Node>;
405+
400406
private readonly startIndex: number;
407+
401408
private readonly endIndexExcl: number;
402409

403410
readonly length: number;

src/Components/Web.JS/src/Rendering/LogicalElements.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
import { ServerComponentDescriptor, WebAssemblyComponentDescriptor } from '../Services/ComponentDescriptorDiscovery';
4+
import { ComponentDescriptor } from '../Services/ComponentDescriptorDiscovery';
55

66
/*
77
A LogicalElement plays the same role as an Element instance from the point of view of the
@@ -34,7 +34,7 @@ const logicalChildrenPropname = Symbol();
3434
const logicalParentPropname = Symbol();
3535
const logicalRootDescriptorPropname = Symbol();
3636

37-
export function toLogicalRootCommentElement(descriptor: ServerComponentDescriptor | WebAssemblyComponentDescriptor): LogicalElement {
37+
export function toLogicalRootCommentElement(descriptor: ComponentDescriptor): LogicalElement {
3838
// Now that we support start/end comments as component delimiters we are going to be setting up
3939
// adding the components rendered output as siblings of the start/end tags (between).
4040
// For that to work, we need to appropriately configure the parent element to be a logical element
@@ -217,7 +217,7 @@ export function getLogicalChild(parent: LogicalElement, childIndex: number): Log
217217
return getLogicalChildrenArray(parent)[childIndex];
218218
}
219219

220-
export function getLogicalRootDescriptor(element: LogicalElement): ServerComponentDescriptor | WebAssemblyComponentDescriptor {
220+
export function getLogicalRootDescriptor(element: LogicalElement): ComponentDescriptor {
221221
return element[logicalRootDescriptorPropname] || null;
222222
}
223223

0 commit comments

Comments
 (0)