"
 
 
 
ASP.NET (snapshot 2017) Microsoft documentation and samples

Bundling and minification

By Scott Addie

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?

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. 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. If you don’t set the expires header correctly on your assets, and if you don’t use bundling and minification, 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. Bundling reduces the number of server requests which are necessary to render a web asset, such as a web page. 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. 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.

Consider the following JavaScript function:

[!code-javascriptMain]

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

Minification reduces the function to the following:

[!code-javascriptMain]

   1:  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 With B/M Without B/M Change
File Requests 7 18 157%
KB Transferred 156 264.68 70%
Load Time (ms) 885 2360 167%

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

The MVC and Razor Pages project templates provide an out-of-the-box solution for bundling and minification consisting of a JSON configuration file. Third-party tools, such as the (xref:)Gulp and (xref:)Grunt task runners, accomplish the same tasks with a bit more complexity. 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

The MVC and Razor Pages project templates provide a bundleconfig.json configuration file which defines the options for each bundle. By default, a single bundle configuration is defined for the custom JavaScript (wwwroot/js/site.js) and stylesheet (wwwroot/css/site.css) files:

[!code-jsonMain]

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

Bundle options include:

Build-time execution of bundling and minification

The BuildBundlerMinifier NuGet package enables the execution of bundling and minification at build time. The package injects MSBuild Targets which run at build and clean time. The bundleconfig.json file is analyzed by the build process to produce the output files based on the defined configuration.

Visual Studio

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

.NET Core CLI

Add the BuildBundlerMinifier package to your project:

dotnet add package BuildBundlerMinifier

If using ASP.NET Core 1.x, restore the newly added package:

dotnet restore

Build the project:

dotnet build

The following appears:

Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.


    Bundler: Begin processing bundleconfig.json
    Bundler: Done processing bundleconfig.json
    BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll

Clean the project:

dotnet clean

The following output appears:

Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.


  Bundler: Cleaning output from bundleconfig.json
  Bundler: Done cleaning output file from bundleconfig.json

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. Add the BundlerMinifier.Core NuGet package to your project:

[!code-xmlMain]

   1:  <Project Sdk="Microsoft.NET.Sdk.Web">
   2:    <PropertyGroup>
   3:      <TargetFramework>netcoreapp2.0</TargetFramework>
   4:    </PropertyGroup>
   5:    <ItemGroup>
   6:      <PackageReference Include="BuildBundlerMinifier" Version="2.6.362" />
   7:      <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
   8:    </ItemGroup>
   9:    <ItemGroup>
  10:      <DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />
  11:      <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
  12:    </ItemGroup>
  13:    <!--
  14:    <Target Name="MyPreCompileTarget" BeforeTargets="Build">
  15:      <Exec Command="gulp min" />
  16:    </Target>
  17:    -->
  18:  </Project>

This package extends the .NET Core CLI to include the dotnet-bundle tool. The following command can be executed in the Package Manager Console (PMC) window or in a command shell:

dotnet bundle

[!IMPORTANT] NuGet Package Manager adds dependencies to the .csproj file as <PackageReference /> nodes. The dotnet bundle command is registered with the .NET Core CLI only when a <DotNetCliToolReference /> node is used. Modify the .csproj file accordingly.

Add files to workflow

Consider an example in which an additional custom.css file is added resembling the following:

[!code-cssMain]

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

To minify custom.css and bundle it with site.css into a site.min.css file, add the relative path to bundleconfig.json:

[!code-jsonMain]

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

[!NOTE] Alternatively, the following globbing pattern could be used:

This globbing pattern matches all CSS files and excludes the minified file pattern.

Build the application. 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 (xref:)Environment Tag Helper in your views. The Environment Tag Helper only renders its contents when running in specific (xref:)environments.

The following environment tag renders the unprocessed CSS files when running in the Development environment:

ASP.NET Core 2.x

