The other day I was poking around Sitecore.Forms.Mvc.dll — this assembly ships with Web Forms for Marketers (WFFM), and is used when WFFM is running on Sitecore MVC — and noticed WFFM does some bundling of JavaScript and CSS files:
WFFM uses the above class as an <initialize> pipeline processor. You can see this defined in Sitecore.Forms.Mvc.config:
This got me thinking: why not build my own class to serve as an <initialize> pipeline processor to bundle my CSS and JavaScript files?
As an experiment I whipped up the following class to do just that:
using System.Collections.Generic; using System.Linq; using System.Web.Optimization; using Sitecore.Pipelines; namespace Sitecore.Sandbox.Forms.Mvc.Pipelines { public class RegisterAdditionalFormBundles { public RegisterAdditionalFormBundles() { CssFiles = new List<string>(); JavaScriptFiles = new List<string>(); } public void Process(PipelineArgs args) { BundleCollection bundles = GetBundleCollection(); if (bundles == null) { return; } AddBundle(bundles, CreateCssBundle()); AddBundle(bundles, CreateJavaScriptBundle()); } protected virtual BundleCollection GetBundleCollection() { return BundleTable.Bundles; } protected virtual Bundle CreateCssBundle() { if (!CanBundleAssets(CssVirtualPath, CssFiles)) { return null; } return new StyleBundle(CssVirtualPath).Include(CssFiles.ToArray()); } protected virtual Bundle CreateJavaScriptBundle() { if (!CanBundleAssets(JavaScriptVirtualPath, JavaScriptFiles)) { return null; } return new ScriptBundle(JavaScriptVirtualPath).Include(JavaScriptFiles.ToArray()); } protected virtual bool CanBundleAssets(string virtualPath, IEnumerable<string> filePaths) { return !string.IsNullOrWhiteSpace(virtualPath) && filePaths != null && filePaths.Any(); } private static void AddBundle(BundleCollection bundles, Bundle bundle) { if(bundle == null) { return; } bundles.Add(bundle); } private string CssVirtualPath { get; set; } private List<string> CssFiles { get; set; } private string JavaScriptVirtualPath { get; set; } private List<string> JavaScriptFiles { get; set; } } }
The class above basically takes in a collection of CSS and JavaScript file paths as well as their virtual bundled paths — these are magically populated by Sitecore’s Configuration Factory using values provided by the configuration file shown below — iterates over both collections, and adds them to the BundleTable — the BundleTable is defined in System.Web.Optimization.dll.
I then glued everything together using a patch configuration file:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <initialize> <processor patch:after="processor[@type='Sitecore.Forms.Mvc.Pipelines.RegisterFormBundles, Sitecore.Forms.Mvc']" type="Sitecore.Sandbox.Forms.Mvc.Pipelines.RegisterAdditionalFormBundles, Sitecore.Sandbox"> <CssVirtualPath>~/wffm-bundles/styles.css</CssVirtualPath> <CssFiles hint="list"> <CssFile>~/css/uniform.aristo.css</CssFile> </CssFiles> <JavaScriptVirtualPath>~/wffm-bundles/scripts.js</JavaScriptVirtualPath> <JavaScriptFiles hint="list"> <JavaScriptFile>~/js/jquery.min.js</JavaScriptFile> <JavaScriptFile>~/js/jquery.uniform.min.js</JavaScriptFile> <JavaScriptFile>~/js/bind.uniform.js</JavaScriptFile> </JavaScriptFiles> </processor> </initialize> </pipelines> </sitecore> </configuration>
I’m adding the <initialize> pipeline processor shown above after WFFM’s though theoretically you could add it anywhere within the <initialize> pipeline.
The CSS and JavaScript files defined in the configuration file above are from the Uniform project — this project includes CSS, JavaScript and images to make forms look nice, though I am in no way endorsing this project. I only needed some CSS and JavaScript files to spin up something quickly for testing.
For testing, I built the following View — it uses some helpers to render the <link> and <script> tags for the bundles — and tied it to my Layout in Sitecore:
@using System.Web.Optimization @using Sitecore.Mvc @using Sitecore.Mvc.Presentation <!DOCTYPE html> <html> <head> <title></title> @Styles.Render("~/wffm-bundles/styles.css") @Scripts.Render("~/wffm-bundles/scripts.js") </head> <body> @Html.Sitecore().Placeholder("page content") </body> </html>
I then built a “Feedback” form in WFFM; mapped it to the “page content” placeholder defined in the View above; published it; and pulled it up in my browser. As you can see the code from the Uniform project styled the form:
For comparison, this is what the form looks like without the <initialize> pipeline processor above:
If you have any thoughts on this, or have alternative ways of bundling CSS and JavaScript files in your Sitecore MVC solutions, please share in a comment.
Mike this will help but when bundling sequence and media type is important. The simplest case is to bundle screen media type and print media type css file separately. This of course can get complicated depending on the markup provided. Do you have any provision for media types for css?
Wish I had a good answer for this but my front-end skills have atrophied. 😦
Interesting approach! I have been tapping into the initialize pipeline using straight web optimization framework for bundling. The way I had it set up is that it would read the file system in alphabetical order and for each folder in root it would create a bundle. This configuration could be modified to do something similar. For now it looks like you would have to create a processor per CSS and js pair.