Component-Based CSS and JS for Sitecore MVC and Bundling

My current project sees me porting an existing, custom-built ASP.NET MVC website onto the Sitecore CMS platform using Sitecore 7.5 and MVC 5. Porting existing sites into Sitecore CMS is a pretty common experience for Sitecore developers, and one of the challenges it can bring is how to handle the existing CSS and JS files that are used in the custom-built site. Since the port maintains the same design of the website, these assets remain relevant to the site being run on top of Sitecore.

Architecture Plan Selection

In my case, the current website pulls in specific CSS and JS files that translate for individual pages. The architecture we are implementing for my current project is similar to the “Anti-Matter” pattern described in my White Paper. The reason I originally chose this particular architecture pattern was because the content on each of the website pages fits into a uniform design that can be handled by 2-3 page templates, and doesn’t require much variance. But this left me with different context from the current custom-built site.

For example, on the current site, there may be a “Products” landing page that pulls in a specific Products.css and Products.js file. On my Sitecore site, the content authors dictate the context of each page, which all use the same template. This means that at the time of coding, I’m unable to determine if the page being rendered is indeed the “Products” landing page as opposed to the “Site” (Home) landing page. In fact, the difference between these pages lies within the components that the content author generates through the Page Editor. Therefore, what I really want is to structure my CSS and JS to pull based on the components that are being rendered on a particular page.

Code Adaptations

One easy way to structure the CSS and JS to pull in relevant components for a particular page is to add <script> or <link> tags directly into the Component View and be done.  This certainly gets the job done, but it isn’t always the best option. The output of this method would result in my CSS and JS references being littered throughout the page output, as opposed to having them centralized in the header (or right before the </body> closing tag).  Worse than having untidy output, the location of a JS file within the markup of a page can be very important to its functionality. For these reasons and more, I’ve avoided this particular method on my current project.

Creating a Custom Pipeline Processor

Instead, I looked for a method that would still be component-based, but also allow for my JS and CSS to be bundled, (using the default BundleConfig.cs) and output all together in a centralized location. With that in mind, I began searching the internet for an existing solution.  I came across this Stack Overflow post from fellow Sitecore MVP Nick Wesselman.  In his answer, Nick recommends using the renderLayout pipeline and Cassette to accomplish this type of task. I went about exploring the renderLayout pipeline to see what I could achieve. Unfortunately, I discovered that the renderLayout pipeline isn’t an option when your Sitecore site is using MVC!  Still, I felt that I was on the proper path, and needed to find a substitute pipeline that would work. Opening up the Sitecore.MVC.config file, I scanned through the MVC-related pipelines and settled on the mvc.getPageRendering pipeline.

Because Sitecore is easily extensible in this manner, I was able to successfully create a custom pipeline processor. Initially, I built my code and ran the debugger on it to establish that my code would be called before my layout was rendered. But I found that it’s better to have components for the page established that are only called once upon a page request.

Next, I needed to figure out a way to know which assets to pull in per component. Nick’s Stack Overflow answer indicates that he extends the actual template used for View Renderings with a new field, but my preference was to keep that sort of extensibility out of my implementation. My reason for this was so that any future Sitecore upgrades wouldn’t have the potential to revert this custom field, and the custom field wouldn’t interfere with the Sitecore upgrade process.

Therefore, I decided to tap into the existing out-of-the-box “Parameters” field of my rendering, and see what I could do. Once I made that decision, the rest was just a matter of coding.  Below, you’ll find my custom code and Sitecore patch to implement if you’re looking to get the same functionality:

Custom Pipeline Processor Code

Sitecore CMS Support

Sitecore Patch to Put Code into Proper Pipeline

Sitecore Support

Lessons Learned

A few caveats I found throughout the coding: My first thought was that I’d simply add page-specific scripts / stylesheets to my main bundles that include universal files. I soon realized that this was an issue, because adding files to these bundles persisted them across page requests. Therefore, if Page A requires PageAScript.js, it would be added to the bundle when Page A was requested by a browser. However, every subsequent page request, regardless of whether that request was for Page A or not, would also include PageAScript.js.  That realization led me to loop through my main bundles, pull out all the specific files from those bundles and put them into the “Page Specific” bundles ahead of any actual page specific files. I then updated my layout to only include the “Page Specific” bundles by removing the references to the main bundles from the layout.

Removing the references worked great in development. However, this process eventually suffered from an issue that was caused by two different symptoms in QA.  The first symptom was turning on Sitecore caching at a component level – suddenly, my “Page Specific” scripts didn’t work anymore (regardless of whether the component I was caching required the script or not).  I also found that building my code in release mode (which causes bundleconfig.cs to automatically minify my scripts and style sheets) resulted in the same issue. This symptom was resolved by putting my main bundles back into the layouts, removing their files from my “Page Specific” bundles, and adding references to my Page Specific bundles in addition to the main bundles.  The code above reflects the changes made based on my findings.
What are some of the challenges you face with your own coding? We’d love to hear from you in the comments below. Share this post with fellow Sitecore users like yourself.

 

Want to learn more about the Sitecore content management system? Check out this blog article that can help you determine if Sitecore is right for you. We also offer Sitecore support services. Just contact us and let’s start talking about how Delphic Digital can help you!

  • sivaji nalamothu

    HI

    We have followed the similar approach but little differently.

    Here is the process that we followed

    Step : 1 We have created processor
    ///
    /// Processor to recreate page level bundle on every page request
    ///
    public class GetPageBundle : GetPageRenderingProcessor
    {
    ///
    /// Description: Page level Javascript bundle
    /// Value: “~/bundles/BSEE/pageJs”
    ///
    private const string PAGE_JS_BUNDLE = “~/bundles/BSEE/pageJs”;

    ///
    /// Process
    ///
    ///
    public override void Process(GetPageRenderingArgs args)
    {
    var pageSpecificJsBundle = new ScriptBundle(PAGE_JS_BUNDLE);
    BundleTable.Bundles.Add(pageSpecificJsBundle);
    }
    }
    Step : 2 We have created html helper
    ///
    /// Registers component script files to page level bundle.
    ///
    ///
    ///
    ///
    /// string
    /// bool
    public static void RegisterComponentScript(this HtmlHelper html, string folderPath, string searchPattern = WebUiConstants.BUNDLE_SEARCH_PATTERN, bool includeSubdirectories = true)
    {
    var bundle = BundleTable.Bundles.GetBundleFor(WebUiConstants.PAGE_JS_BUNDLE);
    if (bundle != null)
    {
    bundle.IncludeDirectory(folderPath, searchPattern, includeSubdirectories);
    }
    }

    Step : 3 From my razor view(.cshtml) i called the helper by sending the javascript files
    Html.RegisterComponentScript(“~/Includes/BSEE/js/components/common/datepicker”);

    This approach is working fine in my local developement environment. But the problem that i have ran was in my qa environment the page specific bundle is not rendering the registered scripts. and the problem is not always. it was intermitent not sure the reason.

    Could you please help

    Thanks

« Prev Article
Next Article »