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

作者: Scott AddieDavid 松树By Scott Addie and David Pine

本文介绍了应用绑定和缩减,包括如何使用 ASP.NET Core web apps 使用这些功能的好处。This article explains the benefits of applying bundling and minification, including how these features can be used with ASP.NET Core web apps.

什么是绑定和缩减What is bundling and minification

绑定和缩减是可以在 web 应用中应用的两个不同的性能优化。Bundling and minification are two distinct performance optimizations you can apply in a web app. 绑定和缩减一起使用,可减少服务器请求数并减小请求的静态资产的大小,从而提高性能。Used together, bundling and minification improve performance by reducing the number of server requests and reducing the size of the requested static assets.

绑定和缩减主要改善第一页请求加载时间。Bundling and minification primarily improve the first page request load time. 请求网页后,浏览器会缓存静态资产(JavaScript、CSS 和图像)。Once a web page has been requested, the browser caches the static assets (JavaScript, CSS, and images). 因此,当在同一站点上请求相同的资源时,绑定和缩减不会提高性能。Consequently, bundling and minification don't improve performance when requesting the same page, or pages, on the same site requesting the same assets. 如果未在资产上正确设置 expires 标头,且未使用捆绑和缩减,则浏览器的新鲜度试探法会在几天后将资产过期。If the expires header isn't set correctly on the assets and if bundling and minification isn't used, the browser's freshness heuristics mark the assets stale after a few days. 此外,浏览器还需要对每个资产进行验证请求。Additionally, the browser requires a validation request for each asset. 在这种情况下,绑定和缩减在第一次请求页面后仍能改善性能。In this case, bundling and minification provide a performance improvement even after the first page request.

捆绑Bundling

绑定将多个文件合并到一个文件中。Bundling combines multiple files into a single file. 绑定可减少呈现 web 资产(如网页)所需的服务器请求数。Bundling reduces the number of server requests that are necessary to render a web asset, such as a web page. 可以专门为 CSS、JavaScript 等创建任意数量的单独包。文件越少,从浏览器到服务器的 HTTP 请求或提供应用程序的服务就会减少。You can create any number of individual bundles specifically for CSS, JavaScript, etc. Fewer files means fewer HTTP requests from the browser to the server or from the service providing your application. 这会提高第一页的加载性能。This results in improved first page load performance.

缩减Minification

缩减从代码中删除不必要的字符,而不更改功能。Minification removes unnecessary characters from code without altering functionality. 因此,请求的资产(如 CSS、图像和 JavaScript 文件)的大小大幅减小。The result is a significant size reduction in requested assets (such as CSS, images, and JavaScript files). 缩减的常见副作用包括将变量名称缩短为一个字符、删除注释和不必要的空格。Common side effects of minification include shortening variable names to one character and removing comments and unnecessary whitespace.

请考虑以下 JavaScript 函数:Consider the following JavaScript function:

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/, ''));
}

缩减将函数降到了以下内容:Minification reduces the function to the following:

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

除了删除注释和不必要的空格外,还会将以下参数和变量名称重命名为:In addition to removing the comments and unnecessary whitespace, the following parameter and variable names were renamed as follows:

原始Original 重命名Renamed
imageTagAndImageID t
imageContext a
imageElement r

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

下表概述了单独加载资产与使用绑定和缩减之间的差异:The following table outlines differences between individually loading assets and using bundling and minification:

操作Action 带有 B/M 的With B/M 无 B/MWithout B/M 更改Change
文件请求File Requests 77 1818 157%157%
已传输 KBKB Transferred 156156 264.68264.68 70%70%
加载时间(毫秒)Load Time (ms) 885885 23602360 167%167%

对于 HTTP 请求标头,浏览器非常详细。Browsers are fairly verbose with regard to HTTP request headers. 绑定的字节总数指标明显减少了绑定的时间。The total bytes sent metric saw a significant reduction when bundling. 加载时间显示了显著改进,但本示例在本地运行。The load time shows a significant improvement, however this example ran locally. 将捆绑与缩减与通过网络传输的资产结合使用时,可实现更高的性能提升。Greater performance gains are realized when using bundling and minification with assets transferred over a network.

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

MVC 和 Razor Pages 项目模板提供了一种现成的解决方案,可用于缩减和 JSON 配置文件。The MVC and Razor Pages project templates provide an out-of-the-box solution for bundling and minification consisting of a JSON configuration file. 第三方工具(如Grunt任务运行程序)以更复杂的方式完成相同的任务。Third-party tools, such as the Grunt task runner, accomplish the same tasks with a bit more complexity. 当开发工作流需要处理超过绑定和缩减—(例如 linting 和图像优化)时,第三方工具非常合适。A third-party tool is a great fit when your development workflow requires processing beyond bundling and minification—such as linting and image optimization. 通过使用设计时绑定和缩减,缩小文件是在应用部署之前创建的。By using design-time bundling and minification, the minified files are created prior to the app's deployment. 在部署之前绑定和缩小提供了降低服务器负载的优点。Bundling and minifying before deployment provides the advantage of reduced server load. 但是,必须认识到,设计时绑定和缩减会增加生成的复杂性,并且仅适用于静态文件。However, it's important to recognize that design-time bundling and minification increases build complexity and only works with static files.

