Typescript: Suggestion: put prototype in var for better minification

Created on 12 Jul 2016  路  8Comments  路  Source: microsoft/TypeScript

I recently developed a module in TS, meant for the web, so it is also being minified.
I reviewed the output file and found that the word prototype is not minified (naturally, since it is a reserved word). But my module ended up having 153 occurrences of prototype, which amounted to 1.34 KB out of a total of 21KB in the minified file (I am using Google Closure Compiler with the ADVANCED_OPTIMIZATIONS flag).

So I had an idea that can reduce that by putting the prototype into a variable before using it in every class definition.

Example:

Code

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
    bla() {
        return this.greet() + this.greeting;
    }
    moreBla() {
        return this.greet() + this.greeting;
    }
    blablabla() {
        return this.greet() + this.greeting;
    }
}

class Hello extends Greeter {
    greet() {
        return "Goodbye";
    }
}

let greeter = new Greeter("world");

let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
    alert(greeter.greet());
    var x = [greeter.bla(), greeter.moreBla(), greeter.blablabla()];
    console.log(x);
}

document.body.appendChild(button);

Actual behavior:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    Greeter.prototype.bla = function () {
        return this.greet() + this.greeting;
    };
    Greeter.prototype.moreBla = function () {
        return this.greet() + this.greeting;
    };
    Greeter.prototype.blablabla = function () {
        return this.greet() + this.greeting;
    };
    return Greeter;
}());
var Hello = (function (_super) {
    __extends(Hello, _super);
    function Hello() {
        _super.apply(this, arguments);
    }
    Hello.prototype.greet = function () {
        return "Goodbye";
    };
    return Hello;
}(Greeter));
var greeter = new Greeter("world");
var button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function () {
    alert(greeter.greet());
    var x = [greeter.bla(), greeter.moreBla(), greeter.blablabla()];
    console.log(x);
};
document.body.appendChild(button);

Suggested behavior:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    var p = Greeter.prototype;
    p.greet = function () {
        return "Hello, " + this.greeting;
    };
    p.bla = function () {
        return this.greet() + this.greeting;
    };
    p.moreBla = function () {
        return this.greet() + this.greeting;
    };
    p.blablabla = function () {
        return this.greet() + this.greeting;
    };
    return Greeter;
}());
var Hello = (function (_super) {
    __extends(Hello, _super);
    function Hello() {
        _super.apply(this, arguments);
    }
    var p = Hello.prototype;
    p.greet = function () {
        return "Goodbye";
    };
    return Hello;
}(Greeter));
var greeter = new Greeter("world");
var button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function () {
    alert(greeter.greet());
    var x = [greeter.bla(), greeter.moreBla(), greeter.blablabla()];
    console.log(x);
};
document.body.appendChild(button);

Suggestion help wanted

Most helpful comment

Minifiers (Closure/Uglify/etc.) will typically not mangle property names, because it doesn't know which are "safe". Then there are property names that are "magical" like prototype which they will never mangle. Therefore the only reasonable way to allow a minifier to shorten such things is to create a reference to it that can be mangled.

My personal opinion is that down-emitting in the fashion suggested above would not have any negative consequences and doesn't make the down emit unclear (except maybe instead of the terse p it would be more clear to use something like var proto = Class.prototype which is more readable and would be mangled easily by a minifier if desired.

All 8 comments

Updating title to "minification" since that's usually what people call it when optimizing for download size

This sounds like it could be a suggestion for the Closure compiler. It does these sorts of rewrites in other circumstances, so perhaps there's a good reason it doesn't do it here.

@evmar I thought of that, but since Closure Compiler is not the only minification tool I figured it might be better to change the TS output itself. I can switch minification tools every day, but converting my code to a different language is a lot less likely to happen.

I'm not sure whether this would really reduce the gzipped output size. After gzipping, the string 'prototype' is only stored once. It would be interesting to see some numbers on this, maybe someone can create, well, a prototype 馃槄 and compare the numbers.

Ungzipped size still matters for parse speed, but that's of course much harder to measure deterministically

Minifiers (Closure/Uglify/etc.) will typically not mangle property names, because it doesn't know which are "safe". Then there are property names that are "magical" like prototype which they will never mangle. Therefore the only reasonable way to allow a minifier to shorten such things is to create a reference to it that can be mangled.

My personal opinion is that down-emitting in the fashion suggested above would not have any negative consequences and doesn't make the down emit unclear (except maybe instead of the terse p it would be more clear to use something like var proto = Class.prototype which is more readable and would be mangled easily by a minifier if desired.

The output for subclasses could be shrunk further if __extends returned the prototype it creates.
Before: __extends(Hello, _super); var p = Hello.prototype;
After: var p = __extends(Hello, _super);

Accepting PRs. Be sure to handle name collisions (e.g. a reference to an outer p in the method bodies needs to not be shadowed).

Was this page helpful?
0 / 5 - 0 ratings