Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/en/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,4 +419,4 @@ abp bundle [options]
* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file.
* ```--force``` or ```-f```: Forces to build project before generating references.

For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)
`bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)
47 changes: 47 additions & 0 deletions docs/en/UI/Blazor/Global-Scripts-Styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,50 @@ namespace MyProject.Blazor
```

> There is a BundleContributor class implementing `IBundleContributor` interface coming by default with the startup templates. So, most of the time, you don't need to add it manually.

## Bundling And Minification
`abp bundle` command offers bundling and minification support for client-side resources(JavaScript and CSS files). `abp bundle` command reads the `appsettings.json` file inside the Blazor project and bundles the resources according to the configuration. You can find the bundle configurations inside `AbpCli.Bundle` element.

Here are the options that you can control inside the `appsettings.json` file.

`Mode`: Bundling and minification mode. Possible values are
* `BundleAndMinify`: Bundle all the files into a single file and minify the content.
* `Bundle`: Bundle all files into a single file, but not minify.
* `None`: Add files individually, do not bundle.

`Name`: Bundle file name. Default value is `global`.

`Parameters`: You can define additional key/value pair parameters inside this section. `abp bundle` command automatically sends these parameters to the bundle contributors, and you can check these parameters inside the bundle contributor, take some actions according to these values.

Let's say that you want to exclude some resources from the bundle and control this action using the bundle parameters. You can add a parameter to the bundle section like below.

```json
"AbpCli": {
"Bundle": {
"Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */
"Name": "global",
"Parameters": {
"ExcludeThemeFromBundle":"true"
}
}
}
```

You can check this parameter and take action like below.

