1

I have been tasked with a simple problem that requires a delicate touch since production is very close. We have a web site that didn't invoke good bundling and css structure. There are some bundles, but there are quite a few views that reference css files in the "head" section. I have 3 options:

1) Leave everything the way it is --The app works fine and it is internal so micro-performance tweaks aren't a huge deal 2) Create bundles for each view so that we reduce the requests for each page --There would still be multiple bundles, but at least each page wouldn't make more than one css bundle request 3) Redo all the css to make one bundle --This is pretty unlikely at this point as there is bound to be multiple class and element conflicts

I am leaning in the direction of Option 2 since it would be low risk and provide some benefit. I am perplexed, however, on how to handle css that is shared amongst some views but not site wide. For example, a createlayout.css that is only used for create pages (about 5 pages in a 50ish page app). Would I duplicate this css in each create page bundle?

Here is an example of some code:

_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/PreHead")
    @RenderSection("head", required: false)
    @Styles.Render("~/Content/PostHead")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
@Html.ActionLink("Check the Alpha Path","Alpha","Home")
@Html.ActionLink("Check the Beta Path","Beta","Home")
@Html.ActionLink("Check the Omega Path","Omega","Home")

    @RenderBody()

    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>

This is how our pages generally look:

@{
    ViewBag.Title = "Beta";
}
@section head
{
    <link href="~/Content/shared.css" rel="stylesheet" />
    <link href="~/Content/betabase.css" rel="stylesheet" />
        <link href="~/Content/betanew.css" rel="stylesheet" />
}
<h2>Beta</h2>
<p>This renders the bundle for beta in the head section</p>

<div class="preheadbase">Prehead base gives a red border</div>
<div class="preheadnew">Prehead new gives an orange border</div>
<div class="betabase">Beta base gives a yellow border</div>
<div class="betanew">Beta new gives a grey border</div>
<div class="postheadbase">Posthead base gives a blue border</div>
<div class="postheadnew">Posthead new gives a cyan border</div>

<div class="mydiv">This is what comes out</div>

And this is my thought on how to change it (Option 2):

@{
    ViewBag.Title = "Beta";
}
@section head
{
    @Styles.Render("~/Content/Beta")
}
<h2>Beta</h2>
<p>This renders the bundle for beta in the head section</p>

<div class="preheadbase">Prehead base gives a red border</div>
<div class="preheadnew">Prehead new gives an orange border</div>
<div class="betabase">Beta base gives a yellow border</div>
<div class="betanew">Beta new gives a grey border</div>
<div class="postheadbase">Posthead base gives a blue border</div>
<div class="postheadnew">Posthead new gives a cyan border</div>

<div class="mydiv">This is what comes out</div>

And the bundle, in bundleconfig.cs would looke something like this:

bundles.Add(new  StyleBundle("~/Content/Beta").Include("~/Content/shared.css").Include("~/Content/betabase.css").Include("~/Content/betanew.css"));

But, now let's say i have another page, we'll call it Alpha, it would look like this:

@{
    ViewBag.Title = "Alpha";
}
@section head
{
    @Styles.Render("~/Content/Alpha")
}
<h2>Alpha</h2>
<p>This renders the bundle for Alpha in the head section</p>

<div class="preheadbase">Prehead base gives a red border</div>
<div class="preheadnew">Prehead new gives an orange border</div>
<div class="alphabase">Alpha base gives a yellowgreen border</div>
<div class="alphanew">Alpha new gives a black border</div>
<div class="postheadbase">Posthead base gives a blue border</div>
<div class="postheadnew">Posthead new gives a cyan border</div>

<div class="mydiv">This is what comes out</div>

And Alpha's bundle, in bundleconfig.cs, would look like this:

bundles.Add(new StyleBundle("~/Content/Alpha").Include("~/Content/shared.css").Include("~/Content/alphabase.css").Include("~/Content/alphanew.css"));

My concern is that I need to include shared.css in both of the bundles. Now, let's say we add another 40 pages, but none of them use shared.css, it wouldn't make much sense to put it into the prehead.css. Additionally, in some of the views, shared.css might have to come after another css in that view, so moving it would add risk.

