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

本文介绍应用捆绑和缩小的好处,包括如何在 ASP.NET Core Web 应用中使用这些功能。
最近在B站上看到杨旭老师的ASP.NET Core 3.x 入门视频(完结)的第三节的ASP.NET视频教程,里面提到到ASP.NET Core 中的捆绑和缩小静态资产,可以在微软官方文档ASP.NET Core 中的捆绑和缩小静态资产,特此记录一下,感兴趣的可以直接查看官方文档。

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

什么是捆绑和缩小

捆绑和缩小是可以在 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

捆绑和缩小的影响

操作 使用捆绑/缩小 不使用捆绑/缩小 更改
文件请求 7 18 157%
传输的 KB 156 264.68 70%
加载时间(毫秒) 885 2360 167%

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

选择捆绑和缩小策略

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

配置捆绑和缩小

备注

需要将 BuildBundlerMinifier NuGet 包添加到项目中使其正常工作。
在 ASP.NET Core 2.1 或更高版本中,将名为 bundleconfig.json 的新 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.json 文件定义每个捆绑的选项。 在前面的示例中,为自定义 JavaScript (wwwroot/js/site.js) 和样式表 (wwwroot/css/site.css) 文件定义了单一捆绑配置 。

配置选项包括:

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

向工作流添加文件

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

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

footer {
    margin-top: 10px;
}

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

[
  {
    "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 文件,并排除缩小的文件模式。

生成应用程序。 打开 site.min.css 并注意 custom.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标记将在非 Development 环境中运行时呈现捆绑的和缩小的 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>

从 Gulp 使用 bundleconfig.json

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

手动转换捆绑和缩小工作流以使用 Gulp

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

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.js 文件复制到项目根:

'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 缩小任务:

  1. 安装 BuildBundlerMinifier NuGet 包。
  2. 将以下 MSBuild 目标添加到项目文件:

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

给TA打赏
共{{data.count}}人
人已打赏
.Net Core

一位MVP的.NET Core 博客性能优化经验总结

2022-5-10 15:49:08

.Net CoreDocker

.Net Core in Docker极简入门(上篇)

2022-5-11 17:18:02

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索