16

Using the latest version of MVC4 I can't minify javascript when it contains reserved words as key names!

See the error below with the valid javascript that should have been minified.

Does anyone know how to fix this short of rewriting the javascript to use [""] notation?

PS The code in question is a few thousand lines long, so it's not an option!

/* Minification failed. Returning unminified contents.
(3,9-15): run-time warning JS1010: Expected identifier: delete
(4,9-13): run-time warning JS1010: Expected identifier: case
(5,9-11): run-time warning JS1010: Expected identifier: if
(3,9-15): run-time error JS1137: 'delete' is a new reserved word and should not be used as an identifier: delete
(4,9-13): run-time error JS1137: 'case' is a new reserved word and should not be used as an identifier: case
(5,9-11): run-time error JS1137: 'if' is a new reserved word and should not be used as an identifier: if
 */
var context = {};

context.delete = {};
context.case = {};
context.if = {};

The question is without going with another option like node, cassette, combres, servicestack etc

How do we get MVC4 to play ball with reserved words.

I find it hard to believe that after 6 months plus that there is no support for this!

Hao Kung
  • 28,040
  • 6
  • 84
  • 93
James Kyburz
  • 13,775
  • 1
  • 32
  • 33
  • 3
    Just tried it my self and first thought. Hey it worked, since no obvious exception was displayed. But instead, the shit wasn't minified and a sneaky little comment was input in the JS. That's really ugly. – Daniel Nov 16 '12 at 14:12
  • 1
    Haven't tested, but the Builder seems to be switchable per Bundle. Can't you assign one that switches values? Perhaps using the JsParser in WebGrease and using the Settings.AddRenamePair? – Daniel Nov 16 '12 at 14:32
  • Hey James. A good question but why would Bundling support reserved words when the ECMASscript Standard advises against using them? https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Reserved_Words – garfbradaz Nov 16 '12 at 14:43
  • If you find away around it though I would be interested to know. – garfbradaz Nov 16 '12 at 14:45
  • Actually i wonder if something could be added to the Minify/Bundles (Like we use for LESS etc) to maybe replace reserved words automatically into the """ notation....mmmmmnnn now thats something interesting to build. – garfbradaz Nov 16 '12 at 14:53
  • Which Daniel has done below (Well done Daniel! ) :P – garfbradaz Nov 16 '12 at 15:02
  • @garfbradaz Identifiers are not allowed to have reserved words, but Identifier names are allowed to have reserved words. – James Kyburz Nov 16 '12 at 16:12

2 Answers2

9

Just tried this and it works. Sorry but the ugly code. It will replace your members named delete and the usages of them.

public class CustomBundle : ScriptBundle
{
    public CustomBundle(string virtualPath) : base(virtualPath)
    {
        this.Builder = new CustomBuilder();
    }
    public CustomBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath) {}
}

public class CustomBuilder : IBundleBuilder {
    public string BuildBundleContent(Bundle bundle, BundleContext context, IEnumerable<FileInfo> files)
    {
        var content = new StringBuilder();
        foreach (var fileInfo in files)
        {
            var parser = new Microsoft.Ajax.Utilities.JSParser(Read(fileInfo));
            parser.Settings.AddRenamePair("delete", "fooDelete");
            content.Append(parser.Parse(parser.Settings).ToCode());
            content.Append(";");
        }

        return content.ToString();
    }

    private string Read(FileInfo file)
    {
        using(var r = file.OpenText())
        {
            return r.ReadToEnd();
        }
    }
}
Daniel
  • 8,133
  • 5
  • 36
  • 51
  • This is awesome Daniel, I'm definitely going to have a play with this. – garfbradaz Nov 16 '12 at 14:54
  • 1
    Not much love in the code, but it will switch them, but only when minifiying. Blogged about it here: http://daniel.wertheim.se/2012/11/16/customizing-the-minification-of-javascript-in-asp-net-mvc4-allowing-reserved-words/ – Daniel Nov 16 '12 at 15:14
  • Would you agree if would be more efficient to put this sort of logic say in some MSBuild script? – garfbradaz Nov 16 '12 at 15:20
  • 1
    I would say I think the optimization frameworks works nicely as it is. Used another a while back ago, that was triggered when building the code via call to some NodeJS script which could be used separately in our build process. I feel that that kind of solution is much more cumbersome to work with as a dev. – Daniel Nov 16 '12 at 15:44
  • Thanks Daniel. After I have finished playing with Windows 8/C# XAML, I will have a look at your code and have a good play with it. I love questions like this that stop you from working and to think about a given problem. – garfbradaz Nov 16 '12 at 15:54
  • Thanks Daniel, I am looking into your solution. It's creative! – James Kyburz Nov 16 '12 at 16:36
  • @JamesKyburz Ha, ha. Creative? ;-) BTW, do you mind telling which "nice" framework it was, that used the reserved words? – Daniel Nov 16 '12 at 16:50
  • @garfbradaz Reserved words in identifier names are allowed, "Reserved word usage" paragraph in https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Reserved_Words – James Kyburz Nov 16 '12 at 19:47
  • @Daniel No framework, code from a friend of mine but legal js all the same :) – James Kyburz Nov 16 '12 at 19:47
  • @Daniel Gave you an upvote, however replacing stuff isn't full proof :( Look at this failing code :- var context = {}; context.delete = function() { alert('ok'); }; var method = "delete"; context[method](); // fails context["delete"](); // works the replace is clever enough – James Kyburz Nov 16 '12 at 19:52
  • Has he perhaps used method by convention? I mean, could you do some own text processing with RegEx? – Daniel Nov 16 '12 at 20:06
  • Then don't replace the names but still do the custom dance. Then it will be minified but the error comments will be injected. That you could certainly parse out. BTW. Perhaps you should use Minifier instead of JSParser. They are in the same assembly. – Daniel Nov 16 '12 at 21:13
2

Hope it's not too late. You can implement own minification transform and ignore these errors.

var bundle = new ScriptBundle("~/bundles/xxxbundle", null, new JsMinifyIgnoreReservedWordError()).
    Include("~/Scripts/xxx.js");

private class JsMinifyIgnoreReservedWordError : IBundleTransform
{
    private const string JsContentType = "text/javascript";

    public void Process(BundleContext context, BundleResponse response)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (response == null)
        {
            throw new ArgumentNullException("response");
        }
        if (!context.EnableInstrumentation)
        {
            Minifier minifier = new Minifier();

            string result = minifier.MinifyJavaScript(response.Content, new CodeSettings
            {
                EvalTreatment = EvalTreatment.MakeImmediateSafe,
                PreserveImportantComments = false,
                        IgnoreErrorList = "JS1137" // ignore 'is a new reserved word and should not be used as an identifier' error
                    });

            if (minifier.ErrorList.Count > 0)
            {
                GenerateErrorResponse(response, minifier.ErrorList);
            }
            else
            {
                response.Content = result;
            }
        }
        response.ContentType = JsContentType;
    }

    private static void GenerateErrorResponse(BundleResponse bundle, IEnumerable<object> errors)
    {
        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.Append("/* ");
        stringBuilder.Append("Minification failed. Returning unminified contents.").AppendLine();

        foreach (object error in errors)
        {
            stringBuilder.Append(error).AppendLine();
        }

        stringBuilder.Append(" */").AppendLine();
        stringBuilder.Append(bundle.Content);

        bundle.Content = stringBuilder.ToString();
    }
}
Aizzat Suhardi
  • 753
  • 11
  • 19