I know this isn't ideal, but this is the hand I have right now. I'm really trying to determine if putting the same css file into two different bundles is bad or not and if having a bundle per view is also good or bad. Any insight would be great, I've had some great difficulty getting any valuable information on this and I do have a test project, but the data isn't very conclusive (my files are very small and I'm running locally, with debug=false).

JasonWilczak
  • 2,303
  • 2
  • 21
  • 37

2 Answers2

0

you can use Web Essentials Bundling

A Web Essentials bundle file is a recipe for grouping, and usually compressing, a set of files of the same type to limit the number and the amout the data to be downloaded by the browser.

Web Essentials offers two bundling types:

.bundle : for CSS and JS files

For CSS, it outputs a XML bundle recipe file, a destination CSS file and a minified version of source if you turn on the minify option

on the recipe.

For JavaScript files, it outputs a destination JS file, a minified version of sources and a source-map of that min.
.sprite : for images (PNG, JPG and GIF). It generates a sprite image, CSS with example code for all the possible coordinates in

background property, LESS and SASS files with mixins holding the same background properties and a custom map file (JSON) with all those coordinates.

Download Web Essentials

How to make a bundle?

The procedure to create a bundle is the same for all types of files.

On the Solution Explorer, select files that you want to bundle together.
On the Web Essentials context menu, pick Create image sprite... or Create CSS/JS bundle file depending on your context.
Specify a name for the bundle file.

or maybe you can use Dynamic Bundles too.

using System.Web.Optimization;

namespace YourAppNameHere
{
    public class MvcApplication : System.Web.HttpApplication
    {
        private enum bundleTypes
        {
            Bundle,
            ScriptBundle,
            StyleBundle
        };

        void Application_BeginRequest(Object sender, EventArgs e)
        {
            ParseDynamicBundle();
        }

        private void ParseDynamicBundle()
        {
            string[] pathParts = Request.FilePath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            string fileChain = Request.QueryString["files"];

            if (pathParts.Length < 2 || fileChain == "")
                return;

            string bundleType = pathParts[0];

            if (!Enum.GetNames(typeof(bundleTypes)).Contains(bundleType))
                return;

            string bundleName = pathParts[1];
            string bundleFolder = "";
            Bundle bundle = new Bundle("~/" + bundleType + "/" + bundleName);

            if (bundleType == bundleTypes.ScriptBundle.ToString())
            {
                bundleFolder = "Scripts/";
                bundle = new ScriptBundle("~/" + bundleType + "/" + bundleName);
            }
            else if (bundleType == bundleTypes.StyleBundle.ToString())
            {
                bundleFolder = "Styles/";
                bundle = new StyleBundle("~/" + bundleType + "/" + bundleName);
            }

            foreach (string fileName in fileChain.Split(','))
            {
                bundle.Include("~/" + bundleFolder + fileName);
            }

            BundleTable.Bundles.Add(bundle);
        }
    }
}
Mohamad Shiralizadeh
  • 8,329
  • 6
  • 58
  • 93
  • Thank you for this, the idea looks incredibly promising. However, it doesn't really answer my question, or maybe I'm just not getting it (which is more likely). Will this work to address Option 2 in my scenario? And, if so, will that handle not having duplicate style sheets for different bundles? – JasonWilczak Dec 03 '14 at 21:25
  • @JasonWilczak your right, I think maybe Dynamic Bundles helps. http://debeterevormgever.nl/code/dynamic-bundles – Mohamad Shiralizadeh Dec 04 '14 at 06:17
0

Note: these are my preferences, but I do include specific reasons I believe are valid for my preferences.

Before starting I recommend reading Max parallel http connections in a browser? which apply to script, styles and bundles (well and images etc etc).

1) Leave everything the way it is --The app works fine and it is internal so micro-performance tweaks aren't a huge deal

This is always good option if you are close to deployment. Leave technical debt alone when close deploying to production and work on it during the next release.

2) Create bundles for each view so that we reduce the requests for each page --There would still be multiple bundles, but at least each page wouldn't make more than one css bundle request

You have a couple of options here:

2a) A single bundle per view

This is a very poor choice. Lets assume the following:

bundles.Add(new ScriptBundle("SomeControllerView1").Include(
  "~/scripts/jquery.js",
  "~/scripts/controllerview1.js"
  ));

bundles.Add(new ScriptBundle("SomeControllerView2").Include(
  "~/scripts/jquery.js",
  "~/scripts/controllerview2.js"
  ));

Ignoring the obviousness of using a CDN for jQuery, the result is the same; you are losing the ability of the browser to cache this shared file and increase the download size per view. The result doesn't change between Javascript and Styles.

2b) Multiple bundles per view

This an fair choice. Lets assume the follwoing:

bundles.Add(new ScriptBundle("Site).Include(
  "~/scripts/jquery.js",
  ));

// shared across all methods on this controller
bundles.Add(new ScriptBundle("Controller1").Include(
  "~/scripts/controller1.js"
  ));

// specific to the view
bundles.Add(new ScriptBundle("Controller1View1").Include(
  "~/scripts/controller1view1.js"
  ));

I this this is fair, because I can't honestly think there is any advantage to bundle per view, and think that 2c is a much better option.

2c) Multiple bundles per controller

bundles.Add(new ScriptBundle("Site).Include(
  "~/scripts/jquery.js",
  ));

// shared across all methods on this controller
bundles.Add(new ScriptBundle("Controller1").Include(
  "~/scripts/controller1.js",
  "~/scripts/controller1View1.js",
  "~/scripts/controller1View2.js"
  ));

What are the chances that someone will only ever visit a single page on a controller (typically for me it's not the case) so I like to take advantage of less downloads and more caching.

3) Redo all the css to make one bundle --This is pretty unlikely at this point as there is bound to be multiple class and element conflicts.

Depending on the size of the site, this may actually be a good option.

There are so many variables here it's hard to really determine what everyone should do, but these should at least help understand when to use what.

Lastly, I'm not a fan of the bundle names as magic strings so I'd recommend doing something like:

public static BundleConfig
{
  public const string SiteScripts = "~/bundle/scripts/site";
  public const string SiteSytles = "~/bundle/styles/site";

  public const string Controller1View1Scripts = "~/bundle/scripts/controller1";
  public const string Controller1View1Sytles = "~/bundle/styles/controller1";


  public static void RegisterBundles(BundleCollection bundles)
  {
    BundleConfig.RegisterScripts(bundles);
    BundleConfig.RegisterStyles(bundles);
  }

  public static void RegisterScripts(bundles)
  {
    bundles.Add(new ScriptBundle(BundleConfig.SiteScripts).Include(
      "...",
      "..."
      ));
  }

  //.. the rest should be obvious from these examples
}

Then in your view/layout:

@Scripts.Render(BundleConfig.SiteScripts);
Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150