There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are:
- Using the Bundler & Minifier Visual Studio extension or the nuget package.
- Using Gulp/Grunt task managers and their plugins.
ABP offers a simpler, dynamic, powerful, modular and built-in way.
This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually.
Install the Volo.Abp.AspNetCore.Mvc.UI.Bundling
nuget package to your project:
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling
Then you can add the AbpAspNetCoreMvcUiBundlingModule
dependency to your module:
using Volo.Abp.Modularity;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
public class MyWebModule : AbpModule
{
//...
}
}
The simplest way of creating a bundle is to use abp-script-bundle
or abp-style-bundle
tag helpers. Example:
<abp-style-bundle name="MyGlobalBundle">
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/styles/my-global-style.css" />
</abp-style-bundle>
This bundle defines a style bundle with a unique name: MyGlobalBundle. It's very easy to understand how to use it. Let's see how it works:
- ABP creates the bundle as lazy from the provided files when it's first requested. For the subsequent calls, it's returned from the cache. That means if you conditionally add files to the bundle, it's executed only once and any change of the condition will not effect the bundle for the next requests.
- ABP adds bundle files individually to the page for the
development
environment. It automatically bundles & minifies for other environments (staging
,production
...). - The bundle files may be physical files or virtual/embedded files.
- ABP automatically adds version query string (like ?_v=67872834243042 - generated from last change date of the related files) to the bundle file URL which prevents browser caching when a bundle changes. The versioning works even if the bundle files are individually added to the page (on the development environment).
This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually.
In order to use bundle tag helpers, you need to add it into your _ViewImports.cshtml
file or into your page:
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
The name
is optional for the razor bundle tag helpers. If you don't define a name, it's automatically calculated based on the used bundle file names (they are concatenated and hashed). Example:
<abp-style-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
@if (ViewBag.IncludeCustomStyles != false)
{
<abp-style src="/styles/my-global-style.css" />
}
</abp-style-bundle>
This will potentially create two different bundles (one incudes the my-global-style.css
and other does not).
Advantages of unnamed bundles:
- Can conditionally add items to the bundle. But this may lead to multiple variations of the bundle based on the conditions.
Advantages of named bundles:
- Other modules can contribute to the bundle by its name (see the sections below).
If you need to just add a single file to the page, you can use the abp-script
or abp-style
tag without a surrounding abp-script-bundle
or abp-style-bundle
tag. Example:
<abp-script src="/scripts/my-script.js" />
The bundle name will be scripts.my-scripts for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too.
If you need to use same bundle in multiple pages or want to use some more powerful features, you can configure bundles by code in your module class.
Example usage:
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
public class MyWebModule : AbpModule
{
public override void ConfigureServices(IServiceCollection services)
{
services.Configure<BundlingOptions>(options =>
{
options
.ScriptBundles
.Add("MyGlobalBundle", bundle => {
bundle.AddFiles(
"/libs/jquery/jquery.js",
"/libs/bootstrap/js/bootstrap.js",
"/libs/toastr/toastr.min.js",
"/scripts/my-global-scripts.js"
);
});
});
}
}
You can use the same name (MyGlobalBundle here) for a script & style bundle since they are added to different collections (
ScriptBundles
andStyleBundles
).
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example:
<abp-script-bundle name="MyGlobalBundle" />
This time, no file defined in the tag helper definition because the bundle files are defined by the code.
ABP supports modularity for bundling too. A module can modify an existing bundle that is created by a depended module. Example:
[DependsOn(typeof(MyWebModule))]
public class MyWebExtensionModule : AbpModule
{
public override void ConfigureServices(IServiceCollection services)
{
services.Configure<BundlingOptions>(options =>
{
options
.ScriptBundles
.Configure("MyGlobalBundle", bundle => {
bundle.AddFiles(
"/scripts/my-extension-script.js"
);
});
});
}
}
It's not possible to configure unnamed bundle tag helpers by code, because their name are not known on the development time. It's suggested to always use a name for a bundle tag helper.
Adding files to an existing bundle seems useful. What if you need to replace a file in the bundle or you want to conditionally add files? Defining a bundle contributor provides extra power for such cases.
An example bundle contributor that replaces bootstrap.css with a customized version:
public class MyExtensionGlobalStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.ReplaceOne(
"/libs/bootstrap/css/bootstrap.css",
"/styles/extensions/bootstrap-customized.css"
);
}
}
Then you can use this contributor like that:
services.Configure<BundlingOptions>(options =>
{
options
.ScriptBundles
.Configure("MyGlobalBundle", bundle => {
bundle.AddContributors(typeof(MyExtensionStyleBundleContributor));
});
});
Contributors can also be used in bundle tag helpers. Example:
<abp-style-bundle>
<abp-style type="@typeof(BootstrapStyleContributor)" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
</abp-style-bundle>
abp-style
and abp-script
tags can get type
attributes (instead of src
attributes) as shown in this sample. When you add a bundle contributor, it's dependencies are also automatically added to the bundle.
A bundle contributor can have one or more dependencies to other contributors. Example:
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency
public class MyExtensionStyleBundleContributor : BundleContributor
{
//...
}
When a bundle contributor is added, its dependencies are automatically and recursively added. Dependencies added by the dependency order by preventing duplicates. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications.
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules.
While it is rarely needed, BundleConfigurationContext
has a ServiceProvider
property that you can resolve service dependencies inside the ConfigureBundle
method.
Adding a specific NPM package resources (js, css files) into a bundle is pretty standard for that package. For instance you always add the bootstrap.css
file for the bootstrap NPM package.
There are built-in contributors for all standard NPM packages. For instance, if your contributor depends on the bootstrap, you can just declare it instead of adding the bootstrap.css yourself:
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency
public class MyExtensionStyleBundleContributor : BundleContributor
{
//...
}
Using the built-in contributors for standard packages;
- Prevents you wrongly typing the resource paths.
- Prevents changing your contributor if the resource path changes (the depended contributor will handle it).
- Prevents multiple modules add the same file multiple times.
- Manages dependencies recursively (adds dependencies of dependencies... if necessary).
This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually.
Standard package contributors are defined in the Volo.Abp.AspNetCore.Mvc.UI.Packages
nuget package. To install it into your project:
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages
Then add the AbpAspNetCoreMvcUiPackagesModule
module dependency to your own module
using Volo.Abp.Modularity;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))]
public class MyWebModule : AbpModule
{
//...
}
}
In some specific cases, it may be needed to create a new bundle inherited from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors without modifying the original bundle. Example:
services.Configure<BundlingOptions>(options =>
{
options
.StyleBundles
.Add("MyTheme.MyGlobalBundle", bundle => {
bundle
.AddBaseBundles("MyGlobalBundle") //Can add multiple
.AddFiles(
"/styles/mytheme-global-styles.css"
);
});
});
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the theming documentation for more.
It's suggested to define multiple bundles for an application, each one is used for different purposes.
- Global bundle: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them.
- Layout bundles: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice.
- Module bundles: For shared resources among the pages of an individual module.
- Page bundles: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice.
Establish a balance between performance, network bandwidth usage and managing too many bundles.