配置捆绑和缩减Configure bundling and minification

在 ASP.NET Core 2.0 或更早版本中,MVC 和 Razor Pages 项目模板提供了一个bundleconfig配置文件,该文件定义每个绑定的选项:In ASP.NET Core 2.0 or earlier, the MVC and Razor Pages project templates provide a bundleconfig.json configuration file that defines the options for each bundle:

在 ASP.NET Core 2.1 或更高版本中,将名为bundleconfig的新 JSON 文件添加到 MVC 或 Razor Pages 项目根。In ASP.NET Core 2.1 or later, add a new JSON file, named bundleconfig.json, to the MVC or Razor Pages project root. 在该文件中包含以下 JSON 作为起始点:Include the following JSON in that file as a starting point:

[
  {
    "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文件定义每个绑定的选项。The bundleconfig.json file defines the options for each bundle. 在前面的示例中,为自定义 JavaScript (wwwroot/js/node.js)和样式表(wwwroot/css/站点导航)文件定义了单个捆绑配置。In the preceding example, a single bundle configuration is defined for the custom JavaScript (wwwroot/js/site.js) and stylesheet (wwwroot/css/site.css) files.

配置选项包括:Configuration options include:

  • outputFileName:要输出的绑定文件的名称。outputFileName: The name of the bundle file to output. 可以包含bundleconfig文件中的相对路径。Can contain a relative path from the bundleconfig.json file. 必填required
  • inputFiles:要捆绑在一起的文件的数组。inputFiles: An array of files to bundle together. 这些是配置文件的相对路径。These are relative paths to the configuration file. 可选,* 空值将导致空的输出文件。optional, *an empty value results in an empty output file. 支持通配模式。globbing patterns are supported.
  • minify:输出类型的缩减选项。minify: The minification options for the output type. 可选默认值minify: { enabled: true } -optional, default - minify: { enabled: true }
  • includeInProject:指示是否将生成的文件添加到项目文件的标记。includeInProject: Flag indicating whether to add generated files to project file. 可选默认值为 falseoptional, default - false
  • sourceMap:指示是否为捆绑的文件生成源映射的标记。sourceMap: Flag indicating whether to generate a source map for the bundled file. 可选默认值为 falseoptional, default - false
  • sourceMapRootPath:用于存储所生成的源映射文件的根路径。sourceMapRootPath: The root path for storing the generated source map file.

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

BuildBundlerMinifier NuGet 包允许在生成时执行绑定和缩减。The BuildBundlerMinifier NuGet package enables the execution of bundling and minification at build time. 包注入在生成和清理时间运行的MSBuild 目标The package injects MSBuild Targets which run at build and clean time. Bundleconfig文件由生成过程进行分析,以便基于定义的配置生成输出文件。The bundleconfig.json file is analyzed by the build process to produce the output files based on the defined configuration.

备注

BuildBundlerMinifier 属于 GitHub 上的社区驱动项目,Microsoft 不提供支持。BuildBundlerMinifier belongs to a community-driven project on GitHub for which Microsoft provides no support. 应在此处归档问题。Issues should be filed here.

BuildBundlerMinifier包添加到项目。Add the BuildBundlerMinifier package to your project.

生成项目。Build the project. "输出" 窗口中将显示以下内容:The following appears in the Output window:

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

清理项目。Clean the project. "输出" 窗口中将显示以下内容:The following appears in the Output window:

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

绑定和缩减的即席执行Ad hoc execution of bundling and minification

可以在不生成项目的情况下即席运行捆绑和缩减任务。It's possible to run the bundling and minification tasks on an ad hoc basis, without building the project. BundlerMinifier NuGet 包添加到项目:Add the BundlerMinifier.Core NuGet package to your project:

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

备注

BundlerMinifier 属于 GitHub 上的社区驱动项目,Microsoft 不提供支持。BundlerMinifier.Core belongs to a community-driven project on GitHub for which Microsoft provides no support. 应在此处归档问题。Issues should be filed here.

此包扩展以包括.NET Core CLI dotnet 捆绑工具。This package extends the .NET Core CLI to include the dotnet-bundle tool. 可以在 "包管理器控制台" (PMC)窗口或命令行界面中执行以下命令:The following command can be executed in the Package Manager Console (PMC) window or in a command shell:

dotnet bundle

重要

NuGet 包管理器将依赖项添加到 * .csproj 文件<PackageReference />作为节点。NuGet Package Manager adds dependencies to the *.csproj file as <PackageReference /> nodes. dotnet bundle命令注册.NET Core CLI 时,才<DotNetCliToolReference />使用节点。The dotnet bundle command is registered with the .NET Core CLI only when a <DotNetCliToolReference /> node is used. 请相应地修改 * .csproj 文件。Modify the *.csproj file accordingly.

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

假设添加了其他自定义 .css文件的示例类似于以下内容:Consider an example in which an additional custom.css file is added resembling the following:

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

footer {
    margin-top: 10px;
}

若要缩小自定义 css并将其与站点 中的内容捆绑在一起,请将相对路径添加到bundleconfigTo minify custom.css and bundle it with site.css into a site.min.css file, add the relative path to 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
  }
]

