From 0ca2ed9af69e7e334b8e3c1de1d015017f138988 Mon Sep 17 00:00:00 2001
From: Javier Calvarro Nelson <jacalvar@microsoft.com>
Date: Tue, 17 Aug 2021 10:52:12 +0200
Subject: [PATCH] [Blazor] Custom JS initializers (#34798)

* Users can create an ES6 module with `<packageName>.lib.module.js` and declare a beforeStart and afterStarted function to integrate into the blazor start process.
* In WebAssembly beforeStart receives the BlazorWebAssemblyStartOptions object and any publish extension as part of the method arguments.
* In Server beforeStart receives the CircuitStartOptions object and can configure any necessary details.
* In Desktop beforeStart does not receive any argument for the time being.
* afterStarted receives the Blazor object as argument in all cases.
* Callbacks in beforeStart and afterStarted are not invoked in any defined order and are invoked in parallel.
---
 .../Authorization/src/AuthorizeRouteView.cs   |   5 +
 .../ComponentEndpointConventionBuilder.cs     |   5 +-
 ...ComponentEndpointRouteBuilderExtensions.cs |  10 +-
 ...rcuitJavaScriptInitializationMiddleware.cs |  26 ++
 src/Components/Server/src/CircuitOptions.cs   |   5 +-
 ...ionsJavaScriptInitializersConfiguration.cs |  33 ++
 src/Components/Server/src/ComponentHub.cs     |   1 +
 .../ComponentServiceCollectionExtensions.cs   |   1 +
 ...onentEndpointRouteBuilderExtensionsTest.cs |   7 +-
 .../Web.JS/dist/Release/blazor.server.js      | Bin 128426 -> 129140 bytes
 .../Web.JS/dist/Release/blazor.webview.js     | Bin 39334 -> 40069 bytes
 src/Components/Web.JS/src/Boot.Server.ts      |   6 +-
 src/Components/Web.JS/src/Boot.WebAssembly.ts |  19 +-
 src/Components/Web.JS/src/Boot.WebView.ts     |   4 +
 .../JSInitializers/JSInitializers.Server.ts   |  16 +
 .../JSInitializers.WebAssembly.ts             |  15 +
 .../JSInitializers/JSInitializers.WebView.ts  |  14 +
 .../src/JSInitializers/JSInitializers.ts      |  40 +++
 .../Web.JS/src/Platform/BootConfig.ts         |  34 +-
 .../src/Platform/WebAssemblyStartOptions.ts   |   2 +-
 src/Components/Web.JS/tsconfig.json           |   1 +
 .../src/JSComponents/JSComponentInterop.cs    |   3 +
 .../Web/src/WebEventData/WebEventData.cs      |   2 +
 .../Server/Properties/launchSettings.json     |  16 +-
 .../src/HotReload/HotReloadAgent.cs           |   3 +
 .../PhotinoTestApp/PhotinoTestApp.csproj      |   4 +-
 ...osoft.AspNetCore.Components.WebView.csproj |   8 +-
 .../WebView/src/StaticWebAssetsLoader.cs      | 292 ------------------
 .../WebView/WebView/src/WebViewManager.cs     |  35 +++
 .../WebView/WebView/src/blazor.modules.json   |   1 +
 ...rosoft.AspNetCore.Components.WebView.props |   5 +
 .../JsInitializersTest.cs                     |  24 ++
 .../test/E2ETest/Tests/JsInitializersTest.cs  |  43 +++
 .../wwwroot/BasicTestApp.lib.module.js        |  48 +++
 .../ComponentFromPackage.razor                |  15 +-
 .../ComponentFromPackage.razor.js             |   5 +
 .../src/Microsoft.AspNetCore.Hosting.csproj   |   1 +
 .../StaticWebAssets/StaticWebAssetsLoader.cs  |  63 +---
 .../StaticWebAssets/StaticWebAssetsReader.cs  |  75 -----
 ...anifestStaticWebAssetsFileProviderTests.cs |   5 +-
 .../StaticWebAssetsFileProviderTests.cs       | 210 -------------
 .../StaticWebAssetsLoaderTests.cs             | 116 -------
 .../Infrastructure/ServerFactory.cs           |  59 ----
 .../StaticWebAssetsFileProvider.cs            | 149 +--------
 44 files changed, 448 insertions(+), 978 deletions(-)
 create mode 100644 src/Components/Server/src/CircuitJavaScriptInitializationMiddleware.cs
 create mode 100644 src/Components/Server/src/Circuits/CircuitOptionsJavaScriptInitializersConfiguration.cs
 create mode 100644 src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts
 create mode 100644 src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts
 create mode 100644 src/Components/Web.JS/src/JSInitializers/JSInitializers.WebView.ts
 create mode 100644 src/Components/Web.JS/src/JSInitializers/JSInitializers.ts
 delete mode 100644 src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs
 create mode 100644 src/Components/WebView/WebView/src/blazor.modules.json
 create mode 100644 src/Components/WebView/WebView/src/buildTransitive/any/Microsoft.AspNetCore.Components.WebView.props
 create mode 100644 src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs
 create mode 100644 src/Components/test/E2ETest/Tests/JsInitializersTest.cs
 create mode 100644 src/Components/test/testassets/BasicTestApp/wwwroot/BasicTestApp.lib.module.js
 create mode 100644 src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor.js
 delete mode 100644 src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsReader.cs
 delete mode 100644 src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsFileProviderTests.cs
 delete mode 100644 src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsLoaderTests.cs
 rename src/{Hosting/Hosting/src => Shared}/StaticWebAssets/StaticWebAssetsFileProvider.cs (74%)

diff --git a/src/Components/Authorization/src/AuthorizeRouteView.cs b/src/Components/Authorization/src/AuthorizeRouteView.cs
index ef1fb45dfd9..291da9bf12d 100644
--- a/src/Components/Authorization/src/AuthorizeRouteView.cs
+++ b/src/Components/Authorization/src/AuthorizeRouteView.cs
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics.CodeAnalysis;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Components.Rendering;
@@ -95,6 +96,10 @@ namespace Microsoft.AspNetCore.Components.Authorization
             builder.CloseComponent();
         }
 
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:RequiresUnreferencedCode",
+            Justification = "OpenComponent already has the right set of attributes")]
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2110:RequiresUnreferencedCode",
+            Justification = "OpenComponent already has the right set of attributes")]
         private void RenderContentInDefaultLayout(RenderTreeBuilder builder, RenderFragment content)
         {
             builder.OpenComponent<LayoutView>(0);
diff --git a/src/Components/Server/src/Builder/ComponentEndpointConventionBuilder.cs b/src/Components/Server/src/Builder/ComponentEndpointConventionBuilder.cs
index 66c8c260e8c..e8f40cb25a7 100644
--- a/src/Components/Server/src/Builder/ComponentEndpointConventionBuilder.cs
+++ b/src/Components/Server/src/Builder/ComponentEndpointConventionBuilder.cs
@@ -12,11 +12,13 @@ namespace Microsoft.AspNetCore.Builder
     {
         private readonly IEndpointConventionBuilder _hubEndpoint;
         private readonly IEndpointConventionBuilder _disconnectEndpoint;
+        private readonly IEndpointConventionBuilder _jsInitializersEndpoint;
 
-        internal ComponentEndpointConventionBuilder(IEndpointConventionBuilder hubEndpoint, IEndpointConventionBuilder disconnectEndpoint)
+        internal ComponentEndpointConventionBuilder(IEndpointConventionBuilder hubEndpoint, IEndpointConventionBuilder disconnectEndpoint, IEndpointConventionBuilder jsInitializersEndpoint)
         {
             _hubEndpoint = hubEndpoint;
             _disconnectEndpoint = disconnectEndpoint;
+            _jsInitializersEndpoint = jsInitializersEndpoint;
         }
 
         /// <summary>
@@ -27,6 +29,7 @@ namespace Microsoft.AspNetCore.Builder
         {
             _hubEndpoint.Add(convention);
             _disconnectEndpoint.Add(convention);
+            _jsInitializersEndpoint.Add(convention);
         }
     }
 }
diff --git a/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs b/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs
index 2b021d4aa08..aebe39281f0 100644
--- a/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs
+++ b/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs
@@ -3,9 +3,12 @@
 
 using System;
 using Microsoft.AspNetCore.Components.Server;
+using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http.Connections;
 using Microsoft.AspNetCore.Routing;
 using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
 
 namespace Microsoft.AspNetCore.Builder
 {
@@ -110,7 +113,12 @@ namespace Microsoft.AspNetCore.Builder
                 endpoints.CreateApplicationBuilder().UseMiddleware<CircuitDisconnectMiddleware>().Build())
                 .WithDisplayName("Blazor disconnect");
 
-            return new ComponentEndpointConventionBuilder(hubEndpoint, disconnectEndpoint);
+            var jsInitializersEndpoint = endpoints.Map(
+                (path.EndsWith('/') ? path : path + "/") + "initializers/",
+                endpoints.CreateApplicationBuilder().UseMiddleware<CircuitJavaScriptInitializationMiddleware>().Build())
+                .WithDisplayName("Blazor initializers");
+
+            return new ComponentEndpointConventionBuilder(hubEndpoint, disconnectEndpoint, jsInitializersEndpoint);
         }
     }
 }
diff --git a/src/Components/Server/src/CircuitJavaScriptInitializationMiddleware.cs b/src/Components/Server/src/CircuitJavaScriptInitializationMiddleware.cs
new file mode 100644
index 00000000000..845f65308ea
--- /dev/null
+++ b/src/Components/Server/src/CircuitJavaScriptInitializationMiddleware.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.AspNetCore.Components.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    internal class CircuitJavaScriptInitializationMiddleware
+    {
+        private readonly IList<string> _initializers;
+
+        // We don't need the request delegate for anything, however we need to inject it to satisfy the middleware
+        // contract.
+        public CircuitJavaScriptInitializationMiddleware(IOptions<CircuitOptions> options, RequestDelegate _)
+        {
+            _initializers = options.Value.JavaScriptInitializers;
+        }
+
+        public async Task InvokeAsync(HttpContext context)
+        {
+            await context.Response.WriteAsJsonAsync(_initializers);
+        }
+    }
+}
diff --git a/src/Components/Server/src/CircuitOptions.cs b/src/Components/Server/src/CircuitOptions.cs
index 1d6323e53f3..2eb51b174f4 100644
--- a/src/Components/Server/src/CircuitOptions.cs
+++ b/src/Components/Server/src/CircuitOptions.cs
@@ -1,8 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using Microsoft.AspNetCore.Components.Web;
+using Microsoft.Extensions.Hosting;
 
 namespace Microsoft.AspNetCore.Components.Server
 {
@@ -82,5 +81,7 @@ namespace Microsoft.AspNetCore.Components.Server
         /// Gets options for root components within the circuit.
         /// </summary>
         public CircuitRootComponentOptions RootComponents { get; } = new CircuitRootComponentOptions();
+
+        internal IList<string> JavaScriptInitializers { get; } = new List<string>();
     }
 }
