项目

ASP.NET Core MVC 捆绑与压缩

客户端资源(JavaScript和CSS文件)的捆绑与压缩有多种方式。最常见的方式是:

ABP 提供了一种简单、动态、强大、模块化且内置的方式。

Volo.Abp.AspNetCore.Mvc.UI.Bundling 包

启动模板默认已安装此包。因此,大多数情况下,您无需手动安装。

如果您没有使用启动模板,可以使用 ABP CLI 将其安装到您的项目中。在包含项目 .csproj 文件的文件夹中执行以下命令:

abp add-package Volo.Abp.AspNetCore.Mvc.UI.Bundling

如果您尚未安装,首先需要安装 ABP CLI。关于其他安装选项,请参阅 包描述页面

Razor 捆绑标签助手

创建捆绑包最简单的方法是使用 abp-script-bundleabp-style-bundle 标签助手。示例:

<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>

此捆绑包定义了一个具有唯一名称 MyGlobalBundle 的样式捆绑包。理解如何使用它非常容易。让我们看看它是如何工作的:

  • 首次请求时,ABP 会根据提供的文件延迟创建捆绑包。对于后续调用,它将从缓存中返回。这意味着,如果您有条件地向捆绑包添加文件,该条件仅执行一次,并且条件的任何更改都不会影响后续请求的捆绑包。
  • 对于 development 环境,ABP 会将捆绑文件单独添加到页面。对于其他环境(stagingproduction...),它会自动进行捆绑和压缩。请参阅 捆绑模式 部分以更改此行为。
  • 捆绑文件可以是物理文件或虚拟/嵌入式文件
  • ABP 会自动向捆绑文件 URL 添加版本查询字符串,以防止捆绑包更新时浏览器进行缓存(例如 ?_v=67872834243042 - 根据相关文件的最后更改日期生成)。即使捆绑文件被单独添加到页面(在开发环境中),版本控制仍然有效。

导入捆绑标签助手

启动模板默认已导入此功能。因此,大多数情况下,您无需手动添加。

为了使用捆绑标签助手,您需要将其添加到 _ViewImports.cshtml 文件或页面中:

@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling

未命名捆绑包

name 对于 Razor 捆绑标签助手是可选的。如果您不定义名称,它将根据使用的捆绑文件名自动计算(它们会被连接哈希)。示例:

<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>

这可能会创建两个不同的捆绑包(一个包含 my-global-style.css,另一个不包含)。

未命名捆绑包的优点:

  • 可以有条件地添加项目到捆绑包。但这可能导致基于条件生成多个捆绑包变体。

命名捆绑包的优点:

  • 其他模块可以通过其名称贡献到捆绑包(参见以下部分)。

单文件

如果您只需要向页面添加单个文件,可以使用 abp-scriptabp-style 标签,而无需包装在 abp-script-bundleabp-style-bundle 标签中。示例:

<abp-script src="/scripts/my-script.js" />

对于上面的示例,捆绑包名称将为 scripts.my-scripts("/" 被替换为 ".")。所有捆绑功能对于单文件捆绑包同样有效。

捆绑选项

如果您需要在多个页面中使用相同的捆绑包,或者希望使用一些更强大的功能,您可以在模块类中通过代码配置捆绑包。

创建新捆绑包

示例用法:

[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
public class MyWebModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBundlingOptions>(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"
                    );
                });                
        });
    }
}

您可以为脚本和样式捆绑包使用相同的名称(此处为 MyGlobalBundle),因为它们被添加到不同的集合(ScriptBundlesStyleBundles)。

定义这样的捆绑包后,可以使用上面定义的相同标签助手将其包含到页面中。示例:

<abp-script-bundle name="MyGlobalBundle" />

这次,标签助手定义中没有指定文件,因为捆绑文件是由代码定义的。

配置现有捆绑包

ABP 的捆绑功能也支持模块化。模块可以修改依赖模块创建的现有捆绑包。示例:

[DependsOn(typeof(MyWebModule))]
public class MyWebExtensionModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBundlingOptions>(options =>
        {
            options
                .ScriptBundles
                .Configure("MyGlobalBundle", bundle => {
                    bundle.AddFiles(
                        "/scripts/my-extension-script.js"
                    );
                });
        });
    }
}

您还可以使用 ConfigureAll 方法来配置所有现有捆绑包:

[DependsOn(typeof(MyWebModule))]
public class MyWebExtensionModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBundlingOptions>(options =>
        {
            options
                .ScriptBundles
                .ConfigureAll(bundle => {
                    bundle.AddFiles(
                        "/scripts/my-extension-script.js"
                    );
                });
        });
    }
}

捆绑贡献者

向现有捆绑包添加文件似乎很有用。如果您需要替换捆绑包中的文件,或者希望有条件地添加文件,该怎么办?定义捆绑贡献者为这类情况提供了额外的能力。

一个将 bootstrap.css 替换为自定义版本的捆绑贡献者示例:

public class MyExtensionGlobalStyleContributor : BundleContributor
{
    public override void ConfigureBundle(BundleConfigurationContext context)
    {
        context.Files.ReplaceOne(
            "/libs/bootstrap/css/bootstrap.css",
            "/styles/extensions/bootstrap-customized.css"
        );
    }
}

