Prepared tailwind system for plugin builds and exports via nuget. Removed obsolete old css bundling. Added helper scripts for building. Rewritten build scripts

This commit is contained in:
2025-05-11 00:07:41 +02:00
parent 1a67fcffb4
commit 1b4d32eed3
28 changed files with 424 additions and 519 deletions

View File

@@ -10,6 +10,5 @@ public class PluginManifest
public string[] Scripts { get; set; } = [];
public string[] Styles { get; set; } = [];
public string[] BundledStyles { get; set; } = [];
public Dictionary<string, string[]> Assemblies { get; set; } = new();
}

View File

@@ -19,6 +19,7 @@
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
<Pack>false</Pack>
</Content>
</ItemGroup>

View File

@@ -1,183 +0,0 @@
using ExCSS;
using Microsoft.Extensions.FileProviders;
using MoonCore.Helpers;
namespace Moonlight.ApiServer.Services;
public class BundleGenerationService : IHostedService
{
private readonly ILogger<BundleGenerationService> Logger;
private readonly IWebHostEnvironment HostEnvironment;
private readonly PluginService PluginService;
private readonly BundleService BundleService;
public BundleGenerationService(
ILogger<BundleGenerationService> logger,
IWebHostEnvironment hostEnvironment,
BundleService bundleService,
PluginService pluginService
)
{
Logger = logger;
HostEnvironment = hostEnvironment;
BundleService = bundleService;
PluginService = pluginService;
}
private async Task Bundle(CancellationToken cancellationToken)
{
Logger.LogInformation("Bundling css files...");
// Search the physical path for the defined files
var physicalCssFiles = new List<string>();
foreach (var cssFile in BundleService.GetCssFiles())
{
var fileInfo = HostEnvironment.WebRootFileProvider.GetFileInfo(cssFile);
if (fileInfo is NotFoundFileInfo || fileInfo.PhysicalPath == null)
{
fileInfo = PluginService.WwwRootFileProvider.GetFileInfo(cssFile);
if (fileInfo is NotFoundFileInfo || fileInfo.PhysicalPath == null)
{
Logger.LogWarning(
"Unable to find physical path for the requested css file '{file}'. Make sure its inside a wwwroot folder",
cssFile
);
continue;
}
}
Logger.LogTrace("Discovered css file '{path}' at '{physicalPath}'", cssFile, fileInfo.PhysicalPath);
physicalCssFiles.Add(fileInfo.PhysicalPath);
}
if (physicalCssFiles.Count == 0)
Logger.LogWarning(
"No physical paths to css files loaded. The generated bundle will be empty. Unless this is intended by you this is a bug");
// TODO: Implement cache
// TODO: File system watcher for development
var bundleContent = await CreateCssBundle(physicalCssFiles);
Directory.CreateDirectory(PathBuilder.Dir("storage", "tmp"));
await File.WriteAllTextAsync(PathBuilder.File("storage", "tmp", "bundle.css"), bundleContent,
cancellationToken);
Logger.LogInformation("Successfully built css bundle");
}
private async Task<string> CreateCssBundle(List<string> physicalPaths)
{
if (physicalPaths.Count == 0) // No stylesheets defined => nothing to process
return "";
if (physicalPaths.Count == 1) // Only one stylesheet => nothing to process
return await File.ReadAllTextAsync(physicalPaths[0]);
// Simple bundler just to test
var result = "";
foreach (var path in physicalPaths)
{
result += await File.ReadAllTextAsync(path);
}
return result;
// Create bundle by stripping out double declared classes and combining all css files into one bundle
var parser = new StylesheetParser();
string? content = null;
Stylesheet? mainStylesheet = null;
var additionalStyleSheets = new List<Stylesheet>();
foreach (var physicalPath in physicalPaths)
{
try
{
var fileContent = await File.ReadAllTextAsync(physicalPath);
var stylesheet = await parser.ParseAsync(fileContent);
// Check if it's the first stylesheet we are loading
if (mainStylesheet == null || content == null)
{
// Delegate the first stylesheet to be the main one
content = fileContent + "\n";
mainStylesheet = stylesheet;
}
else
additionalStyleSheets.Add(stylesheet); // All other stylesheets are to be processed
}
catch (Exception e)
{
Logger.LogError("An error occured while parsing css file: {e}", e);
}
}
// Handle an empty main stylesheet delegation
if (mainStylesheet == null || content == null)
{
Logger.LogError("An unable to delegate main stylesheet. Did every load attempt of an stylesheet fail?");
return "";
}
// Process stylesheets against the main one
foreach (var stylesheet in additionalStyleSheets)
{
// Style
foreach (var styleRule in stylesheet.StyleRules)
{
if (mainStylesheet.StyleRules.Any(x => x.Selector.Text == styleRule.Selector.Text))
continue;
content += styleRule.StylesheetText.Text + "\n";
}
// Container
foreach (var containerRule in stylesheet.ContainerRules)
{
if (mainStylesheet.ContainerRules.Any(x => x.ConditionText == containerRule.ConditionText))
continue;
content += containerRule.StylesheetText.Text + "\n";
}
// Import Rule
foreach (var importRule in stylesheet.ImportRules)
{
if (mainStylesheet.ImportRules.Any(x => x.Text == importRule.Text))
continue;
content = importRule.StylesheetText.Text + "\n" + content;
}
// 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.StylesheetText.Text + "\n";
}
}
return content;
}
//
public Task StartAsync(CancellationToken cancellationToken)
=> Bundle(cancellationToken);
public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
}

View File

@@ -1,14 +0,0 @@
namespace Moonlight.ApiServer.Services;
public class BundleService
{
private readonly List<string> CssFiles = new();
public void BundleCss(string path)
=> CssFiles.Add(path);
public void BundleCssRange(string[] paths)
=> CssFiles.AddRange(paths);
public IEnumerable<string> GetCssFiles() => CssFiles;
}

View File

@@ -54,9 +54,6 @@ public class Startup
private PluginService PluginService;
private AssemblyLoadContext PluginLoadContext;
// Asset bundling
private BundleService BundleService = new();
private IPluginStartup[] PluginStartups;
public async Task Run(string[] args, Assembly[]? additionalAssemblies = null,
@@ -71,7 +68,6 @@ public class Startup
await CreateStorage();
await SetupAppConfiguration();
await SetupLogging();
await SetupBundling();
await LoadPlugins();
await InitializePlugins();
@@ -132,15 +128,6 @@ public class Startup
return Task.CompletedTask;
}
private Task SetupBundling()
{
BundleService = new();
BundleService.BundleCss("css/core.min.css");
return Task.CompletedTask;
}
#region Base
private Task RegisterBase()
@@ -257,13 +244,6 @@ public class Startup
// Configure base services for initialisation
startupSc.AddSingleton(Configuration);
// Add bundle service so plugins can do additional bundling if required
startupSc.AddSingleton(BundleService);
// Auto add all files specified in the bundledStyles section to the bundle job
foreach (var plugin in PluginService.LoadedPlugins.Keys)
BundleService.BundleCssRange(plugin.BundledStyles);
startupSc.AddLogging(builder =>
{
builder.ClearProviders();
@@ -308,10 +288,6 @@ public class Startup
private Task RegisterPluginAssets()
{
WebApplicationBuilder.Services.AddHostedService(sp => sp.GetRequiredService<BundleGenerationService>());
WebApplicationBuilder.Services.AddSingleton<BundleGenerationService>();
WebApplicationBuilder.Services.AddSingleton(BundleService);
return Task.CompletedTask;
}