diff --git a/src/Components/Server/src/Circuits/CircuitOptionsJavaScriptInitializersConfiguration.cs b/src/Components/Server/src/Circuits/CircuitOptionsJavaScriptInitializersConfiguration.cs
new file mode 100644
index 00000000000..e1e70427b9a
--- /dev/null
+++ b/src/Components/Server/src/Circuits/CircuitOptionsJavaScriptInitializersConfiguration.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.Json;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Components.Server.Circuits
+{
+    internal class CircuitOptionsJavaScriptInitializersConfiguration : IConfigureOptions<CircuitOptions>
+    {
+        private readonly IWebHostEnvironment _environment;
+
+        public CircuitOptionsJavaScriptInitializersConfiguration(IWebHostEnvironment environment)
+        {
+            _environment = environment;
+        }
+
+        public void Configure(CircuitOptions options)
+        {
+            var file = _environment.WebRootFileProvider.GetFileInfo($"{_environment.ApplicationName}.modules.json");
+            if (file.Exists)
+            {
+                var initializers = JsonSerializer.Deserialize<string[]>(file.CreateReadStream());
+                for (var i = 0; i < initializers.Length; i++)
+                {
+                    var initializer = initializers[i];
+                    options.JavaScriptInitializers.Add(initializer);
+                }
+            }
+        }
+    }
+}
diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs
index bb802199a7a..256b3f2e787 100644
--- a/src/Components/Server/src/ComponentHub.cs
+++ b/src/Components/Server/src/ComponentHub.cs
@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.SignalR;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 
 namespace Microsoft.AspNetCore.Components.Server
 {
diff --git a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs
index 3a7cd4ce842..43bd3c85c99 100644
--- a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs
+++ b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs
@@ -83,6 +83,7 @@ namespace Microsoft.Extensions.DependencyInjection
             services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
 
             services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
+            services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJavaScriptInitializersConfiguration>());
 
             if (configure != null)
             {
diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
index 7a6e892fc50..977228551a2 100644
--- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
@@ -2,10 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Diagnostics;
-using System.Threading.Tasks;
 using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
 using Microsoft.Extensions.Hosting;
 using Moq;
 using Xunit;
@@ -54,6 +55,9 @@ namespace Microsoft.AspNetCore.Components.Server.Tests
 
         private IApplicationBuilder CreateAppBuilder()
         {
+            var environment = new Mock<IWebHostEnvironment>();
+            environment.SetupGet(e => e.ApplicationName).Returns("app");
+            environment.SetupGet(e => e.WebRootFileProvider).Returns(new NullFileProvider());
             var services = new ServiceCollection();
             services.AddSingleton(Mock.Of<IHostApplicationLifetime>());
             services.AddLogging();
@@ -64,6 +68,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests
             services.AddRouting();
             services.AddSignalR();
             services.AddServerSideBlazor();
+            services.AddSingleton(environment.Object);
             services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
 
             var serviceProvider = services.BuildServiceProvider();
diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js
index c09fced56bd940af437ddc21d82efdbfb09fdc67..c591e6ab9a26d8f37d612d638d25e070b9171502 100644
GIT binary patch
delta 1376
zcmaJ>U1%It6y_u=*=j3-q4-ywjLULwyiO8D)ZNb3Y-=^C5iz!=wyBw&z1zKZ_D*u=
zZi3m2`yfaosSobMfC#Np6r{C<JZvdahz~*$AF80~lT_NcX`$r7`rr?GcV^e-p>-bS
z+_~qTbH4MP@6H$3x_(>hI(^lIMD3c{1KGt*nGf!U#Nx*<{jyPgRrmHMl<Tzre(jY1
z@Z#7zvAEhDfA23)XWlqmh$-jN!l#`HifTVEY^l9_WJLKd9Fg}b|ISZ}oywd#J_?Yn
zUAnZncI-s_!_OaF-1=GWG37XV;l6{AsGoTT?&yGQa4rQO-Jg|aq0DW`T#hJ_xFV=5
z3y!k&sWj|WdFtP8g)W7u{=)=JBV~u@;r!Doyn6}GDD&MCjBZSfo04Ra#F%93<ZC$J
ztq%VFBWzNzgV-`?@oX!7aPlX3D0p!hJeB&ZW%&NCs_j^wr_J(;?Q&0wvMpVKa77lG
zXIKSE#i+D|r1|ZZ<K!&+kZ0~2_bu;jZfmSG=?Xc-nPis3W~uN7oBL5G){MoZ_KI*z
z%%g@1M${--lL%2W6NQJJ$D`Ic4*t9dTWZfa8>%_Wqq|=nN=IC1(x5!=z0PD2>3urh
zHj$`EKT)CnL@HeNg=-&iOk6Xn8UaHBr&$3_yDYEuo2KdFC}TS8w34F*SJ1G#lt*s1
zTBZ3E^G&y1pCywD|E-uXCd*zCF-*K|9x@C=;#5@3M@{+&#p-Eu#i!jvbnsSb5XF^s
zZ0&E+b^8fKJW4YoRcNz>?@*}?bF*iFOf}!>bp$JZwLNP5xT3h23hI|(0H%V~%W%AQ
zZf0}s?8|pnqv8cBZAI~$ImeoHMIT!?R-IHzR2JQQN`K+`*K}eFny0)ubuXneZad{X
zRlAm5q$!=dkB2MW$Aq^AB8|5^C2>rI$?1$zTbtZw>0YS`<91r&WFH+@af(v)4i>J!
zvmk<RuE2;g-77EzZg5})devQ+UxBmxJu|^k-nGjm1rQS!*4x36OT4)`UB|>~LE6QU
zB!aL|BRwtM7+zEC+nKP-Od<=n6r+V>6~WP8;fVpeNpL73%`p)+GfiT9Y#d!6jfNF>
zx}vnhk`|FB3pyI`DA+-=0U2;=2M#_ER2vWnCpg@Ifx%(ks`6i2Zqm8A2+>WHt=rHB
zDyV|MsVxx?{%Am-VxxBzzEtMoDrBA{++hEsxZSj^%l7X_+f_$b;h)afFi$3fzZ;+j
uBiG@nuBNK`$yj~<Iutr!tiHAekHlasxaS7+!=7O9225;F^1?nwIQ=gN3h_Ds

delta 720
zcmZ8eT}TvB6z1sKuBky}nUytbgWOv~>_(Ba&IXh8vzt$XWMnvvvo>INa^|kmx?6-0
z6&leHmqO@=D3C;9A4Zl4>`kJ;f|6ds9&Fw9AuFnfpzgBTgD>~H_k8Dk=ey_p`05?a
zdna--C{AAXgP5)g-&_YudgR3CO8&as9uPS-k-FiGr?#h0Uh?>OwBza@;5nzR#XOvx
z8hTW!=*amrRObv1w{U)Dm~P<wk%#9>IgCGU1rVLNxlPW4XTIA{x2Bt(oZZcd`!k!n
zL2)M=p{xW%_TnJis}-pnixZ6+CPBJH@qne%xJ8iY_J*LH>$tC)z{@?k?>eCmag1Dt
z=>xpHdI}~vyq<#AN~J@ll87kAlITh=VY-?p{(KKr+&1&fg5VRE<=NN=*v5{{g3YC$
z&%)aZ+@n(=L1ceWiWJU}YF{Lpu;NPS<&}8Ah@l9(GY`SCLQ=pPIbO%I^DxNvXQ2@Y
zBUxzSaAgrTvY9OG1B<O$fL+{RZ~-!>O<5tR%hclMbwaUZ%S7)NnhCNbGnuTf$C4qd
z{i;EQHiD=`BpZi#M}IM9@3E<B(ZhzN#SMxKjEh4O`b?usAna%k_SUEc>zYYyDrhpo
za=2jGud-V?s0Evi=3pnN>`e~BpgCu{x0J_8kEwRmOBxI0A=sQC%c}onELl$`SG!v7
zykgAKc}t>e6j}HXVi&Uf`<Xn{tc>DI9_|+J;q|FigKcxaEyCYYPl7Zw6f4&*f#NN!
mA|OTG)+LCQK-A5AhaDb>vX8$Y0KKf@H*}Wso!}HkIP?$s;sO@{

diff --git a/src/Components/Web.JS/dist/Release/blazor.webview.js b/src/Components/Web.JS/dist/Release/blazor.webview.js
index 0b09560ad7a2b07bb43f60a3fa2265f6f8f4293c..ab6e66d75bc964ee375effa58458cef2c5c26286 100644
GIT binary patch
delta 1150
zcmaJ>O-vI(6izWQsa_xkDHxM=MVKYa0unE6wg_SjhbBZt2`wqZc4`;4GtJBt0?nqS
zQhtI_k~ye}2QGTXR1d}@7vsT;i3h!V@#59!!P(t5NCNKR<=gkYpLyS#-B<ON+Pjwh
zH}TMdcpD#TPXMeVoohOFvqfxin_wkT{~bSbA(RlOXQo4mdV1z{NJh7%)}%|MMkM~6
z+LT0cc1#st%jd*?xldBx%J-zbT6rq%$I34mwpZ;HKh`cb&|p<<pw;RXA0?&zQ!PlF
zfA~_`-CBp}TYuC*pVm7i+S}+7Ewza6i-_Tx(m+n_vX5T)wnL0>bV=kzuWa=AXxe8Y
zqHWV-c54wazB%eMW8%YRP<u>Nw!)%+tF_^;Y#qk-`P}!disT+_n|_V+_4nJvQ^Pyz
z!A_@0?o0-6^@E+Wr^=QC8B-`PSp+jqixwvoXeFMrnE|sL(K{Scjxsl(<IF>Ak(tR^
z7qS&>DR#a<D4)PKx1nRNBFaWRKk&9wg3Hk6$|NOun;}Cc17zf30RUvigLHp;t#W-b
z0c$~jg^JeQc5&Sam**ivQ@1B#6!9X(3O5kWu=_U80d+vtM&`Op-0dzQcP{F~0Ux^n
zYdSH5=E3;@)naxQED<}S44S4%w4h?lAGkD+vV<ak$SBkgCoZE*l({Bp_Gdjka*X2y
zF$zVN15DH52n2><a4i}%bAhL#jx_l+CBsW(5se;K2B0INO*K?Ic@cq}+>CRX>a}nk
z#1+ELOGB}L6!yLk$mL3<2t@pRJXH2V27~bx-q7l3f{ganQBZ@<B5q0H(^(4hXqnK(
zfq4h65^Cg0rsyDMEHHvqy_82hM>0|M*3G-BZc&s$*t;Aos@m9ciWwxmX=vq8RK=v<
z@{sCkdh-;>uLI<_ZDO=6Z)H>cV4>JJ>B@0EIqUrqfEEMw-Y3_>Ax)&7eHqq+L~hvs
Z5k8GHH;oMDU33!)^Z8uZ6U^0QwZBDStDFD;

