Hook for MVC4 bundles

Oct 13, 2012 at 7:50 AM

Has anyone managed to get T4MVC to work with MVC4's new bundling and minification system? The call to Styles.Render() and Scripts.Render() need a virtual path, which is a magic string.

I made a static "constants" class which defines all the strings in one place. But I'd like to get it into the T4MVC namespace, so that in addition to "MVC.Links." I'd also have "MVC.Paths." or something like that.

Problem is I don't think I can do much in the way of subclassing or extension methods as all of the classes are static. Is there some obvious way I'm missing?

Coordinator
Oct 13, 2012 at 8:57 PM

If we made the generated classes partial, you should be able to add members. Do you want to experiment with that?

Oct 14, 2012 at 5:36 AM

I made this change to T4MVC.tt:

  namespace <#=LinksNamespace #> {
  <#
  foreach (string folder in StaticFilesFolders) {
      ProcessStaticFiles(Project, folder);
  }
  #>
      [<#= GeneratedCode #>, DebuggerNonUserCode]   ------NEW
      public static partial class bundles {}                           ------NEW
  }

And then in another file:

  namespace Links {
    public static partial class bundles {
      public static readonly string scriptBundleFoo = "~/scripts/foo";
    }
  }

I used lowercase "bundles" vs "Bundles" as my "images", "scripts" and "styles" are all also lowercase.

