Finished compile time plugin loading. Refactored plugin loading. Extended build helper script

This commit is contained in:
2025-05-13 20:48:50 +02:00
parent 8126250d1a
commit a579dd4759
28 changed files with 1169 additions and 741 deletions

View File

@@ -1,11 +1,12 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Moonlight.Client.Interfaces;
using Moonlight.Client.Plugins;
namespace Moonlight.Client.Implementations;
public class CoreStartup : IPluginStartup
{
public Task BuildApplication(WebAssemblyHostBuilder builder)
public Task BuildApplication(IServiceProvider serviceProvider, WebAssemblyHostBuilder builder)
{
builder.Services.AddSingleton<ISidebarItemProvider, DefaultSidebarItemProvider>();
builder.Services.AddSingleton<IOverviewElementProvider, DefaultOverviewElementProvider>();
@@ -13,6 +14,6 @@ public class CoreStartup : IPluginStartup
return Task.CompletedTask;
}
public Task ConfigureApplication(WebAssemblyHost app)
public Task ConfigureApplication(IServiceProvider serviceProvider, WebAssemblyHost app)
=> Task.CompletedTask;
}

View File

@@ -1,9 +0,0 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace Moonlight.Client.Interfaces;
public interface IPluginStartup
{
public Task BuildApplication(WebAssemblyHostBuilder builder);
public Task ConfigureApplication(WebAssemblyHost app);
}

View File

