260

I have a weird issue with the mvc4 bundler not including files with extension .min.js

In my BundleConfig class, I declare

public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/Scripts/jquery")
        .Include("~/Scripts/jquery-1.8.0.js")
        .Include("~/Scripts/jquery.tmpl.min.js"));            
}

In my view, I declare

<html>
    <head>
    @Scripts.Render("~/Scripts/jquery")
    </head><body>test</body>
</html>

And when it renders, it only renders

<html>
    <head>
         <script src="/Scripts/jquery-1.8.0.js"></script>
    </head>
    <body>test</body>
</html>

If I rename the jquery.tmpl.min.js to jquery.tmpl.js (and update the path in the bundle accordingly), both scripts are rendered correctly.

Is there some config setting that is causing it to ignore '.min.js' files?

Fatal
  • 3,338
  • 3
  • 19
  • 15
  • I am using MVC 4 bundler and it is including .min.js files. – Eric J. Aug 16 '12 at 03:52
  • the RTM version or the RC? it was working ok in the RC for me too – Fatal Aug 16 '12 at 03:56
  • 10
    The idea is that working in debug mode, that the "dev" version without minification will be used and when you are in non-debug mode, that the minified version is picked. To see it in action, change your web.config debug value from true to false. – Pieter Germishuys Aug 16 '12 at 04:04
  • 28
    in some cases you dont have the non-minified version of the script though. I could *possibly* understand it if both files existed. – Fatal Aug 16 '12 at 04:12
  • 1
    It's a bummer that it works like this by default... sure, the file may already be *minified*, but I think Microsoft failed to see the benefit of adding pre-minified scripts to bundles for cache-busting purposes (the nice little `v` parameter hash that gets added to the url, and changes when file contents change) – nothingisnecessary Apr 30 '15 at 15:45

9 Answers9

258

The solution I originally posted is questionable (is a dirty hack). The tweaked behaviour has changed in Microsoft.AspNet.Web.Optimization package and the tweak does not work anymore, as pointed out by many commenters. Right now I cannot reproduce the issue at all with the version 1.1.3 of the package.

Please see sources of System.Web.Optimization.BundleCollection (you can use dotPeek for example) for better understanding of what you are about to do. Also read Max Shmelev's answer.

Original answer:

Either rename .min.js to .js or do something like

    public static void AddDefaultIgnorePatterns(IgnoreList ignoreList)
    {
        if (ignoreList == null)
            throw new ArgumentNullException("ignoreList");
        ignoreList.Ignore("*.intellisense.js");
        ignoreList.Ignore("*-vsdoc.js");
        ignoreList.Ignore("*.debug.js", OptimizationMode.WhenEnabled);
        //ignoreList.Ignore("*.min.js", OptimizationMode.WhenDisabled);
        ignoreList.Ignore("*.min.css", OptimizationMode.WhenDisabled);
    }

    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.IgnoreList.Clear();
        AddDefaultIgnorePatterns(bundles.IgnoreList);
        //NOTE: it's bundles.DirectoryFilter in Microsoft.AspNet.Web.Optimization.1.1.3 and not bundles.IgnoreList

        //...your code
     }
Community
  • 1
  • 1
