Browse Source

Merge pull request #6365 from abpframework/blazor-bundling-minification

CLI- blazor bundle and minification support
pull/6439/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
a9f6c470ff
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  2. 21
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleOptions.cs
  3. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleTypeDefinition.cs
  4. 116
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlerBase.cs
  5. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingConsts.cs
  6. 106
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs
  7. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundler.cs
  8. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs
  9. 23
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/PathHelper.cs
  10. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/IScriptBundler.cs
  11. 34
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/ScriptBundler.cs
  12. 77
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs
  13. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/IStyleBundler.cs
  14. 37
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs
  15. 44
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs

@ -3,6 +3,7 @@ using Volo.Abp.Cli.Commands;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.IdentityModel; using Volo.Abp.IdentityModel;
using Volo.Abp.Json; using Volo.Abp.Json;
using Volo.Abp.Minify;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.Cli namespace Volo.Abp.Cli
@ -10,7 +11,8 @@ namespace Volo.Abp.Cli
[DependsOn( [DependsOn(
typeof(AbpDddDomainModule), typeof(AbpDddDomainModule),
typeof(AbpJsonModule), typeof(AbpJsonModule),
typeof(AbpIdentityModelModule) typeof(AbpIdentityModelModule),
typeof(AbpMinifyModule)
)] )]
public class AbpCliCoreModule : AbpModule public class AbpCliCoreModule : AbpModule
{ {

21
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleOptions.cs

@ -0,0 +1,21 @@
using JetBrains.Annotations;
namespace Volo.Abp.Cli.Bundling
{
public class BundleOptions
{
[NotNull]
public string Directory { get; set; }
[NotNull]
public string BundleName { get; set; }
[NotNull]
public string FrameworkVersion { get; set; }
[NotNull]
public string ProjectFileName { get; set; }
public bool Minify { get; set; }
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundleTypeDefinition.cs

@ -6,6 +6,6 @@ namespace Volo.Abp.Cli.Bundling
{ {
public int Level { get; set; } public int Level { get; set; }
public Type BundleContributerType { get; set; } public Type BundleContributorType { get; set; }
} }
} }

116
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlerBase.cs

@ -0,0 +1,116 @@
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Volo.Abp.Bundling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify;
using Volo.Abp.Minify.NUglify;
namespace Volo.Abp.Cli.Bundling
{
public abstract class BundlerBase : IBundler, ITransientDependency
{
private static string[] _minFileSuffixes = { "min", "prod" };
protected IMinifier Minifier { get; }
public ILogger<BundlerBase> Logger { get; set; }
public abstract string FileExtension { get; }
public abstract string GenerateDefinition(string bundleFilePath);
protected BundlerBase(IMinifier minifier)
{
Minifier = minifier;
}
public string Bundle(BundleOptions options, BundleContext context)
{
var bundleFilePath = Path.Combine(PathHelper.GetWwwRootPath(options.Directory), $"{options.BundleName}{FileExtension}");
var bundledContent = BundleFiles(options, context);
File.WriteAllText(bundleFilePath, bundledContent);
return GenerateDefinition(bundleFilePath);
}
private bool IsMinFile(string fileName)
{
foreach (var suffix in _minFileSuffixes)
{
if (fileName.EndsWith($".{suffix}{FileExtension}", StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}
private string BundleFiles(BundleOptions options, BundleContext context)
{
var staticAssetsFilePath = Path.Combine(options.Directory, "bin", "Debug", options.FrameworkVersion, $"{options.ProjectFileName}.StaticWebAssets.xml");
if (!File.Exists(staticAssetsFilePath))
{
throw new BundlingException("Unable to find static web assets file. You need to build the project to generate static web assets file.");
}
var staticAssetsDefinitions = new XmlDocument();
staticAssetsDefinitions.Load(staticAssetsFilePath);
var builder = new StringBuilder();
foreach (var definition in context.BundleDefinitions)
{
string content;
if (definition.Source.StartsWith("_content"))
{
var pathFragments = definition.Source.Split('/').ToList();
var basePath = $"{pathFragments[0]}/{pathFragments[1]}";
var path = staticAssetsDefinitions.SelectSingleNode($"//ContentRoot[@BasePath='{basePath}']").Attributes["Path"].Value;
var absolutePath = definition.Source.Replace(basePath, path);
content = GetFileContent(absolutePath, options.Minify);
}
else if (definition.Source.StartsWith("_framework"))
{
var slashIndex = definition.Source.IndexOf('/');
var fileName = definition.Source.Substring(slashIndex + 1, definition.Source.Length - slashIndex - 1);
var filePath = Path.Combine(PathHelper.GetFrameworkFolderPath(options.Directory, options.FrameworkVersion), fileName);
content = GetFileContent(filePath, false);
}
else
{
var filePath = Path.Combine(PathHelper.GetWwwRootPath(options.Directory), definition.Source);
content = GetFileContent(filePath, options.Minify);
}
content = ProcessBeforeAddingToTheBundle(definition.Source, Path.Combine(options.Directory, "wwwroot"), content);
builder.AppendLine(content);
}
return builder.ToString();
}
private string GetFileContent(string filePath, bool minify)
{
var content = File.ReadAllText(filePath);
if (minify && !IsMinFile(filePath))
{
try
{
content = Minifier.Minify(content);
}
catch (NUglifyException ex)
{
Logger.LogWarning($"Unable to minify the file: {Path.GetFileName(filePath)}. Adding file to the bundle without minification.", ex);
}
}
return content;
}
protected virtual string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent)
{
return fileContent;
}
}
}

11
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingConsts.cs

@ -0,0 +1,11 @@
namespace Volo.Abp.Cli.Bundling
{
internal static class BundlingConsts
{
internal const string StylePlaceholderStart = "<!--ABP:Styles-->";
internal const string StylePlaceholderEnd = "<!--/ABP:Styles-->";
internal const string ScriptPlaceholderStart = "<!--ABP:Scripts-->";
internal const string ScriptPlaceholderEnd = "<!--/ABP:Scripts-->";
internal const string SupportedWebAssemblyProjectType = "Microsoft.NET.Sdk.BlazorWebAssembly";
}
}

106
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs

@ -1,4 +1,5 @@
using System; using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -8,22 +9,25 @@ using System.Threading.Tasks;
using System.Xml; using System.Xml;
using Volo.Abp.Bundling; using Volo.Abp.Bundling;
using Volo.Abp.Cli.Build; using Volo.Abp.Cli.Build;
using Volo.Abp.Cli.Bundling.Scripts;
using Volo.Abp.Cli.Bundling.Styles;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Scripts;
using Volo.Abp.Minify.Styles;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Abp.Cli.Bundling namespace Volo.Abp.Cli.Bundling
{ {
public class BundlingService : IBundlingService, ITransientDependency public class BundlingService : IBundlingService, ITransientDependency
{ {
const string StylePlaceholderStart = "<!--ABP:Styles-->";
const string StylePlaceholderEnd = "<!--/ABP:Styles-->";
const string ScriptPlaceholderStart = "<!--ABP:Scripts-->";
const string ScriptPlaceholderEnd = "<!--/ABP:Scripts-->";
const string SupportedWebAssemblyProjectType = "Microsoft.NET.Sdk.BlazorWebAssembly";
public IDotNetProjectBuilder DotNetProjectBuilder { get; set; } public IDotNetProjectBuilder DotNetProjectBuilder { get; set; }
public IJavascriptMinifier JsMinifier { get; set; }
public ICssMinifier CssMinifier { get; set; }
public ILogger<BundlingService> Logger { get; set; }
public IScriptBundler ScriptBundler { get; set; }
public IStyleBundler StyleBundler { get; set; }
public async Task BundleAsync(string directory, bool forceBuild) public async Task BundleAsync(string directory, bool forceBuild, bool bundle, bool minify, string bundleName)
{ {
var projectFiles = Directory.GetFiles(directory, "*.csproj"); var projectFiles = Directory.GetFiles(directory, "*.csproj");
if (!projectFiles.Any()) if (!projectFiles.Any())
@ -44,17 +48,38 @@ namespace Volo.Abp.Cli.Bundling
} }
var frameworkVersion = GetTargetFrameworkVersion(projectFilePath); var frameworkVersion = GetTargetFrameworkVersion(projectFilePath);
var assemblyFilePath = GetAssemblyFilePath(directory, frameworkVersion, Path.GetFileNameWithoutExtension(projectFilePath)); var projectName = Path.GetFileNameWithoutExtension(projectFilePath);
var assemblyFilePath = PathHelper.GetAssemblyFilePath(directory, frameworkVersion, projectName);
var startupModule = GetStartupModule(assemblyFilePath); var startupModule = GetStartupModule(assemblyFilePath);
var bundleDefinitions = new List<BundleTypeDefinition>(); var bundleDefinitions = new List<BundleTypeDefinition>();
FindBundleContributersRecursively(startupModule, 0, bundleDefinitions); FindBundleContributorsRecursively(startupModule, 0, bundleDefinitions);
bundleDefinitions = bundleDefinitions.OrderByDescending(t => t.Level).ToList(); bundleDefinitions = bundleDefinitions.OrderByDescending(t => t.Level).ToList();
var styleContext = GetStyleContext(bundleDefinitions); var styleContext = GetStyleContext(bundleDefinitions);
var scriptContext = GetScriptContext(bundleDefinitions); var scriptContext = GetScriptContext(bundleDefinitions);
var styleDefinitions = GenerateStyleDefinitions(styleContext); string styleDefinitions;
var scriptDefinitions = GenerateScriptDefinitions(scriptContext); string scriptDefinitions;
if (bundle || minify)
{
var options = new BundleOptions
{
Directory = directory,
FrameworkVersion = frameworkVersion,
ProjectFileName = projectName,
BundleName = bundleName,
Minify = minify
};
styleDefinitions = StyleBundler.Bundle(options, styleContext);
scriptDefinitions = ScriptBundler.Bundle(options, scriptContext);
}
else
{
styleDefinitions = GenerateStyleDefinitions(styleContext);
scriptDefinitions = GenerateScriptDefinitions(scriptContext);
}
await UpdateDependenciesInHtmlFileAsync(directory, styleDefinitions, scriptDefinitions); await UpdateDependenciesInHtmlFileAsync(directory, styleDefinitions, scriptDefinitions);
} }
@ -65,8 +90,8 @@ namespace Volo.Abp.Cli.Bundling
foreach (var bundleDefinition in bundleDefinitions) foreach (var bundleDefinition in bundleDefinitions)
{ {
var contributer = CreateContributerInstance(bundleDefinition.BundleContributerType); var contributor = CreateContributorInstance(bundleDefinition.BundleContributorType);
contributer.AddScripts(scriptContext); contributor.AddScripts(scriptContext);
} }
scriptContext.Add("_framework/blazor.webassembly.js"); scriptContext.Add("_framework/blazor.webassembly.js");
@ -79,8 +104,8 @@ namespace Volo.Abp.Cli.Bundling
foreach (var bundleDefinition in bundleDefinitions) foreach (var bundleDefinition in bundleDefinitions)
{ {
var contributer = CreateContributerInstance(bundleDefinition.BundleContributerType); var contributor = CreateContributorInstance(bundleDefinition.BundleContributorType);
contributer.AddStyles(styleContext); contributor.AddStyles(styleContext);
} }
return styleContext; return styleContext;
@ -88,7 +113,7 @@ namespace Volo.Abp.Cli.Bundling
private async Task UpdateDependenciesInHtmlFileAsync(string directory, string styleDefinitions, string scriptDefinitions) private async Task UpdateDependenciesInHtmlFileAsync(string directory, string styleDefinitions, string scriptDefinitions)
{ {
var htmlFilePath = Path.Combine(directory, "wwwroot", "index.html"); var htmlFilePath = Path.Combine(PathHelper.GetWwwRootPath(directory), "index.html");
if (!File.Exists(htmlFilePath)) if (!File.Exists(htmlFilePath))
{ {
throw new BundlingException($"index.html file could not be found in the following path:{htmlFilePath}"); throw new BundlingException($"index.html file could not be found in the following path:{htmlFilePath}");
@ -102,8 +127,8 @@ namespace Volo.Abp.Cli.Bundling
content = await reader.ReadToEndAsync(); content = await reader.ReadToEndAsync();
} }
content = UpdatePlaceholders(content, StylePlaceholderStart, StylePlaceholderEnd, styleDefinitions); content = UpdatePlaceholders(content, BundlingConsts.StylePlaceholderStart, BundlingConsts.StylePlaceholderEnd, styleDefinitions);
content = UpdatePlaceholders(content, ScriptPlaceholderStart, ScriptPlaceholderEnd, scriptDefinitions); content = UpdatePlaceholders(content, BundlingConsts.ScriptPlaceholderStart, BundlingConsts.ScriptPlaceholderEnd, scriptDefinitions);
using (var writer = new StreamWriter(htmlFilePath, false, fileEncoding)) using (var writer = new StreamWriter(htmlFilePath, false, fileEncoding))
{ {
@ -124,7 +149,7 @@ namespace Volo.Abp.Cli.Bundling
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine($"{StylePlaceholderStart}"); builder.AppendLine($"{BundlingConsts.StylePlaceholderStart}");
foreach (var style in context.BundleDefinitions) foreach (var style in context.BundleDefinitions)
{ {
@ -138,7 +163,7 @@ namespace Volo.Abp.Cli.Bundling
builder.AppendLine("/>"); builder.AppendLine("/>");
} }
builder.Append($" {StylePlaceholderEnd}"); builder.Append($" {BundlingConsts.StylePlaceholderEnd}");
return builder.ToString(); return builder.ToString();
} }
@ -146,7 +171,7 @@ namespace Volo.Abp.Cli.Bundling
private string GenerateScriptDefinitions(BundleContext context) private string GenerateScriptDefinitions(BundleContext context)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine($"{ScriptPlaceholderStart}"); builder.AppendLine($"{BundlingConsts.ScriptPlaceholderStart}");
foreach (var script in context.BundleDefinitions) foreach (var script in context.BundleDefinitions)
{ {
builder.Append($" <script src=\"{script.Source}\""); builder.Append($" <script src=\"{script.Source}\"");
@ -156,35 +181,35 @@ namespace Volo.Abp.Cli.Bundling
} }
builder.AppendLine("></script>"); builder.AppendLine("></script>");
} }
builder.Append($" {ScriptPlaceholderEnd}"); builder.Append($" {BundlingConsts.ScriptPlaceholderEnd}");
return builder.ToString(); return builder.ToString();
} }
private IBundleContributor CreateContributerInstance(Type bundleContributerType) private IBundleContributor CreateContributorInstance(Type bundleContributorType)
{ {
return (IBundleContributor)Activator.CreateInstance(bundleContributerType); return (IBundleContributor)Activator.CreateInstance(bundleContributorType);
} }
private void FindBundleContributersRecursively( private void FindBundleContributorsRecursively(
Type module, Type module,
int level, int level,
List<BundleTypeDefinition> bundleDefinitions) List<BundleTypeDefinition> bundleDefinitions)
{ {
var bundleContributers = module.Assembly var bundleContributors = module.Assembly
.GetTypes() .GetTypes()
.Where(t => t.IsAssignableTo<IBundleContributor>()) .Where(t => t.IsAssignableTo<IBundleContributor>())
.ToList(); .ToList();
if (bundleContributers.Count > 1) if (bundleContributors.Count > 1)
{ {
throw new BundlingException($"Each project must contain only one class implementing {nameof(IBundleContributor)}"); throw new BundlingException($"Each project must contain only one class implementing {nameof(IBundleContributor)}");
} }
if (bundleContributers.Any()) if (bundleContributors.Any())
{ {
var bundleContributer = bundleContributers[0]; var bundleContributor = bundleContributors[0];
var definition = bundleDefinitions.SingleOrDefault(t => t.BundleContributerType == bundleContributer); var definition = bundleDefinitions.SingleOrDefault(t => t.BundleContributorType == bundleContributor);
if (definition != null) if (definition != null)
{ {
if (definition.Level < level) if (definition.Level < level)
@ -197,7 +222,7 @@ namespace Volo.Abp.Cli.Bundling
bundleDefinitions.Add(new BundleTypeDefinition bundleDefinitions.Add(new BundleTypeDefinition
{ {
Level = level, Level = level,
BundleContributerType = bundleContributer BundleContributorType = bundleContributor
}); });
} }
} }
@ -210,7 +235,7 @@ namespace Volo.Abp.Cli.Bundling
{ {
foreach (var dependedModuleType in descriptor.GetDependedTypes()) foreach (var dependedModuleType in descriptor.GetDependedTypes())
{ {
FindBundleContributersRecursively(dependedModuleType, level + 1, bundleDefinitions); FindBundleContributorsRecursively(dependedModuleType, level + 1, bundleDefinitions);
} }
} }
} }
@ -223,28 +248,17 @@ namespace Volo.Abp.Cli.Bundling
.SingleOrDefault(AbpModule.IsAbpModule); .SingleOrDefault(AbpModule.IsAbpModule);
} }
private string GetFrameworkFolderPath(string projectDirectory, string frameworkVersion)
{
return Path.Combine(projectDirectory, "bin", "Debug", frameworkVersion, "wwwroot", "_framework"); ;
}
private string GetTargetFrameworkVersion(string projectFilePath) private string GetTargetFrameworkVersion(string projectFilePath)
{ {
var document = new XmlDocument(); var document = new XmlDocument();
document.Load(projectFilePath); document.Load(projectFilePath);
var sdk = document.DocumentElement.GetAttribute("Sdk"); var sdk = document.DocumentElement.GetAttribute("Sdk");
if (sdk != SupportedWebAssemblyProjectType) if (sdk != BundlingConsts.SupportedWebAssemblyProjectType)
{ {
throw new BundlingException($"Unsupported project type. Project type must be {SupportedWebAssemblyProjectType}."); throw new BundlingException($"Unsupported project type. Project type must be {BundlingConsts.SupportedWebAssemblyProjectType}.");
} }
return document.SelectSingleNode("//TargetFramework").InnerText; return document.SelectSingleNode("//TargetFramework").InnerText;
} }
private string GetAssemblyFilePath(string directory, string frameworkVersion, string projectFileName)
{
var outputDirectory = GetFrameworkFolderPath(directory, frameworkVersion);
return Path.Combine(outputDirectory, projectFileName + ".dll");
}
} }
} }

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundler.cs

@ -0,0 +1,9 @@
using Volo.Abp.Bundling;
namespace Volo.Abp.Cli.Bundling
{
public interface IBundler
{
string Bundle(BundleOptions options, BundleContext context);
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs

@ -4,6 +4,6 @@ namespace Volo.Abp.Cli.Bundling
{ {
public interface IBundlingService public interface IBundlingService
{ {
Task BundleAsync(string directory, bool forceBuild); Task BundleAsync(string directory, bool forceBuild, bool bundle, bool minify, string bundleName);
} }
} }

23
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/PathHelper.cs

@ -0,0 +1,23 @@
using System.IO;
namespace Volo.Abp.Cli.Bundling
{
internal static class PathHelper
{
internal static string GetFrameworkFolderPath(string projectDirectory, string frameworkVersion)
{
return Path.Combine(projectDirectory, "bin", "Debug", frameworkVersion, "wwwroot", "_framework"); ;
}
internal static string GetAssemblyFilePath(string directory, string frameworkVersion, string projectFileName)
{
var outputDirectory = GetFrameworkFolderPath(directory, frameworkVersion);
return Path.Combine(outputDirectory, projectFileName + ".dll");
}
internal static string GetWwwRootPath(string directory)
{
return Path.Combine(directory, "wwwroot");
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/IScriptBundler.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Cli.Bundling.Scripts
{
public interface IScriptBundler : IBundler
{
}
}

34
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/ScriptBundler.cs

@ -0,0 +1,34 @@
using System;
using System.IO;
using System.Text;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.Cli.Bundling.Scripts
{
public class ScriptBundler : BundlerBase, IScriptBundler, ITransientDependency
{
public override string FileExtension => ".js";
public ScriptBundler(IJavascriptMinifier minifier)
: base(minifier)
{
}
public override string GenerateDefinition(string bundleFilePath)
{
var lastModifiedTicks = File.GetLastWriteTime(bundleFilePath).Ticks;
var builder = new StringBuilder();
builder.AppendLine($"{BundlingConsts.ScriptPlaceholderStart}");
builder.AppendLine($" <script src=\"{Path.GetFileName(bundleFilePath)}?_v={lastModifiedTicks}\"></script>");
builder.Append($" {BundlingConsts.ScriptPlaceholderEnd}");
return builder.ToString();
}
protected override string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent)
{
return fileContent.EnsureEndsWith(';') + Environment.NewLine;
}
}
}

77
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs

@ -0,0 +1,77 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace Volo.Abp.Cli.Bundling.Styles
{
internal static class CssRelativePathAdjuster
{
private static readonly Regex _rxUrl = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public static string Adjust(
string cssFileContents,
string absoluteInputFilePath,
string absoluteOutputPath)
{
var matches = _rxUrl.Matches(cssFileContents);
if (matches.Count <= 0)
{
return cssFileContents;
}
var cssDirectoryPath = Path.GetDirectoryName(absoluteInputFilePath);
foreach (Match match in matches)
{
string quoteDelimiter = match.Groups[1].Value; //url('') vs url("")
string relativePathToCss = match.Groups[2].Value;
// Ignore root relative references
if (relativePathToCss.StartsWith("/", StringComparison.Ordinal))
continue;
//prevent query string from causing error
var pathAndQuery = relativePathToCss.Split(new[] { '?' }, 2, StringSplitOptions.RemoveEmptyEntries);
var pathOnly = pathAndQuery[0];
var queryOnly = pathAndQuery.Length == 2 ? pathAndQuery[1] : string.Empty;
string absolutePath = GetAbsolutePath(cssDirectoryPath, pathOnly);
string serverRelativeUrl = MakeRelative(absoluteOutputPath, absolutePath);
if (!string.IsNullOrEmpty(queryOnly))
serverRelativeUrl += "?" + queryOnly;
string replace = string.Format("url({0}{1}{0})", quoteDelimiter, serverRelativeUrl);
cssFileContents = cssFileContents.Replace(match.Groups[0].Value, replace);
}
return cssFileContents;
}
private static string GetAbsolutePath(string cssFilePath, string pathOnly)
{
return Path.GetFullPath(Path.Combine(cssFilePath, pathOnly));
}
private static readonly string _protocol = "file:///";
private static string MakeRelative(string baseFile, string file)
{
if (string.IsNullOrEmpty(file))
return file;
Uri baseUri = new Uri(_protocol + baseFile, UriKind.RelativeOrAbsolute);
Uri fileUri = new Uri(_protocol + file, UriKind.RelativeOrAbsolute);
if (baseUri.IsAbsoluteUri)
{
return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fileUri).ToString());
}
else
{
return baseUri.ToString();
}
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/IStyleBundler.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Cli.Bundling.Styles
{
public interface IStyleBundler : IBundler
{
}
}

37
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs

@ -0,0 +1,37 @@
using System.IO;
using System.Text;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Styles;
namespace Volo.Abp.Cli.Bundling.Styles
{
public class StyleBundler : BundlerBase, IStyleBundler, ITransientDependency
{
public override string FileExtension => ".css";
public StyleBundler(ICssMinifier minifier)
: base(minifier)
{
}
public override string GenerateDefinition(string bundleFilePath)
{
var lastModifiedTicks = File.GetLastWriteTime(bundleFilePath).Ticks;
var builder = new StringBuilder();
builder.AppendLine($"{BundlingConsts.StylePlaceholderStart}");
builder.AppendLine($" <link href=\"{Path.GetFileName(bundleFilePath)}?_v={lastModifiedTicks}\" rel=\"stylesheet\"/>");
builder.Append($" {BundlingConsts.StylePlaceholderEnd}");
return builder.ToString();
}
protected override string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent)
{
return CssRelativePathAdjuster.Adjust(
fileContent,
referencePath,
bundleDirectory
);
}
}
}

44
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs

@ -28,6 +28,26 @@ namespace Volo.Abp.Cli.Commands
var forceBuild = commandLineArgs.Options.ContainsKey(Options.ForceBuild.Short) || var forceBuild = commandLineArgs.Options.ContainsKey(Options.ForceBuild.Short) ||
commandLineArgs.Options.ContainsKey(Options.ForceBuild.Long); commandLineArgs.Options.ContainsKey(Options.ForceBuild.Long);
var bundle = commandLineArgs.Options.ContainsKey(Options.Bundle.Short) ||
commandLineArgs.Options.ContainsKey(Options.Bundle.Long);
var minify = commandLineArgs.Options.ContainsKey(Options.Minify.Short) ||
commandLineArgs.Options.ContainsKey(Options.Minify.Long);
var name = commandLineArgs.Options.GetOrNull(
Options.Name.Short,
Options.Name.Long
);
if ((minify || bundle) && name.IsNullOrEmpty())
{
throw new CliUsageException(
"Please specify a bundle name." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
if (!Directory.Exists(workingDirectory)) if (!Directory.Exists(workingDirectory))
{ {
throw new CliUsageException( throw new CliUsageException(
@ -39,7 +59,7 @@ namespace Volo.Abp.Cli.Commands
try try
{ {
await BundlingService.BundleAsync(workingDirectory, forceBuild); await BundlingService.BundleAsync(workingDirectory, forceBuild, bundle, minify, name);
} }
catch (BundlingException ex) catch (BundlingException ex)
{ {
@ -65,6 +85,10 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(""); sb.AppendLine("");
sb.AppendLine("-wd|--working-directory <directory-path> (default: empty)"); sb.AppendLine("-wd|--working-directory <directory-path> (default: empty)");
sb.AppendLine("-f | --force (default: false)"); sb.AppendLine("-f | --force (default: false)");
sb.AppendLine("-f | --force (default: false)");
sb.AppendLine("-b | --bundle (default: false)");
sb.AppendLine("-m | --minify (default: false)");
sb.AppendLine("-n | --name (default: empty)");
sb.AppendLine(""); sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
@ -84,6 +108,24 @@ namespace Volo.Abp.Cli.Commands
public const string Short = "f"; public const string Short = "f";
public const string Long = "force"; public const string Long = "force";
} }
public static class Bundle
{
public const string Short = "b";
public const string Long = "bundle";
}
public static class Minify
{
public const string Short = "m";
public const string Long = "minify";
}
public static class Name
{
public const string Short = "n";
public const string Long = "name";
}
} }
} }
} }

Loading…
Cancel
Save