From 4564d3e6d5255a84d5e9fa2016f4db8f4eebfa41 Mon Sep 17 00:00:00 2001 From: Ilkay Ilknur Date: Tue, 3 Nov 2020 14:15:53 +0300 Subject: [PATCH] bundle command initial implementation. --- .../BundleContributer.cs | 18 ++ .../BundleContributer.cs | 20 ++ .../WebAssembly/BundleContributer.cs | 18 ++ .../Volo.Abp.BlazoriseUI/BundleContributer.cs | 21 ++ .../Volo/Abp/Cli/AbpCliCoreModule.cs | 1 + .../Volo/Abp/Cli/Bundling/BundleDefinition.cs | 10 + .../Abp/Cli/Bundling/BundlingException.cs | 12 + .../Volo/Abp/Cli/Bundling/BundlingService.cs | 224 ++++++++++++++++++ .../Volo/Abp/Cli/Bundling/IBundlingService.cs | 9 + .../Volo/Abp/Cli/Commands/BundleCommand.cs | 74 ++++++ .../Volo/Abp/Bundling/IBundleContributer.cs | 12 + .../WebAssembly/BundleContributer.cs | 18 ++ 12 files changed, 437 insertions(+) create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BundleContributer.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/BundleContributer.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/BundleContributer.cs create mode 100644 framework/src/Volo.Abp.BlazoriseUI/BundleContributer.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleDefinition.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingException.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/Bundling/IBundleContributer.cs create mode 100644 framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/BundleContributer.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BundleContributer.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BundleContributer.cs new file mode 100644 index 0000000000..90b45c2215 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BundleContributer.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Volo.Abp.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme +{ + public class BundleContributer : IBundleContributer + { + public void AddScripts(List scripts) + { + + } + + public void AddStyles(List styles) + { + styles.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/libs/abp/css/theme.css"); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/BundleContributer.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/BundleContributer.cs new file mode 100644 index 0000000000..0c8e70429f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/BundleContributer.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Volo.Abp.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming +{ + public class BundleContributer : IBundleContributer + { + public void AddScripts(List scripts) + { + + } + + public void AddStyles(List styles) + { + styles.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/bootstrap/css/bootstrap.css"); + styles.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/fontawesome/css/all.css"); + styles.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/flag-icon/css/flag-icon.css"); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/BundleContributer.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/BundleContributer.cs new file mode 100644 index 0000000000..4d65f03e4e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/BundleContributer.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Volo.Abp.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class BundleContributer : IBundleContributer + { + public void AddScripts(List scripts) + { + scripts.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly/libs/abp/js/abp.js"); + } + + public void AddStyles(List styles) + { + + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/BundleContributer.cs b/framework/src/Volo.Abp.BlazoriseUI/BundleContributer.cs new file mode 100644 index 0000000000..10e35c5e10 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/BundleContributer.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Volo.Abp.Bundling; + +namespace Volo.Abp.BlazoriseUI +{ + public class BundleContributer : IBundleContributer + { + public void AddScripts(List scripts) + { + scripts.AddIfNotContains("_content/Blazorise/blazorise.js"); + scripts.AddIfNotContains("_content/Blazorise.Bootstrap/blazorise.bootstrap.js"); + } + + public void AddStyles(List styles) + { + styles.AddIfNotContains("_content/Blazorise/blazorise.css"); + styles.AddIfNotContains("_content/Blazorise.Bootstrap/blazorise.bootstrap.css"); + styles.AddIfNotContains("_content/Blazorise.Snackbar/blazorise.snackbar.css"); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 29238a3e35..d4d1d1d22e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -37,6 +37,7 @@ namespace Volo.Abp.Cli options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand); options.Commands["translate"] = typeof(TranslateCommand); options.Commands["build"] = typeof(BuildCommand); + options.Commands["bundle"] = typeof(BundleCommand); }); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleDefinition.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleDefinition.cs new file mode 100644 index 0000000000..fef5e3d5d2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleDefinition.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Cli.Bundling +{ + internal class BundleDefinition + { + public int Level { get; set; } + public Type BundleContributerType { get; set; } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingException.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingException.cs new file mode 100644 index 0000000000..31f40a2cd7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.Cli.Bundling +{ + public class BundlingException : Exception + { + public BundlingException(string message) : base(message) + { + + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs new file mode 100644 index 0000000000..f2f8e24a3c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs @@ -0,0 +1,224 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Volo.Abp.Bundling; +using Volo.Abp.Cli.Build; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Cli.Bundling +{ + public class BundlingService : IBundlingService, ITransientDependency + { + const string StylePlaceholderStart = ""; + const string StylePlaceholderEnd = ""; + const string ScriptPlaceholderStart = ""; + const string ScriptPlaceholderEnd = ""; + const string SupportedWebAssemblyProjectType = "Microsoft.NET.Sdk.BlazorWebAssembly"; + + protected IDotNetProjectBuilder DotNetProjectBuilder { get; set; } + protected ILogger Logger { get; set; } + + public async Task BundleAsync(string directory, bool forceBuild) + { + var projectFiles = Directory.GetFiles(directory, "*.csproj"); + if (!projectFiles.Any()) + { + throw new BundlingException("No project file found in the directory"); + } + + var projectFilePath = projectFiles[0]; + + if (forceBuild) + { + var projects = new List() + { + new DotNetProjectInfo(string.Empty, projectFilePath, true) + }; + Logger.LogInformation("Build starting..."); + DotNetProjectBuilder.Build(projects, string.Empty); + Logger.LogInformation("Build completed..."); + } + + var frameworkVersion = GetTargetFrameworkVersion(projectFilePath); + var assemblyFilePath = GetAssemblyFilePath(directory, frameworkVersion, Path.GetFileNameWithoutExtension(projectFilePath)); + var startupModule = GetStartupModule(assemblyFilePath); + + var bundleDefinitions = new List(); + FindBundleContributersRecursively(startupModule, 0, bundleDefinitions); + bundleDefinitions = bundleDefinitions.OrderByDescending(t => t.Level).ToList(); + + var styleDefinitons = GenerateStyleDefinitions(bundleDefinitions); + var scriptDefinitions = GenerateScriptDefinitions(bundleDefinitions); + + await UpdateDependenciesInHtmlFileAsync(directory, styleDefinitons, scriptDefinitions); + } + + protected virtual async Task UpdateDependenciesInHtmlFileAsync(string directory, string styleDefinitions, string scriptDefinitions) + { + var htmlFilePath = Path.Combine(directory, "wwwroot", "index.html"); + if (!File.Exists(htmlFilePath)) + { + throw new BundlingException($"index.html file could not be found in the following path:{htmlFilePath}"); + } + + Encoding fileEncoding; + string content; + using (var reader = new StreamReader(htmlFilePath, true)) + { + fileEncoding = reader.CurrentEncoding; + content = await reader.ReadToEndAsync(); + } + + content = UpdatePlaceholders(content, StylePlaceholderStart, StylePlaceholderEnd, styleDefinitions); + content = UpdatePlaceholders(content, ScriptPlaceholderStart, ScriptPlaceholderEnd, scriptDefinitions); + + using var writer = new StreamWriter(htmlFilePath, false, fileEncoding); + await writer.WriteAsync(content); + await writer.FlushAsync(); + } + + private string UpdatePlaceholders(string content, string placeholderStart, string placeholderEnd, string definitions) + { + var placeholderStartIndex = content.IndexOf(placeholderStart); + var placeholderEndIndex = content.IndexOf(placeholderEnd); + var updatedContent = content.Remove(placeholderStartIndex, (placeholderEndIndex + placeholderEnd.Length) - placeholderStartIndex); + return updatedContent.Insert(placeholderStartIndex, definitions); + } + + private string GenerateStyleDefinitions(List bundleDefinitions) + { + var styles = new List(); + foreach (var bundleDefinition in bundleDefinitions) + { + var contributer = CreateContributerInstance(bundleDefinition.BundleContributerType); + contributer.AddStyles(styles); + } + + var builder = new StringBuilder(); + builder.AppendLine($"{StylePlaceholderStart}"); + foreach (var style in styles) + { + builder.AppendLine($"\t"); + } + builder.AppendLine($"\t{StylePlaceholderEnd}"); + + return builder.ToString(); + } + + private string GenerateScriptDefinitions(List bundleDefinitions) + { + var scripts = new List + { + "_framework/blazor.webassembly.js" + }; + foreach (var bundleDefinition in bundleDefinitions) + { + var contributer = CreateContributerInstance(bundleDefinition.BundleContributerType); + contributer.AddScripts(scripts); + } + + var builder = new StringBuilder(); + builder.AppendLine($"{ScriptPlaceholderStart}"); + foreach (var script in scripts) + { + builder.AppendLine($"\t"); + } + builder.AppendLine($"\t{ScriptPlaceholderEnd}"); + + return builder.ToString(); + } + + private IBundleContributer CreateContributerInstance(Type bundleContributerType) + { + var instance = Activator.CreateInstance(bundleContributerType); + return instance.As(); + } + + private void FindBundleContributersRecursively(Type module, int level, List bundleDefinitions) + { + var dependencyDescriptors = module + .GetCustomAttributes() + .OfType(); + + var bundleContributer = module.Assembly.GetTypes().SingleOrDefault(t => t.IsAssignableTo()); + if (bundleContributer != null) + { + var definition = bundleDefinitions.SingleOrDefault(t => t.BundleContributerType == bundleContributer); + if (definition != null) + { + if (definition.Level < level) + { + definition.Level = level; + } + } + else + { + bundleDefinitions.Add(new BundleDefinition + { + Level = level, + BundleContributerType = bundleContributer + }); + } + } + + foreach (var descriptor in dependencyDescriptors) + { + foreach (var dependedModuleType in descriptor.GetDependedTypes()) + { + FindBundleContributersRecursively(dependedModuleType, level + 1, bundleDefinitions); + } + } + } + + private Type GetStartupModule(string assemblyPath) + { + var assembly = Assembly.LoadFrom(assemblyPath); + return assembly.GetTypes().SingleOrDefault(IsAbpModule); + + static bool IsAbpModule(Type type) + { + var typeInfo = type.GetTypeInfo(); + + return + typeInfo.IsClass && + !typeInfo.IsAbstract && + !typeInfo.IsGenericType && + typeof(IAbpModule).GetTypeInfo().IsAssignableFrom(type); + } + } + + private string GetFrameworkFolderPath(string projectDirectory, string frameworkVersion) + { + return Path.Combine(projectDirectory, "bin", "Debug", frameworkVersion, "wwwroot", "_framework"); ; + } + + private string GetTargetFrameworkVersion(string projectFilePath) + { + var document = new XmlDocument(); + document.Load(projectFilePath); + var sdk = document.DocumentElement.GetAttribute("Sdk"); + if (sdk == SupportedWebAssemblyProjectType) + { + var frameworkVersion = document.SelectSingleNode("//TargetFramework").InnerText; + return frameworkVersion; + } + else + { + throw new BundlingException($"Unsupported project type. Project type must be {SupportedWebAssemblyProjectType}."); + } + } + + private string GetAssemblyFilePath(string directory, string frameworkVersion, string projectFileName) + { + var outputDirectory = GetFrameworkFolderPath(directory, frameworkVersion); + return Path.Combine(outputDirectory, projectFileName + ".dll"); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs new file mode 100644 index 0000000000..817735ee20 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Cli.Bundling +{ + public interface IBundlingService + { + Task BundleAsync(string directory, bool forceBuild); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs new file mode 100644 index 0000000000..e3ca0397a8 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs @@ -0,0 +1,74 @@ +using Microsoft.Extensions.Logging; +using System; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Bundling; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class BundleCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + public IBundlingService BundlingService { get; set; } + + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var workingDirectoryArg = commandLineArgs.Options.GetOrNull( + Options.WorkingDirectory.Short, + Options.WorkingDirectory.Long + ); + + var workingDirectory = workingDirectoryArg ?? Directory.GetCurrentDirectory(); + + var forceBuild = commandLineArgs.Options.ContainsKey(Options.ForceBuild.Short) || + commandLineArgs.Options.ContainsKey(Options.ForceBuild.Long); + + if (!Directory.Exists(workingDirectory)) + { + throw new CliUsageException( + "Specified directory does not exist." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + try + { + await BundlingService.BundleAsync(workingDirectory, forceBuild); + } + catch (BundlingException ex) + { + Logger.LogError(ex.Message); + } + } + + public string GetShortDescription() + { + throw new NotImplementedException(); + } + + public string GetUsageInfo() + { + throw new NotImplementedException(); + } + + public static class Options + { + public static class WorkingDirectory + { + public const string Short = "wd"; + public const string Long = "working-directory"; + } + + public static class ForceBuild + { + public const string Short = "f"; + public const string Long = "force"; + } + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Bundling/IBundleContributer.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Bundling/IBundleContributer.cs new file mode 100644 index 0000000000..c9201fe007 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Bundling/IBundleContributer.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.Bundling +{ + public interface IBundleContributer + { + void AddScripts(List scripts); + void AddStyles(List styles); + } +} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/BundleContributer.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/BundleContributer.cs new file mode 100644 index 0000000000..da4c01512a --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/BundleContributer.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Bundling; + +namespace Volo.Abp.Http.Client.IdentityModel.WebAssembly +{ + public class BundleContributer : IBundleContributer + { + public void AddScripts(List scripts) + { + scripts.AddIfNotContains("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); + } + + public void AddStyles(List styles) + { + } + } +}