Pavel
  • 3,537
  • 1
  • 16
  • 14
  • 4
    Thanks from me too - so adding this to my MVC4 gotcha list :) – Dan B Aug 20 '12 at 21:12
  • 27
    this was long wtf moment, they could have been adding those min files commented out with reason or something to result, now whole hour wasted wondering who is stealing my script files from output. – Giedrius Aug 27 '12 at 13:53
  • 5
    Per Fatal's comment in the OP, this solution will end up rednering duplicate references for both the minified and regular versions if they both exist (e.g. jquery). – danmiser Aug 27 '12 at 20:38
  • I don't like the idea of doing this, but I have no other option, the library I am using didn't provide anything but the .min.js :( – GONeale Nov 07 '12 at 06:34
  • 11
    M$, when somefile.min.js is explicitely specified, or when just the .min.js file exists, then only include the .min.js file! Otherwise just apply the current behavior! – DATEx2 Mar 06 '13 at 09:13
  • 3
    Incredible that the actual class is called "IgnoreList", yet it derives straight from Object. No LINQ, no iteration. - http://msdn.microsoft.com/en-us/library/system.web.optimization.ignorelist.aspx – J.P. May 08 '13 at 12:45
  • 2
    I also had to add: bundles.DirectoryFilter.Clear(); – jmrnet May 29 '14 at 22:27
  • Doing the code above did nothing for me. After calling Clear() on bundles.DirectoryFilter did things start working (without clearing the ignore list). – Mike Cheel Jun 04 '14 at 16:43
  • 1
    I can reproduce it with Microsoft.AspNet.Web.Optimization 1.0.0, and it is fixed at v. 1.1.3. – mykola.rykov Jul 15 '14 at 15:23
  • Minification automatically happens with bundling when debug mode is set to false in web.cong . Even debug mode we cab force to bundle using or bundletable.enableoptimizations = true in bundleconfig.cs – SKARVA Bodavula Sep 15 '16 at 11:56
180

Microsoft implies the following behavior (and I prefer to follow it in my projects):

short version

  1. You have both debug and minified versions of a script in your project under the same folder:
    • script.js
    • script.min.js
  2. You add only script.js to a bundle in your code.

As a result you will automatically have the script.js included in DEBUG mode and script.min.js in RELEASE mode.

long version

You can have also .debug.js version. In that case the file is included in the following priority in DEBUG:

  1. script.debug.js
  2. script.js

in RELEASE:

  1. script.min.js
  2. script.js

note

And by the way, the only reason to have a .min versions of your scripts in MVC4 is the case when the minified version can not be processed automatically. For example the following code can not be obfuscated automatically:

if (DEBUG) console.log("Debug message");

In all the other cases you can go with just a debug version of your script.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Max Shmelev
  • 3,774
  • 4
  • 26
  • 26
  • 6
    nice explanation however, the OP's problem was that some scripts (`jquery.tmpl`) don't have, didn't come with, didn't get downloaded the non-min version of the file. The bundler ignores the script files altogether if it doesn't have a non-min version in debug mode – Eonasdan Oct 15 '12 at 17:57
  • Agree, nice explanation but it does not answer at all OP's question. – Diego Oct 18 '12 at 15:09
  • 2
    Short version didn't work for me. "script.js" would be included in both of these release types. I toggled DEBUG/RELEASE and (when I looked at the source) 'script.js' was the one selected/rendered. – user981375 Oct 29 '12 at 19:28
  • 4
    user981375, you also need to set in the web.config file – Max Shmelev Oct 31 '12 at 17:05
  • 5
    I prefer this answer because it is a "best practices" approach and explains how the bundler's default rules can be followed to achieve the same goal. – James Reategui Mar 07 '13 at 19:32
  • Is it possible to add custom transformation logic (IItemTransform) for *.min.css files? The CssRewriteUrlTransform transformation doesn't seem to get invoked on *.min.css files, so just a presence of *.min.css file breaks URL references (they're not being transformed). – Buthrakaur Aug 14 '14 at 11:32
5

If all you have is a minified version of a file, the simplest solution I've found is to copy the minified file, remove .min from the copied file's name, then reference the non-minified file name in your bundle.

For example, let's say you purchased a js component and they gave you a file called some-lib-3.2.1.min.js. To use this file in a bundle, do the following:

  1. Copy some-lib-3.2.1.min.js and rename the copied file to some-lib-3.2.1.js. Include both files in your project.

  2. Reference the non-minified file in your bundle, like this:

    bundles.Add(new ScriptBundle("~/bundles/libraries").Include(
        "~/Scripts/some-lib-{version}.js"
    ));
    

Just because the file without 'min' in the name is actually minified shouldn't cause any issues (other than the fact it's essentially unreadable). It's only used in debug mode and gets written out as a separate script. When not in debug mode the pre-compiled min file should be included in your bundle.

joelfp
  • 131
  • 3
  • 8