[!code-cshtmlMain]

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <meta charset="utf-8" />
   5:      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   6:      <title>@ViewData["Title"] - BuildBundlerMinifierApp</title>
   7:   
   8:      @*
   9:      <environment names="Development">
  10:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  11:          <link rel="stylesheet" href="~/css/site.css" />
  12:      </environment>
  13:      <environment names="Staging,Production">
  14:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  15:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  16:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  17:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  18:      </environment>
  19:      *@
  20:   
  21:      <environment include="Development">
  22:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  23:          <link rel="stylesheet" href="~/css/site.css" />
  24:      </environment>
  25:      <environment exclude="Development">
  26:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  27:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  28:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  29:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  30:      </environment>
  31:  </head>
  32:  <body>
  33:      <nav class="navbar navbar-inverse navbar-fixed-top">
  34:          <div class="container">
  35:              <div class="navbar-header">
  36:                  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  37:                      <span class="sr-only">Toggle navigation</span>
  38:                      <span class="icon-bar"></span>
  39:                      <span class="icon-bar"></span>
  40:                      <span class="icon-bar"></span>
  41:                  </button>
  42:                  <a asp-page="/Index" class="navbar-brand">BuildBundlerMinifierApp</a>
  43:              </div>
  44:              <div class="navbar-collapse collapse">
  45:                  <ul class="nav navbar-nav">
  46:                      <li><a asp-page="/Index">Home</a></li>
  47:                      <li><a asp-page="/About">About</a></li>
  48:                      <li><a asp-page="/Contact">Contact</a></li>
  49:                  </ul>
  50:              </div>
  51:          </div>
  52:      </nav>
  53:      <div class="container body-content">
  54:          @RenderBody()
  55:          <hr />
  56:          <footer>
  57:              <p>&copy; 2017 - BuildBundlerMinifierApp</p>
  58:          </footer>
  59:      </div>
  60:   
  61:      <environment include="Development">
  62:          <script src="~/lib/jquery/dist/jquery.js"></script>
  63:          <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
  64:          <script src="~/js/site.js" asp-append-version="true"></script>
  65:      </environment>
  66:      <environment exclude="Development">
  67:          <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
  68:                  asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
  69:                  asp-fallback-test="window.jQuery"
  70:                  crossorigin="anonymous"
  71:                  integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
  72:          </script>
  73:          <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
  74:                  asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
  75:                  asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
  76:                  crossorigin="anonymous"
  77:                  integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
  78:          </script>
  79:          <script src="~/js/site.min.js" asp-append-version="true"></script>
  80:      </environment>
  81:   
  82:      @RenderSection("Scripts", required: false)
  83:  </body>
  84:  </html>

ASP.NET Core 1.x

[!code-cshtmlMain]

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <meta charset="utf-8" />
   5:      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   6:      <title>@ViewData["Title"] - BuildBundlerMinifierApp</title>
   7:   
   8:      @*
   9:      <environment names="Development">
  10:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  11:          <link rel="stylesheet" href="~/css/site.css" />
  12:      </environment>
  13:      <environment names="Staging,Production">
  14:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  15:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  16:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  17:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  18:      </environment>
  19:      *@
  20:   
  21:      <environment include="Development">
  22:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  23:          <link rel="stylesheet" href="~/css/site.css" />
  24:      </environment>
  25:      <environment exclude="Development">
  26:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  27:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  28:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  29:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  30:      </environment>
  31:  </head>
  32:  <body>
  33:      <nav class="navbar navbar-inverse navbar-fixed-top">
  34:          <div class="container">
  35:              <div class="navbar-header">
  36:                  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  37:                      <span class="sr-only">Toggle navigation</span>
  38:                      <span class="icon-bar"></span>
  39:                      <span class="icon-bar"></span>
  40:                      <span class="icon-bar"></span>
  41:                  </button>
  42:                  <a asp-page="/Index" class="navbar-brand">BuildBundlerMinifierApp</a>
  43:              </div>
  44:              <div class="navbar-collapse collapse">
  45:                  <ul class="nav navbar-nav">
  46:                      <li><a asp-page="/Index">Home</a></li>
  47:                      <li><a asp-page="/About">About</a></li>
  48:                      <li><a asp-page="/Contact">Contact</a></li>
  49:                  </ul>
  50:              </div>
  51:          </div>
  52:      </nav>
  53:      <div class="container body-content">
  54:          @RenderBody()
  55:          <hr />
  56:          <footer>
  57:              <p>&copy; 2017 - BuildBundlerMinifierApp</p>
  58:          </footer>
  59:      </div>
  60:   
  61:      <environment include="Development">
  62:          <script src="~/lib/jquery/dist/jquery.js"></script>
  63:          <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
  64:          <script src="~/js/site.js" asp-append-version="true"></script>
  65:      </environment>
  66:      <environment exclude="Development">
  67:          <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
  68:                  asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
  69:                  asp-fallback-test="window.jQuery"
  70:                  crossorigin="anonymous"
  71:                  integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
  72:          </script>
  73:          <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
  74:                  asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
  75:                  asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
  76:                  crossorigin="anonymous"
  77:                  integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
  78:          </script>
  79:          <script src="~/js/site.min.js" asp-append-version="true"></script>
  80:      </environment>
  81:   
  82:      @RenderSection("Scripts", required: false)
  83:  </body>
  84:  </html>


