Preserve constructor argument names

Dec 5, 2012 at 10:25 AM

Hi, we have a requirement to preserve the names of constructor arguments. For example, given the following JavaScript:

var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    return Greeter;
})();

We would like this to be minified into the following:

var Greeter = (function () {
    function a(message) {
        this.greeting = message
    } return a
})();

In the above the the constructor argument "message" is not mangled. The reason for wanting to do this is because of a dependency injection framework that uses the names of constructor arguments to resolve a particular dependency.

Ideally if an option "--preserveConstructorArgs" exists then the problem is solved. But there doesn't seem anything like that in the documentation.

Is there a way to preserve constructor argument names using the Ajax Minifier?

Thanks.

Noel

Coordinator
Dec 5, 2012 at 4:14 PM

Well, yes and no (but mostly no). From just that code snippet, I'm not sure I could programmatically tell that that is a "constructor"; as far as JavaScript is concerned, you are calling an anonymous function expression that defines and then returns a function declaration. The best you could do at the moment would be to mark "message" as a variable name you don't want minified -- but that would not rename any instance of any variable named "message," not just this one parameter. Not sure if that's what you want. On the command-line, -norename:message would do the trick. Or you could put all your no-rename names in an XML file and use -norename xmlpath (see the XML Format Docs, scroll down to the last section, "XML Naming File"). For the DLL, you need to register all the names on a CodeSettings object, either individually via CodeSettings.AddNoAutoRename(string) or as a group using CodeSettings.SetNoAutoRenames(IEnumerable<string>).

I've always meant to add comment-based annotations to AjaxMin for just such things. Maybe that would be something that could help you out. What I mean by that is I'd like to be able to parse special comments that modify how the following code is (or is not) minified. For instance, I'd love to be able to have you place a comment right before the line "function Greeter(message){" that says something like: ///#NORENAME message, and then the next instance of a field named "message" would not have any of its references automatically renamed (but any other fields that also happen to have the same name would still be). But I don't have that ability in the code yet.

Dec 6, 2012 at 1:26 PM

Hi, ronlo, the reason I picked that particular example above is because that is how TypeScript compiles into JavaScript; and any solution to this problem will need to deal with that - since TypeScript is rather likely to catch on. (We also have switched over to TypeScript.)

The "norename" option sounds viable. I guess that is the best, if not the only, solution there is at present.

Regarding the annotation idea, the YUI compressor at present permits a "nomunge" annotation, which must be defined as a string declaration just below the function (or constructor). E.g. function MyClass(message){ "message:nomunge"; }. This sounds like a solution, but the following TypeScript: 

 

class Greeter {
	greeting: string = "";
	constructor (message: string) {
		"message:nomunge";
		this.greeting = message;
	}
}

 

 

Complies into

var Greeter = (function () {
    function Greeter(message) {
        this.greeting = "";
        "message:nomunge";
        this.greeting = message;
    }
    return Greeter;
})();

Which doesn't work because the annotation is required to be located immediately below the constructor.

The second option is to implement annotation via comments instead as you suggest. That would solve the problem above. But I'm not sure how happy the community will be to specify critical information in a comment.

I wonder if there is a better solution that can be implemented? One that is perhaps compatible with TypeScript.

Thanks.

Noel

Coordinator
Dec 6, 2012 at 4:52 PM

Well that sounds do-able, and I can make it work even if it's not the first statement in the block. I assume we want that directive removed from the minified output, correct?

Coordinator
Dec 6, 2012 at 6:05 PM

Okay, this will be in the next release.

I'll allow hints to be anywhere in the code, not just the top, and they will be stripped from the output. Hints will be expression statements consisting of a single string literal of the format: "IDENT:nomunge[,IDENT:nomunge]*". This will cause the named IDENT field(s) defined within the current scope to not be automatically renamed. If the IDENT identifier is not specified or is an asterisk (*), then ALL fields defined within the current scope will not be automatically renamed. If the IDENT identifier resolves to an outer field, it will be ignored (cannot cause outer fields to not be renamed - let me know if we want to remove that restriction before release).

Coordinator
Dec 6, 2012 at 10:13 PM

I went ahead and released this code as version 4.77.

Dec 7, 2012 at 9:07 AM

Hi, Ron,

The specification you have outlined looks good - the nomunge within the current scope should fix the problem. We have some work to do to get the Ajax minifier incorporated, and we'll get back if there are any problems.

Since we are on the subject of TypeScript, are there any plans to adapt the Ajax Minifier to work off TypeScript files (rather than the compiled JavaScript)?

The TypeScript source offers much richer information, which can be used by the minifier to effect a better minification ratio. For example, the following TypeScript:

class Greeter {
	private greeting: string;
	constructor (message: string) {
		this.greeting = message;
	}
	
	public greet() {
		console.log(this.getMessage());
	}
	
	private getMessage()
	{
		return "Hello, " + this.greeting;
	}
}

 

Compiles into this JavaScript:

var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        console.log(this.getMessage());
    };
    Greeter.prototype.getMessage = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();

Running the minifier on the JavaScript will be suboptimal, because the information that "getMessage" is a private function has been lost, and the minifer will preserve the function name. The same goes for the private field "greeting".

Furthermore, my original request for a global --preserveConstructorArgs should also be implementable.

Thanks.

Noel

Coordinator
Dec 7, 2012 at 7:31 PM

There are no plans for AjaxMin to work off TypeScript source files -- that would require AjaxMin to understand TypeScript syntax and essentially replace whatever process they currently have to compile TypeScript to JavaScript. The best that could be done would be for the TypeScript compiler (or whatever it's called) to provide hints in the compiled JS that AjaxMin would then be able to pick up on. Or to just skip the AjaxMin step entirely and minify the compiled JS directly. I'm not affiliated with the TypeScript team at all, so I have no insight into their processes or plans. I thoroughly agree, though, that the extra information would definitely make for much tighter minified JS code.

Dec 7, 2012 at 10:20 PM

Well, I like the option of providing "hints" for the minimizer. I would like to have public (by default), protected and private properties/methods that can be munged to provide better compression.

Since I'm working with my own C#>js compiler, I can add any kind of information to the js sources from the original C# code, providing that you add that into ajaxmin.

Private members/methods should be easy: They may have a comment with a special formatting. For protected, I believe you can simply rename all the protected items, in any "class" declaration, with the same name (so you don't need to know the relationship between classes)...

I don't know, thinking aloud here...