4

I have found a good solution that works at least in MVC5, you can just use Bundle instead of ScriptBundle. It does not have the smart behavior of ScriptBundle that we don't like (ignoring .min, etc.) in this case. In my solution I use Bundle for 3d party scripts with .min and .map files and I use ScriptBundle for our code. I have not noticed any drawbacks of doing it. To make it work this way you will need to add original file e.g. angular.js, and it will load angular.js in debug and it will bundle the angular.min.js in release mode.

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
  • It doesn't seem to work when optimizations are disabled (`ScriptTable.EnableOptimizations = false`). Script paths that contain a pattern are not rendered (e.g. `~/Scripts/thirdpartylib/*.js`). It *does* work when `EnableOptimizations = true`, but so does `ScriptBundle`. – Steven Liekens Apr 20 '16 at 10:47
  • I use it with false as well (during development) and I don't have problems that you describe, so it might be something else that's affecting it for you.You also need to do IncludeDirectory instead of include for the pattern to work. – Ilya Chernomordik Apr 20 '16 at 11:33
  • Are you sure it is rendering the ".min.js" version of your file? Are you using bundles from the namespace `System.Web.Optimizations` or from the nuget package `Microsoft.Web.Optimizations`? – Steven Liekens Apr 20 '16 at 11:47
  • I use System.Web.Optimization and I checked that it renders min version, but I have figured out I do not use IncludeDirectory in fact, but it would be strange if that did not work with min version though... As IncludeDirectory is not enough for me I do a custom scan and add all the filter using Include, so might be that the pattern and IncludeDirectory does not work properly there, though it's a bit strange. – Ilya Chernomordik Apr 20 '16 at 13:09
  • The problem only exists when including files using a wildcard pattern. Pattern matching doesn't work for `*.min.js` files. Not even when using `Bundle` instead of `ScriptBundle`. – Steven Liekens Apr 20 '16 at 13:33
  • You should do *.js, not *.min.js, the whole point is that it figures out itself. Probably that is the reason? – Ilya Chernomordik Apr 20 '16 at 13:57
  • 1
    No, the problem is two-fold: only a `.min.js` file exists, and the `*.js` pattern does not match files ending in `.min.js`. – Steven Liekens Apr 20 '16 at 14:06
  • You can try adding the .js file in addition to the .min.js file to the same folder. That might fix it. I think it ignores all the .min.js files alltogether. As .min.js never handled on their own, but only as a part of original file when optimization is turned on. Sounds reasonable to have both files since one is used for development and another for production. – Ilya Chernomordik Apr 20 '16 at 14:07
  • That's assuming that the non-minified file is available and that you are licensed to use it. That's not a given for many libraries and plugins on the internet. – Steven Liekens Apr 20 '16 at 14:09
  • Then just rename you .min.js to .js since you will never have access to the full version :) Another option is to copy the file and make .js. But I see the problem, but I never used any libraries that are not available in both (or at least unminifed) version, so never had this problem. – Ilya Chernomordik Apr 20 '16 at 14:11
3

To render *.min.js files, you must disable BundleTable.EnableOptimizations, which is a global setting that applies to all bundles.

If you want to enable optimizations for specific bundles only, you can create your own ScriptBundle type that temporarily enables optimizations when enumerating files in the bundle.

public class OptimizedScriptBundle : ScriptBundle
{
    public OptimizedScriptBundle(string virtualPath)
        : base(virtualPath)
    {
    }

    public OptimizedScriptBundle(string virtualPath, string cdnPath)
        : base(virtualPath, cdnPath)
    {
    }

    public override IEnumerable<BundleFile> EnumerateFiles(BundleContext context)
    {
        if (context.EnableOptimizations)
            return base.EnumerateFiles(context);
        try
        {
            context.EnableOptimizations = true;
            return base.EnumerateFiles(context);
        }
        finally
        {
            context.EnableOptimizations = false;
        }
    }
}

Use OptimizedScriptBundle instead of ScriptBundle for bundles whose minified file should always be used, regardless of whether a non-minified file exists.

