捆绑和缩小

ASP.NET Core 中的捆绑和缩小静态资产

作者: Scott AddieDavid 松树

本文介绍了应用绑定和缩减,包括如何使用 ASP.NET Core web apps 使用这些功能的好处。

什么是绑定和缩减

绑定和缩减是可以在 web 应用中应用的两个不同的性能优化。 绑定和缩减一起使用,可减少服务器请求数并减小请求的静态资产的大小,从而提高性能。

绑定和缩减主要改善第一页请求加载时间。 请求网页后,浏览器会缓存静态资产(JavaScript、CSS 和图像)。 因此,当在同一站点上请求相同的资源时,绑定和缩减不会提高性能。 如果未在资产上正确设置 expires 标头,且未使用捆绑和缩减,则浏览器的新鲜度试探法会在几天后将资产过期。 此外,浏览器还需要对每个资产进行验证请求。 在这种情况下,绑定和缩减在第一次请求页面后仍能改善性能。

捆绑

捆绑将多个文件合并到单个文件中。 绑定可减少呈现 web 资产(如网页)所需的服务器请求数。 可以专门为 CSS、JavaScript 等创建任意数量的单独包。文件越少,从浏览器到服务器的 HTTP 请求或提供应用程序的服务就会减少。 这会提高第一页的加载性能。

缩减

缩减从代码中删除不必要的字符,而不更改功能。 因此,请求的资产(如 CSS、图像和 JavaScript 文件)的大小大幅减小。 缩减的常见副作用包括将变量名称缩短为一个字符、删除注释和不必要的空格。

请考虑以下 JavaScript 函数:

AddAltToImg = function (imageTagAndImageID, imageContext) {
    ///<signature>
    ///<summary> Adds an alt tab to the image
    // </summary>
    //<param name="imgElement" type="String">The image selector.</param>
    //<param name="ContextForImage" type="String">The image context.</param>
    ///</signature>
    var imageElement = $(imageTagAndImageID, imageContext);
    imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));
}

缩减将函数降到了以下内容:

AddAltToImg=function(t,a){var r=$(t,a);r.attr("alt",r.attr("id").replace(/ID/,""))};

除了删除注释和不必要的空格外,还会将以下参数和变量名称重命名为:

原始 重命名
imageTagAndImageID t
imageContext a
imageElement r

捆绑和缩减的影响

下表概述了单独加载资产与使用绑定和缩减之间的差异:

操作 带有 B/M 的 无 B/M 更改
文件请求 7 18 157%
已传输 KB 156 264.68 70%
加载时间(毫秒) 885 2360 167%

对于 HTTP 请求标头,浏览器非常详细。 绑定的字节总数指标明显减少了绑定的时间。 加载时间显示了显著改进,但本示例在本地运行。 将捆绑与缩减与通过网络传输的资产结合使用时,可实现更高的性能提升。

选择捆绑和缩减策略

MVC 和 Razor Pages 项目模板提供了一种现成的解决方案,可用于缩减和 JSON 配置文件。 第三方工具(如Grunt任务运行程序)以更复杂的方式完成相同的任务。 当开发工作流需要处理超过绑定和缩减—如 linting 和图像优化)时,第三方工具非常合适。 通过使用设计时绑定和缩减,缩小文件是在应用部署之前创建的。 在部署之前绑定和缩小提供了降低服务器负载的优点。 但是,必须认识到,设计时绑定和缩减会增加生成的复杂性,并且仅适用于静态文件。

配置捆绑和缩减

在 ASP.NET Core 2.0 或更早版本中,MVC 和 Razor Pages 项目模板提供了一个bundleconfig配置文件,该文件定义每个绑定的选项:

在 ASP.NET Core 2.1 或更高版本中,将名为bundleconfig的新 JSON 文件添加到 MVC 或 Razor Pages 项目根。 在该文件中包含以下 JSON 作为起始点:

[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "wwwroot/css/site.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/site.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    },
    "sourceMap": false
  }
]

Bundleconfig文件定义每个绑定的选项。 在前面的示例中,为自定义 JavaScript (wwwroot/js/node.js)和样式表(wwwroot/css/站点导航)文件定义了单个捆绑配置。

