diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs
index 3a2ccfbaaee2..b90878fdde36 100644
--- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs
+++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs
@@ -19,6 +19,11 @@ internal class WebAssemblyHost : IWebAssemblyHost
public WebAssemblyHost(IServiceProvider services, IJSRuntime runtime)
{
+ // To ensure JS-invoked methods don't get linked out, have a reference to their enclosing types
+ GC.KeepAlive(typeof(EntrypointInvoker));
+ GC.KeepAlive(typeof(JSInteropMethods));
+ GC.KeepAlive(typeof(WebAssemblyEventDispatcher));
+
Services = services ?? throw new ArgumentNullException(nameof(services));
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
}
diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs b/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs
new file mode 100644
index 000000000000..8a56b7fc3deb
--- /dev/null
+++ b/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.AspNetCore.Blazor.Build.Tasks
+{
+ public class GenerateTypeGranularityLinkingConfig : Task
+ {
+ [Required]
+ public ITaskItem[] Assemblies { get; set; }
+
+ [Required]
+ public string OutputPath { get; set; }
+
+ public override bool Execute()
+ {
+ var linkerElement = new XElement("linker",
+ new XComment(" THIS IS A GENERATED FILE - DO NOT EDIT MANUALLY "));
+
+ foreach (var assembly in Assemblies)
+ {
+ var assemblyElement = CreateTypeGranularityConfig(assembly);
+ linkerElement.Add(assemblyElement);
+ }
+
+ using var fileStream = File.Open(OutputPath, FileMode.Create);
+ new XDocument(linkerElement).Save(fileStream);
+
+ return true;
+ }
+
+ private XElement CreateTypeGranularityConfig(ITaskItem assembly)
+ {
+ // We match all types in the assembly, and for each one, tell the linker to preserve all
+ // its members (preserve=all) but only if there's some reference to the type (required=false)
+ return new XElement("assembly",
+ new XAttribute("fullname", Path.GetFileNameWithoutExtension(assembly.ItemSpec)),
+ new XElement("type",
+ new XAttribute("fullname", "*"),
+ new XAttribute("preserve", "all"),
+ new XAttribute("required", "false")));
+ }
+ }
+}
diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
index 0a3ba70a2374..4bdb2f431156 100644
--- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
+++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
@@ -160,6 +160,7 @@
+
- <_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" IsLinkable="$([System.String]::Copy('%(FileName)').StartsWith('System.'))" />
+ <_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" />
+ <_BlazorDependencyAssembly IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
+ <_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
+ <_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
<_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
@@ -201,6 +205,15 @@
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe
+
+ <_TypeGranularityLinkingConfig>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml
+
+
+
+
+
+
+