delta 416
zcmZqO$+T=U(}pc}jJ}h%+8K&wY2@jo>Xc~e<khB?<|UV8=I1G7ZT@Y?CC!*MnJp@U
zF>7;t)J8_2YQdOBpwQ$Pdm#IAOgm5_I@U>P@~wK|$s6jmfKs>X!+@kw!*n2ds^KS4
z*@7nZ$*&t_p)7+YB`B+^NfpeB29kf83V>v8^HLx=p;dgcR!a|*b*@Dc$Xd`QHJPbZ
z0xTvm*|=2!%F1a~2D9dXN%6@pZBjrMNOfhK8kiLUmXVk&*bb7J&?+(6rQH!M<23n1
zJH%|K$qgNxlXW`;pyCZ32(?;Zxn~^>K)LizTd)~on|F8SgF*x3)WhA9lcT!>AiT|o
zySbTJb5ctbiY9NIAi|knqM=!xnWj-R`P>9UwxSYSMFXA9cPE;1P8Obhjk`8JRj;@t
T5h$ELS!PZGSaq$YhNd+Dc-Egn

diff --git a/src/Components/Web.JS/src/Boot.Server.ts b/src/Components/Web.JS/src/Boot.Server.ts
index 5e23268a67d..9f5fc9e608b 100644
--- a/src/Components/Web.JS/src/Boot.Server.ts
+++ b/src/Components/Web.JS/src/Boot.Server.ts
@@ -13,6 +13,7 @@ import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnect
 import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
 import { discoverComponents, discoverPersistedState, ServerComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
 import { sendJSDataStream } from './Platform/Circuits/CircuitStreamingInterop';
+import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.Server';
 
 let renderingFailed = false;
 let started = false;
@@ -25,6 +26,8 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
 
   // Establish options to be used
   const options = resolveOptions(userOptions);
+  const jsInitializer = await fetchAndInvokeInitializers(options);
+
   const logger = new ConsoleLogger(options.logLevel);
   Blazor.defaultReconnectionHandler = new DefaultReconnectionHandler(logger);
 
@@ -35,7 +38,6 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
   const appState = discoverPersistedState(document);
   const circuit = new CircuitDescriptor(components, appState || '');
 
-
   const initialConnection = await initializeConnection(options, logger, circuit);
   const circuitStarted = await circuit.startCircuit(initialConnection);
   if (!circuitStarted) {
@@ -77,6 +79,8 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
   Blazor.reconnect = reconnect;
 
   logger.log(LogLevel.Information, 'Blazor server-side application started.');
+
+  jsInitializer.invokeAfterStartedCallbacks(Blazor);
 }
 
 async function initializeConnection(options: CircuitStartOptions, logger: Logger, circuit: CircuitDescriptor): Promise<HubConnection> {
diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts
index 13b6f2b4432..76e103a1f12 100644
--- a/src/Components/Web.JS/src/Boot.WebAssembly.ts
+++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts
@@ -14,6 +14,8 @@ import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
 import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
 import { discoverComponents, discoverPersistedState, WebAssemblyComponentDescriptor } from './Services/ComponentDescriptorDiscovery';
 import { setDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods';
+import { AfterBlazorStartedCallback, JSInitializer } from './JSInitializers/JSInitializers';
+import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebAssembly';
 
 declare var Module: EmscriptenModule;
 let started = false;
@@ -80,11 +82,13 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
     );
   });
 
-  // Get the custom environment setting if defined
-  const environment = options?.environment;
+  const candidateOptions = options ?? {};
+
+  // Get the custom environment setting and blazorBootJson loader if defined
+  const environment = candidateOptions.environment;
 
   // Fetch the resources and prepare the Mono runtime
-  const bootConfigPromise = BootConfigResult.initAsync(environment);
+  const bootConfigPromise = BootConfigResult.initAsync(candidateOptions.loadBootResource, environment);
 
   // Leverage the time while we are loading boot.config.json from the network to discover any potentially registered component on
   // the document.
@@ -110,9 +114,11 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
     }
   };
 
-  const bootConfigResult = await bootConfigPromise;
+  const bootConfigResult: BootConfigResult = await bootConfigPromise;
+  const jsInitializer = await fetchAndInvokeInitializers(bootConfigResult.bootConfig, candidateOptions);
+
   const [resourceLoader] = await Promise.all([
-    WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, options || {}),
+    WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, candidateOptions || {}),
     WebAssemblyConfigLoader.initAsync(bootConfigResult)]);
 
   try {
@@ -123,6 +129,9 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
 
   // Start up the application
   platform.callEntryPoint(resourceLoader.bootConfig.entryAssembly);
+  // At this point .NET has been initialized (and has yielded), we can't await the promise becasue it will
+  // only end when the app finishes running
+  jsInitializer.invokeAfterStartedCallbacks(Blazor);
 }
 
 function invokeJSFromDotNet(callInfo: Pointer, arg0: any, arg1: any, arg2: any): any {
diff --git a/src/Components/Web.JS/src/Boot.WebView.ts b/src/Components/Web.JS/src/Boot.WebView.ts
index 0b5a5f17767..b7740928550 100644
--- a/src/Components/Web.JS/src/Boot.WebView.ts
+++ b/src/Components/Web.JS/src/Boot.WebView.ts
@@ -4,6 +4,7 @@ import { shouldAutoStart } from './BootCommon';
 import { internalFunctions as navigationManagerFunctions } from './Services/NavigationManager';
 import { startIpcReceiver } from './Platform/WebView/WebViewIpcReceiver';
 import { sendAttachPage, sendBeginInvokeDotNetFromJS, sendEndInvokeJSFromDotNet, sendByteArray, sendLocationChanged } from './Platform/WebView/WebViewIpcSender';
+import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebView';
 
 let started = false;
 
@@ -13,6 +14,8 @@ async function boot(): Promise<void> {
   }
   started = true;
 
+  const jsInitializer = await fetchAndInvokeInitializers();
+
   startIpcReceiver();
 
   DotNet.attachDispatcher({
@@ -25,6 +28,7 @@ async function boot(): Promise<void> {
   navigationManagerFunctions.listenForNavigationEvents(sendLocationChanged);
 
   sendAttachPage(navigationManagerFunctions.getBaseURI(), navigationManagerFunctions.getLocationHref());
+  await jsInitializer.invokeAfterStartedCallbacks(Blazor);
 }
 
 Blazor.start = boot;
diff --git a/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts b/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts
new file mode 100644
index 00000000000..44a94169014
--- /dev/null
+++ b/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts
@@ -0,0 +1,16 @@
+import { BootJsonData } from "../Platform/BootConfig";
+import { CircuitStartOptions } from "../Platform/Circuits/CircuitStartOptions";
+import { JSInitializer } from "./JSInitializers";
+
+export async function fetchAndInvokeInitializers(options: Partial<CircuitStartOptions>) : Promise<JSInitializer> {
+  const jsInitializersResponse = await fetch('_blazor/initializers', {
+    method: 'GET',
+    credentials: 'include',
+    cache: 'no-cache'
+  });
+
+  const initializers: string[] = await jsInitializersResponse.json();
+  const jsInitializer = new JSInitializer();
+  await jsInitializer.importInitializersAsync(initializers, [options]);
+  return jsInitializer;
+}
diff --git a/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts b/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts
new file mode 100644
index 00000000000..23127e0b662
--- /dev/null
+++ b/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts
@@ -0,0 +1,15 @@
+import { BootJsonData } from "../Platform/BootConfig";
+import { WebAssemblyStartOptions } from "../Platform/WebAssemblyStartOptions";
+import { JSInitializer } from "./JSInitializers";
+
+export async function fetchAndInvokeInitializers(bootConfig: BootJsonData, options: Partial<WebAssemblyStartOptions>) : Promise<JSInitializer> {
+  const initializers = bootConfig.resources.libraryInitializers;
+  const jsInitializer = new JSInitializer();
+  if (initializers) {
+    await jsInitializer.importInitializersAsync(
+      Object.keys(initializers),
+      [options, bootConfig.resources.extensions]);
+  }
+
+  return jsInitializer;
+}
diff --git a/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebView.ts b/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebView.ts
new file mode 100644
index 00000000000..1cfd05092c4
--- /dev/null
+++ b/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebView.ts
@@ -0,0 +1,14 @@
+import { JSInitializer } from "./JSInitializers";
+
+export async function fetchAndInvokeInitializers() : Promise<JSInitializer> {
+  const jsInitializersResponse = await fetch('_framework/blazor.modules.json', {
+    method: 'GET',
+    credentials: 'include',
+    cache: 'no-cache'
+  });
+
+  const initializers: string[] = await jsInitializersResponse.json();
+  const jsInitializer = new JSInitializer();
+  await jsInitializer.importInitializersAsync(initializers, []);
+  return jsInitializer;
+}
diff --git a/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts b/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts
new file mode 100644
index 00000000000..da0634357ff
--- /dev/null
+++ b/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts
@@ -0,0 +1,40 @@
+import { Blazor } from '../GlobalExports';
+
+type BeforeBlazorStartedCallback = (...args: unknown[]) => Promise<void>;
+export type AfterBlazorStartedCallback = (blazor: typeof Blazor) => Promise<void>;
+type BlazorInitializer = { beforeStart: BeforeBlazorStartedCallback, afterStarted: AfterBlazorStartedCallback };
+
+export class JSInitializer {
+  private afterStartedCallbacks: AfterBlazorStartedCallback[] = [];
+
+  async importInitializersAsync(initializerFiles: string[], initializerArguments: unknown[]): Promise<void> {
+    await Promise.all(initializerFiles.map(f => importAndInvokeInitializer(this, f)));
+
+    function adjustPath(path: string): string {
+      // This is the same we do in JS interop with the import callback
+      const base = document.baseURI;
+      path = base.endsWith('/') ? `${base}${path}` : `${base}/${path}`;
+      return path;
+    }
+
+    async function importAndInvokeInitializer(jsInitializer: JSInitializer, path: string): Promise<void> {
+      const adjustedPath = adjustPath(path);
+      const initializer = await import(/* webpackIgnore: true */ adjustedPath) as Partial<BlazorInitializer>;
+      if (initializer === undefined) {
+        return;
+      }
+      const { beforeStart: beforeStart, afterStarted: afterStarted } = initializer;
+      if (afterStarted) {
+        jsInitializer.afterStartedCallbacks.push(afterStarted);
+      }
+
+      if (beforeStart) {
+        return beforeStart(...initializerArguments);
+      }
+    }
+  }
+
+  async invokeAfterStartedCallbacks(blazor: typeof Blazor) {
+    await Promise.all(this.afterStartedCallbacks.map(callback => callback(blazor)));
+  }
+}
diff --git a/src/Components/Web.JS/src/Platform/BootConfig.ts b/src/Components/Web.JS/src/Platform/BootConfig.ts
index 6a0079e5f16..55ad90380d0 100644
--- a/src/Components/Web.JS/src/Platform/BootConfig.ts
+++ b/src/Components/Web.JS/src/Platform/BootConfig.ts
@@ -1,13 +1,20 @@
+import { WebAssemblyBootResourceType } from './WebAssemblyStartOptions';
+
+type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) =>
+  string | Promise<Response> | null | undefined;
+
 export class BootConfigResult {
   private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) {
   }
 
-  static async initAsync(environment?: string): Promise<BootConfigResult> {
-    const bootConfigResponse = await fetch('_framework/blazor.boot.json', {
-      method: 'GET',
-      credentials: 'include',
-      cache: 'no-cache'
-    });
+  static async initAsync(loadBootResource?: LoadBootResourceCallback, environment?: string): Promise<BootConfigResult> {
+    const loaderResponse = loadBootResource !== undefined ?
+      loadBootResource('manifest', 'blazor.boot.json', '_framework/blazor.boot.json', '') :
+      defaultLoadBlazorBootJson('_framework/blazor.boot.json');
+
+    const bootConfigResponse = loaderResponse instanceof Promise ?
+      await loaderResponse :
+      await defaultLoadBlazorBootJson(loaderResponse ?? '_framework/blazor.boot.json');
 
     // While we can expect an ASP.NET Core hosted application to include the environment, other
     // hosts may not. Assume 'Production' in the absence of any specified value.
@@ -16,7 +23,16 @@ export class BootConfigResult {
     bootConfig.modifiableAssemblies = bootConfigResponse.headers.get('DOTNET-MODIFIABLE-ASSEMBLIES');
 
     return new BootConfigResult(bootConfig, applicationEnvironment);
+
+    async function defaultLoadBlazorBootJson(url: string) : Promise<Response> {
+      return fetch(url, {
+        method: 'GET',
+        credentials: 'include',
+        cache: 'no-cache'
+      });
+    }
   };
+
 }
 
 // Keep in sync with bootJsonData from the BlazorWebAssemblySDK
@@ -34,12 +50,16 @@ export interface BootJsonData {
   modifiableAssemblies: string | null;
 }
 
+export type BootJsonDataExtension = { [extensionName: string]: ResourceList };
+
 export interface ResourceGroups {
   readonly assembly: ResourceList;
   readonly lazyAssembly: ResourceList;
   readonly pdb?: ResourceList;
   readonly runtime: ResourceList;
-  readonly satelliteResources?: { [cultureName: string] : ResourceList };
+  readonly satelliteResources?: { [cultureName: string]: ResourceList };
+  readonly libraryInitializers?: ResourceList,
+  readonly extensions?: BootJsonDataExtension
 }
 
 export type ResourceList = { [name: string]: string };
diff --git a/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts b/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts
index ffacdbb11f4..c48a5100a5d 100644
--- a/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts
+++ b/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts
@@ -24,4 +24,4 @@ export interface WebAssemblyStartOptions {
 // This type doesn't have to align with anything in BootConfig.
 // Instead, this represents the public API through which certain aspects
 // of boot resource loading can be customized.
-export type WebAssemblyBootResourceType = 'assembly' | 'pdb' | 'dotnetjs' | 'dotnetwasm' | 'globalization';
+export type WebAssemblyBootResourceType = 'assembly' | 'pdb' | 'dotnetjs' | 'dotnetwasm' | 'globalization' | 'manifest';
diff --git a/src/Components/Web.JS/tsconfig.json b/src/Components/Web.JS/tsconfig.json
index a7813ded591..e1595b625df 100644
--- a/src/Components/Web.JS/tsconfig.json
+++ b/src/Components/Web.JS/tsconfig.json
@@ -6,6 +6,7 @@
     "removeComments": false,
     "sourceMap": true,
     "target": "es2019",
+    "module": "es2020",
     "lib": ["es2019", "dom"],
     "strict": true
   }
diff --git a/src/Components/Web/src/JSComponents/JSComponentInterop.cs b/src/Components/Web/src/JSComponents/JSComponentInterop.cs
index c37466ceac2..1418ea5ab43 100644
--- a/src/Components/Web/src/JSComponents/JSComponentInterop.cs
+++ b/src/Components/Web/src/JSComponents/JSComponentInterop.cs
@@ -3,6 +3,7 @@
 
 using System.Collections.Concurrent;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Reflection.Metadata;
 using System.Text.Json;
@@ -76,6 +77,8 @@ namespace Microsoft.AspNetCore.Components.Web.Infrastructure
         /// <summary>
         /// For framework use only.
         /// </summary>
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "OpenComponent already has the right set of attributes")]
         protected internal void SetRootComponentParameters(int componentId, int parameterCount, JsonElement parametersJson, JsonSerializerOptions jsonOptions)
         {
             // In case the client misreports the number of parameters, impose bounds so we know the amount
diff --git a/src/Components/Web/src/WebEventData/WebEventData.cs b/src/Components/Web/src/WebEventData/WebEventData.cs
index c8fc38f0add..01db2752e5c 100644
--- a/src/Components/Web/src/WebEventData/WebEventData.cs
+++ b/src/Components/Web/src/WebEventData/WebEventData.cs
@@ -59,6 +59,8 @@ namespace Microsoft.AspNetCore.Components.Web
 
         public EventArgs EventArgs { get; }
 
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "We are already using the appropriate overload")]
         private static EventArgs ParseEventArgsJson(
             Renderer renderer,
             JsonSerializerOptions jsonSerializerOptions,
diff --git a/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Properties/launchSettings.json b/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Properties/launchSettings.json
index 6ef6d4adc39..4b10fb01378 100644
--- a/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Properties/launchSettings.json
+++ b/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Properties/launchSettings.json
@@ -8,13 +8,6 @@
     }
   },
   "profiles": {
-    "IIS Express": {
-      "commandName": "IISExpress",
-      "launchBrowser": true,
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      }
-    },
     "HostedBlazorWebassemblyApp.Server": {
       "commandName": "Project",
       "launchBrowser": true,
@@ -22,6 +15,13 @@
         "ASPNETCORE_ENVIRONMENT": "Development"
       },
       "applicationUrl": "https://localhost:5001;http://localhost:5000"
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs
index db2817a9542..fcf1c19a2f9 100644
--- a/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs
+++ b/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs
@@ -6,6 +6,7 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Reflection;
 using System.Reflection.Metadata;
@@ -217,6 +218,8 @@ namespace Microsoft.Extensions.HotReload
             }
         }
 
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "OpenComponent already has the right set of attributes")]
         private Type[] GetMetadataUpdateTypes(IReadOnlyList<UpdateDelta> deltas)
         {
             List<Type>? types = null;
diff --git a/src/Components/WebView/Samples/PhotinoPlatform/testassets/PhotinoTestApp/PhotinoTestApp.csproj b/src/Components/WebView/Samples/PhotinoPlatform/testassets/PhotinoTestApp/PhotinoTestApp.csproj
index b9bdce6a5ea..2e581d2a73f 100644
--- a/src/Components/WebView/Samples/PhotinoPlatform/testassets/PhotinoTestApp/PhotinoTestApp.csproj
+++ b/src/Components/WebView/Samples/PhotinoPlatform/testassets/PhotinoTestApp/PhotinoTestApp.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk.Razor">
+<Project Sdk="Microsoft.NET.Sdk.Razor">
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -7,6 +7,8 @@
     <SignAssembly>false</SignAssembly>
   </PropertyGroup>
 
+  <Import Project="..\..\..\..\WebView\src\buildTransitive\any\Microsoft.AspNetCore.Components.WebView.props" />
+
   <ItemGroup>
     <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components.WebView.Photino.csproj" />
     <ProjectReference Include="..\..\..\..\..\test\testassets\BasicTestApp\BasicTestApp.csproj" />
diff --git a/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj b/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj
index 075ed8d99f9..89f647db6b4 100644
--- a/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj
+++ b/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -26,11 +26,16 @@
     <Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" LinkBase="Shared" />
     <Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)LinkerFlags.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)StaticWebAssets\**\*.cs" LinkBase="Shared" />
     <Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="Shared" />
     <Compile Include="$(ComponentsSharedSourceRoot)src\RenderBatchWriter.cs" LinkBase="Shared" />
     <Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilderMemoryStream.cs" LinkBase="Shared" />
   </ItemGroup>
 
+  <ItemGroup>
+    <None Include="buildTransitive\any\Microsoft.AspNetCore.Components.WebView.props" Pack="true" PackagePath="%(Identity)" />
+  </ItemGroup>
+
   <ItemGroup>
     <Reference Include="Microsoft.AspNetCore.Components.Web" />
     <Reference Include="Microsoft.Extensions.Configuration.Json" />
@@ -72,6 +77,7 @@
 
   <Target Name="_AddEmbeddedBlazorWebView" BeforeTargets="_CalculateEmbeddedFilesManifestInputs" DependsOnTargets="_CheckBlazorWebViewJSPath">
     <ItemGroup>
+      <EmbeddedResource Include="blazor.modules.json" LogicalName="_framework/blazor.modules.json" />
       <EmbeddedResource Include="$(BlazorWebViewJSFile)" LogicalName="_framework/$(BlazorWebViewJSFilename)" />
       <EmbeddedResource Include="$(BlazorWebViewJSFile).map" LogicalName="_framework/$(BlazorWebViewJSFilename).map" Condition="Exists('$(BlazorWebViewJSFile).map')" />
     </ItemGroup>
diff --git a/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs b/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs
deleted file mode 100644
index f1202ba0a9b..00000000000
--- a/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs
+++ /dev/null
@@ -1,292 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Xml.Linq;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Primitives;
-
-namespace Microsoft.AspNetCore.Components.WebView
-{
-    internal class StaticWebAssetsLoader
-    {
-        internal const string StaticWebAssetsManifestName = "Microsoft.AspNetCore.StaticWebAssets.xml";
-
-        internal static IFileProvider UseStaticWebAssets(IFileProvider systemProvider)
-        {
-            using var manifest = GetManifestStream();
-            if (manifest != null)
-            {
-                return UseStaticWebAssetsCore(systemProvider, manifest);
-            }
-            else
-            {
-                return systemProvider;
-            }
-
-            static Stream? GetManifestStream()
-            {
-                try
-                {
-                    var filePath = ResolveRelativeToAssembly();
-
-                    if (filePath != null && File.Exists(filePath))
-                    {
-                        return File.OpenRead(filePath);
-                    }
-                    else
-                    {
-                        // A missing manifest might simply mean that the feature is not enabled, so we simply
-                        // return early. Misconfigurations will be uncommon given that the entire process is automated
-                        // at build time.
-                        return null;
-                    }
-                }
-                catch
-                {
-                    return null;
-                }
-            }
-        }
-
-        internal static IFileProvider UseStaticWebAssetsCore(IFileProvider systemProvider, Stream manifest)
-        {
-            var webRootFileProvider = systemProvider;
-
-            var additionalFiles = StaticWebAssetsReader.Parse(manifest)
-                .Select(cr => new StaticWebAssetsFileProvider(cr.BasePath, cr.Path))
-                .OfType<IFileProvider>() // Upcast so we can insert on the resulting list.
-                .ToList();
-
-            if (additionalFiles.Count == 0)
-            {
-                return systemProvider;
-            }
-            else
-            {
-                additionalFiles.Insert(0, webRootFileProvider);
-                return new CompositeFileProvider(additionalFiles);
-            }
-        }
-
-        private static string? ResolveRelativeToAssembly()
-        {
-            var assembly = Assembly.GetEntryAssembly();
-            if (string.IsNullOrEmpty(assembly?.Location))
-            {
-                return null;
-            }
-
-            var name = Path.GetFileNameWithoutExtension(assembly.Location);
-
-            return Path.Combine(Path.GetDirectoryName(assembly.Location)!, $"{name}.StaticWebAssets.xml");
-        }
-
-        internal static class StaticWebAssetsReader
-        {
-            private const string ManifestRootElementName = "StaticWebAssets";
-            private const string VersionAttributeName = "Version";
-            private const string ContentRootElementName = "ContentRoot";
-
-            internal static IEnumerable<ContentRootMapping> Parse(Stream manifest)
-            {
-                var document = XDocument.Load(manifest);
-                if (!string.Equals(document.Root!.Name.LocalName, ManifestRootElementName, StringComparison.OrdinalIgnoreCase))
-                {
-                    throw new InvalidOperationException($"Invalid manifest format. Manifest root must be '{ManifestRootElementName}'");
-                }
-
-                var version = document.Root.Attribute(VersionAttributeName);
-                if (version == null)
-                {
-                    throw new InvalidOperationException($"Invalid manifest format. Manifest root element must contain a version '{VersionAttributeName}' attribute");
-                }
-
-                if (version.Value != "1.0")
-                {
-                    throw new InvalidOperationException($"Unknown manifest version. Manifest version must be '1.0'");
-                }
-
-                foreach (var element in document.Root.Elements())
-                {
-                    if (!string.Equals(element.Name.LocalName, ContentRootElementName, StringComparison.OrdinalIgnoreCase))
-                    {
-                        throw new InvalidOperationException($"Invalid manifest format. Invalid element '{element.Name.LocalName}'. All {StaticWebAssetsLoader.StaticWebAssetsManifestName} child elements must be '{ContentRootElementName}' elements.");
-                    }
-                    if (!element.IsEmpty)
-                    {
-                        throw new InvalidOperationException($"Invalid manifest format. {ContentRootElementName} can't have content.");
-                    }
-
-                    var basePath = ParseRequiredAttribute(element, "BasePath");
-                    var path = ParseRequiredAttribute(element, "Path");
-                    yield return new ContentRootMapping(basePath, path);
-                }
-            }
-
-            private static string ParseRequiredAttribute(XElement element, string attributeName)
-            {
-                var attribute = element.Attribute(attributeName);
-                if (attribute == null)
-                {
-                    throw new InvalidOperationException($"Invalid manifest format. Missing {attributeName} attribute in '{ContentRootElementName}' element.");
-                }
-                return attribute.Value;
-            }
-
-            internal readonly struct ContentRootMapping
-            {
-                public ContentRootMapping(string basePath, string path)
-                {
-                    BasePath = basePath;
-                    Path = path;
-                }
-
-                public string BasePath { get; }
-                public string Path { get; }
-            }
-        }
-
-        internal class StaticWebAssetsFileProvider : IFileProvider
-        {
-            private static readonly StringComparison FilePathComparison = OperatingSystem.IsWindows() ?
-                StringComparison.OrdinalIgnoreCase :
-                StringComparison.Ordinal;
-
-            public StaticWebAssetsFileProvider(string pathPrefix, string contentRoot)
-            {
-                BasePath = NormalizePath(pathPrefix);
-                InnerProvider = new PhysicalFileProvider(contentRoot);
-            }
-
-            public PhysicalFileProvider InnerProvider { get; }
-
-            public PathString BasePath { get; }
-
-            /// <inheritdoc />
-            public IDirectoryContents GetDirectoryContents(string subpath)
-            {
-                var modifiedSub = NormalizePath(subpath);
-
-                if (BasePath == "/")
-                {
-                    return InnerProvider.GetDirectoryContents(modifiedSub);
-                }
-
-                if (StartsWithBasePath(modifiedSub, out var physicalPath))
-                {
-                    return InnerProvider.GetDirectoryContents(physicalPath.Value);
-                }
-                else if (string.Equals(subpath, string.Empty) || string.Equals(modifiedSub, "/"))
-                {
-                    return new StaticWebAssetsDirectoryRoot(BasePath);
-                }
-                else if (BasePath.StartsWithSegments(modifiedSub, FilePathComparison, out var remaining))
-                {
-                    return new StaticWebAssetsDirectoryRoot(remaining);
-                }
-
-                return NotFoundDirectoryContents.Singleton;
-            }
-
-            /// <inheritdoc />
-            public IFileInfo GetFileInfo(string subpath)
-            {
-                var modifiedSub = NormalizePath(subpath);
-
-                if (BasePath == "/")
-                {
-                    return InnerProvider.GetFileInfo(subpath);
-                }
-
-                if (!StartsWithBasePath(modifiedSub, out var physicalPath))
-                {
-                    return new NotFoundFileInfo(subpath);
-                }
-                else
-                {
-                    return InnerProvider.GetFileInfo(physicalPath.Value);
-                }
-            }
-
-            /// <inheritdoc />
-            public IChangeToken Watch(string filter)
-            {
-                return InnerProvider.Watch(filter);
-            }
-
-            private static string NormalizePath(string path)
-            {
-                path = path.Replace('\\', '/');
-                return path.StartsWith('/') ? path : "/" + path;
-            }
-
-            private bool StartsWithBasePath(string subpath, out PathString rest)
-            {
-                return new PathString(subpath).StartsWithSegments(BasePath, FilePathComparison, out rest);
-            }
-
-            private class StaticWebAssetsDirectoryRoot : IDirectoryContents
-            {
-                private readonly string _nextSegment;
-
-                public StaticWebAssetsDirectoryRoot(PathString remainingPath)
-                {
-                    // We MUST use the Value property here because it is unescaped.
-                    _nextSegment = remainingPath.Value?.Split("/", StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? string.Empty;
-                }
-
-                public bool Exists => true;
-
-                public IEnumerator<IFileInfo> GetEnumerator()
-                {
-                    return GenerateEnum();
-                }
-
-                IEnumerator IEnumerable.GetEnumerator()
-                {
-                    return GenerateEnum();
-                }
-
-                private IEnumerator<IFileInfo> GenerateEnum()
-                {
-                    return new[] { new StaticWebAssetsFileInfo(_nextSegment) }
-                        .Cast<IFileInfo>().GetEnumerator();
-                }
-
-                private class StaticWebAssetsFileInfo : IFileInfo
-                {
-                    public StaticWebAssetsFileInfo(string name)
-                    {
-                        Name = name;
-                    }
-
-                    public bool Exists => true;
-
-                    public long Length => throw new NotImplementedException();
-
-                    public string PhysicalPath => throw new NotImplementedException();
-
-                    public DateTimeOffset LastModified => throw new NotImplementedException();
-
-                    public bool IsDirectory => true;
-
-                    public string Name { get; }
-
-                    public Stream CreateReadStream()
-                    {
-                        throw new NotImplementedException();
-                    }
-                }
-            }
-        }
-    }
-}
-#nullable restore
diff --git a/src/Components/WebView/WebView/src/WebViewManager.cs b/src/Components/WebView/WebView/src/WebViewManager.cs
index 885473032c2..cc285129f61 100644
--- a/src/Components/WebView/WebView/src/WebViewManager.cs
+++ b/src/Components/WebView/WebView/src/WebViewManager.cs
@@ -4,12 +4,14 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Reflection;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Components.Web;
 using Microsoft.AspNetCore.Components.Web.Infrastructure;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.FileProviders;
 using Microsoft.JSInterop;
+using Microsoft.AspNetCore.StaticWebAssets;
 
 namespace Microsoft.AspNetCore.Components.WebView
 {
@@ -244,5 +246,38 @@ namespace Microsoft.AspNetCore.Components.WebView
             GC.SuppressFinalize(this);
             await DisposeAsyncCore();
         }
+
+        private class StaticWebAssetsLoader
+        {
+            internal static IFileProvider UseStaticWebAssets(IFileProvider fileProvider)
+            {
+                var manifestPath = ResolveRelativeToAssembly();
+                if (File.Exists(manifestPath))
+                {
+                    using var manifestStream = File.OpenRead(manifestPath);
+                    var manifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(manifestStream);
+                    if (manifest.ContentRoots.Length > 0)
+                    {
+                        var manifestProvider = new ManifestStaticWebAssetFileProvider(manifest, (path) => new PhysicalFileProvider(path));
+                        return new CompositeFileProvider(manifestProvider, fileProvider);
+                    }
+                }
+
+                return fileProvider;
+            }
+
+            private static string? ResolveRelativeToAssembly()
+            {
+                var assembly = Assembly.GetEntryAssembly();
+                if (string.IsNullOrEmpty(assembly?.Location))
+                {
+                    return null;
+                }
+
+                var name = Path.GetFileNameWithoutExtension(assembly.Location);
+
+                return Path.Combine(Path.GetDirectoryName(assembly.Location)!, $"{name}.staticwebassets.runtime.json");
+            }
+        }
     }
 }
diff --git a/src/Components/WebView/WebView/src/blazor.modules.json b/src/Components/WebView/WebView/src/blazor.modules.json
new file mode 100644
index 00000000000..fe51488c706
--- /dev/null
+++ b/src/Components/WebView/WebView/src/blazor.modules.json
@@ -0,0 +1 @@
+[]
diff --git a/src/Components/WebView/WebView/src/buildTransitive/any/Microsoft.AspNetCore.Components.WebView.props b/src/Components/WebView/WebView/src/buildTransitive/any/Microsoft.AspNetCore.Components.WebView.props
new file mode 100644
index 00000000000..f43c2047fb3
--- /dev/null
+++ b/src/Components/WebView/WebView/src/buildTransitive/any/Microsoft.AspNetCore.Components.WebView.props
@@ -0,0 +1,5 @@
+<Project>
+  <PropertyGroup>
+    <JSModuleManifestRelativePath>_framework/blazor.modules.json</JSModuleManifestRelativePath>    
+  </PropertyGroup>
+</Project>
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs
new file mode 100644
index 00000000000..d39e70a2d6a
--- /dev/null
+++ b/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using BasicTestApp;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.AspNetCore.Components.E2ETest.Tests;
+using Microsoft.AspNetCore.E2ETesting;
+using OpenQA.Selenium;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
+{
+    public class ServerJsInitializersTest : JsInitializersTest
+    {
+        public ServerJsInitializersTest(
+            BrowserFixture browserFixture,
+            ToggleExecutionModeServerFixture<Program> serverFixture,
+            ITestOutputHelper output)
+            : base(browserFixture, serverFixture.WithServerExecution(), output)
+        {
+        }
+    }
+}
diff --git a/src/Components/test/E2ETest/Tests/JsInitializersTest.cs b/src/Components/test/E2ETest/Tests/JsInitializersTest.cs
new file mode 100644
index 00000000000..e8fc1371596
--- /dev/null
+++ b/src/Components/test/E2ETest/Tests/JsInitializersTest.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using BasicTestApp;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.AspNetCore.E2ETesting;
+using OpenQA.Selenium;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components.E2ETest.Tests
+{
+    public class JsInitializersTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
+    {
+        public JsInitializersTest(
+            BrowserFixture browserFixture,
+            ToggleExecutionModeServerFixture<Program> serverFixture,
+            ITestOutputHelper output)
+            : base(browserFixture, serverFixture, output)
+        {
+        }
+
+        protected override void InitializeAsyncCore()
+        {
+            Navigate(ServerPathBase + "#initializer");
+        }
+
+        [Fact]
+        public void InitializersWork()
+        {
+            Browser.Exists(By.Id("initializer-start"));
+            Browser.Exists(By.Id("initializer-end"));
+        }
+
+        [Fact]
+        public void CanLoadJsModulePackagesFromLibrary()
+        {
+            Browser.MountTestComponent<ExternalContentPackage>();
+            Browser.Equal<string>("Hello from module", () => Browser.Exists(By.CssSelector(".js-module-message > p")).Text);
+        }
+    }
+}
diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/BasicTestApp.lib.module.js b/src/Components/test/testassets/BasicTestApp/wwwroot/BasicTestApp.lib.module.js
new file mode 100644
index 00000000000..d0e8652a3c4
--- /dev/null
+++ b/src/Components/test/testassets/BasicTestApp/wwwroot/BasicTestApp.lib.module.js
@@ -0,0 +1,48 @@
+let runInitializer = false;
+let resourceRequests = [];
+export async function beforeStart(options) {
+    const url = new URL(document.URL);
+    runInitializer = url.hash.indexOf('initializer') !== -1;
+    if (runInitializer) {
+        if (!options.logLevel) {
+            // Simple way of detecting we are in web assembly
+            options.loadBootResource = function (type, name, defaultUri, integrity) {
+                resourceRequests.push([type, name, defaultUri, integrity]);
+                return defaultUri;
+            }
+        }
+        const start = document.createElement('p');
+        start.innerText = 'Before starting';
+        start.style = 'background-color: green; color: white';
+        start.setAttribute('id', 'initializer-start');
+        document.body.appendChild(start);
+    }
+}
+
+export async function afterStarted() {
+    if (runInitializer) {
+        const end = document.createElement('p');
+        end.setAttribute('id', 'initializer-end');
+        end.innerText = 'After started';
+        end.style = 'background-color: pink';
+        document.body.appendChild(end);
+
+        if (resourceRequests.length > 0) {
+
+            const resourceRow = (row) => `<tr><td>${row[0]}</td><td>${row[1]}</td><td>${row[2]}</td><td>${row[3]}</td></tr>`;
+            const rows = resourceRequests.reduce((previewRows, currentRow) => previewRows + resourceRow(currentRow), '');
+
+            const requestTable = document.createElement('table');
+            requestTable.setAttribute('id', 'total-requests');
+            requestTable.innerHTML = `<tr>
+  <th>type</th>
+  <th>name</th>
+  <th>default-uri</th>
+  <th>integrity</th>
+</tr>
+${rows}
+`;
+            document.body.appendChild(requestTable);
+        }
+    }
+}
diff --git a/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor b/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor
index 22c9c43e829..553de9db700 100644
--- a/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor
+++ b/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor
@@ -1,11 +1,24 @@
-<div class="special-style">
+@using Microsoft.JSInterop
+@inject IJSRuntime JS
+
+<div class="special-style">
     This component, including the CSS and image required to produce its
     elegant styling, is in an external NuGet package.
     <button @onclick="ChangeLabel">@buttonLabel </button>
 </div>
 
+<div class="js-module-message">
+</div>
+
 @code {
     string buttonLabel = "Click me";
+    private IJSObjectReference _module = null;
+
+    protected async override Task OnInitializedAsync()
+    {
+        _module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/TestContentPackage/ComponentFromPackage.razor.js");
+        await _module.InvokeVoidAsync("displayMessage", "Hello from module");
+    }
 
     void ChangeLabel()
     {
diff --git a/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor.js b/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor.js
new file mode 100644
index 00000000000..965a355da10
--- /dev/null
+++ b/src/Components/test/testassets/TestContentPackage/ComponentFromPackage.razor.js
@@ -0,0 +1,5 @@
+export function displayMessage(message) {
+    const element = document.createElement("p");
+    element.innerText = message;
+    document.querySelector('.js-module-message').appendChild(element);
+}
diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
index d6478259374..107e1d40c68 100644
--- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
+++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
@@ -14,6 +14,7 @@
     <Compile Include="$(SharedSourceRoot)RazorViews\*.cs" />
     <Compile Include="$(SharedSourceRoot)StackTrace\**\*.cs" />
     <Compile Include="$(SharedSourceRoot)ErrorPage\**\*.cs" />
+    <Compile Include="$(SharedSourceRoot)StaticWebAssets\**\*.cs" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs b/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs
index 483fff518cf..f353900b93e 100644
--- a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs
+++ b/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs
@@ -8,6 +8,7 @@ using System.Linq;
 using System.Reflection;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.FileProviders;
+using Microsoft.AspNetCore.StaticWebAssets;
 
 namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
 {
@@ -16,8 +17,6 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
     /// </summary>
     public class StaticWebAssetsLoader
     {
-        internal const string StaticWebAssetsManifestName = "Microsoft.AspNetCore.StaticWebAssets.xml";
-
         /// <summary>
         /// Configure the <see cref="IWebHostEnvironment"/> to use static web assets.
         /// </summary>
@@ -25,61 +24,34 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
         /// <param name="configuration">The host <see cref="IConfiguration"/>.</param>
         public static void UseStaticWebAssets(IWebHostEnvironment environment, IConfiguration configuration)
         {
-            var (manifest, isJson) = ResolveManifest(environment, configuration);
-            using (manifest)
+            var manifest = ResolveManifest(environment, configuration);
+            if (manifest != null)
             {
-                if (manifest != null)
+                using (manifest)
                 {
-                    UseStaticWebAssetsCore(environment, manifest, isJson);
+                    UseStaticWebAssetsCore(environment, manifest);
                 }
             }
         }
 
-        internal static void UseStaticWebAssetsCore(IWebHostEnvironment environment, Stream manifest, bool isJson)
+        internal static void UseStaticWebAssetsCore(IWebHostEnvironment environment, Stream manifest)
         {
-            if (isJson)
-            {
-                var staticWebAssetManifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(manifest);
-                var provider = new ManifestStaticWebAssetFileProvider(
-                    staticWebAssetManifest,
-                    (contentRoot) => new PhysicalFileProvider(contentRoot));
-
-                environment.WebRootFileProvider = new CompositeFileProvider(new[] { environment.WebRootFileProvider, provider });
-                return;
-            }
-
-            var webRootFileProvider = environment.WebRootFileProvider;
-
-            var additionalFiles = StaticWebAssetsReader.Parse(manifest)
-                .Select(cr => new StaticWebAssetsFileProvider(cr.BasePath, cr.Path))
-                .OfType<IFileProvider>() // Upcast so we can insert on the resulting list.
-                .ToList();
+            var staticWebAssetManifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(manifest);
+            var provider = new ManifestStaticWebAssetFileProvider(
+                staticWebAssetManifest,
+                (contentRoot) => new PhysicalFileProvider(contentRoot));
 
-            if (additionalFiles.Count == 0)
-            {
-                return;
-            }
-            else
-            {
-                additionalFiles.Insert(0, webRootFileProvider);
-                environment.WebRootFileProvider = new CompositeFileProvider(additionalFiles);
-            }
+            environment.WebRootFileProvider = new CompositeFileProvider(new[] { provider, environment.WebRootFileProvider });
         }
 
-        internal static (Stream, bool) ResolveManifest(IWebHostEnvironment environment, IConfiguration configuration)
+        internal static Stream? ResolveManifest(IWebHostEnvironment environment, IConfiguration configuration)
         {
             try
             {
-                var manifestPath = configuration.GetValue<string>(WebHostDefaults.StaticWebAssetsKey);
-                var isJson = manifestPath != null && Path.GetExtension(manifestPath).EndsWith(".json", OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-                var candidates = manifestPath != null ? new[] { (manifestPath, isJson) } : ResolveRelativeToAssembly(environment);
-
-                foreach (var (candidate, json) in candidates)
+                var candidate = configuration.GetValue<string>(WebHostDefaults.StaticWebAssetsKey) ?? ResolveRelativeToAssembly(environment);
+                if (candidate != null && File.Exists(candidate))
                 {
-                    if (candidate != null && File.Exists(candidate))
-                    {
-                        return (File.OpenRead(candidate), json);
-                    }
+                    return File.OpenRead(candidate);
                 }
 
                 // A missing manifest might simply mean that the feature is not enabled, so we simply
@@ -93,12 +65,11 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
             }
         }
 
-        private static IEnumerable<(string candidatePath, bool isJson)> ResolveRelativeToAssembly(IWebHostEnvironment environment)
+        private static string ResolveRelativeToAssembly(IWebHostEnvironment environment)
         {
             var assembly = Assembly.Load(environment.ApplicationName);
             var basePath = string.IsNullOrEmpty(assembly.Location) ? AppContext.BaseDirectory : Path.GetDirectoryName(assembly.Location);
-            yield return (Path.Combine(basePath!, $"{environment.ApplicationName}.staticwebassets.runtime.json"), isJson: true);
-            yield return (Path.Combine(basePath!, $"{environment.ApplicationName}.StaticWebAssets.xml"), isJson: false);
+            return Path.Combine(basePath!, $"{environment.ApplicationName}.staticwebassets.runtime.json");
         }
     }
 }
diff --git a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsReader.cs b/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsReader.cs
deleted file mode 100644
index 6f1bae0b682..00000000000
--- a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsReader.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Xml.Linq;
-
-namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
-{
-    internal static class StaticWebAssetsReader
-    {
-        private const string ManifestRootElementName = "StaticWebAssets";
-        private const string VersionAttributeName = "Version";
-        private const string ContentRootElementName = "ContentRoot";
-
-        internal static IEnumerable<ContentRootMapping> Parse(Stream manifest)
-        {
-            var document = XDocument.Load(manifest);
-            if (!string.Equals(document.Root!.Name.LocalName, ManifestRootElementName, StringComparison.OrdinalIgnoreCase))
-            {
-                throw new InvalidOperationException($"Invalid manifest format. Manifest root must be '{ManifestRootElementName}'");
-            }
-
-            var version = document.Root.Attribute(VersionAttributeName);
-            if (version == null)
-            {
-                throw new InvalidOperationException($"Invalid manifest format. Manifest root element must contain a version '{VersionAttributeName}' attribute");
-            }
-
-            if (version.Value != "1.0")
-            {
-                throw new InvalidOperationException($"Unknown manifest version. Manifest version must be '1.0'");
-            }
-
-            foreach (var element in document.Root.Elements())
-            {
-                if (!string.Equals(element.Name.LocalName, ContentRootElementName, StringComparison.OrdinalIgnoreCase))
-                {
-                    throw new InvalidOperationException($"Invalid manifest format. Invalid element '{element.Name.LocalName}'. All {StaticWebAssetsLoader.StaticWebAssetsManifestName} child elements must be '{ContentRootElementName}' elements.");
-                }
-                if (!element.IsEmpty)
-                {
-                    throw new InvalidOperationException($"Invalid manifest format. {ContentRootElementName} can't have content.");
-                }
-
-                var basePath = ParseRequiredAttribute(element, "BasePath");
-                var path = ParseRequiredAttribute(element, "Path");
-                yield return new ContentRootMapping(basePath, path);
-            }
-        }
-
-        private static string ParseRequiredAttribute(XElement element, string attributeName)
-        {
-            var attribute = element.Attribute(attributeName);
-            if (attribute == null)
-            {
-                throw new InvalidOperationException($"Invalid manifest format. Missing {attributeName} attribute in '{ContentRootElementName}' element.");
-            }
-            return attribute.Value;
-        }
-
-        internal readonly struct ContentRootMapping
-        {
-            public ContentRootMapping(string basePath, string path)
-            {
-                BasePath = basePath;
-                Path = path;
-            }
-
-            public string BasePath { get; }
-            public string Path { get; }
-        }
-    }
-}
diff --git a/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs b/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs
index 198ce8932f0..24f537afc6e 100644
--- a/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs
+++ b/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs
@@ -8,6 +8,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Hosting.StaticWebAssets;
+using Microsoft.AspNetCore.StaticWebAssets;
 using Microsoft.Extensions.FileProviders;
 using Moq;
 using Xunit;
@@ -23,7 +24,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests.StaticWebAssets
             var comparer = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.PathComparer;
             var expectedResult = OperatingSystem.IsWindows();
             var manifest = new ManifestStaticWebAssetFileProvider.StaticWebAssetManifest();
-            manifest.ContentRoots = new[] { Path.GetDirectoryName(typeof(StaticWebAssetsFileProviderTests).Assembly.Location) };
+            manifest.ContentRoots = new[] { Path.GetDirectoryName(typeof(ManifestStaticWebAssetsFileProviderTest).Assembly.Location) };
             manifest.Root = new()
             {
                 Children = new(comparer)
@@ -360,7 +361,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests.StaticWebAssets
             var comparer = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.PathComparer;
             var expectedResult = OperatingSystem.IsWindows();
             var manifest = new ManifestStaticWebAssetFileProvider.StaticWebAssetManifest();
-            manifest.ContentRoots = new[] { Path.GetDirectoryName(typeof(StaticWebAssetsFileProviderTests).Assembly.Location) };
+            manifest.ContentRoots = new[] { Path.GetDirectoryName(typeof(ManifestStaticWebAssetsFileProviderTest).Assembly.Location) };
             manifest.Root = new()
             {
                 Children = new(comparer)
diff --git a/src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsFileProviderTests.cs b/src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsFileProviderTests.cs
deleted file mode 100644
index fa13a43c497..00000000000
--- a/src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsFileProviderTests.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.IO;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
-{
-    public class StaticWebAssetsFileProviderTests
-    {
-        [Fact]
-        public void StaticWebAssetsFileProvider_ConstructorThrows_WhenPathIsNotFound()
-        {
-            // Arrange, Act & Assert
-            var provider = Assert.Throws<DirectoryNotFoundException>(() => new StaticWebAssetsFileProvider("/prefix", "/nonexisting"));
-        }
-
-        [Fact]
-        public void StaticWebAssetsFileProvider_Constructor_PrependsPrefixWithSlashIfMissing()
-        {
-            // Arrange & Act
-            var provider = new StaticWebAssetsFileProvider("_content", AppContext.BaseDirectory);
-
-            // Assert
-            Assert.Equal("/_content", provider.BasePath);
-        }
-
-        [Fact]
-        public void StaticWebAssetsFileProvider_Constructor_DoesNotPrependPrefixWithSlashIfPresent()
-        {
-            // Arrange & Act
-            var provider = new StaticWebAssetsFileProvider("/_content", AppContext.BaseDirectory);
-
-            // Assert
-            Assert.Equal("/_content", provider.BasePath);
-        }
-
-        [Theory]
-        [InlineData("\\", "_content")]
-        [InlineData("\\_content\\RazorClassLib\\Dir", "Castle.Core.dll")]
-        [InlineData("", "_content")]
-        [InlineData("/", "_content")]
-        [InlineData("/_content", "RazorClassLib")]
-        [InlineData("/_content/RazorClassLib", "Dir")]
-        [InlineData("/_content/RazorClassLib/Dir", "Microsoft.AspNetCore.Hosting.Tests.dll")]
-        [InlineData("/_content/RazorClassLib/Dir/testroot/", "TextFile.txt")]
-        [InlineData("/_content/RazorClassLib/Dir/testroot/wwwroot/", "README")]
-        public void GetDirectoryContents_WalksUpContentRoot(string searchDir, string expected)
-        {
-            // Arrange
-            var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib/Dir", AppContext.BaseDirectory);
-
-            // Act
-            var directory = provider.GetDirectoryContents(searchDir);
-
-            // Assert
-            Assert.NotEmpty(directory);
-            Assert.Contains(directory, file => string.Equals(file.Name, expected));
-        }
-
-        [Fact]
-        public void GetDirectoryContents_DoesNotFindNonExistentFiles()
-        {
-            // Arrange
-            var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib/", AppContext.BaseDirectory);
-
-            // Act
-            var directory = provider.GetDirectoryContents("/_content/RazorClassLib/False");
-
-            // Assert
-            Assert.Empty(directory);
-        }
-
-        [Theory]
-        [InlineData("/False/_content/RazorClassLib/")]
-        [InlineData("/_content/RazorClass")]
-        public void GetDirectoryContents_PartialMatchFails(string requestedUrl)
-        {
-            // Arrange
-            var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib", AppContext.BaseDirectory);
-
-            // Act
-            var directory = provider.GetDirectoryContents(requestedUrl);
-
-            // Assert
-            Assert.Empty(directory);
-        }
-
-        [Fact]
-        public void GetDirectoryContents_HandlersEmptyPath()
-        {
-            // Arrange
-            var provider = new StaticWebAssetsFileProvider("/_content",
-                Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
-
-            // Act
-            var directory = provider.GetDirectoryContents("");
-
-            // Assert
-            Assert.True(directory.Exists);
-        }
-
-        [Fact]
-        public void GetDirectoryContents_HandlesWhitespaceInBase()
-        {
-            // Arrange
-            var provider = new StaticWebAssetsFileProvider("/_content/Static Web Assets",
-                Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
-
-            // Act
-            var directory = provider.GetDirectoryContents("/_content/Static Web Assets/Static Web/");
-
-            // Assert
-            Assert.Collection(directory,
-                file =>
-                {
-                    Assert.Equal("Static Web.txt", file.Name);
-                });
-        }
-
-        [Fact]
-        public void StaticWebAssetsFileProvider_FindsFileWithSpaces()
-        {
-            // Arrange & Act
-            var provider = new StaticWebAssetsFileProvider("/_content",
-                Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
-
-            // Assert
-            Assert.True(provider.GetFileInfo("/_content/Static Web Assets.txt").Exists);
-        }
-
-        [Fact]
-        public void GetDirectoryContents_HandlesEmptyBasePath()
-        {
-            // Arrange
-            var provider = new StaticWebAssetsFileProvider("/",
-                Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
-
-            // Act
-            var directory = provider.GetDirectoryContents("/Static Web/");
-
-            // Assert
-            Assert.Collection(directory,
-                file =>
-                {
-                    Assert.Equal("Static Web.txt", file.Name);
-                });
-        }
-
-        [Fact]
-        public void StaticWebAssetsFileProviderWithEmptyBasePath_FindsFile()
-        {
-            // Arrange & Act
-            var provider = new StaticWebAssetsFileProvider("/",
-                Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
-
-            // Assert
-            Assert.True(provider.GetFileInfo("/Static Web Assets.txt").Exists);
-        }
-
-        [Fact]
-        public void GetFileInfo_DoesNotMatch_IncompletePrefixSegments()
-        {
-            // Arrange
-            var expectedResult = OperatingSystem.IsWindows();
-            var provider = new StaticWebAssetsFileProvider(
-                "_cont",
-                Path.GetDirectoryName(typeof(StaticWebAssetsFileProviderTests).Assembly.Location));
-
-            // Act
-            var file = provider.GetFileInfo("/_content/Microsoft.AspNetCore.TestHost.StaticWebAssets.xml");
-
-            // Assert
-            Assert.False(file.Exists, "File exists");
-        }
-
-        [Fact]
-        public void GetFileInfo_Prefix_RespectsOsCaseSensitivity()
-        {
-            // Arrange
-            var expectedResult = OperatingSystem.IsWindows();
-            var provider = new StaticWebAssetsFileProvider(
-                "_content",
-                Path.GetDirectoryName(typeof(StaticWebAssetsFileProviderTests).Assembly.Location));
-
-            // Act
-            var file = provider.GetFileInfo("/_CONTENT/Microsoft.AspNetCore.Hosting.StaticWebAssets.xml");
-
-            // Assert
-            Assert.Equal(expectedResult, file.Exists);
-        }
-
-        [Fact]
-        public void GetDirectoryContents_Prefix_RespectsOsCaseSensitivity()
-        {
-            // Arrange
-            var expectedResult = OperatingSystem.IsWindows();
-            var provider = new StaticWebAssetsFileProvider(
-                "_content",
-                Path.GetDirectoryName(typeof(StaticWebAssetsFileProviderTests).Assembly.Location));
-
-            // Act
-            var directory = provider.GetDirectoryContents("/_CONTENT");
-
-            // Assert
-            Assert.Equal(expectedResult, directory.Exists);
-        }
-    }
-}
diff --git a/src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsLoaderTests.cs b/src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsLoaderTests.cs
deleted file mode 100644
index f714b344071..00000000000
--- a/src/Hosting/Hosting/test/StaticWebAssets/StaticWebAssetsLoaderTests.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Hosting;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
-{
-    public class StaticWebAssetsLoaderTests
-    {
-        [Fact]
-        public void UseStaticWebAssetsCore_CreatesCompositeRoot_WhenThereAreContentRootsInTheManifest()
-        {
-            // Arrange
-            var manifestContent = @$"<StaticWebAssets Version=""1.0"">
-    <ContentRoot Path=""{AppContext.BaseDirectory}"" BasePath=""/BasePath"" />
-</StaticWebAssets>";
-
-            var manifest = CreateManifest(manifestContent);
-            var originalRoot = new NullFileProvider();
-            var environment = new HostingEnvironment()
-            {
-                WebRootFileProvider = originalRoot
-            };
-
-            // Act
-            StaticWebAssetsLoader.UseStaticWebAssetsCore(environment, manifest, false);
-
-            // Assert
-            var composite = Assert.IsType<CompositeFileProvider>(environment.WebRootFileProvider);
-            Assert.Equal(2, composite.FileProviders.Count());
-            Assert.Equal(originalRoot, composite.FileProviders.First());
-        }
-
-        [Fact]
-        public void UseStaticWebAssetsCore_DoesNothing_WhenManifestDoesNotContainEntries()
-        {
-            // Arrange
-            var manifestContent = @$"<StaticWebAssets Version=""1.0"">
-</StaticWebAssets>";
-
-            var manifest = CreateManifest(manifestContent);
-            var originalRoot = new NullFileProvider();
-            var environment = new HostingEnvironment()
-            {
-                WebRootFileProvider = originalRoot
-            };
-
-            // Act
-            StaticWebAssetsLoader.UseStaticWebAssetsCore(environment, manifest, false);
-
-            // Assert
-            Assert.Equal(originalRoot, environment.WebRootFileProvider);
-        }
-
-        [Fact]
-        public void ResolveManifest_ManifestFromFile()
-        {
-            // Arrange
-            var expectedManifest = @"<StaticWebAssets Version=""1.0"">
-  <ContentRoot Path=""/Path"" BasePath=""/BasePath"" />
-</StaticWebAssets>
-";
-
-            var environment = new HostingEnvironment()
-            {
-                ApplicationName = "Microsoft.AspNetCore.Hosting"
-            };
-
-            // Act
-            var (manifest,_) = StaticWebAssetsLoader.ResolveManifest(environment, new ConfigurationBuilder().Build());
-
-            // Assert
-            Assert.Equal(expectedManifest, new StreamReader(manifest).ReadToEnd());
-        }
-
-        [Fact]
-        public void ResolveManifest_UsesConfigurationKey_WhenProvided()
-        {
-            // Arrange
-            var expectedManifest = @"<StaticWebAssets Version=""1.0"">
-  <ContentRoot Path=""/Path"" BasePath=""/BasePath"" />
-</StaticWebAssets>
-";
-            var path = Path.ChangeExtension(typeof(StaticWebAssetsLoader).Assembly.Location, ".StaticWebAssets.xml");
-            var environment = new HostingEnvironment()
-            {
-                ApplicationName = "NonExistingDll"
-            };
-
-            var configuration = new ConfigurationBuilder()
-                .AddInMemoryCollection(new Dictionary<string, string>() {
-                    [WebHostDefaults.StaticWebAssetsKey] = path
-                }).Build();
-
-            // Act
-            var (manifest,_) = StaticWebAssetsLoader.ResolveManifest(environment, configuration);
-
-            // Assert
-            Assert.Equal(expectedManifest, new StreamReader(manifest).ReadToEnd());
-        }
-
-
-        private Stream CreateManifest(string manifestContent)
-        {
-            return new MemoryStream(Encoding.UTF8.GetBytes(manifestContent));
-        }
-    }
-}
diff --git a/src/Identity/test/Identity.FunctionalTests/Infrastructure/ServerFactory.cs b/src/Identity/test/Identity.FunctionalTests/Infrastructure/ServerFactory.cs
index 912e817dca9..de41e17e4ad 100644
--- a/src/Identity/test/Identity.FunctionalTests/Infrastructure/ServerFactory.cs
+++ b/src/Identity/test/Identity.FunctionalTests/Infrastructure/ServerFactory.cs
@@ -57,71 +57,12 @@ namespace Microsoft.AspNetCore.Identity.FunctionalTests
                     .AddCookieTempDataProvider(o => o.Cookie.IsEssential = true);
             });
 
-            UpdateStaticAssets(builder);
             UpdateApplicationParts(builder);
         }
 
         private void UpdateApplicationParts(IWebHostBuilder builder) =>
             builder.ConfigureServices(services => AddRelatedParts(services, BootstrapFrameworkVersion));
 
-        private void UpdateStaticAssets(IWebHostBuilder builder)
-        {
-            var manifestPath = Path.GetDirectoryName(typeof(ServerFactory<,>).Assembly.Location);
-            builder.ConfigureAppConfiguration((ctx, cb) =>
-            {
-                if (ctx.HostingEnvironment.WebRootFileProvider is CompositeFileProvider composite)
-                {
-                    var originalWebRoot = composite.FileProviders.First();
-                    ctx.HostingEnvironment.WebRootFileProvider = originalWebRoot;
-                }
-            });
-
-            string versionedPath = Path.Combine(manifestPath, $"Testing.DefaultWebSite.StaticWebAssets.{BootstrapFrameworkVersion}.xml");
-            UpdateManifest(versionedPath);
-
-            builder.ConfigureAppConfiguration((context, configBuilder) =>
-            {
-                using (var manifest = File.OpenRead(versionedPath))
-                {
-                    typeof(StaticWebAssetsLoader)
-                        .GetMethod("UseStaticWebAssetsCore", BindingFlags.NonPublic | BindingFlags.Static)
-                        .Invoke(null, new object[] { context.HostingEnvironment, manifest, false });
-                }
-            });
-        }
-
-        private void UpdateManifest(string versionedPath)
-        {
-            var content = File.ReadAllText(versionedPath);
-            var path = typeof(ServerFactory<,>).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
-                    .Single(a => a.Key == "Microsoft.AspNetCore.Testing.IdentityUIProjectPath").Value;
-
-            path = Directory.Exists(path) ? Path.Combine(path, "wwwroot") : Path.Combine(FindHelixSlnFileDirectory(), "UI", "wwwroot");
-
-            var updatedContent = content.Replace("{TEST_PLACEHOLDER}", path);
-
-            File.WriteAllText(versionedPath, updatedContent);
-        }
-
-        private string FindHelixSlnFileDirectory()
-        {
-            var applicationPath = Path.GetDirectoryName(typeof(ServerFactory<,>).Assembly.Location);
-            var directoryInfo = new DirectoryInfo(applicationPath);
-            do
-            {
-                var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, "*.sln").FirstOrDefault();
-                if (solutionPath != null)
-                {
-                    return directoryInfo.FullName;
-                }
-
-                directoryInfo = directoryInfo.Parent;
-            }
-            while (directoryInfo.Parent != null);
-
-            throw new InvalidOperationException($"Solution root could not be located using application root {applicationPath}.");
-        }
-
         protected override IHost CreateHost(IHostBuilder builder)
         {
             var result = base.CreateHost(builder);
diff --git a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsFileProvider.cs b/src/Shared/StaticWebAssets/StaticWebAssetsFileProvider.cs
similarity index 74%
rename from src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsFileProvider.cs
rename to src/Shared/StaticWebAssets/StaticWebAssetsFileProvider.cs
index fd8b8cffa58..a5a5ed15598 100644
--- a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsFileProvider.cs
+++ b/src/Shared/StaticWebAssets/StaticWebAssetsFileProvider.cs
@@ -6,159 +6,12 @@ using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.FileProviders;
 using Microsoft.Extensions.FileSystemGlobbing;
 using Microsoft.Extensions.Primitives;
 
-namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
+namespace Microsoft.AspNetCore.StaticWebAssets
 {
-    // A file provider used for serving static web assets from referenced projects and packages during development.
-    // The file provider maps folders from referenced projects and packages and prepends a prefix to their relative
-    // paths.
-    // At publish time the assets end up in the wwwroot folder of the published app under the prefix indicated here
-    // as the base path.
-    // For example, for a referenced project mylibrary with content under <<mylibrarypath>>\wwwroot will expose
-    // static web assets under _content/mylibrary (this is by convention). The path prefix or base path we apply
-    // is that (_content/mylibrary).
-    // when the app gets published, the build pipeline puts the static web assets for mylibrary under
-    // publish/wwwroot/_content/mylibrary/sample-asset.js
-    // To allow for the same experience during development, StaticWebAssetsFileProvider maps the contents of
-    // <<mylibrarypath>>\wwwroot\** to _content/mylibrary/**
-    internal class StaticWebAssetsFileProvider : IFileProvider
-    {
-        private static readonly StringComparison FilePathComparison = OperatingSystem.IsWindows() ?
-            StringComparison.OrdinalIgnoreCase :
-            StringComparison.Ordinal;
-
-        public StaticWebAssetsFileProvider(string pathPrefix, string contentRoot)
-        {
-            BasePath = NormalizePath(pathPrefix);
-            InnerProvider = new PhysicalFileProvider(contentRoot);
-        }
-
-        public PhysicalFileProvider InnerProvider { get; }
-
-        public PathString BasePath { get; }
-
-        /// <inheritdoc />
-        public IDirectoryContents GetDirectoryContents(string subpath)
-        {
-            var modifiedSub = NormalizePath(subpath);
-
-            if (BasePath == "/")
-            {
-                return InnerProvider.GetDirectoryContents(modifiedSub);
-            }
-
-            if (StartsWithBasePath(modifiedSub, out var physicalPath))
-            {
-                return InnerProvider.GetDirectoryContents(physicalPath.Value);
-            }
-            else if (string.Equals(subpath, string.Empty) || string.Equals(modifiedSub, "/"))
-            {
-                return new StaticWebAssetsDirectoryRoot(BasePath);
-            }
-            else if (BasePath.StartsWithSegments(modifiedSub, FilePathComparison, out var remaining))
-            {
-                return new StaticWebAssetsDirectoryRoot(remaining);
-            }
-
-            return NotFoundDirectoryContents.Singleton;
-        }
-
-        /// <inheritdoc />
-        public IFileInfo GetFileInfo(string subpath)
-        {
-            var modifiedSub = NormalizePath(subpath);
-
-            if (BasePath == "/")
-            {
-                return InnerProvider.GetFileInfo(subpath);
-            }
-
-            if (!StartsWithBasePath(modifiedSub, out var physicalPath))
-            {
-                return new NotFoundFileInfo(subpath);
-            }
-            else
-            {
-                return InnerProvider.GetFileInfo(physicalPath.Value);
-            }
-        }
-
-        /// <inheritdoc />
-        public IChangeToken Watch(string filter)
-        {
-            return InnerProvider.Watch(filter);
-        }
-
-        private static string NormalizePath(string path)
-        {
-            path = path.Replace('\\', '/');
-            return path.StartsWith('/') ? path : "/" + path;
-        }
-
-        private bool StartsWithBasePath(string subpath, out PathString rest)
-        {
-            return new PathString(subpath).StartsWithSegments(BasePath, FilePathComparison, out rest);
-        }
-
-        private class StaticWebAssetsDirectoryRoot : IDirectoryContents
-        {
-            private readonly string _nextSegment;
-
-            public StaticWebAssetsDirectoryRoot(PathString remainingPath)
-            {
-                // We MUST use the Value property here because it is unescaped.
-                _nextSegment = remainingPath.Value?.Split("/", StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? string.Empty;
-            }
-
-            public bool Exists => true;
-
-            public IEnumerator<IFileInfo> GetEnumerator()
-            {
-                return GenerateEnum();
-            }
-
-            IEnumerator IEnumerable.GetEnumerator()
-            {
-                return GenerateEnum();
-            }
-
-            private IEnumerator<IFileInfo> GenerateEnum()
-            {
-                return new[] { new StaticWebAssetsFileInfo(_nextSegment) }
-                    .Cast<IFileInfo>().GetEnumerator();
-            }
-
-            private class StaticWebAssetsFileInfo : IFileInfo
-            {
-                public StaticWebAssetsFileInfo(string name)
-                {
-                    Name = name;
-                }
-
-                public bool Exists => true;
-
-                public long Length => throw new NotImplementedException();
-
-                public string PhysicalPath => throw new NotImplementedException();
-
-                public DateTimeOffset LastModified => throw new NotImplementedException();
-
-                public bool IsDirectory => true;
-
-                public string Name { get; }
-
-                public Stream CreateReadStream()
-                {
-                    throw new NotImplementedException();
-                }
-            }
-        }
-    }
-
     internal sealed class ManifestStaticWebAssetFileProvider : IFileProvider
     {
         private static readonly StringComparison _fsComparison = OperatingSystem.IsWindows() ?
-- 
GitLab