备注

或者,可以使用以下组合模式:Alternatively, the following globbing pattern could be used:

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

此组合模式匹配所有 CSS 文件,并排除缩小文件模式。This globbing pattern matches all CSS files and excludes the minified file pattern.

生成应用程序。Build the application. 打开 "网站"。最小 css ,请注意,自定义 css的内容将追加到文件末尾。Open site.min.css and notice the content of custom.css is appended to the end of the file.

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

最佳做法是,应在生产环境中使用应用的捆绑文件和缩小文件。As a best practice, the bundled and minified files of your app should be used in a production environment. 在开发过程中,原始文件可简化应用程序的调试。During development, the original files make for easier debugging of the app.

通过在视图中使用环境标记帮助器来指定要包含在页面中的文件。Specify which files to include in your pages by using the Environment Tag Helper in your views. 环境标记帮助程序仅在特定环境中运行时呈现其内容。The Environment Tag Helper only renders its contents when running in specific environments.

在环境中运行时,以下environment标记将呈现未处理的 CSS 文件: DevelopmentThe following environment tag renders the unprocessed CSS files when running in the Development environment:

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

当在environment以外的环境Development中运行时,以下标记将呈现捆绑的和缩小的 CSS 文件。The following environment tag renders the bundled and minified CSS files when running in an environment other than Development. 例如,在中Production运行或Staging触发这些样式表的呈现:For example, running in Production or Staging triggers the rendering of these stylesheets:

<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 使用 bundleconfigConsume bundleconfig.json from Gulp

在某些情况下,应用的绑定和缩减工作流需要额外处理。There are cases in which an app's bundling and minification workflow requires additional processing. 示例包括图像优化、缓存清除和 CDN 资产处理。Examples include image optimization, cache busting, and CDN asset processing. 为了满足这些要求,你可以将绑定和缩减工作流转换为使用 Gulp。To satisfy these requirements, you can convert the bundling and minification workflow to use Gulp.

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

Visual Studio捆绑程序 & 缩小器扩展处理到 Gulp 的转换。The Visual Studio Bundler & Minifier extension handles the conversion to Gulp.

备注

捆绑程序 & 缩小器扩展属于 Microsoft 不提供支持的 GitHub 上的社区驱动项目。The Bundler & Minifier extension belongs to a community-driven project on GitHub for which Microsoft provides no support. 应在此处归档问题。Issues should be filed here.

右键单击解决方案资源管理器中的bundleconfig文件,然后选择捆绑程序 & 缩小器 > 转换为 Gulp ...Right-click the bundleconfig.json file in Solution Explorer and select Bundler & Minifier > Convert To Gulp...:

转换为 Gulp 上下文菜单项

gulpfile文件添加到项目。The gulpfile.js and package.json files are added to the project. 安装了package. json文件的devDependencies部分中列出的支持npm包。The supporting npm packages listed in the package.json file's devDependencies section are installed.

在 PMC 窗口中运行以下命令,以将 Gulp CLI 作为全局依赖项安装:Run the following command in the PMC window to install the Gulp CLI as a global dependency:

npm i -g gulp-cli

Gulpfile文件读取输入、输出和设置的bundleconfig文件。The gulpfile.js file reads the bundleconfig.json file for the inputs, outputs, and settings.

'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

手动转换Convert manually

如果 Visual Studio 和/或捆绑程序 & 缩小器扩展不可用,请手动转换。If Visual Studio and/or the Bundler & Minifier extension aren't available, convert manually.

包 json文件devDependencies添加到项目根目录:Add a package.json file, with the following devDependencies, to the project root:

警告

gulp-uglify模块不支持 ECMAScript (ES) 2015/ES6 和更高版本。The gulp-uglify module doesn't support ECMAScript (ES) 2015 / ES6 and later. 安装gulp-terser gulp-uglify ,而不是使用 ES2015/ES6 或更高版本。Install gulp-terser instead of gulp-uglify to use ES2015 / ES6 or later.

"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 相同的级别上运行以下命令来安装依赖项 。 jsonInstall the dependencies by running the following command at the same level as package.json:

npm i

安装 Gulp CLI 作为全局依赖项:Install the Gulp CLI as a global dependency:

npm i -g gulp-cli

将下面的gulpfile文件复制到项目根:Copy the gulpfile.js file below to the project root:

'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 任务Run Gulp tasks

若要在 Visual Studio 中生成项目之前触发 Gulp 缩减任务,请将以下MSBuild 目标添加到 * .csproj 文件:To trigger the Gulp minification task before the project builds in Visual Studio, add the following MSBuild Target to the *.csproj file:

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

在此示例中,在MyPreCompileTarget目标内定义的任何任务都在预定义Build目标之前运行。In this example, any tasks defined within the MyPreCompileTarget target run before the predefined Build target. Visual Studio 的输出窗口中显示类似于以下内容的输出:Output similar to the following appears in Visual Studio's Output window:

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

其他资源Additional resources