Skip to content

Commit

Permalink
Improve local emulator to read nuget packages as remote does
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianRappl committed May 9, 2024
1 parent 698eba5 commit 6eed680
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 205 deletions.
9 changes: 9 additions & 0 deletions src/Piral.Blazor.Orchestrator/Constants.cs
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 4 additions & 22 deletions src/Piral.Blazor.Orchestrator/Loader/MfLocalLoaderService.cs
Original file line number Diff line number Diff line change
@@ -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>(T originalLoader, IMfRepository repository, IModuleContainerService container, IEvents events, IData data) : IMfLoaderService
internal class MfLocalLoaderService<T>(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;

Expand All @@ -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<JsonObject?>(text);
await _repository.SetPackage(new LocalMicrofrontendPackage(path, _config, _container, _events, _data));
}

return null;
}
}
97 changes: 47 additions & 50 deletions src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs
Original file line number Diff line number Diff line change
@@ -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<string> _contentRoots = [];
private readonly Dictionary<string, DependencyDescription> _deps = [];
private readonly List<PackageArchiveReader> _packages = [];

private Assembly? LoadAssembly(PackageArchiveReader package, string path)
Expand All @@ -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<JsonObject?>(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()
Expand All @@ -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));
}
}
}
}

Expand Down Expand Up @@ -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<MemoryStream?> GetFile(PackageArchiveReader package, string path)
{
try
Expand Down
2 changes: 1 addition & 1 deletion src/Piral.Blazor.Orchestrator/MfPackageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class MfPackageService(IPiralConfig config, IModuleContainerService con
public async Task<MicrofrontendPackage> 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<List<PackageArchiveReader>> CollectPackages(MfPackageMetadata entry)
Expand Down
6 changes: 3 additions & 3 deletions src/Piral.Blazor.Orchestrator/MicrofrontendLoadContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

namespace Piral.Blazor.Orchestrator;

internal class MicrofrontendLoadContext(string name, Func<AssemblyName, Assembly?> resolve) : AssemblyLoadContext(name, true)
internal class MicrofrontendLoadContext(string name, Func<string, Assembly?> resolve) : AssemblyLoadContext(name, true)
{
private readonly AssemblyLoadContext _root = All.FirstOrDefault(m => m.Name == "root") ?? Default;

public readonly Func<AssemblyName, Assembly?> _resolve = resolve;
public readonly Func<string, Assembly?> _resolve = resolve;

protected override Assembly? Load(AssemblyName assemblyName)
{
Expand All @@ -32,7 +32,7 @@ internal class MicrofrontendLoadContext(string name, Func<AssemblyName, Assembly
}

// Let's find it via the custom resolve
return _resolve(assemblyName);
return _resolve($"{assemblyName.Name}.dll");
}

private static Assembly? GetExisting(IEnumerable<Assembly> assemblies, AssemblyName name)
Expand Down
19 changes: 16 additions & 3 deletions src/Piral.Blazor.Orchestrator/MicrofrontendPackage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using NuGet.Frameworks;
using Piral.Blazor.Shared;
using System.Reflection;
using System.Runtime.Loader;
Expand All @@ -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);
}
Expand Down Expand Up @@ -90,16 +93,26 @@ 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;

protected abstract string GetCssName();

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)
Expand Down
Loading

0 comments on commit 6eed680

Please sign in to comment.