Implemented css bundling

This commit is contained in:
2024-12-10 16:28:11 +01:00
parent 64b20e26ac
commit 150a18cc0b
4 changed files with 175 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Primitives;
using MoonCore.Helpers;
namespace Moonlight.ApiServer.Helpers;
public class BundleAssetFileProvider : IFileProvider
{
public IDirectoryContents GetDirectoryContents(string subpath)
=> NotFoundDirectoryContents.Singleton;
public IFileInfo GetFileInfo(string subpath)
{
if(subpath != "/css/bundle.css")
return new NotFoundFileInfo(subpath);
var physicalPath = PathBuilder.File("storage", "tmp", "bundle.css");
if(!File.Exists(physicalPath))
return new NotFoundFileInfo(subpath);
return new PhysicalFileInfo(new FileInfo(physicalPath));
}
public IChangeToken Watch(string filter)
=> NullChangeToken.Singleton;
}

View File

@@ -18,6 +18,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ExCSS" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.10"/> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@@ -0,0 +1,132 @@
using ExCSS;
using Microsoft.Extensions.Hosting.Internal;
using MoonCore.Helpers;
namespace Moonlight.ApiServer.Services;
public class BundleService : IHostedService
{
private readonly ILogger<BundleService> Logger;
private readonly List<string> CssFiles = new();
private readonly AssetService AssetService;
private readonly IWebHostEnvironment HostEnvironment;
public BundleService(ILogger<BundleService> logger, PluginService pluginService, IWebHostEnvironment hostEnvironment, AssetService assetService)
{
Logger = logger;
HostEnvironment = hostEnvironment;
AssetService = assetService;
}
public async Task Bundle(CancellationToken cancellationToken)
{
Logger.LogInformation("Collecting css files...");
var fi = HostEnvironment.WebRootFileProvider.GetFileInfo("css/core.min.css");
CssFiles.Add(fi.PhysicalPath!);
CssFiles.AddRange(AssetService.GetCssAssets().Select(webPath =>
{
var fii = HostEnvironment.WebRootFileProvider.GetFileInfo(webPath.TrimStart('/'));
return fii.PhysicalPath!;
}));
Logger.LogInformation("Bundling css files...");
var content = "";
if (CssFiles.Count > 0)
{
var cssFileContents = new List<string>();
foreach (var cssFile in CssFiles)
{
var fileContent = await File.ReadAllTextAsync(cssFile, cancellationToken);
cssFileContents.Add(fileContent);
}
content = cssFileContents[0];
if (cssFileContents.Count > 1)
{
var styleSheets = new List<Stylesheet>();
var parser = new StylesheetParser();
foreach (var fileContent in cssFileContents)
{
try
{
var sh = await parser.ParseAsync(fileContent, cancellationToken);
styleSheets.Add(sh);
}
catch (Exception e)
{
Logger.LogWarning("An error occured while parsing css file: {e}", e);
}
}
var mainStylesheet = styleSheets.First();
foreach (var stylesheet in styleSheets.Skip(1))
{
// Style
foreach (var styleRule in stylesheet.StyleRules)
{
if (mainStylesheet.StyleRules.Any(x => x.Selector.Text == styleRule.Selector.Text))
continue;
content += styleRule.ToCss() + "\n";
}
// Container
foreach (var containerRule in stylesheet.ContainerRules)
{
if (mainStylesheet.ContainerRules.Any(x => x.ConditionText == containerRule.ConditionText))
continue;
content += containerRule.ToCss() + "\n";
}
// Import Rule
foreach (var importRule in stylesheet.ImportRules)
{
if (mainStylesheet.ImportRules.Any(x => x.Text == importRule.Text))
continue;
content += importRule.ToCss() + "\n";
}
// Media Rules
foreach (var mediaRule in stylesheet.MediaRules)
content += mediaRule.StylesheetText.Text + "\n";
// Page Rules
foreach (var pageRule in stylesheet.PageRules)
{
if (mainStylesheet.PageRules.Any(x => x.SelectorText == pageRule.SelectorText))
continue;
content += pageRule.ToCss() + "\n";
}
}
}
}
Directory.CreateDirectory(PathBuilder.Dir("storage", "tmp"));
await File.WriteAllTextAsync(PathBuilder.File("storage", "tmp", "bundle.css"), content, cancellationToken);
Logger.LogInformation("Successfully built css bundle");
}
public void AddCssFile(string file) => CssFiles.Add(file);
//
public Task StartAsync(CancellationToken cancellationToken)
=> Bundle(cancellationToken);
public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
}

View File

@@ -79,6 +79,7 @@ public class Startup
await RegisterCaching(); await RegisterCaching();
await HookPluginBuild(); await HookPluginBuild();
await HandleConfigureArguments(); await HandleConfigureArguments();
await RegisterPluginAssets();
await BuildWebApplication(); await BuildWebApplication();
@@ -341,8 +342,21 @@ public class Startup
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task RegisterPluginAssets()
{
WebApplicationBuilder.Services.AddHostedService(sp => sp.GetRequiredService<BundleService>());
WebApplicationBuilder.Services.AddSingleton<BundleService>();
return Task.CompletedTask;
}
private Task UsePluginAssets() private Task UsePluginAssets()
{ {
WebApplication.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new BundleAssetFileProvider()
});
WebApplication.UseStaticFiles(new StaticFileOptions() WebApplication.UseStaticFiles(new StaticFileOptions()
{ {
FileProvider = new PluginAssetFileProvider(PluginService) FileProvider = new PluginAssetFileProvider(PluginService)