```csharp
public class MyProjectNameBundleContributor : IBundleContributor
{
public void AddScripts(BundleContext context)
{
}

public void AddStyles(BundleContext context)
{
var excludeThemeFromBundle = bool.Parse(context.Parameters.GetValueOrDefault("ExcludeThemeFromBundle"));
context.Add("mytheme.css", excludeFromBundle: excludeThemeFromBundle);
context.Add("main.css");
}
}
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using Volo.Abp.Bundling;

namespace Volo.Abp.Cli.Bundling
{
public class BundleConfig
{
public BundlingMode Mode { get; set; }
public string Name { get; set; }
public BundleParameterDictionary Parameters { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
Expand All @@ -13,12 +14,13 @@ namespace Volo.Abp.Cli.Bundling
{
public abstract class BundlerBase : IBundler, ITransientDependency
{
private static string[] _minFileSuffixes = { "min", "prod" };
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);
public abstract string GenerateDefinition(string bundleFilePath,
List<BundleDefinition> bundleDefinitionsExcludingFromBundle);

protected BundlerBase(IMinifier minifier)
{
Expand All @@ -27,11 +29,15 @@ protected BundlerBase(IMinifier minifier)

public string Bundle(BundleOptions options, BundleContext context)
{
var bundleFilePath = Path.Combine(PathHelper.GetWwwRootPath(options.Directory), $"{options.BundleName}{FileExtension}");
var bundledContent = BundleFiles(options, context);
var bundleFilePath = Path.Combine(PathHelper.GetWwwRootPath(options.Directory),
$"{options.BundleName}{FileExtension}");
var bundleFileDefinitions = context.BundleDefinitions.Where(t => t.ExcludeFromBundle == false).ToList();
var fileDefinitionsExcludingFromBundle = context.BundleDefinitions.Where(t => t.ExcludeFromBundle).ToList();

var bundledContent = BundleFiles(options, bundleFileDefinitions);
File.WriteAllText(bundleFilePath, bundledContent);

return GenerateDefinition(bundleFilePath);
return GenerateDefinition(bundleFilePath,fileDefinitionsExcludingFromBundle);
}

private bool IsMinFile(string fileName)
Expand All @@ -47,34 +53,40 @@ private bool IsMinFile(string fileName)
return false;
}

private string BundleFiles(BundleOptions options, BundleContext context)
private string BundleFiles(BundleOptions options, List<BundleDefinition> bundleDefinitions)
{
var staticAssetsFilePath = Path.Combine(options.Directory, "bin", "Debug", options.FrameworkVersion, $"{options.ProjectFileName}.StaticWebAssets.xml");
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.");
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)
foreach (var definition in 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 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);
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
Expand All @@ -83,7 +95,8 @@ private string BundleFiles(BundleOptions options, BundleContext context)
content = GetFileContent(filePath, options.Minify);
}

content = ProcessBeforeAddingToTheBundle(definition.Source, Path.Combine(options.Directory, "wwwroot"), content);
content = ProcessBeforeAddingToTheBundle(definition.Source, Path.Combine(options.Directory, "wwwroot"),
content);
builder.AppendLine(content);
}

Expand All @@ -101,16 +114,19 @@ private string GetFileContent(string filePath, bool minify)
}
catch (NUglifyException ex)
{
Logger.LogWarning($"Unable to minify the file: {Path.GetFileName(filePath)}. Adding file to the bundle without minification.", 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)
protected virtual string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory,
string fileContent)
{
return fileContent;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Volo.Abp.Cli.Bundling
{
public enum BundlingMode
{
None,
Bundle,
BundleAndMinify,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Volo.Abp.Cli.Build;
using Volo.Abp.Cli.Bundling.Scripts;
using Volo.Abp.Cli.Bundling.Styles;
using Volo.Abp.Cli.Configuration;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify.Scripts;
using Volo.Abp.Minify.Styles;
Expand All @@ -26,17 +27,27 @@ public class BundlingService : IBundlingService, ITransientDependency
public ILogger<BundlingService> Logger { get; set; }
public IScriptBundler ScriptBundler { get; set; }
public IStyleBundler StyleBundler { get; set; }
public IConfigReader ConfigReader { get; set; }

public async Task BundleAsync(string directory, bool forceBuild, bool bundle, bool minify, string bundleName)
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. The working directory must have a Blazor project file.");
throw new BundlingException(
"No project file found in the directory. The working directory must have a Blazor project file.");
}

var projectFilePath = projectFiles[0];

var config = ConfigReader.Read(PathHelper.GetWwwRootPath(directory));
var bundleConfig = config?.Bundle;

if (bundleConfig == null)
{
throw new BundlingException("Bundle section is missing in the appsettings.json configuration file.");
}

if (forceBuild)
{
var projects = new List<DotNetProjectInfo>()
Expand All @@ -56,20 +67,20 @@ public async Task BundleAsync(string directory, bool forceBuild, bool bundle, bo
FindBundleContributorsRecursively(startupModule, 0, bundleDefinitions);
bundleDefinitions = bundleDefinitions.OrderByDescending(t => t.Level).ToList();

var styleContext = GetStyleContext(bundleDefinitions);
var scriptContext = GetScriptContext(bundleDefinitions);
var styleContext = GetStyleContext(bundleDefinitions,bundleConfig.Parameters);
var scriptContext = GetScriptContext(bundleDefinitions,bundleConfig.Parameters);
string styleDefinitions;
string scriptDefinitions;

if (bundle || minify)
if (bundleConfig.Mode is BundlingMode.Bundle || bundleConfig.Mode is BundlingMode.BundleAndMinify)
{
var options = new BundleOptions
{
Directory = directory,
FrameworkVersion = frameworkVersion,
ProjectFileName = projectName,
BundleName = bundleName,
Minify = minify
BundleName = bundleConfig.Name.IsNullOrEmpty() ? "global" : bundleConfig.Name,
Minify = bundleConfig.Mode == BundlingMode.BundleAndMinify
};

styleDefinitions = StyleBundler.Bundle(options, styleContext);
Expand All @@ -84,9 +95,13 @@ public async Task BundleAsync(string directory, bool forceBuild, bool bundle, bo
await UpdateDependenciesInHtmlFileAsync(directory, styleDefinitions, scriptDefinitions);
}

private BundleContext GetScriptContext(List<BundleTypeDefinition> bundleDefinitions)
private BundleContext GetScriptContext(List<BundleTypeDefinition> bundleDefinitions,
BundleParameterDictionary parameters)
{
var scriptContext = new BundleContext();
var scriptContext = new BundleContext
{
Parameters = parameters
};

foreach (var bundleDefinition in bundleDefinitions)
{
Expand All @@ -98,9 +113,13 @@ private BundleContext GetScriptContext(List<BundleTypeDefinition> bundleDefiniti
return scriptContext;
}

private BundleContext GetStyleContext(List<BundleTypeDefinition> bundleDefinitions)
private BundleContext GetStyleContext(List<BundleTypeDefinition> bundleDefinitions,
BundleParameterDictionary parameters)
{
var styleContext = new BundleContext();
var styleContext = new BundleContext
{
Parameters = parameters
};

foreach (var bundleDefinition in bundleDefinitions)
{
Expand All @@ -111,7 +130,8 @@ private BundleContext GetStyleContext(List<BundleTypeDefinition> bundleDefinitio
return styleContext;
}

private async Task UpdateDependenciesInHtmlFileAsync(string directory, string styleDefinitions, string scriptDefinitions)
private async Task UpdateDependenciesInHtmlFileAsync(string directory, string styleDefinitions,
string scriptDefinitions)
{
var htmlFilePath = Path.Combine(PathHelper.GetWwwRootPath(directory), "index.html");
if (!File.Exists(htmlFilePath))
Expand All @@ -127,8 +147,10 @@ private async Task UpdateDependenciesInHtmlFileAsync(string directory, string st
content = await reader.ReadToEndAsync();
}

content = UpdatePlaceholders(content, BundlingConsts.StylePlaceholderStart, BundlingConsts.StylePlaceholderEnd, styleDefinitions);
content = UpdatePlaceholders(content, BundlingConsts.ScriptPlaceholderStart, BundlingConsts.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))
{
Expand All @@ -137,11 +159,13 @@ private async Task UpdateDependenciesInHtmlFileAsync(string directory, string st
}
}

private string UpdatePlaceholders(string content, string placeholderStart, string placeholderEnd, string definitions)
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);
var updatedContent = content.Remove(placeholderStartIndex,
(placeholderEndIndex + placeholderEnd.Length) - placeholderStartIndex);
return updatedContent.Insert(placeholderStartIndex, definitions);
}

Expand Down Expand Up @@ -179,16 +203,18 @@ private string GenerateScriptDefinitions(BundleContext context)
{
builder.Append($"{additionalProperty.Key}={additionalProperty.Value} ");
}

builder.AppendLine("></script>");
}

builder.Append($" {BundlingConsts.ScriptPlaceholderEnd}");

return builder.ToString();
}

private IBundleContributor CreateContributorInstance(Type bundleContributorType)
{
return (IBundleContributor)Activator.CreateInstance(bundleContributorType);
return (IBundleContributor) Activator.CreateInstance(bundleContributorType);
}

private void FindBundleContributorsRecursively(
Expand All @@ -203,7 +229,8 @@ private void FindBundleContributorsRecursively(

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 (bundleContributors.Any())
Expand Down Expand Up @@ -255,10 +282,11 @@ private string GetTargetFrameworkVersion(string projectFilePath)
var sdk = document.DocumentElement.GetAttribute("Sdk");
if (sdk != BundlingConsts.SupportedWebAssemblyProjectType)
{
throw new BundlingException($"Unsupported project type. Project type must be {BundlingConsts.SupportedWebAssemblyProjectType}.");
throw new BundlingException(
$"Unsupported project type. Project type must be {BundlingConsts.SupportedWebAssemblyProjectType}.");
}

return document.SelectSingleNode("//TargetFramework").InnerText;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Volo.Abp.Cli.Bundling
{
public interface IBundlingService
{
Task BundleAsync(string directory, bool forceBuild, bool bundle, bool minify, string bundleName);
Task BundleAsync(string directory, bool forceBuild);
}
}
Loading