ASP.NET Core 中的捆绑和缩小静态资产Bundle and minify static assets in ASP.NET Core

本文内容

作者:Scott AddieDavid Pine

本文介绍应用捆绑和缩小的好处,包括如何在 ASP.NET Core Web 应用中使用这些功能。

什么是捆绑和缩小What is bundling and minification

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

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

捆绑Bundling

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

缩小Minification

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

考虑以下 JavaScript 函数:

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

缩小将函数缩减为以下内容:

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

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

原始重命名
imageTagAndImageIDt
imageContexta
imageElementr

捆绑和缩小的影响Impact of bundling and minification

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

操作使用捆绑/缩小不使用捆绑/缩小更改
文件请求718157%
传输的 KB156264.6870%
加载时间(毫秒)8852360167%

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

选择捆绑和缩小策略Choose a bundling and minification strategy

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

配置捆绑和缩小Configure bundling and minification

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

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

  1. [
  2. {
  3. "outputFileName": "wwwroot/css/site.min.css",
  4. "inputFiles": [
  5. "wwwroot/css/site.css"
  6. ]
  7. },
  8. {
  9. "outputFileName": "wwwroot/js/site.min.js",
  10. "inputFiles": [
  11. "wwwroot/js/site.js"
  12. ],
  13. "minify": {
  14. "enabled": true,
  15. "renameLocals": true
  16. },
  17. "sourceMap": false
  18. }
  19. ]

bundleconfig.json 文件定义每个捆绑的选项 。在前面的示例中,为自定义 JavaScript (wwwroot/js/site.js) 和样式表 (wwwroot/css/site.css) 文件定义了单一捆绑配置 。

配置选项包括:

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

捆绑和缩小的生成时执行Build-time execution of bundling and minification

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

备注

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

将 BuildBundlerMinifier 包添加到项目 。

生成项目。“输出”窗口中会显示以下内容:

  1. 1>------ Build started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
  2. 1>
  3. 1>Bundler: Begin processing bundleconfig.json
  4. 1> Minified wwwroot/css/site.min.css
  5. 1> Minified wwwroot/js/site.min.js
  6. 1>Bundler: Done processing bundleconfig.json
  7. 1>BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll
  8. ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

清理项目。“输出”窗口中会显示以下内容:

  1. 1>------ Clean started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
  2. 1>
  3. 1>Bundler: Cleaning output from bundleconfig.json
  4. 1>Bundler: Done cleaning output file from bundleconfig.json
  5. ========== Clean: 1 succeeded, 0 failed, 0 skipped ==========

将 BuildBundlerMinifier 包添加到项目 :

  1. dotnet add package BuildBundlerMinifier

如果使用 ASP.NET Core 1.x,则还原新添加的包:

  1. dotnet restore

生成项目:

  1. dotnet build

将显示以下内容:

  1. Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
  2. Copyright (C) Microsoft Corporation. All rights reserved.
  3. Bundler: Begin processing bundleconfig.json
  4. Bundler: Done processing bundleconfig.json
  5. BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll

清理项目:

  1. dotnet clean

将显示以下输出:

  1. Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
  2. Copyright (C) Microsoft Corporation. All rights reserved.
  3. Bundler: Cleaning output from bundleconfig.json
  4. Bundler: Done cleaning output file from bundleconfig.json

捆绑和缩小的即席执行Ad hoc execution of bundling and minification

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

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

备注

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

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

  1. dotnet bundle

重要

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

向工作流添加文件Add files to workflow

假设添加了额外的 custom.css 文件,类似于以下内容 :

  1. .about, [role=main], [role=complementary] {
  2. margin-top: 60px;
  3. }
  4. footer {
  5. margin-top: 10px;
  6. }

若要缩小 custom.css 并将其与 site.css 捆绑到 site.min.css 文件中,请将相对路径添加到 bundleconfig.json :

  1. [
  2. {
  3. "outputFileName": "wwwroot/css/site.min.css",
  4. "inputFiles": [
  5. "wwwroot/css/site.css",
  6. "wwwroot/css/custom.css"
  7. ]
  8. },
  9. {
  10. "outputFileName": "wwwroot/js/site.min.js",
  11. "inputFiles": [
  12. "wwwroot/js/site.js"
  13. ],
  14. "minify": {
  15. "enabled": true,
  16. "renameLocals": true
  17. },
  18. "sourceMap": false
  19. }
  20. ]

