MVC4 Bundle minification doesn't work with javascript reserved words
Asked Answered
L

2

17

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!

Litha answered 16/11, 2012 at 9:34 Comment(7)
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.Polycarp
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?Polycarp
Hey James. A good question but why would Bundling support reserved words when the ECMASscript Standard advises against using them? developer.mozilla.org/en-US/docs/JavaScript/Reference/…Burton
If you find away around it though I would be interested to know.Burton
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.Burton
Which Daniel has done below (Well done Daniel! ) :PBurton
@Burton Identifiers are not allowed to have reserved words, but Identifier names are allowed to have reserved words.Litha
P
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();
        }
    }
}
Polycarp answered 16/11, 2012 at 14:49 Comment(12)
This is awesome Daniel, I'm definitely going to have a play with this.Burton
Not much love in the code, but it will switch them, but only when minifiying. Blogged about it here: daniel.wertheim.se/2012/11/16/…Polycarp
Would you agree if would be more efficient to put this sort of logic say in some MSBuild script?Burton
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.Polycarp
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.Burton
Thanks Daniel, I am looking into your solution. It's creative!Litha
@JamesKyburz Ha, ha. Creative? ;-) BTW, do you mind telling which "nice" framework it was, that used the reserved words?Polycarp
@Burton Reserved words in identifier names are allowed, "Reserved word usage" paragraph in developer.mozilla.org/en-US/docs/JavaScript/Reference/…Litha
@Polycarp No framework, code from a friend of mine but legal js all the same :)Litha
@Polycarp 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 enoughLitha
Has he perhaps used method by convention? I mean, could you do some own text processing with RegEx?Polycarp
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.Polycarp
G
4

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();
    }
}
Golgi answered 18/11, 2015 at 17:12 Comment(3)
thanks @Golgi i am able to accomplish this but i have to change ScriptBundle to Bundle as it has the correct constructor parameters.Kittenish
also, to add more error numbers to the ignoreerrorlist, you can use comma-separated value.Kittenish
Answers like this really show what Stack Overflow is all about. I'm not sure if I can find documentation on this technology anywhere else.Labio

© 2022 - 2024 — McMap. All rights reserved.