Bundler Now Supports Css And .less

Writing

A few days ago I pushed a new version of Bundler out to GitHub. The first change I made is that now JavaScriptBundler is just called Bundler. This is because of the title of this post. I want to use it with more than just JavaScript, so I decided to change it.

For right now, the command line version of Bundler is disabled. I want to get it in there and working again, but I am focused on the ASP.NET WebForms and ASP.NET MVC integrated versions directly. I changed the syntax a bit for the JavaScript bundling, so here is a quick example of how you would use it now.

Bundle some JavaScript:

<%= Bundle.JavaScript()
        .AddJs("~/js/jquery-1.4.2.js")
        .AddJs("~/js/jquery-ui-1.8rc3.js")
        .RenderJs("~/js/combined.js") %>

Bundle some CSS:

<%= Bundle.Css()
        .AddCss("~/css/jquery-ui-1.8rc3.css")
        .AddCss("~/css/CodeThinked.css")
        .RenderCss("~/css/combined.css") %>

CSS also has support for .less now. Any css file that ends in .less will be run through the .less compiler:

<%= Bundle.Css()
        .AddCss("~/css/testdotless.css.less")
        .RenderCss("~/css/combined.css") %>

The JavaScript minifying and combining is still being handled by JSMin. I’ll make changes soon so that you can pick which minifier you want to use. Currently the css compressing is being done with the stock YUI compressor. I will also updated this soon so that you can pick which compressor you want to use.

How To Use Bundler

So, how do you get it working? Well, go to GitHub and download the beta from the downloads page. Then, all you need to do is reference the Bundler.Framework.dll from your website. Then just throw the code in that I provided above. You will just put the files in the order you want them in, and they will be minified or compressed and then combined into the an output file and written to disk where you specify. Once a file is written to disk, it is then cached and fed back over and over. The output will look like this:

<script type="text/javascript" src="/js/combined.js?r=C4F1EE2DBA3F667EB911E27BDD42216E"></script>
<link rel="stylesheet" type="text/css" href="/css/combined.css?r=87CABE31E3D5180E6FAAC6AB4BFE631E" />

Notice that we are putting a hash on the end of the path to the javascript and css. This way, when we change the files that go into the combined file, this will invalidate your browser’s cache and you will get the new file fed up to your immediately. This way you don’t need to change the file name in order to get browsers to pull the latest version of your file.

Where Do We Go From Here