I am going to try modify the tt to open the BundleConfig.cs, and search for all instances of:

  new ScriptBundle("~/scripts/foo",
  new ScriptBundle("~/scripts/foo")
  new StyleBundle("~/styles/foo",
  new StyleBundle("~/styles/foo")
  new Bundle("~/styles/foo",
  new Bundle("~/scripts/foo")

...and extract the virtual path and automatically add the paths. But my regex is poor, so might take a while.

Phase 2:
- I'll specify in the settings.t4 file, variables for: "bundles" namespace, path to "BundleConfig.cs" or whatever you're using
- Instead of just "bundles.foo", I'll add "bundles.scripts.foo" and "bundles.styles.foo" (easy just add two nested classes in the partial bundles class)

Hope this helps someone else!

Oct 14, 2012 at 5:48 AM

I've changed the tt:

    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public static partial class bundles {
      [<#= GeneratedCode #>, DebuggerNonUserCode]
      public static partial class scripts {}
      [<#= GeneratedCode #>, DebuggerNonUserCode]
      public static partial class styles {}
    }

Then in my separate partial class:

  namespace Links {
    public static partial class bundles {
      public static partial class scripts {
        public static readonly string scriptBundleFoo = "~/scripts/foo";
      }
      public static partial class styles {
        public static readonly string styleBundleFoo = "~/styles/foo";
      }
    }
  }

Now I can use "Links.bundles.scripts.scriptBundleFoo" and "Links.bundles.styles.styleBundleFoo". To automate the extraction as I described above, if the tt finds "new StyleBundle(..." then it will add to the "styles" class, and if it finds "new ScriptBundle(..." then it will add to the "scripts" class. Not sure about the "new Bundle(..." scenario yet.

Incidentally, I've stuck my customised partial class into the same file as BundleConfig.cs just to keep all the bundling stuff in one place. Not ideal but works until I get the auto extraction to work, which won't be soon as my regex is really bad.

Oct 14, 2012 at 5:57 AM
Edited Oct 14, 2012 at 5:57 AM

More thought about the auto-extraction scenario:

Right now I've defined all the virtual paths in the new bundles static class. Then I can use "Links.bundles.scripts.foo" in the call to:
  bundles.Add(new ScriptBundle(Links.bundles.scripts.foo).Include(...

As well as in a layout or view:
  @Bundles.RenderScripts(Links.bundles.scripts.foo)

But to automate the extraction of the virutal paths, they must be defined in the "new ScriptBundle(..." calls. And you only get to use them in the layouts/views. That's fine for my needs, I'm happy for them to all be in the same place, as I have plenty strings there anyway to define the files to add to each bundle. And its a few less static classes.

Coordinator
Oct 14, 2012 at 10:26 PM

Cool. sounds like you got somewhere! Would you be able to send a pull request for the relevant T4MVC changes? I assume that it can be done in a way that doesn't break anything for people who don't use it?

Oct 15, 2012 at 5:09 AM

I know its going to sound like I'm hopelessly backward, but I've never used git! :) If I knew how, I would commit this:

  namespace <#=LinksNamespace #> {
  <#
  foreach (string folder in StaticFilesFolders) {
      ProcessStaticFiles(Project, folder);
  }
  #>
      [<#= GeneratedCode #>, DebuggerNonUserCode]
      public static partial class bundles {
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public static partial class scripts {}
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public static partial class styles {}
      }
  }

The changes are in bold. They don't break anything, only add static partial classes which you can "hook on to" with external code. I'm using it now and it works nicely. All the other stuff I wrote was for "phase 2" as soon as I can figure out how to use regex in the tt.

Coordinator
Oct 16, 2012 at 7:09 AM

No problem, I can work on getting this in. One thing I'd ask: could you contribute to the doc to explain how this is used? I think without proper context, if someone sees these classes they wouldn't know what to make of them. I'd probably go somewhere in section 2.4.

thanks,
David 

Oct 16, 2012 at 2:34 PM

I looked for an edit feature but couldn't find one. Maybe I don't have the edit credentials or something. Anyways, some documentation as requested:

 

TITLE: Support for MVC's script and style bundles
------------------------------------------

Starting from MVC4, one can define "bundles" of scripts and stylesheets which are automatically combined and minified. However, magic strings are used to define the resource URLs. To eliminate them, follow steps 1 to 3 below.

1. Add the following to your own code, which will extend T4MVC's static partial "Links.bundles.scripts" and "Links.bundles.styles" classes:
  namespace Links {
    public static partial class bundles {
      public static partial class scripts {
        public static readonly string jquery = "~/scripts/jquery";
        public static readonly string jqueryui = "~/scripts/jqueryui";
      }
      public static partial class styles {
        public static readonly string bootstrap = "~/styles/boostrap";
        public static readonly string theme = "~/styles/theme";
        public static readonly string common = "~/styles/common";
      }
    }
  }

Notice how the magic strings are now defined in one place only, and are thus easy to maintain.

2. Use the following to add your bundles to the runtime:
  bundles.Add(new ScriptBundle(Links.bundles.scripts.jquery).Include("~/scripts/jquery-{version}.js"));
  bundles.Add(new StyleBundle(Links.bundles.styles.bootstrap).Include("~/styles/bootstrap*.css"));

3. Use the following to use a bundle in a cshtml layout/view:
  @Scripts.Render(Links.bundles.scripts.jquery)
  @Styles.Render(Links.bundles.scripts.bootstrap)

Coordinator
Oct 17, 2012 at 2:41 AM

Thanks much! I just added this to the doc (please double check), and a new T4MVC build with your change in now on NuGet (v2.12.0).

Oct 17, 2012 at 6:53 AM

Cool I feel important! :-) Docs and nuget package seem fine, thanks for making the change.

For "phase 2", where T4MVC automatically extracts the virtual paths from one's BundeConfig.cs I will need more time, as I can't get it right.

If anyone else needs that functionality, drop me a comment here and we can collaborate. My T4 skills are good, but regex no so good. Till then this update is good enough for me.

Developer
Dec 8, 2012 at 4:26 PM

BugMeNot2

    Do you think you could put the option in place for the names of the classes.   The lower case 'bundles', 'scripts', 'styles' does not fit with anything else in T4MVC and my resharper bugs me about it not matching naming rules.  I of course want to see all those with an uppercase letter in the class name.

    I think it should be uppercase as everything else is - and I understand you want it lowercase - so perhaps it should be uppercase and then have an option in the settings file to make it lowercase.

  Thoughts?

Developer
Dec 12, 2012 at 6:53 AM

@BugMeNot2 - FYI, We changed this to be uppercase like all other naming.