The following environment tag renders the bundled and minified CSS files when running in an environment other than Development. For example, running in Production or Staging triggers the rendering of these stylesheets:

ASP.NET Core 2.x

[!code-cshtmlMain]

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <meta charset="utf-8" />
   5:      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   6:      <title>@ViewData["Title"] - BuildBundlerMinifierApp</title>
   7:   
   8:      @*
   9:      <environment names="Development">
  10:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  11:          <link rel="stylesheet" href="~/css/site.css" />
  12:      </environment>
  13:      <environment names="Staging,Production">
  14:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  15:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  16:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  17:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  18:      </environment>
  19:      *@
  20:   
  21:      <environment include="Development">
  22:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  23:          <link rel="stylesheet" href="~/css/site.css" />
  24:      </environment>
  25:      <environment exclude="Development">
  26:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  27:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  28:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  29:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  30:      </environment>
  31:  </head>
  32:  <body>
  33:      <nav class="navbar navbar-inverse navbar-fixed-top">
  34:          <div class="container">
  35:              <div class="navbar-header">
  36:                  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  37:                      <span class="sr-only">Toggle navigation</span>
  38:                      <span class="icon-bar"></span>
  39:                      <span class="icon-bar"></span>
  40:                      <span class="icon-bar"></span>
  41:                  </button>
  42:                  <a asp-page="/Index" class="navbar-brand">BuildBundlerMinifierApp</a>
  43:              </div>
  44:              <div class="navbar-collapse collapse">
  45:                  <ul class="nav navbar-nav">
  46:                      <li><a asp-page="/Index">Home</a></li>
  47:                      <li><a asp-page="/About">About</a></li>
  48:                      <li><a asp-page="/Contact">Contact</a></li>
  49:                  </ul>
  50:              </div>
  51:          </div>
  52:      </nav>
  53:      <div class="container body-content">
  54:          @RenderBody()
  55:          <hr />
  56:          <footer>
  57:              <p>&copy; 2017 - BuildBundlerMinifierApp</p>
  58:          </footer>
  59:      </div>
  60:   
  61:      <environment include="Development">
  62:          <script src="~/lib/jquery/dist/jquery.js"></script>
  63:          <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
  64:          <script src="~/js/site.js" asp-append-version="true"></script>
  65:      </environment>
  66:      <environment exclude="Development">
  67:          <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
  68:                  asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
  69:                  asp-fallback-test="window.jQuery"
  70:                  crossorigin="anonymous"
  71:                  integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
  72:          </script>
  73:          <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
  74:                  asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
  75:                  asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
  76:                  crossorigin="anonymous"
  77:                  integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
  78:          </script>
  79:          <script src="~/js/site.min.js" asp-append-version="true"></script>
  80:      </environment>
  81:   
  82:      @RenderSection("Scripts", required: false)
  83:  </body>
  84:  </html>