For me, bundler is already to the point where it is extremely useful. I set out to create a very simple framework that I could leverage to combine my css and javascript without a lot of fuss. Right now I am considering a few other features for future versions, but I want to make sure that I keep things easy and simple, so I’m not entirely sure what new features I’ll throw in. I hope that you’ll go check it out, and let me know if you use it, how you use it, and what your thoughts are.

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Comments (24)

  1. Very good Job.
    If I can I’d like to suggest a little thing. Could be possible to set groups of javascritps and css files, this because I can use some javascript only in certain user control and there’s no need to render this javascript all over the website.
    Also if we have groups, this groups should have priorities, because javascript has dependencies, like Jquery needs to be loaded before the javascript used in my user control that uses jQuery.
    I can contribbute with this.

  2. Cool library. I have been looking for a library like that to integrate in my websites. All the ones I tested have some problems and had to remove them. Will test this soon and see
    Riga

  3. Very good job !

    For the css Bundler, the attribute media of the link tag (all, print, screen …) is missing.

    Thanks

  4. @Stephen Did you download the binaries? If so, I made a fix that I hadn’t included in the latest download. Just go grab the new binary that I put up on the GitHub downloads page and let me know if that fixes your problems.

  5. I think it will be a good idea if we could just specify the directory. For example delivering the whole app_theme. Since generally ur javascript files and css files would be inside one directory.

  6. @Mohit That isn’t a bad idea, but javascript and sometimes css is dependent on the order. So you’d have to define the order of the files somewhere, or make sure that the files were sorted alphabetically (or someway) in the order you want. It is just easier to specify the order yourself.

    I am adding support for named groups of files, so that you’ll define the files somewhere, give them a name, then render them by just calling the name and not writing out each file. Will this let you accomplish what you want?

  7. Hey Justin,
    Ya, I understand order is important in case of js files. but, I don’t think order is important in case of css files. I am not sure of .less files though. I think the scope of less variables is only the current css files.
    even in case of javascript files until or unless you have put pageload or document.ready in a js file. order should not be important correct me if I am wrong.
    Can you please explain the approach. You are thinking of where will you define that these files belong to this group.
    What, I am trying to accomplish is to deliver 24 css in one request.
    and a bunch of js files in one request.

    Regards
    Mohit Thakral

  8. The files don’t get proccesed if isDebuggingEnabled is true. But so how do you preview your DotLess files while developing your website?

    Also if debugging is on it will return the normal <link> tags with all the normal css files. But the href of those link tags returns with "~/" in the path.

    Like in the example project you get this:
    href="~/css/CodeThinked.css"
    But html doesn’t understand the "~/" which results in broken links or am I missing something obvious?

  9. Not sure if my comments need to be approved first or that my previous comment got destroyed. Just wanted to say that the questions I had were already fixed in the newer version on Github.

    Very handy library you created Justin. Keep up the good work.

  10. why don’t you allow both features like, appending a whole directory or individual file. If the developer thinks that order is important he can consider the way it is done right now. What i am talking about is something like

    <%= Bundle.JavaScript()
    .AddAllJsFromDir("~/js/")
    .AddJs("~/js/jquery-ui-1.8rc3.js")
    .RenderJs("~/js/combined.js") %>

    and it can be done including the sub directories.
    considering that order is not important.

  11. @Mohit I was thinking something along these lines for this syntax:

    Put this somewhere like Global.asax in app start:
    Bundle.Css()
    .Add("~/css/SomeFile.css")
    .Add("~/css/SomeOtherFile.css")
    .AsNamed("MyCss");

    Then in a page, when you need to render this bundle, just call this:
    Bundle.Css().RenderNamed("MyCss");

    Would this work for you?

  12. Thanks a lot justin for your response. I could use the way you were telling me. Only issue is, we use a compiled application class, so it is not in global.asax.

    along with that,I was reading following lines.

    It can pull in entire directories of JavaScript files by specifying the directory. You can also put a ordering.txt file in the directory which will contain a list of the file names in the order that you want them concatenated. Remember, ordering can be important when you are importing JavaScript!

    As per your following post

    http://www.codethinked.com/post/2010/02/09/Combine_Minify_And_Compress_Your_JavaScript.aspx

    and if the ordering file is not specified. then just add it alphabetically. I think this will satisfy my requirement. My requirement is simple specify the directory and all files are added, if order is specified use it else don’t use it.

    Justin, I really appreciate your so frequent response.

    Regards
    Mohit Thakral

  13. @Mohit I’ll look at it and see how hard it would be to get some of that back in there. I’m not sure currently that any of it is working, but the infrastructure is still there.

    Right now my focus is on getting the code base more testable, since originally I had no plans of ever releasing this. Now that it has grown a tiny bit, a few bugs are popping up which could have been easily prevented with better unit tests. Just goes to show that even the smallest applications need to be easily testable, especially if you don’t have a lot of time for development. 🙂

  14. Thanks for releasing this. Previously, I had done this via an MSBuild task, but this is more full-featured and easier to integrate.

    A couple of suggestions, though. The CssBundle and JsBundle classes seem to perform caching by using a static Dictionary instance, with the Render() methods first inspecting that dictionary before attempting to render the file.

    (1) It would be nice if the Render() method tested for the file’s existence even if the cached entry appears in the dictionary. This avoids the overhead of rehashing or regenerating the file but would also help sanity check situations where someone deleted the generated file for whatever reason. Not a big issue but nice to have.

    (2) If this is run in a Web site that runs under n worker processes on the same machine (a Web garden), the file will get regenerated and rehashed n times, since each process will have its own (empty) Dictionary instance and the Render() method does not check for file existence. While you could add support for out-of-proc caching, that seems overkill, so a simple does-the-file-already-exist-if-so-hash-it-and-cache-it in the Render() would also solve that problem.

    (3) If generation fails for whatever reason (permission problem, missing jar file, etc), it would be nice if it auto-reverted to the debug mode behavior instead of throwing the exception. This one is certainly a stylistic preference and I’m sure I’ll get some flames on this one =)

    Thanks for taking the time to post this!

  15. @Nicholas Thanks!

    1) Excellent point, I’ll make this change. Can you submit it as an issue on the GitHub issue tracker?

    2) Hmmm, I see what you are saying, but how would you know if the file was the latest version, or if it was an old leftover version of the file?

    3) I’ll see what we can do in this case, but I’m not sure what action I’ll take on this.

  16. @Justin

    2) You wouldn’t, but I see where you are coming from. I think it is probably a result of trying to accommodate two incompatible workflows … that is, caching a hash of the file versus just using the file’s existence as an indicator of whether or not it needs to be regenerated. I’ve usually taken the approach that if the file exists, use it, if it’s not there, re-generate it (and to get it to update you would delete the generated file, which would usually happen as the site is uninstalled/reinstalled as part of a WiX installation package). But I see the use case that some people want to be able to edit the CSS file and then recycle the app pool to clear out the cache. (The alternative being editing the CSS file and then deleting the generated file, no recycling/restart/cache.Clear()/whatever required.)

    It is perhaps moot because in a multiple Web server setup, we are probably not going to be editing CSS files individually anyway, instead depending on a deployment mechanism like Windows Installer. (Indeed, since I use a Windows Installer uninstall/reinstall to roll out updates, the generated files don’t get reinstalled as part of the upgrade process, letting them get generated on the first hit.) But if one were doing an xcopy-style deployment where a generated file might get copied over, one would probably expect that file to get re-generated. (In that case, wouldn’t the last-modified timestamp be good enough, a quick run through of all of the constituent files’ timestamps compared to the generated file’s? That would work across copies among compatible file systems.)

    I reckon that neither philosophy is more correct than one or the other, only to note that the one chosen has an unfortunate side effect with Web gardens. I think it’s perfectly reasonable in this case to note the limitation and say "who uses Web gardens anyway, if it bothers you pre-generate the file as part of your deployment process."

    Hmm, I suppose I see now why they say cache management is one of the hardest things to deal with 😉

  17. For both MVC and WebForms integration I would suggest creating a wrapper container to create your CSS and JavaScript references ie:

    <Builder:JavaScriptIncludes runat="server">
    <script src="~/scripts/jquery-1.4.2-vsdoc.min.js"></script>
    </Builder:JavaScriptIncludes>

    By doing this visual studio will still provide intellisense code highlighting for jQuery, while you can still pull out the script source. This also makes integrating this idea into an existing site easier as Builder:JavaScriptIncludes tag just needs to inserted around your existing script references. This is the solution we are using internally for our applications.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All