Implemented css bundling
This commit is contained in:
28
Moonlight.ApiServer/Helpers/BundleAssetFileProvider.cs
Normal file
28
Moonlight.ApiServer/Helpers/BundleAssetFileProvider.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
132
Moonlight.ApiServer/Services/BundleService.cs
Normal file
132
Moonlight.ApiServer/Services/BundleService.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user