
Since September last year, when the 
MSIE JavaScript Engine for .NET library was replaced by the 
JavaScript Engine Switcher library and the 
BundleTransformer.CleanCss module was created, there were almost no revolutionary changes in the Bundle Transformer. The changes were mainly evolutionary: support for new versions of minimizers and translators was added (the most routine and difficult part of the project), minor bugs were fixed, and work went on to increase productivity.
But this summer everything changed: from the end of May to July, a huge number of recommendations for improving the project were received from users of Bundle Transformer. Most of them were implemented in version 1.9.0 and subsequent summer updates. In this article we consider the most significant of them:
Classes StyleTransformer and ScriptTransformer
Initially, not entirely successful names were chosen for the 
CssTransformer and 
JsTransformer classes, because the choice of names was made by analogy with the 
CssMinify and 
JsMinify from 
System.Web.Optimization . When 
StyleBundle and 
ScriptBundle classes appeared in System.Web.Optimization, it became clear that these names are no good.
')
For a long time I postponed the renaming of these classes for later, but when working on version 1.9.0 I decided to rename them all the same. Now they are called 
StyleTransformer and 
ScriptTransformer . The old 
CssTransformer and 
JsTransformer classes are still available in the kernel (implemented as wrappers over new classes), but they are considered obsolete (marked with the 
Obsolete attribute) and will be removed in version 2.0.0.
Postprocessors
In late May, I received a pull request 
"Support for Autoprefixer" from Vegard Larsen (an employee of the Norwegian company 
Digital Creations ). When viewing the code, it immediately became clear that the current architecture of the Bundle Transformer is not suitable for implementing such modules. Vegard implemented the functionality of this module as a style translator, which should have been launched after all other translators (LESS, Sass, etc.). This whole implementation looked like a hack, so I decided to reject this pull request. As a result, Vegard published an unofficial version of the module - 
BundleTransformer.Autoprefixer.Unofficial , in NuGet, and I began work on a new architecture of the Bundle Transformer.
A new type of modules was required, which should have been launched after translators and before minimizers, and the number and order of calling such modules should be determined by the developer. As a name for a new type of module, I decided to use the term “postprocessors”, which was invented by Andrei Sitnik (if you don’t know who Andrei Sitnik is or what postprocessors are, I recommend you to listen to 
the 6th edition of the Frontflip podcast ).
A postprocessor in a Bundle Transformer can be any class that implements the 
IPostProcessor interface or inherits the 
PostProcessorBase base class from the 
BundleTransformer.Core.PostProcessors namespace. Like other types of modules (adapters), postprocessors must be registered in the 
Web.config file. Consider the registration process on the example of CSS postprocessors:
 <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> <core …> <css defaultPostProcessors="UrlRewritingCssPostProcessor,AutoprefixCssPostProcessor" …> <postProcessors> <add name="UrlRewritingCssPostProcessor" type="BundleTransformer.Core.PostProcessors.UrlRewritingCssPostProcessor, BundleTransformer.Core" useInDebugMode="false" /> <add name="AutoprefixCssPostProcessor" type="BundleTransformer.Autoprefixer.PostProcessors.AutoprefixCssPostProcessor, BundleTransformer.Autoprefixer" useInDebugMode="true" /> </postProcessors> </css> … </core> … </bundleTransformer> … </configuration> 
Two postprocessors are registered in the 
/configuration/bundleTransformer/core/css/postProcessors :
- UrlRewritingCssPostProcessor. Converts relative paths to absolute paths (the standard Bundle Transformer feature, implemented as a postprocessor).
- AutoprefixCssPostProcessor. Adds Autoprefix support to Bundle Transformer.
At first glance, this is very similar to the registration of minimizers, but there is one small difference: if we can specify only one minimizer in the 
defaultMinifier attribute of the 
/configuration/bundleTransformer/core/css element, then we can specify any number of postprocessors in the 
defaultPostProcessors attribute (even zero). ). Moreover, the order in which we specify the names of postprocessors in this attribute determines the order of their execution. If the attribute is missing, then the default 
UrlRewritingCssPostProcessor used as the postprocessor.
The code also shows that the value of the 
useInDebugMode attribute of postprocessors differs: in 
UrlRewritingCssPostProcessor it is 
false (conversion of relative paths to absolute is needed only in release mode, when all files are merged into one), and in 
AutoprefixCssPostProcessor - 
true (updating vendor prefixes is necessary and in debugging mode and release mode).
Registering JavaScript postprocessors is almost the same as registering CSS postprocessors, except that it must be done in the configuration element 
/configuration/bundleTransformer/core/js .
Advanced HTTP debug handlers
As a rule, most users of Bundle Transformer configure modules in a declarative way (via 
Web.config ), but in some cases it is necessary to use the imperative approach. For example, when working with LESS variables:
 using System.Collections.Generic; using System.Web.Optimization; using BundleTransformer.Core.Builders; using BundleTransformer.Core.Orderers; using BundleTransformer.Core.Transformers; using BundleTransformer.Core.Translators; using BundleTransformer.Less.Translators; public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { var nullBuilder = new NullBuilder(); var nullOrderer = new NullOrderer(); var lessTranslator = new LessTranslator { GlobalVariables = "my-variable='Hurrah!'", ModifyVariables = "font-family-base='Comic Sans MS';body-bg=lime;font-size-h1=50px" }; var styleTransformer = new StyleTransformer( new List<ITranslator> { lessTranslator }); var commonStylesBundle = new Bundle("~/Bundles/BootstrapStyles"); commonStylesBundle.Include("~/Content/bootstrap/bootstrap.less"); commonStylesBundle.Builder = nullBuilder; commonStylesBundle.Transforms.Add(styleTransformer); commonStylesBundle.Orderer = nullOrderer; bundles.Add(commonStylesBundle); } } 
In the above code, we explicitly create an instance of the 
LessTranslator class and use the 
GlobalVariables and 
ModifyVariables to configure LESS variables. With this approach, we can transmit to the translator the values ​​of LESS variables obtained from an external source (for example, a database).
There is a second way to work with LESS variables. First you need to create a custom element transformation:
 using System.Text; using System.Web.Optimization; public sealed class InjectContentItemTransform : IItemTransform { private readonly string _beforeContent; private readonly string _afterContent; public InjectContentItemTransform(string beforeContent, string afterContent) { _beforeContent = beforeContent ?? string.Empty; _afterContent = afterContent ?? string.Empty; } public string Process(string includedVirtualPath, string input) { if (_beforeContent.Length == 0 && _afterContent.Length == 0) { return input; } var contentBuilder = new StringBuilder(); if (_beforeContent.Length > 0) { contentBuilder.AppendLine(_beforeContent); } contentBuilder.AppendLine(input); if (_afterContent.Length > 0) { contentBuilder.AppendLine(_afterContent); } return contentBuilder.ToString(); } } 
And then register it when adding a file to the bundle:
 using System.Web.Optimization; using BundleTransformer.Core.Bundles; using BundleTransformer.Core.Orderers; public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { var nullOrderer = new NullOrderer(); const string beforeLessCodeToInject = @"@my-variable: 'Hurrah!';"; const string afterLessCodeToInject = @"@font-family-base: 'Comic Sans MS'; @body-bg: lime; @font-size-h1: 50px;"; var commonStylesBundle = new CustomStyleBundle("~/Bundles/BootstrapStyles"); commonStylesBundle.Include( "~/Content/bootstrap/bootstrap.less", new InjectContentItemTransform(beforeLessCodeToInject, afterLessCodeToInject)); commonStylesBundle.Orderer = nullOrderer; bundles.Add(commonStylesBundle); } } 
Unfortunately, the above code examples previously only worked in release mode. This was due to the fact that the HTTP debug handlers "did not know anything" about the settings of the bundles and simply translated the code of the requested files.
To solve this problem, it was necessary, first of all, to find a way to transfer debug HTTP handlers to the URL of the bundle, which includes the requested file. After examining the 
System.Web.Optimization.dll assembly code using the decompiler, the solution was found: you need to write your own version of the 
BundleResolver class and register it in the appropriate class. I will not go into the implementation details, but just show you how to use the created class:
 … using BundleTransformer.Core.Resolvers; public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { BundleResolver.Current = new CustomBundleResolver(); … } } 
After that, in debug mode, links to the following files will be generated:
 <link href="/Content/bootstrap/bootstrap.less?bundleVirtualPath=%7e%2fBundles%2fBootstrapStyles" rel="stylesheet"> 
where the query string parameter 
bundleVirtualPath contains the URL of the bundle.
Thus, with the bandl URL, I added to the basic debug HTTP-handler the ability to apply custom element transformations and transformations set at the bundle level (translators and postprocessors) to the requested file.
In addition, two additional HTTP handlers were created:
- CssAssetHandler. To handle CSS files.
- JsAssetHandler. To handle javascript files.
They allow you to apply custom element transformations and postprocessors to static files. If the requested static file is not included in any of the bundles, then these HTTP handlers pass the request to an instance of the 
System.Web.StaticFileHandler class. In contrast to the HTTP debugger henders bundled with translators, these HTTP handlers are not registered in the 
Web.config file automatically (during the installation of NuGet packages), they must be registered manually:
 <configuration> … <system.webServer> … <handlers> … <add name="CssAssetHandler" path="*.css" verb="GET" type="BundleTransformer.Core.HttpHandlers.CssAssetHandler, BundleTransformer.Core" resourceType="File" preCondition="" /> <add name="JsAssetHandler" path="*.js" verb="GET" type="BundleTransformer.Core.HttpHandlers.JsAssetHandler, BundleTransformer.Core" resourceType="File" preCondition="" /> … </handlers> … </system.webServer> … </configuration> 
Mapping File Extensions and Resource Types in Web.config
Previously, the mapping of file extensions and resource types was hard-wired in the 
Asset class code. Now the configuration elements 
fileExtensions from the 
Web.config file are used for this:
 <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> <core …> <css …> … <fileExtensions> <add fileExtension=".css" assetTypeCode="Css" /> <add fileExtension=".less" assetTypeCode="Less" /> <add fileExtension=".sass" assetTypeCode="Sass" /> <add fileExtension=".scss" assetTypeCode="Scss" /> </fileExtensions> </css> <js …> … <fileExtensions> <add fileExtension=".js" assetTypeCode="JavaScript" /> <add fileExtension=".coffee" assetTypeCode="CoffeeScript" /> <add fileExtension=".litcoffee" assetTypeCode="LiterateCoffeeScript" /> <add fileExtension=".coffee.md" assetTypeCode="LiterateCoffeeScript" /> <add fileExtension=".ts" assetTypeCode="TypeScript" /> <add fileExtension=".mustache" assetTypeCode="Mustache" /> <add fileExtension=".handlebars" assetTypeCode="Handlebars" /> <add fileExtension=".hbs" assetTypeCode="Handlebars" /> </fileExtensions> </js> … </core> … </bundleTransformer> … </configuration> 
The example above shows the situation when all official Bundle Transformer modules are installed (comparisons for the 
.css and 
.js extensions are added during the kernel installation, and the rest when the corresponding translator modules are installed). This architecture gives us the following advantages:
- No need to store unused mappings. As a rule, in real projects it is not necessary to install all types of translators (for example, the simultaneous use of LESS and Sass is very rare), thus fewer comparisons will be stored in the project.
- The ability to create informal modules-translators. Since now there is no dependence on the kernel code, users of Bundle Transformer have the opportunity to create their own module-translators. An example of such a module is the AngularBundle NuGet package, which , when installed, adds the following mapping to the Web.configfile:
 
  <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> <core> … <js …> … <fileExtensions> … <add fileExtension=".html" assetTypeCode="AngularTemplate" /> … </fileExtensions> </js> … </core> … </bundleTransformer> … </configuration>
 
 
- Binding of new file extensions to existing modules-translators. For example, if we want the BundleTransformer.Hogan module to start processing files with the .htmlextension, we just need to add the following code to theWeb.configfile:
 
  <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> <core …> … <js …> … <fileExtensions> … <add fileExtension=".html" assetTypeCode="Mustache" /> … </fileExtensions> </js> … </core> … </bundleTransformer> … <system.webServer> … <handlers> … <add name="HtmlAssetHandler" path="*.html" verb="GET" type="BundleTransformer.Hogan.HttpHandlers.HoganAssetHandler, BundleTransformer.Hogan" resourceType="File" preCondition="" /> … </handlers> … </system.webServer> … </configuration>
 
Merging code files before minimizing
The Bundle Transformer, unlike System.Web.Optimization, processes each file separately and this approach offers several advantages:
- It becomes possible to combine different types of resources into one bundle (for example, CSS-, LESS- and Sass-files).
- The minimization of previously minimized files (files with the extensions .min.cssand.min.js) is not.min.js, which in most cases increases the speed of minimization during the initial access to the bundle.
But some users of Bundle Transformer did not like this approach, because they wanted to take full advantage of the structural minimization features that modern minimizers provide (for example, 
CSSO from Yandex).
Therefore, in the new version, the 
css and 
js configuration elements have the 
combineFilesBeforeMinification attribute (the default value is 
false ), which allows you to enable the merging of the file code before minimizing:
 <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> <core <css … combineFilesBeforeMinification="true"> … </css> <js … combineFilesBeforeMinification="true"> … </js> … </core> … </bundleTransformer> … </configuration> 
New modules
During this time, three official modules were created for Bundle Transformer at once:
- Postprocessor BundleTransformer.Autoprefixer
- Translator BundleTransformer.Handlebars
- Translator BundleTransformer.Hogan
Since all three modules are based on the code of JavaScript libraries, immediately after installation you need to select your own JavaScript engine for each of them (for more information, see the 
readme.txt files of the corresponding NuGet packages).
Consider each of them separately:
Bundle Transformer: Autoprefixer
The 
BundleTransformer.Autoprefixer module contains the 
AutoprefixCssPostProcessor postprocessor 
AutoprefixCssPostProcessor , which updates the vendor prefixes in the CSS code. 
AutoprefixCssPostProcessor is based on the popular CSS postprocessor - 
Autoprefixer (version 3.1 is currently supported). I will not explain why Autoprefixer is needed, because you can emphasize all the basic information about this product from Andrei Sitnik 
’s article 
“Autoprefixer - the final solution to the problem of prefixes in CSS” .
In this section, I will discuss how to properly configure BundleTransformer.Autoprefixer. If you have not read the Postprocessors and the Advanced HTTP Debug Handlers sections of this article, be sure to read them, since they address many important points related to the work of BundleTransformer.Autoprefixer.
After installing BundleTransformer.Autoprefixer and selecting the JavaScript engine, you need to perform the following steps:
- Add the AutoprefixCssPostProcessorpostprocessor to the end of the list of active CSS postprocessors, which is specified in thedefaultPostProcessorsattribute of the configuration element/configuration/bundleTransformer/core/css.
- Register the CssAssetHandlerHTTP debug handler in theWeb.configfile (required for debug mode).
- Register an instance of the CustomBundleResolverclass as the currentBundleResolver`(required for debug mode).
Then, in the configuration section 
/configuration/bundleTransformer/autoprefixer the 
Web.config file, you can make the optional settings of the Autoprefixer algorithm:
 <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> … <autoprefixer cascade="true" safe="false"> <browsers> <add conditionalExpression="> 1%" /> <add conditionalExpression="last 2 versions" /> <add conditionalExpression="Firefox ESR" /> <add conditionalExpression="Opera 12.1" /> </browsers> … </autoprefixer> … </bundleTransformer> … </configuration> 
Consider in detail all the properties of the configuration section 
autoprefixer :
| Property | Data type | Default value | Description | 
|---|
| browsers | List of conditional expressions | 1%
 last 2 versions,
 Firefox ESR
 Opera 12.1 | Contains a list of conditional expressions to define a subset of supported browsers. The syntax of conditional expressions is described in detail in the official documentation of Autoprefixer . If the browserselement is not specified or is empty, the default value is used. To completely disable the addition of vendor prefixes, you need to leave only one conditional expression equal tononein thebrowserselement. | 
| cascade | Boolean | true | Creates a visual cascade of the following prefixes: 
 
  -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; | 
| safe | Boolean | false | Includes special safe mode for parsing broken CSS code. | 
Bundle Transformer: Handlebars
The 
BundleTransformer.Handlebars module contains the 
HandlebarsTranslator adapter-translator, which precompiles Handlebars templates in JavaScript. 
HandlebarsTranslator is based on the popular template engine - 
Handlebars.js (version 2.0.0 is currently supported). Despite the fact that this translator is based on a template engine, it is not much different from other translators that produce JavaScript code as output. Files with template code (by default, the translator processes files with the 
.handlebars and 
.hbs ) must be registered in the script bundles:
 … using Core.Bundles; using Core.Orderers; … public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { … var commonTemplatesBundle = new CustomScriptBundle("~/Bundles/CommonTemplates"); commonTemplatesBundle.Include( … "~/Scripts/handlebars/handlebars.runtime.js", "~/Scripts/handlebars/HandlebarsHelpers.js", "~/Scripts/handlebars/HandlebarsTranslatorBadge.handlebars", …); commonTemplatesBundle.Orderer = nullOrderer; bundles.Add(commonTemplatesBundle); … } } 
Unlike CoffeeScript and TypeScript, compiled Handlebars templates require the file 
handlebars.runtime.js (a stripped-down version of the library 
handlebars.js , from which the code necessary for compiling templates is excluded). This file can be placed in a shared library bundle or in a bundle with Handlebars templates. The main thing that his ad went before the announcement of templates.
In the configuration section 
/configuration/bundleTransformer/handlebars the 
Web.config file, you can configure the precompilation of templates:
 <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> … <handlebars namespace="Handlebars.templates" rootPath="" knownHelpers="link" knownHelpersOnly="true" data="false"> … </handlebars> … </bundleTransformer> … </configuration> 
Consider in detail all the properties of the configuration section of 
handlebars :
| Property | Data type | Default value | Description | 
|---|
| namespace | Line | Handlebars.templates | Sets the namespace for templates. | 
| rootPath | Line | Empty line | Sets the path to the template root directory. Suppose that we have the following template URL, /Scripts/handlebars/discussions/index.hbs. By default, the name of such a template is extracted from the file name -index, but if we assign this property a value of/Scripts/handlebars/, then we get the following template name -discussions/index. | 
| knownHelpers | Line | Empty line | Contains a comma-separated list of known helpers. Adding helper names to this list allows you to optimize the calls to them, which reduces the size of the compiled template. | 
| knownHelpersOnly | Boolean | false | Allows the use of only known helpers. If the value of this property is trueand the template code contains helper calls that are not embedded or not declared in theknownHelpersproperty, an exception will be thrown. | 
| data | Boolean | true | Allows compilation to include in the data template for @ data-variables (for example, @index). If there are iterative blocks in your templates, but @ data variables are not used, it is better to set this property a value equal tofalse- this will increase performance. | 
It is also worth noting that if the name of the file containing the template code begins with an underscore, the template will be compiled as a global partial representation (the initial underscore will be removed from the template name).
Bundle Transformer: Hogan
The 
BundleTransformer.Hogan module contains the 
HoganTranslator adapter-translator, which precompiles Mustache templates in JavaScript. 
HoganTranslator is based on the popular Mustache template compiler - 
Hogan.js (version 3.0.2 is currently supported). The principles of BundleTransformer.Hogan work in many ways similar to the principles of BundleTransformer.Handlebars, so we consider only the key differences. Files with template code (by default, the translator processes files with the 
.mustache extension) must be registered in the script bundles:
 … using Core.Bundles; using Core.Orderers; … public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { … var commonTemplatesBundle = new CustomScriptBundle("~/Bundles/CommonTemplates"); commonTemplatesBundle.Include( "~/Scripts/hogan/template-{version}.js", "~/Scripts/hogan/HoganTranslatorBadge.mustache", …); commonTemplatesBundle.Orderer = nullOrderer; bundles.Add(commonTemplatesBundle); … } } 
As in Handlebars, compiled templates require a special JavaScript library - template-3.0.2.js(in future releases, the version number is likely to change).In the configuration section of the /configuration/bundleTransformer/hoganfile, Web.configyou can configure precompiling templates: <configuration> … <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> … <hogan useNativeMinification="false" variable="templates" namespace="" delimiters=""> <sectionTags> <add sectionName="newWindow" openingTagName="_newWindow" closingTagName="newWindow" /> </sectionTags> … </hogan> … </bundleTransformer> … </configuration> 
Consider in detail all the properties of the configuration section hogan:| Property | Data type | Default value | Description | 
|---|
| useNativeMinification | Boolean | false | If the value of this property is equal true, then the minimization of the code of the compiled template will be performed by means of the translator. | 
| variable | Line | templates | Specifies the name of the JavaScript variable in which the templates will be stored. | 
| namespace | Line | Empty line | Specifies the namespace that is prefixed to the name of the template. | 
| sectionTags | Custom Tag List | Blank list | , . , _newWindownewWindow,{{_newWindow}} target="_blank"{{/newWindow}}. | 
| delimiters | Line | Empty line | , . , ASP, – <% %>(Web.config—<% %>). |