备注

或者,可以使用以下通配模式:

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

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

生成应用程序。打开 site.min.css 并注意 custom.css 的内容将追加到文件末尾 。

基于环境的捆绑和缩小Environment-based bundling and minification

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

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

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

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

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

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

从 Gulp 使用 bundleconfig.jsonConsume bundleconfig.json from Gulp

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

使用捆绑程序和缩小程序扩展Use the Bundler & Minifier extension

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

备注

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

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

转换为 Gulp 上下文菜单项

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

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

  1. npm i -g gulp-cli

gulpfile.js 文件读取输入、输出和设置的 bundleconfig.json 文件 。

  1. 'use strict';
  2. var gulp = require('gulp'),
  3. concat = require('gulp-concat'),
  4. cssmin = require('gulp-cssmin'),
  5. htmlmin = require('gulp-htmlmin'),
  6. uglify = require('gulp-uglify'),
  7. merge = require('merge-stream'),
  8. del = require('del'),
  9. bundleconfig = require('./bundleconfig.json');
  10. // Code omitted for brevity

手动转换Convert manually

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

将 package.json 文件(包含以下 devDependencies)添加到项目根 :

警告

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

  1. "devDependencies": {
  2. "del": "^3.0.0",
  3. "gulp": "^4.0.0",
  4. "gulp-concat": "^2.6.1",
  5. "gulp-cssmin": "^0.2.0",
  6. "gulp-htmlmin": "^3.0.0",
  7. "gulp-uglify": "^3.0.0",
  8. "merge-stream": "^1.0.1"
  9. }

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

  1. npm i

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

  1. npm i -g gulp-cli

将以下 gulpfile.js 文件复制到项目根 :

  1. 'use strict';
  2. var gulp = require('gulp'),
  3. concat = require('gulp-concat'),
  4. cssmin = require('gulp-cssmin'),
  5. htmlmin = require('gulp-htmlmin'),
  6. uglify = require('gulp-uglify'),
  7. merge = require('merge-stream'),
  8. del = require('del'),
  9. bundleconfig = require('./bundleconfig.json');
  10. const regex = {
  11. css: /\.css$/,
  12. html: /\.(html|htm)$/,
  13. js: /\.js$/
  14. };
  15. gulp.task('min:js', async function () {
  16. merge(getBundles(regex.js).map(bundle => {
  17. return gulp.src(bundle.inputFiles, { base: '.' })
  18. .pipe(concat(bundle.outputFileName))
  19. .pipe(uglify())
  20. .pipe(gulp.dest('.'));
  21. }))
  22. });
  23. gulp.task('min:css', async function () {
  24. merge(getBundles(regex.css).map(bundle => {
  25. return gulp.src(bundle.inputFiles, { base: '.' })
  26. .pipe(concat(bundle.outputFileName))
  27. .pipe(cssmin())
  28. .pipe(gulp.dest('.'));
  29. }))
  30. });
  31. gulp.task('min:html', async function () {
  32. merge(getBundles(regex.html).map(bundle => {
  33. return gulp.src(bundle.inputFiles, { base: '.' })
  34. .pipe(concat(bundle.outputFileName))
  35. .pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
  36. .pipe(gulp.dest('.'));
  37. }))
  38. });
  39. gulp.task('min', gulp.series(['min:js', 'min:css', 'min:html']));
  40. gulp.task('clean', () => {
  41. return del(bundleconfig.map(bundle => bundle.outputFileName));
  42. });
  43. gulp.task('watch', () => {
  44. getBundles(regex.js).forEach(
  45. bundle => gulp.watch(bundle.inputFiles, gulp.series(["min:js"])));
  46. getBundles(regex.css).forEach(
  47. bundle => gulp.watch(bundle.inputFiles, gulp.series(["min:css"])));
  48. getBundles(regex.html).forEach(
  49. bundle => gulp.watch(bundle.inputFiles, gulp.series(['min:html'])));
  50. });
  51. const getBundles = (regexPattern) => {
  52. return bundleconfig.filter(bundle => {
  53. return regexPattern.test(bundle.outputFileName);
  54. });
  55. };
  56. gulp.task('default', gulp.series("min"));

运行 Gulp 任务Run Gulp tasks

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

  1. <Target Name="MyPreCompileTarget" BeforeTargets="Build">
  2. <Exec Command="gulp min" />
  3. </Target>

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

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

其他资源Additional resources