配置选项包括:

  • outputFileName:要输出的绑定文件的名称。 可以包含bundleconfig文件中的相对路径。 (必需)
  • inputFiles:要捆绑在一起的文件的数组。 这些是配置文件的相对路径。 可选,* 空值将导致空的输出文件。 支持通配模式。
  • minify:输出类型的缩减选项。 可选默认值-minify: { enabled: true }
  • includeInProject:指示是否将生成的文件添加到项目文件的标记。 可选默认值为 false
  • sourceMap:标记,指示是否为绑定的文件生成源映射。 可选默认值为 false
  • sourceMapRootPath:用于存储生成的源映射文件的根路径。

绑定和缩减的生成时执行

BuildBundlerMinifier NuGet 包允许在生成时执行绑定和缩减。 包注入在生成和清理时间运行的MSBuild 目标 Bundleconfig文件由生成过程进行分析,以便基于定义的配置生成输出文件。

备注

BuildBundlerMinifier 属于 GitHub 上的社区驱动项目,Microsoft 不提供支持。 应在此处归档问题。

绑定和缩减的即席执行

可以在不生成项目的情况下即席运行捆绑和缩减任务。 BundlerMinifier NuGet 包添加到项目:

<DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />

备注

BundlerMinifier 属于 GitHub 上的社区驱动项目,Microsoft 不提供支持。 应在此处归档问题。

此包扩展以包括.NET Core CLI dotnet 捆绑工具。 可以在 "包管理器控制台" (PMC)窗口或命令行界面中执行以下命令:

dotnet bundle

重要

NuGet 包管理器将依赖项添加到 * .csproj 文件作为 <PackageReference /> 节点。 dotnet bundle命令注册.NET Core CLI 时,才<DotNetCliToolReference />使用节点。 请相应地修改 * .csproj 文件。

向工作流添加文件

假设添加了其他自定义 .css文件的示例类似于以下内容:

.about, [role=main], [role=complementary] {
    margin-top: 60px;
}

footer {
    margin-top: 10px;
}

若要缩小自定义 css并将其与站点 中的内容捆绑在一起,请将相对路径添加到bundleconfig

[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "wwwroot/css/site.css",
      "wwwroot/css/custom.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/site.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    },
    "sourceMap": false
  }
]

备注

或者,可以使用以下组合模式:

"inputFiles": ["wwwroot/**/!(*.min).css" ]

此组合模式匹配所有 CSS 文件,并排除缩小文件模式。

生成应用程序。 打开 "网站"。最小 css ,请注意,自定义 css的内容将追加到文件末尾。

基于环境的捆绑和缩减

最佳做法是,应在生产环境中使用应用的捆绑文件和缩小文件。 在开发过程中,原始文件可简化应用程序的调试。

通过在视图中使用环境标记帮助器来指定要包含在页面中的文件。 环境标记帮助程序仅在特定环境中运行时呈现其内容。

以下 environment 标记将在 Development 环境中运行时呈现未处理的 CSS 文件:

<environment include="Development">
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Development">
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</environment>

当在非 Development环境中运行时,以下 environment 标记将呈现捆绑的和缩小的 CSS 文件。 例如,在 ProductionStaging 中运行将触发这些样式表的呈现:

<environment exclude="Development">
    <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
          asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
          asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
<environment names="Staging,Production">
    <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
          asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
          asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>

从 Gulp 使用 bundleconfig

在某些情况下,应用的绑定和缩减工作流需要额外处理。 示例包括图像优化、缓存清除和 CDN 资产处理。 为了满足这些要求,你可以将绑定和缩减工作流转换为使用 Gulp。

使用捆绑程序 & 缩小器扩展

Visual Studio捆绑程序 & 缩小器扩展处理到 Gulp 的转换。

备注

捆绑程序 & 缩小器扩展属于 Microsoft 不提供支持的 GitHub 上的社区驱动项目。 应在此处归档问题。

右键单击解决方案资源管理器中的bundleconfig文件,然后选择捆绑程序 & 缩小器 > 转换为 Gulp ...

转换为 Gulp 上下文菜单项

gulpfile文件添加到项目。 安装 package.json 文件的 devDependencies 部分中列出的支持的 npm 包。

在 PMC 窗口中运行以下命令,以将 Gulp CLI 作为全局依赖项安装:

npm i -g gulp-cli

Gulpfile文件读取输入、输出和设置的bundleconfig文件。

'use strict';

var gulp = require('gulp'),
    concat = require('gulp-concat'),
    cssmin = require('gulp-cssmin'),
    htmlmin = require('gulp-htmlmin'),
    uglify = require('gulp-uglify'),
    merge = require('merge-stream'),
    del = require('del'),
    bundleconfig = require('./bundleconfig.json');

// Code omitted for brevity

手动转换

如果 Visual Studio 和/或捆绑程序 & 缩小器扩展不可用,请手动转换。

使用以下 devDependencies包 json文件添加到项目根目录:

警告

gulp-uglify 模块不支持 ECMAScript (ES) 2015/ES6 和更高版本。 安装gulp-terser而不是 gulp-uglify 来使用 ES2015/ES6 或更高版本。

"devDependencies": {
  "del": "^3.0.0",
  "gulp": "^4.0.0",
  "gulp-concat": "^2.6.1",
  "gulp-cssmin": "^0.2.0",
  "gulp-htmlmin": "^3.0.0",
  "gulp-uglify": "^3.0.0",
  "merge-stream": "^1.0.1"
}

通过在与 package 相同的级别上运行以下命令来安装依赖项 。 json

npm i

安装 Gulp CLI 作为全局依赖项:

npm i -g gulp-cli

将下面的gulpfile文件复制到项目根:

'use strict';

var gulp = require('gulp'),
    concat = require('gulp-concat'),
    cssmin = require('gulp-cssmin'),
    htmlmin = require('gulp-htmlmin'),
    uglify = require('gulp-uglify'),
    merge = require('merge-stream'),
    del = require('del'),
    bundleconfig = require('./bundleconfig.json');

const regex = {
    css: /\.css$/,
    html: /\.(html|htm)$/,
    js: /\.js$/
};

gulp.task('min:js', async function () {
    merge(getBundles(regex.js).map(bundle => {
        return gulp.src(bundle.inputFiles, { base: '.' })
            .pipe(concat(bundle.outputFileName))
            .pipe(uglify())
            .pipe(gulp.dest('.'));
    }))
});

gulp.task('min:css', async function () {
    merge(getBundles(regex.css).map(bundle => {
        return gulp.src(bundle.inputFiles, { base: '.' })
            .pipe(concat(bundle.outputFileName))
            .pipe(cssmin())
            .pipe(gulp.dest('.'));
    }))
});

gulp.task('min:html', async function () {
    merge(getBundles(regex.html).map(bundle => {
        return gulp.src(bundle.inputFiles, { base: '.' })
            .pipe(concat(bundle.outputFileName))
            .pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
            .pipe(gulp.dest('.'));
    }))
});

gulp.task('min', gulp.series(['min:js', 'min:css', 'min:html']));

gulp.task('clean', () => {
    return del(bundleconfig.map(bundle => bundle.outputFileName));
});

gulp.task('watch', () => {
    getBundles(regex.js).forEach(
        bundle => gulp.watch(bundle.inputFiles, gulp.series(["min:js"])));

    getBundles(regex.css).forEach(
        bundle => gulp.watch(bundle.inputFiles, gulp.series(["min:css"])));

    getBundles(regex.html).forEach(
        bundle => gulp.watch(bundle.inputFiles, gulp.series(['min:html'])));
});

const getBundles = (regexPattern) => {
    return bundleconfig.filter(bundle => {
        return regexPattern.test(bundle.outputFileName);
    });
};

gulp.task('default', gulp.series("min"));

运行 Gulp 任务

若要在 Visual Studio 中生成项目之前触发 Gulp 缩减任务,请将以下MSBuild 目标添加到 * .csproj 文件:

<Target Name="MyPreCompileTarget" BeforeTargets="Build">
  <Exec Command="gulp min" />
</Target>

在此示例中,MyPreCompileTarget 目标内定义的所有任务在预定义 Build 目标之前运行。 Visual Studio 的输出窗口中显示类似于以下内容的输出:

1>------ Build started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll
1>[14:17:49] Using gulpfile C:\BuildBundlerMinifierApp\gulpfile.js
1>[14:17:49] Starting 'min:js'...
1>[14:17:49] Starting 'min:css'...
1>[14:17:49] Starting 'min:html'...
1>[14:17:49] Finished 'min:js' after 83 ms
1>[14:17:49] Finished 'min:css' after 88 ms
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

其他资源