mirror of https://github.com/abpframework/abp.git
14 changed files with 435 additions and 34 deletions
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Bundling; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Bundling |
||||
|
{ |
||||
|
public interface IBundler |
||||
|
{ |
||||
|
string Bundle(BundleOptions options, BundleContext context); |
||||
|
} |
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Bundling.Scripts |
||||
|
{ |
||||
|
public interface IScriptBundler : IBundler |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Bundling.Styles |
||||
|
{ |
||||
|
public interface IStyleBundler : IBundler |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue