Creating a Custom Bundler
We decided to use Angular on a public-facing website project for a customer. After we completed a good chunk of the front-end development, I noticed a high number of requests for markup templates were being made when our page loaded. I knew that there had to be a way to reduce this. I remembered from my work with knockout.js that there was a way to embed templates into script tags, so I figured Angular surely had something similar.
Solution
Sure enough, Angular has a way to load templates from a single file using the script directive. But I also knew that using just a single file with several people working on a project would quickly lead to merge conflict hell. After doing a bit of browsing and thinking about it, the idea of using an ASP.Net bundle came up.
Using bundles ended up being quite simple. The first step was to create a new class that inherits from the IBundleTransform interface. In our bundle transform, we just needed to write a bit of code that utilized the Angular template cache to inject the content of all of our template files into a single response. We built a regular expression to trim out any extra whitespace that the template may have had at the beginning of each line and escape any single quotes since this is all being injected into the JavaScript that is run.
Here is the resulting code for the bundle transform:
public class PartialsTransform : IBundleTransform { private static readonly Regex NewLineRegex = new Regex(@"(\n|\r|\r\n)\s*", RegexOptions.Compiled); private readonly string moduleName; public PartialsTransform(string moduleName) { this.moduleName = moduleName; } public void Process(BundleContext context, BundleResponse response) { var strBundleResponse = new StringBuilder(); // Javascript module for Angular that uses templateCache strBundleResponse.AppendFormat( @"angular.module('{0}').run(['$templateCache',function(t){{", this.moduleName); foreach (var file in response.Files) { // Get the partial page, remove line feeds and whitespace and escape quotes var content = NewLineRegex.Replace(file.ApplyTransforms(), " ").Replace("'", @"\'"); // Create insert statement with template strBundleResponse.AppendFormat( @"{2}t.put('.{0}','{1}');{2}", file.VirtualFile.VirtualPath, content, "\n"); } strBundleResponse.Append(@"}]);"); response.Files = Enumerable.Empty(); response.Content = strBundleResponse.ToString(); response.ContentType = "text/javascript"; } }
We then created our own class that inherited from the Bundle class. We could have used a standard bundle and then added a transform to that bundle, but for our work this approach was a bit cleaner.
public class PartialsBundle : Bundle { public PartialsBundle(string moduleName, string virtualPath) : base(virtualPath, new IBundleTransform[] { new PartialsTransform(moduleName) }) {} }
Of course we had to register this bundle on the application startup so we just added in the following lines where all of our other bundles were being created:
bundles.Add( new PartialsBundle("DplApp", "~/bundles/templates") .IncludeDirectory("~/Content/app", "*.html", true));
The final step in the process was to add a script render to our Index.cshtml:
@Scripts.Render("~/bundles/templates")
Conclusions
I don’t have any empirical evidence to suggest how much this reduces load or how many bytes of data is saved. But it sure is nice to look at the Network tab in the Developer Tools and see a single request to get all of our templates in one shot and that the application will not need to go back and retrieve anything else in the future.