@@ -1,71 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DefaultItemExcludes>
<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DefaultItemExcludes>
**\bin\**;**\obj\**;**\node_modules\**;**\Styles\*.json
</DefaultItemExcludes>
<StaticWebAssetsEnabled>True</StaticWebAssetsEnabled>
</PropertyGroup>
<PropertyGroup>
<PackageId>Moonlight.Client</PackageId>
<Version>2.1.0</Version>
<Authors>Moonlight Panel</Authors>
<Description>A build of the client for moonlight development</Description>
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
<DevelopmentDependency>true</DevelopmentDependency>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blazor-ApexCharts" Version="4.0.1"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all"/>
<PackageReference Include="MoonCore" Version="1.8.5"/>
<PackageReference Include="MoonCore.Blazor" Version="1.2.9"/>
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.5"/>
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.4.2"/>
</ItemGroup>
<ItemGroup>
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
<Pack>true</Pack>
<PackagePath>src</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Include="Styles\**\*" Exclude="storage\**\*;bin\**\*;obj\**\*;Styles\node_modules\**\*">
<Pack>true</Pack>
<PackagePath>styles</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<Compile Remove="storage\**\*"/>
<Content Remove="storage\**\*"/>
<None Remove="storage\**\*"/>
</ItemGroup>
<!--
<StaticWebAssetsEnabled>True</StaticWebAssetsEnabled>
<PackageTags>frontend</PackageTags>
</PropertyGroup>
<PropertyGroup>
<PackageId>Moonlight.Client</PackageId>
<Version>2.1.0</Version>
<Authors>Moonlight Panel</Authors>
<Description>A build of the client for moonlight development</Description>
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
<DevelopmentDependency>true</DevelopmentDependency>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blazor-ApexCharts" Version="4.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.15" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.15" PrivateAssets="all" />
<PackageReference Include="MoonCore" Version="1.8.5" />
<PackageReference Include="MoonCore.Blazor" Version="1.2.9" />
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.5" />
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.4.2" />
</ItemGroup>
<ItemGroup>
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
<Pack>true</Pack>
<PackagePath>src</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Include="Styles\**\*" Exclude="storage\**\*;bin\**\*;obj\**\*;Styles\node_modules\**\*">
<Pack>true</Pack>
<PackagePath>styles</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<Compile Remove="storage\**\*" />
<Content Remove="storage\**\*" />
<None Remove="storage\**\*" />
</ItemGroup>
<!--
Specify the /p:BuildPWA=true flag to build moonlight as a PWA.
This flag is by default disabled to allow nuget package generation
-->
<PropertyGroup Condition="'$(BuildPWA)' == 'true'">
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
</PropertyGroup>
<ItemGroup Condition="'$(BuildPWA)' == 'true'">
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj"/>
</ItemGroup>
</Project>
<PropertyGroup Condition="'$(BuildPWA)' == 'true'">
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
</PropertyGroup>
<ItemGroup Condition="'$(BuildPWA)' == 'true'">
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace Moonlight.Client.Plugins;
public interface IPluginStartup
{
public Task BuildApplication(IServiceProvider serviceProvider, WebAssemblyHostBuilder builder);
public Task ConfigureApplication(IServiceProvider serviceProvider, WebAssemblyHost app);
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.Client.Plugins;
[AttributeUsage(AttributeTargets.Class)]
public class PluginStartupAttribute : Attribute
{
}

View File

@@ -9,7 +9,9 @@ using MoonCore.Blazor.Tailwind.Extensions;
using MoonCore.Blazor.Tailwind.Auth;
using MoonCore.Extensions;
using MoonCore.Helpers;
using Moonlight.Client.Implementations;
using Moonlight.Client.Interfaces;
using Moonlight.Client.Plugins;
using Moonlight.Client.Services;
using Moonlight.Shared.Misc;
using Moonlight.Client.UI;
@@ -33,15 +35,14 @@ public class Startup
private WebAssemblyHost WebAssemblyHost;
// Plugin Loading
private AssemblyLoadContext PluginLoadContext;
private Assembly[] AdditionalAssemblies;
private IPluginStartup[] AdditionalPlugins;
private IPluginStartup[] PluginStartups;
private IServiceProvider PluginLoadServiceProvider;
public async Task Run(string[] args, Assembly[]? additionalAssemblies = null)
public async Task Run(string[] args, IPluginStartup[]? additionalPlugins = null)
{
Args = args;
AdditionalAssemblies = additionalAssemblies ?? [];
AdditionalPlugins = additionalPlugins ?? [];
await PrintVersion();
await SetupLogging();
@@ -49,7 +50,6 @@ public class Startup
await CreateWebAssemblyHostBuilder();
await LoadConfiguration();
await LoadPlugins();
await InitializePlugins();
await RegisterLogging();
@@ -94,7 +94,7 @@ public class Startup
httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress);
var jsonText = await httpClient.GetStringAsync("frontend.json");
Configuration = JsonSerializer.Deserialize<FrontendConfiguration>(jsonText, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
@@ -120,7 +120,7 @@ public class Startup
BaseAddress = new Uri(Configuration.ApiUrl)
}
);
WebAssemblyHostBuilder.Services.AddScoped(sp =>
{
var httpClient = sp.GetRequiredService<HttpClient>();
@@ -146,7 +146,7 @@ public class Startup
WebAssemblyHostBuilder.Services.AddScoped<LocalStorageService>();
WebAssemblyHostBuilder.Services.AddScoped<ThemeService>();
WebAssemblyHostBuilder.Services.AutoAddServices<Program>();
return Task.CompletedTask;
@@ -160,39 +160,15 @@ public class Startup
foreach (var scriptName in Configuration.Scripts)
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadJavascript", scriptName);
foreach (var styleName in Configuration.Styles)
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadStylesheet", styleName);
}
#endregion
#region Plugins
private async Task LoadPlugins()
{
// Create everything required to stream plugins
using var clientForStreaming = new HttpClient();
clientForStreaming.BaseAddress = new Uri(Configuration.HostEnvironment == "ApiServer"
? Configuration.ApiUrl
: WebAssemblyHostBuilder.HostEnvironment.BaseAddress
);
PluginLoadContext = new AssemblyLoadContext(null);
foreach (var assembly in Configuration.Assemblies)
{
var assemblyStream = await clientForStreaming.GetStreamAsync($"plugins/{assembly}");
PluginLoadContext.LoadFromStream(assemblyStream);
}
// Add application assembly service
var appAssemblyService = new ApplicationAssemblyService();
appAssemblyService.Assemblies.AddRange(AdditionalAssemblies);
appAssemblyService.Assemblies.AddRange(PluginLoadContext.Assemblies);
WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService);
}
private Task InitializePlugins()
{
// Define minimal service collection
@@ -205,38 +181,31 @@ public class Startup
builder.AddProviders(LoggerProviders);
});
//
var startupSp = startupSc.BuildServiceProvider();
// Initialize plugin startups
var startups = new List<IPluginStartup>();
var startupType = typeof(IPluginStartup);
var assembliesToScan = new List<Assembly>();
assembliesToScan.Add(typeof(Startup).Assembly);
assembliesToScan.AddRange(AdditionalAssemblies);
assembliesToScan.AddRange(PluginLoadContext.Assemblies);
PluginLoadServiceProvider = startupSc.BuildServiceProvider();
foreach (var pluginAssembly in assembliesToScan)
{
var startupTypes = pluginAssembly
.ExportedTypes
.Where(x => !x.IsAbstract && !x.IsInterface && x.IsAssignableTo(startupType))
.ToArray();
// Collect startups
var pluginStartups = new List<IPluginStartup>();
foreach (var type in startupTypes)
{
var startup = ActivatorUtilities.CreateInstance(startupSp, type) as IPluginStartup;
if(startup == null)
continue;
startups.Add(startup);
}
}
pluginStartups.Add(new CoreStartup());
PluginStartups = startups.ToArray();
pluginStartups.AddRange(AdditionalPlugins); // Used by the development server
// Do NOT remove the following comment, as its used to place the plugin startup register calls
// MLBUILD_PLUGIN_STARTUP_HERE
PluginStartups = pluginStartups.ToArray();
// Add application assembly service
var appAssemblyService = new ApplicationAssemblyService();
appAssemblyService.Assemblies.AddRange(
PluginStartups
.Select(x => x.GetType().Assembly)
.Distinct()
);
WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService);
return Task.CompletedTask;
}
@@ -249,7 +218,7 @@ public class Startup
{
try
{
await pluginAppStartup.BuildApplication(WebAssemblyHostBuilder);
await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, WebAssemblyHostBuilder);
}
catch (Exception e)
{
@@ -268,7 +237,7 @@ public class Startup
{
try
{
await pluginAppStartup.ConfigureApplication(WebAssemblyHost);
await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, WebAssemblyHost);
}
catch (Exception e)
{
@@ -336,9 +305,9 @@ public class Startup
{
WebAssemblyHostBuilder.Services.AddAuthorizationCore();
WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState();
WebAssemblyHostBuilder.Services.AddAuthenticationStateManager<RemoteAuthStateManager>();
return Task.CompletedTask;
}

View File

@@ -34,6 +34,15 @@ window.moonlight = {
scriptElement.type = 'text/javascript';
(document.head || document.documentElement).appendChild(scriptElement);
},
loadStylesheet: function (url) {
let linkElement = document.createElement('link');
linkElement.href = url;
linkElement.type = 'text/css';
linkElement.rel = 'stylesheet';
(document.head || document.documentElement).appendChild(linkElement);
}
}
};