然后,您可以按如下方式使用此贡献者:

services.Configure<AbpBundlingOptions>(options =>
{
    options
        .ScriptBundles
        .Configure("MyGlobalBundle", bundle => {
            bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor));
        });
});

您也可以在创建新捆绑包时添加贡献者。

贡献者也可以在捆绑标签助手中使用。示例:

<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-styleabp-script 标签可以获取 type 属性(而不是 src 属性)。当您添加捆绑贡献者时,其依赖项也会自动添加到捆绑包中。

贡献者依赖项

一个捆绑贡献者可以有一个或多个对其他贡献者的依赖项。 示例:

[DependsOn(typeof(MyDependedBundleContributor))] //定义依赖项
public class MyExtensionStyleBundleContributor : BundleContributor
{
    //...
}

添加捆绑贡献者时,其依赖项会自动且递归地添加。依赖项按照依赖顺序添加,并防止重复。即使它们位于不同的捆绑包中,也能防止重复。ABP 会组织页面中的所有捆绑包并消除重复。

创建贡献者并定义依赖项是在不同模块之间组织捆绑创建的一种方式。

贡献者扩展

在一些高级场景中,您可能希望在使用扩展贡献者时进行一些额外的配置。当使用扩展的贡献者时,贡献者扩展可以无缝工作。

以下示例为 prism.js 库添加了一些样式:

public class MyPrismjsStyleExtension : BundleContributor
{
    public override void ConfigureBundle(BundleConfigurationContext context)
    {
        context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css");
    }
}

然后,您可以配置 AbpBundleContributorOptions 来扩展现有的 PrismjsStyleBundleContributor

Configure<AbpBundleContributorOptions>(options =>
{
    options
        .Extensions<PrismjsStyleBundleContributor>()
        .Add<MyPrismjsStyleExtension>();
});

每当 PrismjsStyleBundleContributor 被添加到捆绑包时,MyPrismjsStyleExtension 也会被自动添加。

访问 IServiceProvider

虽然很少需要,但 BundleConfigurationContext 有一个 ServiceProvider 属性,您可以在 ConfigureBundle 方法中解析服务依赖项。

标准包贡献者

将特定的 NPM 包资源(js、css 文件)添加到捆绑包中对于该包来说非常简单。例如,您总是为 bootstrap NPM 包添加 bootstrap.css 文件。

所有标准 NPM 包都有内置的贡献者。例如,如果您的贡献者依赖 bootstrap,您可以声明它,而无需自己添加 bootstrap.css。

[DependsOn(typeof(BootstrapStyleContributor))] //定义 bootstrap 样式依赖
public class MyExtensionStyleBundleContributor : BundleContributor
{
    //...
}

为标准包使用内置贡献者;

  • 防止您键入无效的资源路径
  • 如果资源路径更改,防止更改您的贡献者(依赖的贡献者将处理它)。
  • 防止多个模块添加重复文件
  • 如果需要,递归地管理依赖项(添加依赖项的依赖项)。

Volo.Abp.AspNetCore.Mvc.UI.Packages 包

启动模板默认已安装此包。因此,大多数情况下,您无需手动安装。

如果您没有使用启动模板,可以使用 ABP CLI 将其安装到您的项目中。在包含项目 .csproj 文件的文件夹中执行以下命令:

abp add-package Volo.Abp.AspNetCore.Mvc.UI.Packages

如果您尚未安装,首先需要安装 ABP CLI。关于其他安装选项,请参阅 包描述页面

捆绑继承

在某些特定情况下,可能需要从其他捆绑包继承创建捆绑包。从一个捆绑包继承(递归地)会继承该捆绑包的所有文件/贡献者。然后,派生的捆绑包可以添加或修改文件/贡献者,而不修改原始捆绑包。 示例:

services.Configure<AbpBundlingOptions>(options =>
{
    options
        .StyleBundles
        .Add("MyTheme.MyGlobalBundle", bundle => {
            bundle
                .AddBaseBundles("MyGlobalBundle") //可以添加多个
                .AddFiles(
                    "/styles/mytheme-global-styles.css"
                );
        });
});

附加选项

本节展示了捆绑和压缩系统的其他有用选项。

捆绑模式

ABP 为 development 环境将捆绑文件单独添加到页面。对于其他环境(stagingproduction...),它会自动进行捆绑和压缩。大多数情况下,这是您希望的行为。然而,在某些情况下,您可能希望手动配置它。有四种模式;

  • Auto:根据环境自动确定模式。
  • None:不进行捆绑或压缩。
  • Bundle:捆绑但不压缩。
  • BundleAndMinify:捆绑并压缩。

您可以在模块ConfigureServices 中配置 AbpBundlingOptions

示例:

Configure<AbpBundlingOptions>(options =>
{
    options.Mode = BundlingMode.Bundle;
});

忽略压缩

可以忽略特定文件的压缩。

示例:

Configure<AbpBundlingOptions>(options =>
{
    options.MinificationIgnoredFiles.Add("/scripts/myscript.js");
});