ASP.NET Core 1.x

[!code-cshtmlMain]

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <meta charset="utf-8" />
   5:      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   6:      <title>@ViewData["Title"] - BuildBundlerMinifierApp</title>
   7:   
   8:      @*
   9:      <environment names="Development">
  10:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  11:          <link rel="stylesheet" href="~/css/site.css" />
  12:      </environment>
  13:      <environment names="Staging,Production">
  14:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  15:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  16:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  17:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  18:      </environment>
  19:      *@
  20:   
  21:      <environment include="Development">
  22:          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  23:          <link rel="stylesheet" href="~/css/site.css" />
  24:      </environment>
  25:      <environment exclude="Development">
  26:          <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  27:                asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  28:                asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  29:          <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  30:      </environment>
  31:  </head>
  32:  <body>
  33:      <nav class="navbar navbar-inverse navbar-fixed-top">
  34:          <div class="container">
  35:              <div class="navbar-header">
  36:                  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  37:                      <span class="sr-only">Toggle navigation</span>
  38:                      <span class="icon-bar"></span>
  39:                      <span class="icon-bar"></span>
  40:                      <span class="icon-bar"></span>
  41:                  </button>
  42:                  <a asp-page="/Index" class="navbar-brand">BuildBundlerMinifierApp</a>
  43:              </div>
  44:              <div class="navbar-collapse collapse">
  45:                  <ul class="nav navbar-nav">
  46:                      <li><a asp-page="/Index">Home</a></li>
  47:                      <li><a asp-page="/About">About</a></li>
  48:                      <li><a asp-page="/Contact">Contact</a></li>
  49:                  </ul>
  50:              </div>
  51:          </div>
  52:      </nav>
  53:      <div class="container body-content">
  54:          @RenderBody()
  55:          <hr />
  56:          <footer>
  57:              <p>&copy; 2017 - BuildBundlerMinifierApp</p>
  58:          </footer>
  59:      </div>
  60:   
  61:      <environment include="Development">
  62:          <script src="~/lib/jquery/dist/jquery.js"></script>
  63:          <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
  64:          <script src="~/js/site.js" asp-append-version="true"></script>
  65:      </environment>
  66:      <environment exclude="Development">
  67:          <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
  68:                  asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
  69:                  asp-fallback-test="window.jQuery"
  70:                  crossorigin="anonymous"
  71:                  integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
  72:          </script>
  73:          <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
  74:                  asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
  75:                  asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
  76:                  crossorigin="anonymous"
  77:                  integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
  78:          </script>
  79:          <script src="~/js/site.min.js" asp-append-version="true"></script>
  80:      </environment>
  81:   
  82:      @RenderSection("Scripts", required: false)
  83:  </body>
  84:  </html>


Consume bundleconfig.json from Gulp

There are cases in which an app’s bundling and minification workflow requires additional processing. Examples include image optimization, cache busting, and CDN asset processing. To satisfy these requirements, you can convert the bundling and minification workflow to use Gulp.

Use the Bundler & Minifier extension

The Visual Studio Bundler & Minifier extension handles the conversion to Gulp.

Right-click the bundleconfig.json file in Solution Explorer and select Bundler & Minifier > Convert To Gulp…:

Convert To Gulp context menu item
Convert To Gulp context menu item

The gulpfile.js and package.json files are added to the project. The supporting npm packages listed in the package.json file’s devDependencies section are installed.

Run the following command in the PMC window to install the Gulp CLI as a global dependency:

npm i -g gulp-cli

The gulpfile.js file reads the bundleconfig.json file for the inputs, outputs, and settings.