Example for Kendo UI for ASP.NET MVC, which is distributed as a collection of only minified files.

bundles.Add(new OptimizedScriptBundle("~/bundles/kendo")
        .Include(
            "~/Scripts/kendo/kendo.all.*",
            "~/Scripts/kendo/kendo.aspnetmvc.*",
            "~/Scripts/kendo/cultures/kendo.*",
            "~/Scripts/kendo/messages/kendo.*"));
Steven Liekens
  • 13,266
  • 8
  • 59
  • 85
  • I came across some missing scripts in a production publish action, and thinking it was an MVC issue, I found this answer.. lo and behold, kendo is involved. Having worked with it for too long, I do anything to get away from kendo – Felipe Sep 15 '17 at 17:56
  • @Felipe I was in the same situation which is why I wrote this answer. Kendo is horrible. I hope my example is helpful. – Steven Liekens Sep 16 '17 at 12:49
2

For my case, I was using the (wonderful!) Knockout.js library which comes as Debug/Non versions:

"~/Scripts/knockout-{Version}.js"
"~/Scripts/knockout-{Version}.Debug.js"

To make this work, I included "Knockout-{Version}.js" (non-debug) in my Bundle and got the .debug. js file in Debug mode.

AcidPAT
  • 709
  • 7
  • 13
1

An easy way just Rename the .min file for example you have abc.min.css than just rename this file to abc_min.css and add this to your bundle. I know its not the right way to do it, but just a temporary solution. thanks and happy coding.

R K Sharma
  • 845
  • 8
  • 23
  • 42
0

Bundler have a lot of benefits check this page BUT:

The Microsoft MVC4 Platform Considers that you have at least both minified version & unminified version for each Script or Style Bundle(another files like Debug and vsdoc are available too). So We have trouble in situations that there is only one of these files.

You can change debug state in web.config file permanently to handle output:

<compilation debug="false" targetFramework="4.0" />

See the output changes! File filtering changed. To meet the purpose we must change ignore case filtration that will change application logic!

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
  • 1
    Adding debug="false" still doesn't work for me. min.js files are still not included even though I clear the IgnoreList as well... – MoSs Nov 26 '13 at 14:27
-21

Folks I would just keep it simply until Microsoft gets it act together

Try this

In RegisterBundles create this bundle for Kendo

bundles.Add(new StyleBundle("~/Content/kendo/css").Include(
                    "~/Content/kendo/2012.3.1121/kendo.common.min.css",
                     "~/Content/kendo/2012.3.1121/kendo.dataviz.min.css",
                      "~/Content/kendo/2012.3.1121/kendo.blueopal.min.css"
 ));

In _Layout.cshtml put this:

@if (HttpContext.Current.IsDebuggingEnabled)
 { 
<link href="@Url.Content("~/Content/kendo/2012.3.1121/kendo.common.min.css")"      
rel="stylesheet"  type="text/css" />
<link href="@Url.Content("~/Content/kendo/2012.3.1121/kendo.dataviz.min.css")" 
rel="stylesheet" type="text/css" />   
<link href="@Url.Content("~/Content/kendo/2012.3.1121/kendo.blueopal.min.css")"    
rel="stylesheet" type="text/css" />
 }
 else
 {
     @Styles.Render("~/Content/kendo/css")   
 }

This way we get best of bundles in Production and a reference in Debug

Fix it up when Microsoft fixes their MVC 4 Code

Imad Alazani
  • 6,688
  • 7
  • 36
  • 58
  • 3
    I'm not a fan of this proposed solution at all. You're now having to maintain the set of scripts in (at least) two places – Fatal Dec 11 '12 at 09:22
  • This pretty much defeats the purpose of the bundling, don't you think? – Oliver May 07 '13 at 21:52
  • 4
    Bundling already works this way. If you run the project in debug mode each CSS file is loaded individually, however when you run in release they're combined and minified into one. The code within the if block is identical to what's rendered on the page automatically in debug mode. – Chad Levy Jun 25 '13 at 18:48