在这种情况下,给定的文件仍会添加到捆绑包中,但不会被压缩。

异步加载 JavaScript 和 CSS

您可以配置 AbpBundlingOptions 来异步加载所有或单个 js/css 文件。

示例:

Configure<AbpBundlingOptions>(options =>
{
    options.PreloadStyles.Add("/__bundles/Basic.Global");
    options.DeferScriptsByDefault = true;
});

输出 HTML:

<link rel="preload" href="/__bundles/Basic.Global.F4FA61F368098407A4C972D0A6914137.css?_v=637697363694828051" as="style" onload="this.rel='stylesheet'"/>

<script defer src="/libs/timeago/locales/jquery.timeago.en.js?_v=637674729040000000"></script>

外部/CDN 文件支持

捆绑系统会自动识别外部/CDN 文件,并将它们原样添加到页面中。

AbpBundlingOptions 中使用外部/CDN 文件

Configure<AbpBundlingOptions>(options =>
{
    options.StyleBundles
        .Add("MyStyleBundle", configuration =>
        {
            configuration
                .AddFiles("/styles/my-style1.css")
                .AddFiles("/styles/my-style2.css")
                .AddFiles("https://cdn.abp.io/bootstrap.css")
                .AddFiles("/styles/my-style3.css")
                .AddFiles("/styles/my-style4.css");
        });

    options.ScriptBundles
        .Add("MyScriptBundle", configuration =>
        {
            configuration
                .AddFiles("/scripts/my-script1.js")
                .AddFiles("/scripts/my-script2.js")
                .AddFiles("https://cdn.abp.io/bootstrap.js")
                .AddFiles("/scripts/my-script3.js")
                .AddFiles("/scripts/my-script4.js");
        });
});

输出 HTML:

<link rel="stylesheet" href="/__bundles/MyStyleBundle.EA8C28419DCA43363E9670973D4C0D15.css?_v=638331889644609730" />
<link rel="stylesheet" href="https://cdn.abp.io/bootstrap.css" />
<link rel="stylesheet" href="/__bundles/MyStyleBundle.AC2E0AA6C461A0949A1295E9BDAC049C.css?_v=638331889644623860" />

<script src="/__bundles/MyScriptBundle.C993366DF8840E08228F3EE685CB08E8.js?_v=638331889644937120"></script>
<script src="https://cdn.abp.io/bootstrap.js"></script>
<script src="/__bundles/MyScriptBundle.2E8D0FDC6334D2A6B847393A801525B7.js?_v=638331889644943970"></script>

在标签助手中使用外部/CDN 文件

<abp-style-bundle name="MyStyleBundle">
    <abp-style src="/styles/my-style1.css" />
    <abp-style src="/styles/my-style2.css" />
    <abp-style src="https://cdn.abp.io/bootstrap.css" />
    <abp-style src="/styles/my-style3.css" />
    <abp-style src="/styles/my-style4.css" />
</abp-style-bundle>

<abp-script-bundle name="MyScriptBundle">
    <abp-script src="/scripts/my-script1.js" />
    <abp-script src="/scripts/my-script2.js" />
    <abp-script src="https://cdn.abp.io/bootstrap.js" />
    <abp-script src="/scripts/my-script3.js" />
    <abp-script src="/scripts/my-script4.js" />
</abp-script-bundle>

输出 HTML:

<link rel="stylesheet" href="/__bundles/MyStyleBundle.C60C7B9C1F539659623BB6E7227A7C45.css?_v=638331889645002500" />
<link rel="stylesheet" href="https://cdn.abp.io/bootstrap.css" />
<link rel="stylesheet" href="/__bundles/MyStyleBundle.464328A06039091534650B0E049904C6.css?_v=638331889645012300" />

<script src="/__bundles/MyScriptBundle.55FDCBF2DCB9E0767AE6FA7487594106.js?_v=638331889645050410"></script>
<script src="https://cdn.abp.io/bootstrap.js"></script>
<script src="/__bundles/MyScriptBundle.191CB68AB4F41C8BF3A7AE422F19A3D2.js?_v=638331889645055490"></script>

主题

主题使用标准包贡献者将库资源添加到页面布局。主题还可以定义一些标准/全局捆绑包,以便任何模块都可以为这些标准/全局捆绑包做出贡献。有关更多信息,请参阅主题文档

最佳实践与建议

建议为应用程序定义多个捆绑包,每个捆绑包用于不同的目的。

  • 全局捆绑包:全局样式/脚本捆绑包包含在应用程序的每个页面中。主题已经定义了全局样式和脚本捆绑包。您的模块可以为它们做出贡献。
  • 布局捆绑包:这是针对单个布局的特定捆绑包。仅包含使用该布局的所有页面共享的资源。作为良好实践,使用捆绑标签助手创建捆绑包。
  • 模块捆绑包:用于单个模块页面之间共享的资源。
  • 页面捆绑包:为每个页面创建的特定捆绑包。作为最佳实践,使用捆绑标签助手创建捆绑包。

在性能、网络带宽使用和多个捆绑包数量之间建立平衡。

另请参阅

在本文档中