[!code-javascriptMain]

   1:  "use strict";
   2:   
   3:  var gulp = require("gulp"),
   4:      concat = require("gulp-concat"),
   5:      cssmin = require("gulp-cssmin"),
   6:      htmlmin = require("gulp-htmlmin"),
   7:      uglify = require("gulp-uglify"),
   8:      merge = require("merge-stream"),
   9:      del = require("del"),
  10:      bundleconfig = require("./bundleconfig.json");
  11:   
  12:  // Code omitted for brevity
  13:   
  14:  var regex = {
  15:      css: /\.css$/,
  16:      html: /\.(html|htm)$/,
  17:      js: /\.js$/
  18:  };
  19:   
  20:  gulp.task("min", ["min:js", "min:css", "min:html"]);
  21:   
  22:  gulp.task("min:js", function () {
  23:      var tasks = getBundles(regex.js).map(function (bundle) {
  24:          return gulp.src(bundle.inputFiles, { base: "." })
  25:              .pipe(concat(bundle.outputFileName))
  26:              .pipe(uglify())
  27:              .pipe(gulp.dest("."));
  28:      });
  29:      return merge(tasks);
  30:  });
  31:   
  32:  gulp.task("min:css", function () {
  33:      var tasks = getBundles(regex.css).map(function (bundle) {
  34:          return gulp.src(bundle.inputFiles, { base: "." })
  35:              .pipe(concat(bundle.outputFileName))
  36:              .pipe(cssmin())
  37:              .pipe(gulp.dest("."));
  38:      });
  39:      return merge(tasks);
  40:  });
  41:   
  42:  gulp.task("min:html", function () {
  43:      var tasks = getBundles(regex.html).map(function (bundle) {
  44:          return gulp.src(bundle.inputFiles, { base: "." })
  45:              .pipe(concat(bundle.outputFileName))
  46:              .pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
  47:              .pipe(gulp.dest("."));
  48:      });
  49:      return merge(tasks);
  50:  });
  51:   
  52:  gulp.task("clean", function () {
  53:      var files = bundleconfig.map(function (bundle) {
  54:          return bundle.outputFileName;
  55:      });
  56:   
  57:      return del(files);
  58:  });
  59:   
  60:  gulp.task("watch", function () {
  61:      getBundles(regex.js).forEach(function (bundle) {
  62:          gulp.watch(bundle.inputFiles, ["min:js"]);
  63:      });
  64:   
  65:      getBundles(regex.css).forEach(function (bundle) {
  66:          gulp.watch(bundle.inputFiles, ["min:css"]);
  67:      });
  68:   
  69:      getBundles(regex.html).forEach(function (bundle) {
  70:          gulp.watch(bundle.inputFiles, ["min:html"]);
  71:      });
  72:  });
  73:   
  74:  function getBundles(regexPattern) {
  75:      return bundleconfig.filter(function (bundle) {
  76:          return regexPattern.test(bundle.outputFileName);
  77:      });
  78:  }

Convert manually

If Visual Studio and/or the Bundler & Minifier extension aren’t available, convert manually.

Add a package.json file, with the following devDependencies, to the project root:

[!code-jsonMain]

   1:  {
   2:    "name": "app",
   3:    "version": "1.0.0",
   4:    "private": true,
   5:    "devDependencies": {
   6:      "del": "^3.0.0",
   7:      "gulp": "^3.9.1",
   8:      "gulp-concat": "^2.6.1",
   9:      "gulp-cssmin": "^0.2.0",
  10:      "gulp-htmlmin": "^3.0.0",
  11:      "gulp-uglify": "^3.0.0",
  12:      "merge-stream": "^1.0.1"
  13:    }
  14:  }

Install the dependencies by running the following command at the same level as package.json:

npm i

Install the Gulp CLI as a global dependency:

npm i -g gulp-cli

Copy the gulpfile.js file below to the project root:

[!code-javascriptMain]

   1:  "use strict";
   2:   
   3:  var gulp = require("gulp"),
   4:      concat = require("gulp-concat"),
   5:      cssmin = require("gulp-cssmin"),
   6:      htmlmin = require("gulp-htmlmin"),
   7:      uglify = require("gulp-uglify"),
   8:      merge = require("merge-stream"),
   9:      del = require("del"),
  10:      bundleconfig = require("./bundleconfig.json");
  11:   
  12:  // Code omitted for brevity
  13:   
  14:  var regex = {
  15:      css: /\.css$/,
  16:      html: /\.(html|htm)$/,
  17:      js: /\.js$/
  18:  };
  19:   
  20:  gulp.task("min", ["min:js", "min:css", "min:html"]);
  21:   
  22:  gulp.task("min:js", function () {
  23:      var tasks = getBundles(regex.js).map(function (bundle) {
  24:          return gulp.src(bundle.inputFiles, { base: "." })
  25:              .pipe(concat(bundle.outputFileName))
  26:              .pipe(uglify())
  27:              .pipe(gulp.dest("."));
  28:      });
  29:      return merge(tasks);
  30:  });
  31:   
  32:  gulp.task("min:css", function () {
  33:      var tasks = getBundles(regex.css).map(function (bundle) {
  34:          return gulp.src(bundle.inputFiles, { base: "." })
  35:              .pipe(concat(bundle.outputFileName))
  36:              .pipe(cssmin())
  37:              .pipe(gulp.dest("."));
  38:      });
  39:      return merge(tasks);
  40:  });
  41:   
  42:  gulp.task("min:html", function () {
  43:      var tasks = getBundles(regex.html).map(function (bundle) {
  44:          return gulp.src(bundle.inputFiles, { base: "." })
  45:              .pipe(concat(bundle.outputFileName))
  46:              .pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
  47:              .pipe(gulp.dest("."));
  48:      });
  49:      return merge(tasks);
  50:  });
  51:   
  52:  gulp.task("clean", function () {
  53:      var files = bundleconfig.map(function (bundle) {
  54:          return bundle.outputFileName;
  55:      });
  56:   
  57:      return del(files);
  58:  });
  59:   
  60:  gulp.task("watch", function () {
  61:      getBundles(regex.js).forEach(function (bundle) {
  62:          gulp.watch(bundle.inputFiles, ["min:js"]);
  63:      });
  64:   
  65:      getBundles(regex.css).forEach(function (bundle) {
  66:          gulp.watch(bundle.inputFiles, ["min:css"]);
  67:      });
  68:   
  69:      getBundles(regex.html).forEach(function (bundle) {
  70:          gulp.watch(bundle.inputFiles, ["min:html"]);
  71:      });
  72:  });
  73:   
  74:  function getBundles(regexPattern) {
  75:      return bundleconfig.filter(function (bundle) {
  76:          return regexPattern.test(bundle.outputFileName);
  77:      });
  78:  }

Run Gulp tasks

To trigger the Gulp minification task before the project builds in Visual Studio, add the following MSBuild Target to the *.csproj file:

[!code-xmlMain]

   1:  <Project Sdk="Microsoft.NET.Sdk.Web">
   2:    <PropertyGroup>
   3:      <TargetFramework>netcoreapp2.0</TargetFramework>
   4:    </PropertyGroup>
   5:    <ItemGroup>
   6:      <PackageReference Include="BuildBundlerMinifier" Version="2.6.362" />
   7:      <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
   8:    </ItemGroup>
   9:    <ItemGroup>
  10:      <DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />
  11:      <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
  12:    </ItemGroup>
  13:    <!--
  14:    <Target Name="MyPreCompileTarget" BeforeTargets="Build">
  15:      <Exec Command="gulp min" />
  16:    </Target>
  17:    -->
  18:  </Project>

In this example, any tasks defined within the MyPreCompileTarget target run before the predefined Build target. 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 ==========

Alternatively, Visual Studio’s Task Runner Explorer may be used to bind Gulp tasks to specific Visual Studio events. See (xref:)Running default tasks for instructions on doing that.

Additional resources





Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnetcore/client-side/bundling-and-minification.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>