From 6eed680e4ba8483f8d004a79b63ee105049a7546 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Fri, 10 May 2024 01:21:53 +0200 Subject: [PATCH] Improve local emulator to read nuget packages as remote does --- src/Piral.Blazor.Orchestrator/Constants.cs | 9 ++ .../Loader/MfLocalLoaderService.cs | 26 +--- .../LocalMicrofrontendPackage.cs | 97 +++++++------- .../MfPackageService.cs | 2 +- .../MicrofrontendLoadContext.cs | 6 +- .../MicrofrontendPackage.cs | 19 ++- .../NugetMicrofrontendPackage.cs | 123 ------------------ src/Piral.Blazor.Orchestrator/NugetService.cs | 5 +- .../RemoteMicrofrontendPackage.cs | 102 +++++++++++++++ 9 files changed, 184 insertions(+), 205 deletions(-) create mode 100644 src/Piral.Blazor.Orchestrator/Constants.cs delete mode 100644 src/Piral.Blazor.Orchestrator/NugetMicrofrontendPackage.cs create mode 100644 src/Piral.Blazor.Orchestrator/RemoteMicrofrontendPackage.cs diff --git a/src/Piral.Blazor.Orchestrator/Constants.cs b/src/Piral.Blazor.Orchestrator/Constants.cs new file mode 100644 index 0000000..b9ee834 --- /dev/null +++ b/src/Piral.Blazor.Orchestrator/Constants.cs @@ -0,0 +1,9 @@ +using NuGet.Frameworks; + +namespace Piral.Blazor.Orchestrator; + +internal static class Constants +{ + public const string Target = "net8.0"; + public static readonly NuGetFramework CurrentFramework = NuGetFramework.Parse(Target); +} diff --git a/src/Piral.Blazor.Orchestrator/Loader/MfLocalLoaderService.cs b/src/Piral.Blazor.Orchestrator/Loader/MfLocalLoaderService.cs index b6c3b77..1294243 100644 --- a/src/Piral.Blazor.Orchestrator/Loader/MfLocalLoaderService.cs +++ b/src/Piral.Blazor.Orchestrator/Loader/MfLocalLoaderService.cs @@ -1,15 +1,12 @@ -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; +namespace Piral.Blazor.Orchestrator.Loader; -namespace Piral.Blazor.Orchestrator.Loader; - -internal class MfLocalLoaderService(T originalLoader, IMfRepository repository, IModuleContainerService container, IEvents events, IData data) : IMfLoaderService +internal class MfLocalLoaderService(T originalLoader, IMfRepository repository, IPiralConfig config, IModuleContainerService container, IEvents events, IData data) : IMfLoaderService where T : class, IMfLoaderService { private readonly T _originalLoader = originalLoader; private readonly IMfRepository _repository = repository; private readonly IModuleContainerService _container = container; + private readonly IPiralConfig _config = config; private readonly IEvents _events = events; private readonly IData _data = data; @@ -24,22 +21,7 @@ public async Task LoadMicrofrontends(CancellationToken cancellationToken) foreach (var path in all) { - var cfg = GetMicrofrontendConfig(path); - await _repository.SetPackage(new LocalMicrofrontendPackage(path, cfg, _container, _events, _data)); - } - } - - private static JsonObject? GetMicrofrontendConfig(string path) - { - var dir = Path.GetDirectoryName(path)!; - var cfgPath = Path.Combine(dir, "config.json"); - - if (File.Exists(cfgPath)) - { - var text = File.ReadAllText(cfgPath, Encoding.UTF8); - return JsonSerializer.Deserialize(text); + await _repository.SetPackage(new LocalMicrofrontendPackage(path, _config, _container, _events, _data)); } - - return null; } } diff --git a/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs b/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs index 87eacbf..3e5e76b 100644 --- a/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs +++ b/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs @@ -1,19 +1,17 @@ -using NuGet.Frameworks; -using NuGet.Packaging; +using NuGet.Packaging; using System.Reflection; +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace Piral.Blazor.Orchestrator; -internal class LocalMicrofrontendPackage(string path, JsonObject? config, IModuleContainerService container, IEvents events, IData data) : - MicrofrontendPackage(new MfPackageMetadata { Name = Path.GetFileNameWithoutExtension(path), Version = "0.0.0", Config = config }, container, events, data) +internal class LocalMicrofrontendPackage(string path, IPiralConfig config, IModuleContainerService container, IEvents events, IData data) : + MicrofrontendPackage(MakePackage(path), config, container, events, data) { - private const string target = "net8.0"; private readonly string _path = path; private readonly List _contentRoots = []; - private readonly Dictionary _deps = []; private readonly List _packages = []; private Assembly? LoadAssembly(PackageArchiveReader package, string path) @@ -28,37 +26,53 @@ internal class LocalMicrofrontendPackage(string path, JsonObject? config, IModul return null; } - protected override Assembly? ResolveAssembly(AssemblyName assemblyName) + private static MfPackageMetadata MakePackage(string path) { - var dllName = assemblyName.Name!; - var version = assemblyName.Version?.ToString()!; - return ResolveAssembly(dllName, version); + var name = Path.GetFileNameWithoutExtension(path); + var config = GetMicrofrontendConfig(path); + return new MfPackageMetadata { Name = name, Version = "0.0.0", Config = config }; } - private Assembly? ResolveAssembly(string name, string version) + private static JsonObject? GetMicrofrontendConfig(string path) { - var packageId = $"{name}/{version}"; + var dir = Path.GetDirectoryName(path)!; + var cfgPath = Path.Combine(dir, "config.json"); - if (_deps.TryGetValue(packageId, out var dep) && dep.Type == "package") + if (File.Exists(cfgPath)) { - var packageName = name.ToLowerInvariant(); - var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - var packagePath = Path.Combine(userProfile, ".nuget", "packages", packageName, version, $"{packageName}.{version}.nupkg"); - var stream = File.OpenRead(packagePath); - _packages.Add(new PackageArchiveReader(stream)); - return AddAssemblyToContext(name); + var text = File.ReadAllText(cfgPath, Encoding.UTF8); + return JsonSerializer.Deserialize(text); } - else + + return null; + } + + protected override Assembly? ResolveAssembly(string dll) + { + foreach (var package in _packages) { - var basePath = Path.GetDirectoryName(_path)!; - return Context.LoadFromAssemblyPath(Path.Combine(basePath, $"{name}.dll")); + var libItems = package.GetLibItems().FirstOrDefault(m => IsCompatible(m.TargetFramework))?.Items; + + if (libItems is not null) + { + foreach (var lib in libItems) + { + if (lib.EndsWith(dll)) + { + return LoadAssembly(package, lib); + } + } + } } + + return null; } protected override async Task OnInitializing() { await SetContentRoots(); await SetDependencies(); + await base.OnInitializing(); } private async Task SetContentRoots() @@ -81,7 +95,17 @@ private async Task SetDependencies() if (deps?.Libraries is not null) { - _deps.AddRange(deps.Libraries); + foreach (var lib in deps.Libraries) + { + if (lib.Value.Type == "package" && lib.Value.Path is not null) + { + var packageName = lib.Key.ToLowerInvariant().Replace('/', '.'); + var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var packagePath = Path.Combine(userProfile, ".nuget", "packages", lib.Value.Path, $"{packageName}.nupkg"); + var stream = File.OpenRead(packagePath); + _packages.Add(new PackageArchiveReader(stream)); + } + } } } @@ -128,33 +152,6 @@ private async Task SetDependencies() protected override string GetCssName() => $"{Name}.styles.css"; - private Assembly? AddAssemblyToContext(string dll) - { - foreach (var package in _packages) - { - var libItems = package.GetLibItems().FirstOrDefault(m => IsCompatible(m.TargetFramework))?.Items; - - if (libItems is not null) - { - foreach (var lib in libItems) - { - if (lib.EndsWith(dll)) - { - return LoadAssembly(package, lib); - } - } - } - } - - return null; - } - - private static bool IsCompatible(NuGetFramework framework) - { - var current = NuGetFramework.Parse(target); - return DefaultCompatibilityProvider.Instance.IsCompatible(current, framework); - } - private static async Task GetFile(PackageArchiveReader package, string path) { try diff --git a/src/Piral.Blazor.Orchestrator/MfPackageService.cs b/src/Piral.Blazor.Orchestrator/MfPackageService.cs index 15ba0a7..329534c 100644 --- a/src/Piral.Blazor.Orchestrator/MfPackageService.cs +++ b/src/Piral.Blazor.Orchestrator/MfPackageService.cs @@ -14,7 +14,7 @@ internal class MfPackageService(IPiralConfig config, IModuleContainerService con public async Task LoadMicrofrontend(MfPackageMetadata entry) { var packages = await CollectPackages(entry); - return new NugetMicrofrontendPackage(entry, packages, _config, _container, _events, _data); + return new RemoteMicrofrontendPackage(entry, packages, _config, _container, _events, _data); } private async Task> CollectPackages(MfPackageMetadata entry) diff --git a/src/Piral.Blazor.Orchestrator/MicrofrontendLoadContext.cs b/src/Piral.Blazor.Orchestrator/MicrofrontendLoadContext.cs index d82d7e5..44d5d57 100644 --- a/src/Piral.Blazor.Orchestrator/MicrofrontendLoadContext.cs +++ b/src/Piral.Blazor.Orchestrator/MicrofrontendLoadContext.cs @@ -3,11 +3,11 @@ namespace Piral.Blazor.Orchestrator; -internal class MicrofrontendLoadContext(string name, Func resolve) : AssemblyLoadContext(name, true) +internal class MicrofrontendLoadContext(string name, Func resolve) : AssemblyLoadContext(name, true) { private readonly AssemblyLoadContext _root = All.FirstOrDefault(m => m.Name == "root") ?? Default; - public readonly Func _resolve = resolve; + public readonly Func _resolve = resolve; protected override Assembly? Load(AssemblyName assemblyName) { @@ -32,7 +32,7 @@ internal class MicrofrontendLoadContext(string name, Func assemblies, AssemblyName name) diff --git a/src/Piral.Blazor.Orchestrator/MicrofrontendPackage.cs b/src/Piral.Blazor.Orchestrator/MicrofrontendPackage.cs index 087b2a5..01fb944 100644 --- a/src/Piral.Blazor.Orchestrator/MicrofrontendPackage.cs +++ b/src/Piral.Blazor.Orchestrator/MicrofrontendPackage.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components; +using NuGet.Frameworks; using Piral.Blazor.Shared; using System.Reflection; using System.Runtime.Loader; @@ -10,12 +11,14 @@ public abstract class MicrofrontendPackage : IDisposable { private readonly RelatedMfAppService _app; private readonly IModuleContainerService _container; + private readonly IPiralConfig _config; private readonly MicrofrontendLoadContext _context; public event EventHandler? PackageChanged; - public MicrofrontendPackage(MfPackageMetadata entry, IModuleContainerService container, IEvents events, IData data) + public MicrofrontendPackage(MfPackageMetadata entry, IPiralConfig config, IModuleContainerService container, IEvents events, IData data) { _app = new (entry, events, data); + _config = config; _container = container; _context = new MicrofrontendLoadContext($"{entry.Name}@{entry.Version}", ResolveAssembly); } @@ -90,9 +93,17 @@ public async Task Init() await OnInitialized(); } - protected abstract Assembly? ResolveAssembly(AssemblyName assemblyName); + protected abstract Assembly? ResolveAssembly(string dll); - protected virtual Task OnInitializing() => Task.CompletedTask; + protected virtual Task OnInitializing() + { + foreach (var assembly in _config.IsolatedAssemblies) + { + ResolveAssembly(assembly); + } + + return Task.CompletedTask; + } protected virtual Task OnInitialized() => Task.CompletedTask; @@ -100,6 +111,8 @@ public async Task Init() protected abstract Assembly? GetAssembly(); + protected static bool IsCompatible(NuGetFramework framework) => DefaultCompatibilityProvider.Instance.IsCompatible(Constants.CurrentFramework, framework); + public async Task Destroy() { if (_module is not null) diff --git a/src/Piral.Blazor.Orchestrator/NugetMicrofrontendPackage.cs b/src/Piral.Blazor.Orchestrator/NugetMicrofrontendPackage.cs deleted file mode 100644 index e1146af..0000000 --- a/src/Piral.Blazor.Orchestrator/NugetMicrofrontendPackage.cs +++ /dev/null @@ -1,123 +0,0 @@ -using NuGet.Frameworks; -using NuGet.Packaging; -using System.Reflection; -using System.Text.Json.Nodes; - -namespace Piral.Blazor.Orchestrator; - -internal class NugetMicrofrontendPackage(MfPackageMetadata entry, List packages, IPiralConfig config, IModuleContainerService container, IEvents events, IData data) : - MicrofrontendPackage(entry, container, events, data) -{ - private const string target = "net8.0"; - private readonly IPiralConfig _config = config; - private readonly Dictionary _packages = packages.ToDictionary(m => m.NuspecReader.GetId()); - - private Assembly? LoadAssembly(PackageArchiveReader package, string path) - { - using var msStream = GetFile(package, path).Result; - - if (msStream is not null) - { - return Context.LoadFromStream(msStream); - } - - return null; - } - - private static async Task GetFile(PackageArchiveReader package, string path) - { - try - { - var zip = package.GetEntry(path); - - if (zip is not null) - { - using var zipStream = zip.Open(); - var msStream = new MemoryStream(); - await zipStream.CopyToAsync(msStream); - msStream.Position = 0; - return msStream; - } - } - catch (FileNotFoundException) - { - // This is expected - nothing wrong here - } - catch (InvalidDataException) - { - // This is not expected, but should be handled gracefully - } - - return null; - } - - protected override Assembly? ResolveAssembly(AssemblyName assemblyName) - { - var dll = $"{assemblyName.Name}.dll"; - return AddAssemblyToContext(dll); - } - - private Assembly? AddAssemblyToContext(string dll) - { - foreach (var package in _packages.Values) - { - var libItems = package.GetLibItems().FirstOrDefault(m => IsCompatible(m.TargetFramework))?.Items; - - if (libItems is not null) - { - foreach (var lib in libItems) - { - if (lib.EndsWith(dll)) - { - return LoadAssembly(package, lib); - } - } - } - } - - return null; - } - - private static bool IsCompatible(NuGetFramework framework) - { - var current = NuGetFramework.Parse(target); - return DefaultCompatibilityProvider.Instance.IsCompatible(current, framework); - } - - protected override string GetCssName() => $"{Name}.bundle.scp.css"; - - protected override Assembly? GetAssembly() => LoadAssembly(_packages[Name], $"lib/{target}/{Name}.dll"); - - protected override Task OnInitializing() - { - foreach (var assembly in _config.IsolatedAssemblies) - { - AddAssemblyToContext(assembly); - } - - return base.OnInitializing(); - } - - public override async Task GetFile(string path) - { - if (path.StartsWith("_content")) - { - var segments = path.Split('/'); - var packageName = segments[1]; - var localPath = string.Join('/', segments.Skip(2)); - var package = _packages[packageName]; - - if (package is not null) - { - return await GetFile(package, $"staticwebassets/{localPath}"); - } - - return null; - } - else - { - var package = _packages[Name]; - return await GetFile(package, $"staticwebassets/{path}"); - } - } -} diff --git a/src/Piral.Blazor.Orchestrator/NugetService.cs b/src/Piral.Blazor.Orchestrator/NugetService.cs index ec1dd5d..04f7956 100644 --- a/src/Piral.Blazor.Orchestrator/NugetService.cs +++ b/src/Piral.Blazor.Orchestrator/NugetService.cs @@ -15,7 +15,6 @@ namespace Piral.Blazor.Orchestrator; internal class NugetService : INugetService { private readonly ILogger _logger = NullLogger.Instance; - private readonly NuGetFramework _currentFramework = NuGetFramework.Parse("net8.0"); private readonly FrameworkReducer _frameworkReducer = new(); private readonly SourceCacheContext _cache = new(); @@ -51,7 +50,7 @@ public IEnumerable ListDependencies(PackageArchiveReader read { var dependencyGroups = reader.GetPackageDependencies(); var frameworks = dependencyGroups.Select(m => m.TargetFramework); - var selectedFramework = _frameworkReducer.GetNearest(_currentFramework, frameworks); + var selectedFramework = _frameworkReducer.GetNearest(Constants.CurrentFramework, frameworks); if (selectedFramework is not null) { @@ -116,7 +115,7 @@ private async Task ListAllPackageDependencies(PackageIdentity package, Concurren await Parallel.ForEachAsync(_repositories, async (repository, cancellationToken) => { var dependencyInfoResource = await repository.GetResourceAsync(); - var dependencyInfo = await dependencyInfoResource.ResolvePackage(package, _currentFramework, _cache, _logger, cancellationToken); + var dependencyInfo = await dependencyInfoResource.ResolvePackage(package, Constants.CurrentFramework, _cache, _logger, cancellationToken); if (dependencyInfo is not null && dependencies.TryAdd(dependencyInfo, 1)) { diff --git a/src/Piral.Blazor.Orchestrator/RemoteMicrofrontendPackage.cs b/src/Piral.Blazor.Orchestrator/RemoteMicrofrontendPackage.cs new file mode 100644 index 0000000..4473d30 --- /dev/null +++ b/src/Piral.Blazor.Orchestrator/RemoteMicrofrontendPackage.cs @@ -0,0 +1,102 @@ +using NuGet.Packaging; +using System.Reflection; + +namespace Piral.Blazor.Orchestrator; + +internal class RemoteMicrofrontendPackage(MfPackageMetadata entry, List packages, IPiralConfig config, IModuleContainerService container, IEvents events, IData data) : + MicrofrontendPackage(entry, config, container, events, data) +{ + private readonly List _packages = packages; + + private Assembly? LoadAssembly(PackageArchiveReader? package, string path) + { + if (package is not null) + { + using var msStream = GetFile(package, path).Result; + + if (msStream is not null) + { + return Context.LoadFromStream(msStream); + } + } + + return null; + } + + private static async Task GetFile(PackageArchiveReader? package, string path) + { + try + { + var zip = package?.GetEntry(path); + + if (zip is not null) + { + using var zipStream = zip.Open(); + var msStream = new MemoryStream(); + await zipStream.CopyToAsync(msStream); + msStream.Position = 0; + return msStream; + } + } + catch (FileNotFoundException) + { + // This is expected - nothing wrong here + } + catch (InvalidDataException) + { + // This is not expected, but should be handled gracefully + } + + return null; + } + + protected override Assembly? ResolveAssembly(string dll) + { + foreach (var package in _packages) + { + var libItems = package.GetLibItems().FirstOrDefault(m => IsCompatible(m.TargetFramework))?.Items; + + if (libItems is not null) + { + foreach (var lib in libItems) + { + if (lib.EndsWith(dll)) + { + return LoadAssembly(package, lib); + } + } + } + } + + return null; + } + + protected override string GetCssName() => $"{Name}.bundle.scp.css"; + + protected override Assembly? GetAssembly() => LoadAssembly(FindPackage(Name), $"lib/{Constants.Target}/{Name}.dll"); + + private PackageArchiveReader? FindPackage(string name) + { + return _packages.FirstOrDefault(m => m.NuspecReader.GetId() == name); + } + + public override async Task GetFile(string path) + { + if (path.StartsWith("_content")) + { + var segments = path.Split('/'); + var packageName = segments[1]; + var localPath = string.Join('/', segments.Skip(2)); + var package = FindPackage(packageName); + + if (package is not null) + { + return await GetFile(package, $"staticwebassets/{localPath}"); + } + + return null; + } + + return await GetFile(FindPackage(Name), $"staticwebassets/{path}"); + } +}