Skip to content

Suggestion: put prototype in var for better minification #9638

Closed as not planned

Description

@Malkiz

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);

Activity

  1. changed the title [-]Suggestion: put prototype in var for better obfuscation[/-] [+]Suggestion: put prototype in var for better minification[/+] on Jul 12, 2016
  2. RyanCavanaugh commented on Jul 12, 2016

    @RyanCavanaugh
    Member

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

  3. evmar commented on Jul 12, 2016

    @evmar
    Contributor

    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.

  4. Malkiz commented on Jul 12, 2016

    @Malkiz
    Author

    Evan Martin (@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.

  5. ivogabe commented on Jul 13, 2016

    @ivogabe
    Contributor

    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.

  6. RyanCavanaugh commented on Jul 13, 2016

    @RyanCavanaugh
    Member

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

  7. kitsonk commented on Jul 14, 2016

    @kitsonk
    Contributor

    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.

  8. jeffreymorlan commented on Aug 29, 2016

    @jeffreymorlan
    Contributor

    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);

  9. added this to the milestone on Sep 19, 2016
  10. RyanCavanaugh commented on Sep 19, 2016

    @RyanCavanaugh
    Member

    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).

  11. evmar commented on Nov 16, 2022

    @evmar
    Contributor

    This bug was filed in 2016, which is possibly before Closure Compiler supported ES6 classes natively. Today, I think the right way to solve this problem is to have TS and Closure emit ES6, or if you must downlevel you could downlevel with Closure.
    So from that perspective, I think you could close this bug. I don't think optimizing ES5 output is worth worrying about these days.

    Gena (@GeneGenie) you can change the target emit to ES6 in the playground to fix your output too.

  12. RyanCavanaugh commented on Nov 16, 2022

    @RyanCavanaugh
    Member

    Yeah, I agree this isn't worth any additional effort at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions