Browse Source

bundle and minification support added.

pull/6365/head
Ilkay Ilknur 6 years ago
parent
commit
dcac0ddce8
  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. 116
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlerBase.cs
  4. 11
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingConsts.cs
  5. 75
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs
  6. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundler.cs
  7. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/IBundlingService.cs
  8. 23
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/PathHelper.cs
  9. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/IScriptBundler.cs
  10. 34
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Scripts/ScriptBundler.cs
  11. 77
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs
  12. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/IStyleBundler.cs
  13. 37
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs
  14. 40
      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.IdentityModel;
using Volo.Abp.Json;
using Volo.Abp.Minify;
using Volo.Abp.Modularity;
namespace Volo.Abp.Cli
@ -10,7 +11,8 @@ namespace Volo.Abp.Cli
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpJsonModule),
typeof(AbpIdentityModelModule)
typeof(AbpIdentityModelModule),
typeof(AbpMinifyModule)
)]
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; }
}
}

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; }
protected 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";
}
}

75
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.IO;
using System.Linq;
@ -8,22 +9,25 @@ using System.Threading.Tasks;
using System.Xml;
using Volo.Abp.Bundling;
using Volo.Abp.Cli.Build;
using Volo.Abp.Cli.Bundling.Scripts;
using Volo.Abp.Cli.Bundling.Styles;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Scripts;
using Volo.Abp.Minify.Styles;
using Volo.Abp.Modularity;
namespace Volo.Abp.Cli.Bundling
{
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 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");
if (!projectFiles.Any())
@ -44,7 +48,8 @@ namespace Volo.Abp.Cli.Bundling
}
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 bundleDefinitions = new List<BundleTypeDefinition>();
@ -53,8 +58,27 @@ namespace Volo.Abp.Cli.Bundling
var styleContext = GetStyleContext(bundleDefinitions);
var scriptContext = GetScriptContext(bundleDefinitions);
var styleDefinitions = GenerateStyleDefinitions(styleContext);
var scriptDefinitions = GenerateScriptDefinitions(scriptContext);
string styleDefinitions;
string scriptDefinitions;
if (bundle || minify)
{
var options = new BundleOptions
{
Directory = directory,
FrameworkVersion = frameworkVersion,
ProjectFileName = projectName,
BundleName = bundleName
};
styleDefinitions = StyleBundler.Bundle(options, styleContext);
scriptDefinitions = ScriptBundler.Bundle(options, scriptContext);
}
else
{
styleDefinitions = GenerateStyleDefinitions(styleContext);
scriptDefinitions = GenerateScriptDefinitions(scriptContext);
}
await UpdateDependenciesInHtmlFileAsync(directory, styleDefinitions, scriptDefinitions);
}
@ -88,7 +112,7 @@ namespace Volo.Abp.Cli.Bundling
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))
{
throw new BundlingException($"index.html file could not be found in the following path:{htmlFilePath}");
@ -102,8 +126,8 @@ namespace Volo.Abp.Cli.Bundling
content = await reader.ReadToEndAsync();
}
content = UpdatePlaceholders(content, StylePlaceholderStart, StylePlaceholderEnd, styleDefinitions);
content = UpdatePlaceholders(content, ScriptPlaceholderStart, ScriptPlaceholderEnd, scriptDefinitions);
content = UpdatePlaceholders(content, BundlingConsts.StylePlaceholderStart, BundlingConsts.StylePlaceholderEnd, styleDefinitions);
content = UpdatePlaceholders(content, BundlingConsts.ScriptPlaceholderStart, BundlingConsts.ScriptPlaceholderEnd, scriptDefinitions);
using (var writer = new StreamWriter(htmlFilePath, false, fileEncoding))
{
@ -124,7 +148,7 @@ namespace Volo.Abp.Cli.Bundling
{
var builder = new StringBuilder();
builder.AppendLine($"{StylePlaceholderStart}");
builder.AppendLine($"{BundlingConsts.StylePlaceholderStart}");
foreach (var style in context.BundleDefinitions)
{
@ -138,7 +162,7 @@ namespace Volo.Abp.Cli.Bundling
builder.AppendLine("/>");
}
builder.Append($" {StylePlaceholderEnd}");
builder.Append($" {BundlingConsts.StylePlaceholderEnd}");
return builder.ToString();
}
@ -146,7 +170,7 @@ namespace Volo.Abp.Cli.Bundling
private string GenerateScriptDefinitions(BundleContext context)
{
var builder = new StringBuilder();
builder.AppendLine($"{ScriptPlaceholderStart}");
builder.AppendLine($"{BundlingConsts.ScriptPlaceholderStart}");
foreach (var script in context.BundleDefinitions)
{
builder.Append($" <script src=\"{script.Source}\"");
@ -156,7 +180,7 @@ namespace Volo.Abp.Cli.Bundling
}
builder.AppendLine("></script>");
}
builder.Append($" {ScriptPlaceholderEnd}");
builder.Append($" {BundlingConsts.ScriptPlaceholderEnd}");
return builder.ToString();
}
@ -223,28 +247,17 @@ namespace Volo.Abp.Cli.Bundling
.SingleOrDefault(AbpModule.IsAbpModule);
}
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)
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;
}
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
{
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
);
}
}
}

40
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) ||
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 bundle name." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
if (!Directory.Exists(workingDirectory))
{
throw new CliUsageException(
@ -39,7 +59,7 @@ namespace Volo.Abp.Cli.Commands
try
{
await BundlingService.BundleAsync(workingDirectory, forceBuild);
await BundlingService.BundleAsync(workingDirectory, forceBuild, bundle, minify, name);
}
catch (BundlingException ex)
{
@ -84,6 +104,24 @@ namespace Volo.Abp.Cli.Commands
public const string Short = "f";
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