Mangle nested classes and variables with UglifyJS
Asked Answered
R

5

10

I use UglifyJS to minify a concatenated set of files, which works fine but not good enough. The built lib uses namespaces, so classes, functions and constants are stored in a root namespace variable:

(function() {
  var root = { api:{}, core:{}, names:{} };

  /* util.js file */
  root.names.SOME_LONG_NAMED_CONST='Angel';

  /* Person.js file */
  root.core.Person = function(name) { this.name = name };

  /* API.js with the functions we want to expose */
  root.api.perform = function(param_for_api) { /* do something */ }

  window.lib_name.perform = root.api.perform;

})();

which is minified to the not-so-minimal version

(function(){var a={api:{},core:{},names:{}};a.names.SOME_LONG_NAMED_CONST="Angel",a.core.Person=function(a){this.name=a},a.api.perform=function(){},window.lib_name.perform=a.api.perform})();

I understand uglify probably thinks that root var is a data structure that must be kept as-is and can't be changed. Is there a way to let UglifyJS mangle the nested names in the root namespace?

Roderickroderigo answered 2/5, 2013 at 9:47 Comment(0)
R
8

When you minimize Javascript you can only change names of variables, the api, core and names are not variables but properties of an object. If these were changed by the minimizer, you would potentially get unexpected results. What if in your code you would call

root["api"].perform = function()...

or even something like

function doIt(section, method, argument) {
    root[section][method](argument);
}
doIt('api','perform', 101);

All perfectly legal JS, but a minimizer could never figure out what's going on.

Rosannrosanna answered 2/5, 2013 at 9:53 Comment(2)
Yes, I understand you arguments. Is there anything else I can do?Roderickroderigo
@Jan Developer might decide this, not uglifyjs. I can configure uglifyjs about that: 'do not mangle strings', or, 'do not touch variables that I told you'. I have been using an obfuscator and it's been perfectly uglifying everything without a problem.Repulse
G
8

I have been trying to use --mangle-props of UglifyJS2 and can tell you: 'it makes a mess'.

As someone pointed out: 'Developer should decide what properties to mangle, not uglifyjs'

I am approaching the problem using this options:

--mangle-props
--mangle-regexp="/_$/"

The regex matches any property with a underscore at the end.

You asked to mangle nested names in the root namespace. So, your code:

(function() {
  var root = { api:{}, core:{}, names:{} };

  root.names.SOME_LONG_NAMED_CONST_='Angel';

  root.core.Person_ = function(name) { this.name = name };

  root.api.perform_ = function(param_for_api) {  }

  window.lib_name.perform = root.api.perform;
})();

Would result in this:

(function() {
    var n = {
        api: {},
        core: {},
        names: {}
    };
    n.names.a = "Angel";
    n.core.b = function(n) {
        this.name = n;
    };
    n.api.c = function(n) {};
    window.lib_name.perform = n.api.c;
})();

Command: uglifyjs --beautify --mangle --mangle-props --mangle-regex="/_$/" -- file.js

If you want to mangle first level of root namespace (api, core, names) just put a underscore on them (api_, core_, names_), you are in control ;)

Just a side note: when you are mangling properties usable by other js files, you should mangle all files together with the same command, so the same identifier will be used over all files.

Gillispie answered 15/8, 2015 at 3:47 Comment(2)
Nice. I prefer leading underscores though (a remnant from my C++ days), and mangle with /^_/Amorous
I see, but I did it with trailing underscores because it is easier to type (code completion works better).Gillispie
B
4

Aside from @JanMisker 's point (which is completely valid), rewriting properties is unsafe because they can be exposed to code outside the scope of the minification.

Although the self executing function has a scope, and if the code is only

(function() {
  var root = { api:{}, core:{}, names:{} };
  root.names.SOME_LONG_NAMED_CONST='Angel';
  alert(root.names.SOME_LONG_NAMED_CONST); // some code that does something
})();

It is true that outside of the function, there is no way to access the root object, so rewriting the property names is safe, and the following code would result in the same:

(function() {
  var a = { b:{}, c:{}, d:{} };
  a.d.e='Angel';
  alert(a.d.e);
})();

But even if you are inside your private scope you can access, and more importantly assign to variables from an outer scope! Imagine this:

(function() {
  var root = { api:{}, core:{}, names:{} };
  root.api.perform = function(param_for_api) { /* do something */ }
  window.lib_name = root.api;
})();

You are not only exposing a function but an object with a function on it. And the function will be visible from any place where window is visible.

So, for example writing the following in the javascript console would yield different results with and without minification:

window.lib_name.perform(asdf);

With minification you would have to write:

window.lib_name.f(asdf);

Or something similar.

Remember that there can always be code outside your minification.

It is not that crucial to have the absolute minimal JS, but if IT IS that crucial for some reason (for example: aliens abducted your stepdaughter, and the only way to have her back is to minify this below 100 characters or so), you can manually replace an undesirably long property name to a shorter one, just be sure that it will not be exposed anywhere, and isn't be accessed through associative array notation (root['api']).

Batista answered 2/5, 2013 at 11:21 Comment(3)
Although stepdaughter was not abducted :) the need for nested mangling is crucial for the minification. I think the code size will can be reduced by more than 20%. note that I intentionally used window.lib_name.perform = root.api.perform so the minification will still workRoderickroderigo
The problem is that the dynamic typing and basically everything about objects in javascript is so forgiving that uglifyjs just cannot be 100% sure what you expose just by looking at your script. Also @JanMisker raised a point about strings. They can be built dynamically and then used as an indexer for an associative array. There is just no way to uglifyjs to safely implement the minification for that. You will have to do that manually.Batista
Closure can do it (ADVANCED_OPTIMIZATIONS).Basel
S
1

as @Jan-Misker explained in his answer, property name mangling is NOT an good idea because it could potentially break your code.

However, you can workaround it by define the property names as local variables, and modify all .properties to [keys], to make smaller file size:

(function() {
  var API = 'api';
  var CORE = 'core';
  var NAMES = 'names';
  var SLNC = 'SOME_LONG_NAMED_CONST';

  var root = {};
  root[API]={};
  root[CORE]={};
  root[NAMES]={};

  /* util.js file */
  root[NAMES][SLNC] ='Angel';

  /* Person.js file */
  root[CORE].Person = function(name) { this.name = name };

  /* API.js with the functions we want to expose */
  root[API].perform = function(param_for_api) { /* do something */ }

  window.lib_name.perform = root[API].perform;

})();

Because now all the properties became a local variable, uglify js will mangle/shorten the variable names and as consequence you overall file size reduced:

!function(){var a="api",b="core",c="names",d="SOME_LONG_NAMED_CONST",e={};e[a]={},e[b]={},e[c]={},e[c][d]="Angel",e[b].Person=function(a){this.name=a},e[a].perform=function(){},window.lib_name.perform=e[a].perform}();

However, reduced file size doesn't mean you will get shorter downloading time on real server, because usually our http transport is gzipped, most of the repetitions will be compressed by your http server and it does a better job than human.

Squires answered 12/3, 2014 at 21:47 Comment(0)
G
1

The latest release of uglify (today) has object property mangling, see v2.4.18. It also supports reserved files for excluding both object properties and variables that you don't want mangled. Check it out.

Use the --mangle-props option and --reserved-file filename1.json filename2.json etc....

Goodson answered 29/3, 2015 at 13:7 Comment(1)
I think it is easier to tell uglify to mangle only these instead of mangle except this because in the latter each new keyword you create may be mangled accidentally (forget to insert in exclusion list)Gillispie

© 2022 - 2024 — McMap. All rights reserved.