From bbc116254accb4f279010afc1ceb08a5141d012b Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 9 Jul 2020 01:16:47 +0000 Subject: [PATCH] Add framework support for lazy-loading assemblies on route change (#23290) * Add framework support for lazy-loading assemblies on route change * Configure lazy-loaded assemblies in WebAssemblyLazyLoadDefinition * Move tests to WebAssembly-only scenarios * Refactor RouteTableFactory and add WebAssemblyDynamicResourceLoader * Address feedback from peer review * Rename 'dynamicAssembly' to 'lazyAssembly' and address peer review * Add sample with loading state * Update Router API and assembly loading tests * Support and test cancellation and pre-rendering * Apply suggestions from code review Co-authored-by: Steve Sanderson * Spurce up API and add tests for pre-rendering scenario * Use CT instead of CTS in NavigationContext * Address feedback from peer review * Remove extra test file and update Router Co-authored-by: Steve Sanderson --- ...rosoft.AspNetCore.Components.netcoreapp.cs | 11 ++ .../src/Routing/NavigationContext.cs | 24 +++ .../Components/src/Routing/Router.cs | 87 ++++++++- .../Web.JS/dist/Release/blazor.webassembly.js | 2 +- .../Web.JS/src/Platform/BootConfig.ts | 1 + .../Web.JS/src/Platform/Mono/MonoPlatform.ts | 28 +++ .../BuildLazyLoadTest.cs | 176 ------------------ .../src/Hosting/WebAssemblyHostBuilder.cs | 3 +- .../src/Services/LazyAssemblyLoader.cs | 108 +++++++++++ .../ServerExecutionTests/PrerenderingTest.cs | 25 +++ .../test/E2ETest/Tests/RoutingTest.cs | 36 ++++ .../E2ETest/Tests/WebAssemblyLazyLoadTest.cs | 146 +++++++++++++++ .../BasicTestApp/BasicTestApp.csproj | 7 + .../test/testassets/BasicTestApp/Index.razor | 2 + .../BasicTestApp/RouterTest/Links.razor | 1 + .../TestRouterWithLazyAssembly.razor | 57 ++++++ .../RouterTest/TestRouterWithOnNavigate.razor | 46 +++++ .../RouterTest/WithLazyAssembly.razor | 16 ++ .../WithLazyRouteableAssembly.razor | 5 + .../ComponentFromPackage.razor | 3 + .../LazyTestContentPackage.csproj | 22 +++ .../RouteableComponentFromPackage.razor | 6 + .../LazyTestContentPackage/_Imports.razor | 1 + .../TestServer/Pages/PrerenderedHost.cshtml | 2 +- .../TestServer/PrerenderedStartup.cs | 3 + .../Wasm/WasmBuildLazyLoadTest.cs | 32 ++-- .../src/BootJsonData.cs | 4 +- .../src/GenerateBlazorWebAssemblyBootJson.cs | 4 +- 28 files changed, 652 insertions(+), 206 deletions(-) create mode 100644 src/Components/Components/src/Routing/NavigationContext.cs delete mode 100644 src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildLazyLoadTest.cs create mode 100644 src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs create mode 100644 src/Components/test/E2ETest/Tests/WebAssemblyLazyLoadTest.cs create mode 100644 src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor create mode 100644 src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor create mode 100644 src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyAssembly.razor create mode 100644 src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyRouteableAssembly.razor create mode 100644 src/Components/test/testassets/LazyTestContentPackage/ComponentFromPackage.razor create mode 100644 src/Components/test/testassets/LazyTestContentPackage/LazyTestContentPackage.csproj create mode 100644 src/Components/test/testassets/LazyTestContentPackage/RouteableComponentFromPackage.razor create mode 100644 src/Components/test/testassets/LazyTestContentPackage/_Imports.razor diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs index 77a0e3637b..1ee9bf558a 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs @@ -556,6 +556,12 @@ namespace Microsoft.AspNetCore.Components.Routing public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } + public sealed partial class NavigationContext + { + internal NavigationContext() { } + public System.Threading.CancellationToken CancellationToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public string Path { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + } public partial class Router : Microsoft.AspNetCore.Components.IComponent, Microsoft.AspNetCore.Components.IHandleAfterRender, System.IDisposable { public Router() { } @@ -566,10 +572,15 @@ namespace Microsoft.AspNetCore.Components.Routing [Microsoft.AspNetCore.Components.ParameterAttribute] public Microsoft.AspNetCore.Components.RenderFragment Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment Navigating { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.EventCallback OnNavigateAsync { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { } public void Dispose() { } System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } } } diff --git a/src/Components/Components/src/Routing/NavigationContext.cs b/src/Components/Components/src/Routing/NavigationContext.cs new file mode 100644 index 0000000000..5416b2b454 --- /dev/null +++ b/src/Components/Components/src/Routing/NavigationContext.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; + +namespace Microsoft.AspNetCore.Components.Routing +{ + /// + /// Provides information about the current asynchronous navigation event + /// including the target path and the cancellation token. + /// + public sealed class NavigationContext + { + internal NavigationContext(string path, CancellationToken cancellationToken) + { + Path = path; + CancellationToken = cancellationToken; + } + + public string Path { get; } + + public CancellationToken CancellationToken { get; } + } +} diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index 252dd5c214..f892565773 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -8,8 +8,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Components.Routing @@ -29,6 +29,12 @@ namespace Microsoft.AspNetCore.Components.Routing bool _navigationInterceptionEnabled; ILogger _logger; + private CancellationTokenSource _onNavigateCts; + + private readonly HashSet _assemblies = new HashSet(); + + private bool _onNavigateCalled = false; + [Inject] private NavigationManager NavigationManager { get; set; } [Inject] private INavigationInterception NavigationInterception { get; set; } @@ -56,6 +62,16 @@ namespace Microsoft.AspNetCore.Components.Routing /// [Parameter] public RenderFragment Found { get; set; } + /// + /// Get or sets the content to display when asynchronous navigation is in progress. + /// + [Parameter] public RenderFragment Navigating { get; set; } + + /// + /// Gets or sets a handler that should be called before navigating to a new page. + /// + [Parameter] public EventCallback OnNavigateAsync { get; set; } + private RouteTable Routes { get; set; } /// @@ -69,7 +85,7 @@ namespace Microsoft.AspNetCore.Components.Routing } /// - public Task SetParametersAsync(ParameterView parameters) + public async Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); @@ -93,17 +109,20 @@ namespace Microsoft.AspNetCore.Components.Routing throw new InvalidOperationException($"The {nameof(Router)} component requires a value for the parameter {nameof(NotFound)}."); } + if (!_onNavigateCalled) + { + _onNavigateCalled = true; + await RunOnNavigateAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute)); + } - var assemblies = AdditionalAssemblies == null ? new[] { AppAssembly } : new[] { AppAssembly }.Concat(AdditionalAssemblies); - Routes = RouteTableFactory.Create(assemblies); Refresh(isNavigationIntercepted: false); - return Task.CompletedTask; } /// public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; + _onNavigateCts?.Dispose(); } private static string StringUntilAny(string str, char[] chars) @@ -114,8 +133,24 @@ namespace Microsoft.AspNetCore.Components.Routing : str.Substring(0, firstIndex); } + private void RefreshRouteTable() + { + var assemblies = AdditionalAssemblies == null ? new[] { AppAssembly } : new[] { AppAssembly }.Concat(AdditionalAssemblies); + var assembliesSet = new HashSet(assemblies); + + if (!_assemblies.SetEquals(assembliesSet)) + { + Routes = RouteTableFactory.Create(assemblies); + _assemblies.Clear(); + _assemblies.UnionWith(assembliesSet); + } + + } + private void Refresh(bool isNavigationIntercepted) { + RefreshRouteTable(); + var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute); locationPath = StringUntilAny(locationPath, _queryOrHashStartChar); var context = new RouteContext(locationPath); @@ -155,12 +190,52 @@ namespace Microsoft.AspNetCore.Components.Routing } } + private async Task RunOnNavigateAsync(string path) + { + // If this router instance does not provide an OnNavigateAsync parameter + // then we render the component associated with the route as per usual. + if (!OnNavigateAsync.HasDelegate) + { + return; + } + + // If we've already invoked a task and stored its CTS, then + // cancel the existing task. + _onNavigateCts?.Dispose(); + + // Create a new cancellation token source for this instance + _onNavigateCts = new CancellationTokenSource(); + var navigateContext = new NavigationContext(path, _onNavigateCts.Token); + + // Create a cancellation task based on the cancellation token + // associated with the current running task. + var cancellationTaskSource = new TaskCompletionSource(); + navigateContext.CancellationToken.Register(state => + ((TaskCompletionSource)state).SetResult(), cancellationTaskSource); + + var task = OnNavigateAsync.InvokeAsync(navigateContext); + + // If the user provided a Navigating render fragment, then show it. + if (Navigating != null && task.Status != TaskStatus.RanToCompletion) + { + _renderHandle.Render(Navigating); + } + + await Task.WhenAny(task, cancellationTaskSource.Task); + } + + private async Task RunOnNavigateWithRefreshAsync(string path, bool isNavigationIntercepted) + { + await RunOnNavigateAsync(path); + Refresh(isNavigationIntercepted); + } + private void OnLocationChanged(object sender, LocationChangedEventArgs args) { _locationAbsolute = args.Location; if (_renderHandle.IsInitialized && Routes != null) { - Refresh(args.IsNavigationIntercepted); + _ = RunOnNavigateWithRefreshAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute), args.IsNavigationIntercepted); } } diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js index dba09ec219..1c7a47f755 100644 --- a/src/Components/Web.JS/dist/Release/blazor.webassembly.js +++ b/src/Components/Web.JS/dist/Release/blazor.webassembly.js @@ -1 +1 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=45)}([,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){window.DotNet=e;var t=[],n={},r={},o=1,i=null;function a(e){t.push(e)}function u(e,t,n,r){var o=c();if(o.invokeDotNetFromJS){var i=JSON.stringify(r,m),a=o.invokeDotNetFromJS(e,t,n,i);return a?f(a):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function s(e,t,r,i){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var a=o++,u=new Promise((function(e,t){n[a]={resolve:e,reject:t}}));try{var s=JSON.stringify(i,m);c().beginInvokeDotNetFromJS(a,e,t,r,s)}catch(e){l(a,!1,e)}return u}function c(){if(null!==i)return i;throw new Error("No .NET call dispatcher has been set.")}function l(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,(function(e,n){return t.reduce((function(t,n){return n(e,t)}),n)})):null}function d(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(Object.prototype.hasOwnProperty.call(r,e))return r[e];var t,n=window,o="window";if(e.split(".").forEach((function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e})),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){i=e},e.attachReviver=a,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return r in e||(e[r]=[]),e}function u(e,t,n){var i=e;if(e instanceof Comment&&(c(i)&&c(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(s(i))throw new Error("Not implemented: moving existing logical children");var a=c(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=s,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return c(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=c,t.permuteLogicalChildren=function(e,t){var n=c(e);t.forEach((function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=s(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)})),t.forEach((function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):d(r,e)})),t.forEach((function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,i=r;i;){var a=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=a}n.removeChild(t)})),t.forEach((function(e){n[e.toSiblingIndex]=e.moveRangeStart}))},t.getClosestDomElement=l},,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setPlatform=function(e){return t.platform=e,t.platform}},function(e,t,n){"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.dispatchEvent=function(e,t){if(!r)throw new Error("eventDispatcher not initialized. Call 'setEventDispatcher' to configure it.");return r(e,t)},t.setEventDispatcher=function(e){r=e}},,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(8),o=n(4),i=n(31),a=n(5);window.Blazor={navigateTo:r.navigateTo,_internal:{attachRootComponentToElement:o.attachRootComponentToElement,navigationManager:r.internalFunctions,domWrapper:i.domFunctions,setProfilingEnabled:a.setProfilingEnabled}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(26),o=n(27),i=n(13),a=n(30),u=n(19),s=n(8),c=n(5),l=document.createElement("template"),f=document.createElementNS("http://www.w3.org/2000/svg","g"),d={submit:!0},p={},h=function(){function e(e){var t=this;this.childComponentLocations={},this.browserRendererId=e,this.eventDelegator=new o.EventDelegator((function(e,n,r,o){!function(e,t,n,r,o){d[e.type]&&e.preventDefault();var i={browserRendererId:t,eventHandlerId:n,eventArgsType:r.type,eventFieldInfo:o};u.dispatchEvent(i,r.data)}(e,t.browserRendererId,n,r,o)})),s.attachToEventDelegator(this.eventDelegator)}return e.prototype.attachRootComponentToLogicalElement=function(e,t){this.attachComponentToElement(e,t),p[e]=t},e.prototype.updateComponent=function(e,t,n,r){c.profileStart("updateComponent");var o=this.childComponentLocations[t];if(!o)throw new Error("No element is currently associated with component "+t);var a=p[t];if(a){var u=i.getLogicalSiblingEnd(a);delete p[t],u?function(e,t){var n=i.getLogicalParent(e);if(!n)throw new Error("Can't clear between nodes. The start node does not have a logical parent.");for(var r=i.getLogicalChildrenArray(n),o=r.indexOf(e)+1,a=r.indexOf(t),u=o;u<=a;u++)i.removeLogicalChild(n,o);e.textContent="!"}(a,u):function(e){var t;for(;t=e.firstChild;)e.removeChild(t)}(a)}var s=i.getClosestDomElement(o).ownerDocument,l=s&&s.activeElement;this.applyEdits(e,t,o,0,n,r),l instanceof HTMLElement&&s&&s.activeElement!==l&&l.focus(),c.profileEnd("updateComponent")},e.prototype.disposeComponent=function(e){delete this.childComponentLocations[e]},e.prototype.disposeEventHandler=function(e){this.eventDelegator.removeListener(e)},e.prototype.attachComponentToElement=function(e,t){this.childComponentLocations[e]=t},e.prototype.applyEdits=function(e,t,n,o,a,u){for(var s,c=0,l=o,f=e.arrayBuilderSegmentReader,d=e.editReader,p=e.frameReader,h=f.values(a),m=f.offset(a),v=m+f.count(a),y=m;y0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]>>0)}t.readInt32LE=function(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24},t.readUint32LE=i,t.readUint64LE=function(e,t){var n=i(e,t+4);if(n>o)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*r+i(e,t)},t.readLEB128=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.shouldAutoStart=function(){return!(!document||!document.currentScript||"false"===document.currentScript.getAttribute("autostart"))}},,,,,,,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{s(r.next(e))}catch(e){i(e)}}function u(e){try{s(r.throw(e))}catch(e){i(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}s((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a};Object.defineProperty(t,"__esModule",{value:!0});var a=n(3);n(23);var u=n(18),s=n(46),c=n(4),l=n(49),f=n(35),d=n(19),p=n(50),h=n(51),m=n(52),v=n(5),y=!1;function b(e){return r(this,void 0,void 0,(function(){var t,n,f,b,g,w,E,_=this;return o(this,(function(C){switch(C.label){case 0:if(y)throw new Error("Blazor has already started.");return y=!0,d.setEventDispatcher((function(e,t){return a.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","DispatchEvent",e,JSON.stringify(t))})),t=u.setPlatform(s.monoPlatform),window.Blazor.platform=t,window.Blazor._internal.renderBatch=function(e,t){v.profileStart("renderBatch"),c.renderBatch(e,new l.SharedMemoryRenderBatch(t)),v.profileEnd("renderBatch")},n=window.Blazor._internal.navigationManager.getBaseURI,f=window.Blazor._internal.navigationManager.getLocationHref,window.Blazor._internal.navigationManager.getUnmarshalledBaseURI=function(){return BINDING.js_string_to_mono_string(n())},window.Blazor._internal.navigationManager.getUnmarshalledLocationHref=function(){return BINDING.js_string_to_mono_string(f())},window.Blazor._internal.navigationManager.listenForNavigationEvents((function(e,t){return r(_,void 0,void 0,(function(){return o(this,(function(n){switch(n.label){case 0:return[4,a.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChanged",e,t)];case 1:return n.sent(),[2]}}))}))})),[4,m.BootConfigResult.initAsync()];case 1:return b=C.sent(),[4,Promise.all([p.WebAssemblyResourceLoader.initAsync(b.bootConfig,e||{}),h.WebAssemblyConfigLoader.initAsync(b)])];case 2:g=i.apply(void 0,[C.sent(),1]),w=g[0],C.label=3;case 3:return C.trys.push([3,5,,6]),[4,t.start(w)];case 4:return C.sent(),[3,6];case 5:throw E=C.sent(),new Error("Failed to start platform. Reason: "+E);case 6:return t.callEntryPoint(w.bootConfig.entryAssembly),[2]}}))}))}window.Blazor.start=b,f.shouldAutoStart()&&b().catch((function(e){"undefined"!=typeof Module&&Module.printErr?Module.printErr(e):console.error(e)}))},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{s(r.next(e))}catch(e){i(e)}}function u(e){try{s(r.throw(e))}catch(e){i(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}s((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]>2]}t.monoPlatform={start:function(e){return new Promise((function(t,n){var f,d;u.attachDebuggerHotkey(e),l.initializeProfiling((function(e){v("Microsoft.AspNetCore.Components","Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling","SetCapturing")(e)})),window.Browser={init:function(){}},f=function(){window.Module=function(e,t,n){var l=this,f=e.bootConfig.resources,d=window.Module||{},p=["DEBUGGING ENABLED"];d.print=function(e){return p.indexOf(e)<0&&console.log(e)},d.printErr=function(e){console.error(e),s.showErrorNotification()},d.preRun=d.preRun||[],d.postRun=d.postRun||[],d.preloadPlugins=[];var m,b=e.loadResources(f.assembly,(function(e){return"_framework/"+e}),"assembly"),g=e.loadResources(f.pdb||{},(function(e){return"_framework/"+e}),"pdb"),w=e.loadResource("dotnet.wasm","_framework/dotnet.wasm",e.bootConfig.resources.runtime["dotnet.wasm"],"dotnetwasm");return e.bootConfig.resources.runtime.hasOwnProperty("dotnet.timezones.dat")&&(m=e.loadResource("dotnet.timezones.dat","_framework/dotnet.timezones.dat",e.bootConfig.resources.runtime["dotnet.timezones.dat"],"timezonedata")),d.instantiateWasm=function(e,t){return r(l,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:return o.trys.push([0,3,,4]),[4,w];case 1:return[4,y(o.sent(),e)];case 2:return n=o.sent(),[3,4];case 3:throw r=o.sent(),d.printErr(r),r;case 4:return t(n),[2]}}))})),[]},d.preRun.push((function(){i=cwrap("mono_wasm_add_assembly",null,["string","number","number"]),cwrap("mono_wasm_string_get_utf8","number",["number"]),MONO.loaded_files=[],m&&function(e){r(this,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return t="blazor:timezonedata",addRunDependency(t),[4,e.response];case 1:return[4,r.sent().arrayBuffer()];case 2:return n=r.sent(),c.loadTimezoneData(n),removeRunDependency(t),[2]}}))}))}(m),b.forEach((function(e){return E(e,function(e,t){var n=e.lastIndexOf(".");if(n<0)throw new Error("No extension to replace in '"+e+"'");return e.substr(0,n)+t}(e.name,".dll"))})),g.forEach((function(e){return E(e,e.name)})),window.Blazor._internal.dotNetCriticalError=function(e){d.printErr(BINDING.conv_string(e)||"(null)")},window.Blazor._internal.getSatelliteAssemblies=function(t){var n=BINDING.mono_array_to_js_array(t),i=e.bootConfig.resources.satelliteResources;if(i){var a=Promise.all(n.filter((function(e){return i.hasOwnProperty(e)})).map((function(t){return e.loadResources(i[t],(function(e){return"_framework/"+e}),"assembly")})).reduce((function(e,t){return e.concat(t)}),new Array).map((function(e){return r(l,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,e.response];case 1:return[2,t.sent().arrayBuffer()]}}))}))})));return BINDING.js_to_mono_obj(a.then((function(e){return e.length&&(window.Blazor._internal.readSatelliteAssemblies=function(){for(var t=BINDING.mono_obj_array_new(e.length),n=0;n>1];var n},readInt32Field:function(e,t){return p(e+(t||0))},readUint64Field:function(e,t){return function(e){var t=e>>2,n=Module.HEAPU32[t+1];if(n>d)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*f+Module.HEAPU32[t]}(e+(t||0))},readFloatField:function(e,t){return n=e+(t||0),Module.HEAPF32[n>>2];var n},readObjectField:function(e,t){return p(e+(t||0))},readStringField:function(e,t,n){var r=p(e+(t||0));if(0===r)return null;if(n){var o=BINDING.unbox_mono_obj(r);return"boolean"==typeof o?o?"":null:o}return BINDING.conv_string(r)},readStructField:function(e,t){return e+(t||0)}};var h=document.createElement("a");function m(e){return e+12}function v(e,t,n){var r="["+e+"] "+t+":"+n;return BINDING.bind_static_method(r)}function y(e,t){return r(this,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:if("function"!=typeof WebAssembly.instantiateStreaming)return[3,4];o.label=1;case 1:return o.trys.push([1,3,,4]),[4,WebAssembly.instantiateStreaming(e.response,t)];case 2:return[2,o.sent().instance];case 3:return n=o.sent(),console.info("Streaming compilation failed. Falling back to ArrayBuffer instantiation. ",n),[3,4];case 4:return[4,e.response.then((function(e){return e.arrayBuffer()}))];case 5:return r=o.sent(),[4,WebAssembly.instantiate(r,t)];case 6:return[2,o.sent().instance]}}))}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=window.chrome&&navigator.userAgent.indexOf("Edge")<0,o=!1;function i(){return o&&r}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){o=!!e.bootConfig.resources.pdb;var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",(function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(o?r?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Microsoft Edge (80+), or Google Chrome, are supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))}))}},function(e,t,n){"use strict";var r=this&&this.__values||function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a};Object.defineProperty(t,"__esModule",{value:!0});var i=n(33),a=n(34);t.loadTimezoneData=function(e){var t,n,u=new Uint8Array(e),s=i.readInt32LE(u,0);u=u.slice(4);var c=a.decodeUtf8(u.slice(0,s)),l=JSON.parse(c);u=u.slice(s),Module.FS_createPath("/","zoneinfo",!0,!0),new Set(l.map((function(e){return e[0].split("/")[0]}))).forEach((function(e){return Module.FS_createPath("/zoneinfo",e,!0,!0)}));try{for(var f=r(l),d=f.next();!d.done;d=f.next()){var p=o(d.value,2),h=p[0],m=p[1],v=u.slice(0,m);Module.FS_createDataFile("/zoneinfo/"+h,null,v,!0,!0,!0),u=u.slice(m)}}catch(e){t={error:e}}finally{try{d&&!d.done&&(n=f.return)&&n.call(f)}finally{if(t)throw t.error}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(18),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=i,this.arrayBuilderSegmentReader=a,this.diffReader=u,this.editReader=s,this.frameReader=c}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,i.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*i.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*i.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return l(e,t,u.structLength)},e.prototype.referenceFramesEntry=function(e,t){return l(e,t,c.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=l(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=l(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var i={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},a={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},u={structLength:4+a.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return l(e,t,s.structLength)}},s={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},c={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24,!0)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function l(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{s(r.next(e))}catch(e){i(e)}}function u(e){try{s(r.throw(e))}catch(e){i(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}s((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return r in e||(e[r]=[]),e}function s(e,t,n){var i=e;if(e instanceof Comment&&(c(i)&&c(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(u(i))throw new Error("Not implemented: moving existing logical children");var a=c(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=u,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return c(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=c,t.permuteLogicalChildren=function(e,t){var n=c(e);t.forEach((function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=u(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)})),t.forEach((function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):d(r,e)})),t.forEach((function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,i=r;i;){var a=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=a}n.removeChild(t)})),t.forEach((function(e){n[e.toSiblingIndex]=e.moveRangeStart}))},t.getClosestDomElement=l},,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setPlatform=function(e){return t.platform=e,t.platform}},function(e,t,n){"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.dispatchEvent=function(e,t){if(!r)throw new Error("eventDispatcher not initialized. Call 'setEventDispatcher' to configure it.");return r(e,t)},t.setEventDispatcher=function(e){r=e}},,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(8),o=n(4),i=n(31),a=n(5);window.Blazor={navigateTo:r.navigateTo,_internal:{attachRootComponentToElement:o.attachRootComponentToElement,navigationManager:r.internalFunctions,domWrapper:i.domFunctions,setProfilingEnabled:a.setProfilingEnabled}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(26),o=n(27),i=n(13),a=n(30),s=n(19),u=n(8),c=n(5),l=document.createElement("template"),f=document.createElementNS("http://www.w3.org/2000/svg","g"),d={submit:!0},p={},h=function(){function e(e){var t=this;this.childComponentLocations={},this.browserRendererId=e,this.eventDelegator=new o.EventDelegator((function(e,n,r,o){!function(e,t,n,r,o){d[e.type]&&e.preventDefault();var i={browserRendererId:t,eventHandlerId:n,eventArgsType:r.type,eventFieldInfo:o};s.dispatchEvent(i,r.data)}(e,t.browserRendererId,n,r,o)})),u.attachToEventDelegator(this.eventDelegator)}return e.prototype.attachRootComponentToLogicalElement=function(e,t){this.attachComponentToElement(e,t),p[e]=t},e.prototype.updateComponent=function(e,t,n,r){c.profileStart("updateComponent");var o=this.childComponentLocations[t];if(!o)throw new Error("No element is currently associated with component "+t);var a=p[t];if(a){var s=i.getLogicalSiblingEnd(a);delete p[t],s?function(e,t){var n=i.getLogicalParent(e);if(!n)throw new Error("Can't clear between nodes. The start node does not have a logical parent.");for(var r=i.getLogicalChildrenArray(n),o=r.indexOf(e)+1,a=r.indexOf(t),s=o;s<=a;s++)i.removeLogicalChild(n,o);e.textContent="!"}(a,s):function(e){var t;for(;t=e.firstChild;)e.removeChild(t)}(a)}var u=i.getClosestDomElement(o).ownerDocument,l=u&&u.activeElement;this.applyEdits(e,t,o,0,n,r),l instanceof HTMLElement&&u&&u.activeElement!==l&&l.focus(),c.profileEnd("updateComponent")},e.prototype.disposeComponent=function(e){delete this.childComponentLocations[e]},e.prototype.disposeEventHandler=function(e){this.eventDelegator.removeListener(e)},e.prototype.attachComponentToElement=function(e,t){this.childComponentLocations[e]=t},e.prototype.applyEdits=function(e,t,n,o,a,s){for(var u,c=0,l=o,f=e.arrayBuilderSegmentReader,d=e.editReader,p=e.frameReader,h=f.values(a),m=f.offset(a),y=m+f.count(a),v=m;v0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]>>0)}t.readInt32LE=function(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24},t.readUint32LE=i,t.readUint64LE=function(e,t){var n=i(e,t+4);if(n>o)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*r+i(e,t)},t.readLEB128=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.shouldAutoStart=function(){return!(!document||!document.currentScript||"false"===document.currentScript.getAttribute("autostart"))}},,,,,,,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function s(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a};Object.defineProperty(t,"__esModule",{value:!0});var a=n(3);n(23);var s=n(18),u=n(46),c=n(4),l=n(49),f=n(35),d=n(19),p=n(50),h=n(51),m=n(52),y=n(5),v=!1;function b(e){return r(this,void 0,void 0,(function(){var t,n,f,b,g,w,E,_=this;return o(this,(function(C){switch(C.label){case 0:if(v)throw new Error("Blazor has already started.");return v=!0,d.setEventDispatcher((function(e,t){return a.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","DispatchEvent",e,JSON.stringify(t))})),t=s.setPlatform(u.monoPlatform),window.Blazor.platform=t,window.Blazor._internal.renderBatch=function(e,t){y.profileStart("renderBatch"),c.renderBatch(e,new l.SharedMemoryRenderBatch(t)),y.profileEnd("renderBatch")},n=window.Blazor._internal.navigationManager.getBaseURI,f=window.Blazor._internal.navigationManager.getLocationHref,window.Blazor._internal.navigationManager.getUnmarshalledBaseURI=function(){return BINDING.js_string_to_mono_string(n())},window.Blazor._internal.navigationManager.getUnmarshalledLocationHref=function(){return BINDING.js_string_to_mono_string(f())},window.Blazor._internal.navigationManager.listenForNavigationEvents((function(e,t){return r(_,void 0,void 0,(function(){return o(this,(function(n){switch(n.label){case 0:return[4,a.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChanged",e,t)];case 1:return n.sent(),[2]}}))}))})),[4,m.BootConfigResult.initAsync()];case 1:return b=C.sent(),[4,Promise.all([p.WebAssemblyResourceLoader.initAsync(b.bootConfig,e||{}),h.WebAssemblyConfigLoader.initAsync(b)])];case 2:g=i.apply(void 0,[C.sent(),1]),w=g[0],C.label=3;case 3:return C.trys.push([3,5,,6]),[4,t.start(w)];case 4:return C.sent(),[3,6];case 5:throw E=C.sent(),new Error("Failed to start platform. Reason: "+E);case 6:return t.callEntryPoint(w.bootConfig.entryAssembly),[2]}}))}))}window.Blazor.start=b,f.shouldAutoStart()&&b().catch((function(e){"undefined"!=typeof Module&&Module.printErr?Module.printErr(e):console.error(e)}))},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function s(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]>2]}t.monoPlatform={start:function(e){return new Promise((function(t,n){var f,d;s.attachDebuggerHotkey(e),l.initializeProfiling((function(e){y("Microsoft.AspNetCore.Components","Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling","SetCapturing")(e)})),window.Browser={init:function(){}},f=function(){window.Module=function(e,t,n){var l=this,f=e.bootConfig.resources,d=window.Module||{},p=["DEBUGGING ENABLED"];d.print=function(e){return p.indexOf(e)<0&&console.log(e)},d.printErr=function(e){console.error(e),u.showErrorNotification()},d.preRun=d.preRun||[],d.postRun=d.postRun||[],d.preloadPlugins=[];var m,b=e.loadResources(f.assembly,(function(e){return"_framework/"+e}),"assembly"),g=e.loadResources(f.pdb||{},(function(e){return"_framework/"+e}),"pdb"),w=e.loadResource("dotnet.wasm","_framework/dotnet.wasm",e.bootConfig.resources.runtime["dotnet.wasm"],"dotnetwasm");return e.bootConfig.resources.runtime.hasOwnProperty("dotnet.timezones.dat")&&(m=e.loadResource("dotnet.timezones.dat","_framework/dotnet.timezones.dat",e.bootConfig.resources.runtime["dotnet.timezones.dat"],"timezonedata")),d.instantiateWasm=function(e,t){return r(l,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:return o.trys.push([0,3,,4]),[4,w];case 1:return[4,v(o.sent(),e)];case 2:return n=o.sent(),[3,4];case 3:throw r=o.sent(),d.printErr(r),r;case 4:return t(n),[2]}}))})),[]},d.preRun.push((function(){i=cwrap("mono_wasm_add_assembly",null,["string","number","number"]),cwrap("mono_wasm_string_get_utf8","number",["number"]),MONO.loaded_files=[],m&&function(e){r(this,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return t="blazor:timezonedata",addRunDependency(t),[4,e.response];case 1:return[4,r.sent().arrayBuffer()];case 2:return n=r.sent(),c.loadTimezoneData(n),removeRunDependency(t),[2]}}))}))}(m),b.forEach((function(e){return E(e,function(e,t){var n=e.lastIndexOf(".");if(n<0)throw new Error("No extension to replace in '"+e+"'");return e.substr(0,n)+t}(e.name,".dll"))})),g.forEach((function(e){return E(e,e.name)})),window.Blazor._internal.dotNetCriticalError=function(e){d.printErr(BINDING.conv_string(e)||"(null)")},window.Blazor._internal.getSatelliteAssemblies=function(t){var n=BINDING.mono_array_to_js_array(t),i=e.bootConfig.resources.satelliteResources;if(i){var a=Promise.all(n.filter((function(e){return i.hasOwnProperty(e)})).map((function(t){return e.loadResources(i[t],(function(e){return"_framework/"+e}),"assembly")})).reduce((function(e,t){return e.concat(t)}),new Array).map((function(e){return r(l,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,e.response];case 1:return[2,t.sent().arrayBuffer()]}}))}))})));return BINDING.js_to_mono_obj(a.then((function(e){return e.length&&(window.Blazor._internal.readSatelliteAssemblies=function(){for(var t=BINDING.mono_obj_array_new(e.length),n=0;n>1];var n},readInt32Field:function(e,t){return p(e+(t||0))},readUint64Field:function(e,t){return function(e){var t=e>>2,n=Module.HEAPU32[t+1];if(n>d)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*f+Module.HEAPU32[t]}(e+(t||0))},readFloatField:function(e,t){return n=e+(t||0),Module.HEAPF32[n>>2];var n},readObjectField:function(e,t){return p(e+(t||0))},readStringField:function(e,t,n){var r=p(e+(t||0));if(0===r)return null;if(n){var o=BINDING.unbox_mono_obj(r);return"boolean"==typeof o?o?"":null:o}return BINDING.conv_string(r)},readStructField:function(e,t){return e+(t||0)}};var h=document.createElement("a");function m(e){return e+12}function y(e,t,n){var r="["+e+"] "+t+":"+n;return BINDING.bind_static_method(r)}function v(e,t){return r(this,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:if("function"!=typeof WebAssembly.instantiateStreaming)return[3,4];o.label=1;case 1:return o.trys.push([1,3,,4]),[4,WebAssembly.instantiateStreaming(e.response,t)];case 2:return[2,o.sent().instance];case 3:return n=o.sent(),console.info("Streaming compilation failed. Falling back to ArrayBuffer instantiation. ",n),[3,4];case 4:return[4,e.response.then((function(e){return e.arrayBuffer()}))];case 5:return r=o.sent(),[4,WebAssembly.instantiate(r,t)];case 6:return[2,o.sent().instance]}}))}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=window.chrome&&navigator.userAgent.indexOf("Edge")<0,o=!1;function i(){return o&&r}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){o=!!e.bootConfig.resources.pdb;var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",(function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(o?r?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Microsoft Edge (80+), or Google Chrome, are supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))}))}},function(e,t,n){"use strict";var r=this&&this.__values||function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a};Object.defineProperty(t,"__esModule",{value:!0});var i=n(33),a=n(34);t.loadTimezoneData=function(e){var t,n,s=new Uint8Array(e),u=i.readInt32LE(s,0);s=s.slice(4);var c=a.decodeUtf8(s.slice(0,u)),l=JSON.parse(c);s=s.slice(u),Module.FS_createPath("/","zoneinfo",!0,!0),new Set(l.map((function(e){return e[0].split("/")[0]}))).forEach((function(e){return Module.FS_createPath("/zoneinfo",e,!0,!0)}));try{for(var f=r(l),d=f.next();!d.done;d=f.next()){var p=o(d.value,2),h=p[0],m=p[1],y=s.slice(0,m);Module.FS_createDataFile("/zoneinfo/"+h,null,y,!0,!0,!0),s=s.slice(m)}}catch(e){t={error:e}}finally{try{d&&!d.done&&(n=f.return)&&n.call(f)}finally{if(t)throw t.error}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(18),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=i,this.arrayBuilderSegmentReader=a,this.diffReader=s,this.editReader=u,this.frameReader=c}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,i.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*i.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*i.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return l(e,t,s.structLength)},e.prototype.referenceFramesEntry=function(e,t){return l(e,t,c.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=l(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=l(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var i={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},a={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},s={structLength:4+a.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return l(e,t,u.structLength)}},u={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},c={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24,!0)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function l(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function s(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]) : System_Object => { + const assembliesToLoad = BINDING.mono_array_to_js_array(assembliesToLoadDotNetArray); + const lazyAssemblies = resourceLoader.bootConfig.resources.lazyAssembly; + + if (lazyAssemblies) { + const resourcePromises = Promise.all(assembliesToLoad + .filter(assembly => lazyAssemblies.hasOwnProperty(assembly)) + .map(assembly => resourceLoader.loadResource(assembly, `_framework/${assembly}`, lazyAssemblies[assembly], 'assembly')) + .map(async resource => (await resource.response).arrayBuffer())); + + return BINDING.js_to_mono_obj( + resourcePromises.then(resourcesToLoad => { + if (resourcesToLoad.length) { + window['Blazor']._internal.readLazyAssemblies = () => { + const array = BINDING.mono_obj_array_new(resourcesToLoad.length); + for (var i = 0; i < resourcesToLoad.length; i++) { + BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i]))); + } + return array; + }; + } + + return resourcesToLoad.length; + })); + } + return BINDING.js_to_mono_obj(Promise.resolve(0)); + } }); module.postRun.push(() => { diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildLazyLoadTest.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildLazyLoadTest.cs deleted file mode 100644 index dc6be8d80e..0000000000 --- a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildLazyLoadTest.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Xunit; -using static Microsoft.AspNetCore.Components.WebAssembly.Build.WebAssemblyRuntimePackage; - -namespace Microsoft.AspNetCore.Components.WebAssembly.Build -{ - public class BuildLazyLoadTest - { - [Fact] - public async Task Build_LazyLoadExplicitAssembly_Debug_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); - project.Configuration = "Debug"; - - project.AddProjectFileContent( -@" - - - -"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project); - - var buildOutputDirectory = project.BuildOutputDirectory; - - // Verify that a blazor.boot.json file has been created - Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); - // And that the assembly is in the output - Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll"); - - var bootJson = ReadBootJsonData(result, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json")); - - // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; - var assemblies = bootJson.resources.assembly; - - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); - Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); - - // App assembly should not be lazy loaded - Assert.DoesNotContain("standalone.dll", dynamicAssemblies.Keys); - Assert.Contains("standalone.dll", assemblies.Keys); - } - - [Fact] - public async Task Build_LazyLoadExplicitAssembly_Release_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); - project.Configuration = "Release"; - - project.AddProjectFileContent( -@" - - - -"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project); - - var buildOutputDirectory = project.BuildOutputDirectory; - - // Verify that a blazor.boot.json file has been created - Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); - // And that the assembly is in the output - Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll"); - - var bootJson = ReadBootJsonData(result, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json")); - - // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; - var assemblies = bootJson.resources.assembly; - - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); - Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); - - // App assembly should not be lazy loaded - Assert.DoesNotContain("standalone.dll", dynamicAssemblies.Keys); - Assert.Contains("standalone.dll", assemblies.Keys); - } - - [Fact] - public async Task Publish_LazyLoadExplicitAssembly_Debug_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); - project.Configuration = "Debug"; - - project.AddProjectFileContent( -@" - - - -"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); - - var publishDirectory = project.PublishOutputDirectory; - - // Verify that a blazor.boot.json file has been created - Assert.FileExists(result, publishDirectory, "wwwroot", "_framework", "blazor.boot.json"); - // And that the assembly is in the output - Assert.FileExists(result, publishDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll"); - - var bootJson = ReadBootJsonData(result, Path.Combine(publishDirectory, "wwwroot", "_framework", "blazor.boot.json")); - - // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; - var assemblies = bootJson.resources.assembly; - - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); - Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); - - // App assembly should not be lazy loaded - Assert.DoesNotContain("standalone.dll", dynamicAssemblies.Keys); - Assert.Contains("standalone.dll", assemblies.Keys); - } - - [Fact] - public async Task Publish_LazyLoadExplicitAssembly_Release_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); - project.Configuration = "Release"; - - project.AddProjectFileContent( -@" - - - -"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); - - var publishDirectory = project.PublishOutputDirectory; - - // Verify that a blazor.boot.json file has been created - Assert.FileExists(result, publishDirectory, "wwwroot", "_framework", "blazor.boot.json"); - // And that the assembly is in the output - Assert.FileExists(result, publishDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll"); - - var bootJson = ReadBootJsonData(result, Path.Combine(publishDirectory, "wwwroot", "_framework", "blazor.boot.json")); - - // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; - var assemblies = bootJson.resources.assembly; - - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); - Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); - - // App assembly should not be lazy loaded - Assert.DoesNotContain("standalone.dll", dynamicAssemblies.Keys); - Assert.Contains("standalone.dll", assemblies.Keys); - } - - private static GenerateBlazorBootJson.BootJsonData ReadBootJsonData(MSBuildResult result, string path) - { - return JsonSerializer.Deserialize( - File.ReadAllText(Path.Combine(result.Project.DirectoryPath, path)), - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - } - } -} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index f81607bc90..0247720550 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.AspNetCore.Components.Routing; @@ -11,7 +10,6 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; @@ -191,6 +189,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting Services.AddSingleton(DefaultWebAssemblyJSRuntime.Instance); Services.AddSingleton(WebAssemblyNavigationManager.Instance); Services.AddSingleton(WebAssemblyNavigationInterception.Instance); + Services.AddSingleton(provider => new LazyAssemblyLoader(provider)); Services.AddLogging(builder => { builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance)); }); diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs b/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs new file mode 100644 index 0000000000..07f35c236e --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.JSInterop; +using Microsoft.JSInterop.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Services +{ + /// + /// Provides a service for loading assemblies at runtime in a browser context. + /// + /// Supports finding pre-loaded assemblies in a server or pre-rendering context. + /// + public class LazyAssemblyLoader + { + internal const string GetDynamicAssemblies = "window.Blazor._internal.getLazyAssemblies"; + internal const string ReadDynamicAssemblies = "window.Blazor._internal.readLazyAssemblies"; + + private List _loadedAssemblyCache = new List(); + + private readonly IServiceProvider _provider; + + public LazyAssemblyLoader(IServiceProvider provider) + { + _provider = provider; + _loadedAssemblyCache = AppDomain.CurrentDomain.GetAssemblies().ToList(); + } + + /// + /// In a browser context, calling this method will fetch the assemblies requested + /// via a network call and load them into the runtime. In a server or pre-rendered + /// context, this method will look for the assemblies already loaded in the runtime + /// and return them. + /// + /// The names of the assemblies to load (e.g. "MyAssembly.dll") + /// A list of the loaded + public async Task> LoadAssembliesAsync(IEnumerable assembliesToLoad) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Browser)) + { + return await LoadAssembliesInClientAsync(assembliesToLoad); + } + + return await LoadAssembliesInServerAsync(assembliesToLoad); + } + + private Task> LoadAssembliesInServerAsync(IEnumerable assembliesToLoad) + { + var loadedAssemblies = _loadedAssemblyCache.Where(assembly => + assembliesToLoad.Contains(assembly.GetName().Name + ".dll")); + + if (loadedAssemblies.Count() != assembliesToLoad.Count()) + { + var unloadedAssemblies = assembliesToLoad.Except(loadedAssemblies.Select(a => a.GetName().Name + ".dll")); + throw new InvalidOperationException($"Unable to find the following assemblies: {string.Join(",", unloadedAssemblies)}. Make sure that the appplication is referencing the assemblies and that they are present in the output folder."); + } + + return Task.FromResult(loadedAssemblies); + } + + private async Task> LoadAssembliesInClientAsync(IEnumerable assembliesToLoad) + { + var jsRuntime = _provider.GetRequiredService(); + // Only load assemblies that haven't already been lazily-loaded + var newAssembliesToLoad = assembliesToLoad.Except(_loadedAssemblyCache.Select(a => a.GetName().Name + ".dll")); + var loadedAssemblies = new List(); + + var count = (int)await ((WebAssemblyJSRuntime)jsRuntime).InvokeUnmarshalled>( + GetDynamicAssemblies, + assembliesToLoad.ToArray(), + null, + null); + + if (count == 0) + { + return loadedAssemblies; + } + + var assemblies = ((WebAssemblyJSRuntime)jsRuntime).InvokeUnmarshalled( + ReadDynamicAssemblies, + null, + null, + null); + + foreach (byte[] assembly in assemblies) + { + // The runtime loads assemblies into an isolated context by default. As a result, + // assemblies that are loaded via Assembly.Load aren't available in the app's context + // AKA the default context. To work around this, we explicitly load the assemblies + // into the default app context. + var loadedAssembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(assembly)); + loadedAssemblies.Add(loadedAssembly); + _loadedAssemblyCache.Add(loadedAssembly); + } + + return loadedAssemblies; + } + } +} diff --git a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs index aec4adf279..a1c9e8e9b1 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs @@ -60,6 +60,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Browser.Equal("Hello from interop call", () => Browser.FindElement(By.Id("val-set-by-interop")).GetAttribute("value")); } + [Fact] + public void IsCompatibleWithLazyLoadWebAssembly() + { + Navigate("/prerendered/WithLazyAssembly"); + + var button = Browser.FindElement(By.Id("use-package-button")); + + button.Click(); + + AssertLogDoesNotContainCriticalMessages("Could not load file or assembly 'Newtonsoft.Json"); + } + [Fact] public void CanReadUrlHashOnlyOnceConnected() { @@ -121,6 +133,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Browser.FindElement(By.Id("load-boot-script")).Click(); } + private void AssertLogDoesNotContainCriticalMessages(params string[] messages) + { + var log = Browser.Manage().Logs.GetLog(LogType.Browser); + foreach (var message in messages) + { + Assert.DoesNotContain(log, entry => + { + return entry.Level == LogLevel.Severe + && entry.Message.Contains(message); + }); + } + } + private void SignInAs(string userName, string roles, bool useSeparateTab = false) => Browser.SignInAs(new Uri(_serverFixture.RootUri, "/prerendered/"), userName, roles, useSeparateTab); } diff --git a/src/Components/test/E2ETest/Tests/RoutingTest.cs b/src/Components/test/E2ETest/Tests/RoutingTest.cs index 29fec5e599..3f3f3920ff 100644 --- a/src/Components/test/E2ETest/Tests/RoutingTest.cs +++ b/src/Components/test/E2ETest/Tests/RoutingTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.E2ETesting; using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; +using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; @@ -527,6 +528,32 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } } + [Fact] + public void OnNavigate_CanRenderLoadingFragment() + { + var app = Browser.MountTestComponent(); + + SetUrlViaPushState("/LongPage1"); + + new WebDriverWait(Browser, TimeSpan.FromSeconds(2)).Until( + driver => driver.FindElement(By.Id("loading-banner")) != null); + + Assert.True(app.FindElement(By.Id("loading-banner")) != null); + } + + [Fact] + public void OnNavigate_CanCancelCallback() + { + var app = Browser.MountTestComponent(); + + // Navigating from one page to another should + // cancel the previous OnNavigate Task + SetUrlViaPushState("/LongPage2"); + SetUrlViaPushState("/LongPage1"); + + AssertDidNotLog("I'm not happening..."); + } + private long BrowserScrollY { get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY"); @@ -543,6 +570,15 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests return absoluteUri.AbsoluteUri; } + private void AssertDidNotLog(params string[] messages) + { + var log = Browser.Manage().Logs.GetLog(LogType.Browser); + foreach (var message in messages) + { + Assert.DoesNotContain(log, entry => entry.Message.Contains(message)); + } + } + private void AssertHighlightedLinks(params string[] linkTexts) { Browser.Equal(linkTexts, () => Browser diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyLazyLoadTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyLazyLoadTest.cs new file mode 100644 index 0000000000..98b0bc0890 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyLazyLoadTest.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using BasicTestApp; +using BasicTestApp.RouterTest; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyLazyLoadTest : ServerTestBase> + { + public WebAssemblyLazyLoadTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + protected override void InitializeAsyncCore() + { + Navigate(ServerPathBase, noReload: false); + Browser.MountTestComponent(); + Browser.Exists(By.Id("blazor-error-ui")); + + var errorUi = Browser.FindElement(By.Id("blazor-error-ui")); + Assert.Equal("none", errorUi.GetCssValue("display")); + } + + [Fact] + public void CanLazyLoadOnRouteChange() + { + // Navigate to a page without any lazy-loaded dependencies + SetUrlViaPushState("/"); + var app = Browser.MountTestComponent(); + + // Ensure that we haven't requested the lazy loaded assembly + Assert.False(HasLoadedAssembly("Newtonsoft.Json.dll")); + + // Visit the route for the lazy-loaded assembly + SetUrlViaPushState("/WithLazyAssembly"); + + var button = app.FindElement(By.Id("use-package-button")); + + // Now we should have requested the DLL + Assert.True(HasLoadedAssembly("Newtonsoft.Json.dll")); + + button.Click(); + + // We shouldn't get any errors about assemblies not being available + AssertLogDoesNotContainCriticalMessages("Could not load file or assembly 'Newtonsoft.Json"); + } + + [Fact] + public void CanLazyLoadOnFirstVisit() + { + // Navigate to a page with lazy loaded assemblies for the first time + SetUrlViaPushState("/WithLazyAssembly"); + var app = Browser.MountTestComponent(); + + // Wait for the page to finish loading + new WebDriverWait(Browser, TimeSpan.FromSeconds(2)).Until( + driver => driver.FindElement(By.Id("use-package-button")) != null); + + var button = app.FindElement(By.Id("use-package-button")); + + // We should have requested the DLL + Assert.True(HasLoadedAssembly("Newtonsoft.Json.dll")); + + button.Click(); + + // We shouldn't get any errors about assemblies not being available + AssertLogDoesNotContainCriticalMessages("Could not load file or assembly 'Newtonsoft.Json"); + } + + [Fact] + public void CanLazyLoadAssemblyWithRoutes() + { + // Navigate to a page without any lazy-loaded dependencies + SetUrlViaPushState("/"); + var app = Browser.MountTestComponent(); + + // Ensure that we haven't requested the lazy loaded assembly + Assert.False(HasLoadedAssembly("LazyTestContentPackage.dll")); + + // Navigate to the designated route + SetUrlViaPushState("/WithLazyLoadedRoutes"); + + // Wait for the page to finish loading + new WebDriverWait(Browser, TimeSpan.FromSeconds(2)).Until( + driver => driver.FindElement(By.Id("lazy-load-msg")) != null); + + // Now the assembly has been loaded + Assert.True(HasLoadedAssembly("LazyTestContentPackage.dll")); + + var button = app.FindElement(By.Id("go-to-lazy-route")); + button.Click(); + + // Navigating the lazy-loaded route should show its content + var renderedElement = app.FindElement(By.Id("lazy-page")); + Assert.True(renderedElement.Displayed); + } + + private string SetUrlViaPushState(string relativeUri) + { + var pathBaseWithoutHash = ServerPathBase.Split('#')[0]; + var jsExecutor = (IJavaScriptExecutor)Browser; + var absoluteUri = new Uri(_serverFixture.RootUri, $"{pathBaseWithoutHash}{relativeUri}"); + jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')"); + + return absoluteUri.AbsoluteUri; + } + + private bool HasLoadedAssembly(string name) + { + var checkScript = $"return window.performance.getEntriesByType('resource').some(r => r.name.endsWith('{name}'));"; + var jsExecutor = (IJavaScriptExecutor)Browser; + var nameRequested = jsExecutor.ExecuteScript(checkScript); + if (nameRequested != null) + { + return (bool)nameRequested; + } + return false; + } + + private void AssertLogDoesNotContainCriticalMessages(params string[] messages) + { + var log = Browser.Manage().Logs.GetLog(LogType.Browser); + foreach (var message in messages) + { + Assert.DoesNotContain(log, entry => + { + return entry.Level == LogLevel.Severe + && entry.Message.Contains(message); + }); + } + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index f47ac9084a..d3455b0778 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -18,14 +18,21 @@ + + + + + + + diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 0a8ce0a5cc..14cbc9e8fe 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -70,6 +70,8 @@ + + diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/Links.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/Links.razor index 344286213f..2db21d8723 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/Links.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/Links.razor @@ -20,6 +20,7 @@
  • With more parameters
  • Long page 1
  • Long page 2
  • +
  • With lazy assembly
  • preventDefault cases
  • Null href never matches
  • diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor new file mode 100644 index 0000000000..ced83679de --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor @@ -0,0 +1,57 @@ +@using Microsoft.AspNetCore.Components.Routing +@using System.Reflection +@using Microsoft.AspNetCore.Components.WebAssembly.Services + +@inject LazyAssemblyLoader lazyLoader + + + +
    +

    Loading the requested page...

    +
    +
    + + + + + +
    Oops, that component wasn't found!
    +
    +
    +
    + +@code { + private List lazyLoadedAssemblies = new List(); + + private async Task OnNavigateAsync(NavigationContext args) + { + Console.WriteLine($"Running OnNavigate for {args.Path}..."); + await LoadAssemblies(args.Path); + } + + private async Task LoadAssemblies(string uri) + { + try + { + if (uri.EndsWith("WithLazyAssembly")) + { + Console.WriteLine($"Loading assemblies for WithLazyAssembly..."); + var assemblies = await lazyLoader.LoadAssembliesAsync(new List() { "Newtonsoft.Json.dll" }); + lazyLoadedAssemblies.AddRange(assemblies); + } + + if (uri.EndsWith("WithLazyLoadedRoutes")) + { + Console.WriteLine($"Loading assemblies for WithLazyLoadedRoutes..."); + var assemblies = await lazyLoader.LoadAssembliesAsync(new List() { "LazyTestContentPackage.dll" }); + lazyLoadedAssemblies.AddRange(assemblies); + } + } + catch (Exception e) + { + Console.WriteLine($"Error when loading assemblies: {e}"); + } + } +} + + diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor new file mode 100644 index 0000000000..4d51ea9e25 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor @@ -0,0 +1,46 @@ +@using Microsoft.AspNetCore.Components.Routing + + + +
    +

    Loading the requested page...

    +
    +
    + + + + + +
    Oops, that component wasn't found!
    +
    +
    +
    + +@code { + private Dictionary> preNavigateTasks = new Dictionary>() + { + { "LongPage1", new Func(TestLoadingPageShows) }, + { "LongPage2", new Func(TestOnNavCancel) } + }; + + private async Task OnNavigateAsync(NavigationContext args) + { + Console.WriteLine($"Running OnNavigate for {args.Path}..."); + Func task; + if (preNavigateTasks.TryGetValue(args.Path, out task)) + { + await task.Invoke(args); + } + } + + public static async Task TestLoadingPageShows(NavigationContext args) + { + await Task.Delay(2000); + } + + public static async Task TestOnNavCancel(NavigationContext args) + { + await Task.Delay(2000, args.CancellationToken); + Console.WriteLine("I'm not happening..."); + } +} diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyAssembly.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyAssembly.razor new file mode 100644 index 0000000000..783693f529 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyAssembly.razor @@ -0,0 +1,16 @@ +@page "/WithLazyAssembly" + +@using System.Linq +@using System.Reflection +@using Newtonsoft.Json + +

    Just a webpage that uses a lazy-loaded dependency.

    + + + +@code +{ + private void UsePackage() { + JsonConvert.DeserializeObject("{ 'type': 'Test' }"); + } +} diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyRouteableAssembly.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyRouteableAssembly.razor new file mode 100644 index 0000000000..db28db54d6 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/WithLazyRouteableAssembly.razor @@ -0,0 +1,5 @@ +@page "/WithLazyLoadedRoutes" + +

    Click the button below to navigate to a route defined in a lazy-loaded component.

    + +Click Me diff --git a/src/Components/test/testassets/LazyTestContentPackage/ComponentFromPackage.razor b/src/Components/test/testassets/LazyTestContentPackage/ComponentFromPackage.razor new file mode 100644 index 0000000000..e81ceb6a31 --- /dev/null +++ b/src/Components/test/testassets/LazyTestContentPackage/ComponentFromPackage.razor @@ -0,0 +1,3 @@ +
    + This component will be lazily loaded. +
    diff --git a/src/Components/test/testassets/LazyTestContentPackage/LazyTestContentPackage.csproj b/src/Components/test/testassets/LazyTestContentPackage/LazyTestContentPackage.csproj new file mode 100644 index 0000000000..247f036d16 --- /dev/null +++ b/src/Components/test/testassets/LazyTestContentPackage/LazyTestContentPackage.csproj @@ -0,0 +1,22 @@ + + + + $(DefaultNetCoreTargetFramework) + library + _content/TestContentPackage + + + + true + + + + + + + + + + + + diff --git a/src/Components/test/testassets/LazyTestContentPackage/RouteableComponentFromPackage.razor b/src/Components/test/testassets/LazyTestContentPackage/RouteableComponentFromPackage.razor new file mode 100644 index 0000000000..5fd716ac77 --- /dev/null +++ b/src/Components/test/testassets/LazyTestContentPackage/RouteableComponentFromPackage.razor @@ -0,0 +1,6 @@ +@page "/LazyRouteInsidePackage" + +
    + This page will be lazy-loaded. +
    + diff --git a/src/Components/test/testassets/LazyTestContentPackage/_Imports.razor b/src/Components/test/testassets/LazyTestContentPackage/_Imports.razor new file mode 100644 index 0000000000..66ebfa5d18 --- /dev/null +++ b/src/Components/test/testassets/LazyTestContentPackage/_Imports.razor @@ -0,0 +1 @@ +@using Microsoft.AspNetCore.Components.Web diff --git a/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml b/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml index 7e3a70a899..771db99bd3 100644 --- a/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml +++ b/src/Components/test/testassets/TestServer/Pages/PrerenderedHost.cshtml @@ -8,7 +8,7 @@ - + @* So that E2E tests can make assertions about both the prerendered and diff --git a/src/Components/test/testassets/TestServer/PrerenderedStartup.cs b/src/Components/test/testassets/TestServer/PrerenderedStartup.cs index 5b192d91aa..a218cae7f0 100644 --- a/src/Components/test/testassets/TestServer/PrerenderedStartup.cs +++ b/src/Components/test/testassets/TestServer/PrerenderedStartup.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Services; +using Microsoft.JSInterop; namespace TestServer { @@ -22,6 +24,7 @@ namespace TestServer services.AddMvc(); services.AddServerSideBlazor(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); + services.AddSingleton(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs index 674d180c88..e8f1a02d40 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs @@ -37,15 +37,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests var bootJson = ReadBootJsonData(result, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json")); // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; + var lazyAssemblies = bootJson.resources.lazyAssembly; var assemblies = bootJson.resources.assembly; - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); + Assert.NotNull(lazyAssemblies); + Assert.Contains("RazorClassLibrary.dll", lazyAssemblies.Keys); Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); // App assembly should not be lazy loaded - Assert.DoesNotContain("blazorwasm.dll", dynamicAssemblies.Keys); + Assert.DoesNotContain("blazorwasm.dll", lazyAssemblies.Keys); Assert.Contains("blazorwasm.dll", assemblies.Keys); } @@ -75,15 +75,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests var bootJson = ReadBootJsonData(result, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json")); // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; + var lazyAssemblies = bootJson.resources.lazyAssembly; var assemblies = bootJson.resources.assembly; - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); + Assert.NotNull(lazyAssemblies); + Assert.Contains("RazorClassLibrary.dll", lazyAssemblies.Keys); Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); // App assembly should not be lazy loaded - Assert.DoesNotContain("blazorwasm.dll", dynamicAssemblies.Keys); + Assert.DoesNotContain("blazorwasm.dll", lazyAssemblies.Keys); Assert.Contains("blazorwasm.dll", assemblies.Keys); } @@ -113,15 +113,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests var bootJson = ReadBootJsonData(result, Path.Combine(publishDirectory, "wwwroot", "_framework", "blazor.boot.json")); // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; + var lazyAssemblies = bootJson.resources.lazyAssembly; var assemblies = bootJson.resources.assembly; - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); + Assert.NotNull(lazyAssemblies); + Assert.Contains("RazorClassLibrary.dll", lazyAssemblies.Keys); Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); // App assembly should not be lazy loaded - Assert.DoesNotContain("blazorwasm.dll", dynamicAssemblies.Keys); + Assert.DoesNotContain("blazorwasm.dll", lazyAssemblies.Keys); Assert.Contains("blazorwasm.dll", assemblies.Keys); } @@ -151,15 +151,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests var bootJson = ReadBootJsonData(result, Path.Combine(publishDirectory, "wwwroot", "_framework", "blazor.boot.json")); // And that it has been labelled as a dynamic assembly in the boot.json - var dynamicAssemblies = bootJson.resources.dynamicAssembly; + var lazyAssemblies = bootJson.resources.lazyAssembly; var assemblies = bootJson.resources.assembly; - Assert.NotNull(dynamicAssemblies); - Assert.Contains("RazorClassLibrary.dll", dynamicAssemblies.Keys); + Assert.NotNull(lazyAssemblies); + Assert.Contains("RazorClassLibrary.dll", lazyAssemblies.Keys); Assert.DoesNotContain("RazorClassLibrary.dll", assemblies.Keys); // App assembly should not be lazy loaded - Assert.DoesNotContain("blazorwasm.dll", dynamicAssemblies.Keys); + Assert.DoesNotContain("blazorwasm.dll", lazyAssemblies.Keys); Assert.Contains("blazorwasm.dll", assemblies.Keys); } diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/BootJsonData.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/BootJsonData.cs index 0952070c8d..af74b51a24 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/BootJsonData.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/BootJsonData.cs @@ -76,10 +76,10 @@ namespace Microsoft.AspNetCore.Razor.Tasks public Dictionary satelliteResources { get; set; } /// - /// Assembly (.dll) resources that are loaded dynamically during runtime + /// Assembly (.dll) resources that are loaded lazily during runtime /// [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary dynamicAssembly { get; set; } + public ResourceHashesByNameDictionary lazyAssembly { get; set; } } #pragma warning restore IDE1006 // Naming Styles } diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/GenerateBlazorWebAssemblyBootJson.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/GenerateBlazorWebAssemblyBootJson.cs index 4d2c786443..8d1939d06b 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/GenerateBlazorWebAssemblyBootJson.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/GenerateBlazorWebAssemblyBootJson.cs @@ -90,8 +90,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks if (IsLazyLoadedAssembly(fileName)) { - resourceData.dynamicAssembly ??= new ResourceHashesByNameDictionary(); - resourceList = resourceData.dynamicAssembly; + resourceData.lazyAssembly ??= new ResourceHashesByNameDictionary(); + resourceList = resourceData.lazyAssembly; } else if (!string.IsNullOrEmpty(resourceCulture)) {