Skip to content

Suggestion: minification #8

Description

TypeScript should support emitting minified JavaScript.

There are several different things we could support:

  1. Just remove whitespace
  2. Minify unobservable identifiers
  3. Remove provably dead code
  4. Whole-program minification (i.e. closure compiler)
  5. (Others?)

Activity

  1. chaser92 commented on Aug 7, 2014

    @chaser92

    I think that this isn't the best idea - TypeScript should better do what it's best at, one tool should serve one purpose. There are a lot of great minifiers out there.

  2. NoelAbrahams commented on Aug 7, 2014

    @NoelAbrahams

    Mariusz Kierski (@chaser92), this request is to minify TypeScript code, not JavaScript code. A minifier that uses the information (primarily information on access modifiers) available to the TypeScript compiler will be much more efficient than any JavaScript minifier out there.

    I am personally very eager to see this implemented.

  3. RyanCavanaugh commented on Aug 7, 2014

    @RyanCavanaugh
    MemberAuthor

    Motivating examples of things that TypeScript could minify but an external minifier could not would be useful. Things like closure and uglify do a really good job already; we'd have to have evidence we could make real improvements over them to justify spending time on it.

  4. NoelAbrahams commented on Aug 11, 2014

    @NoelAbrahams

    The following TypeScript code

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

    Can be compiled into JavaScript that when run through an external minifier results in the following:

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

    Problems:

    • The private field greeting has not been minified.
    • The private method 'getMessage` has not been minified.
    • The type name "Greeter" has been mangled into "a". This breaks code such as (/function (.{1,})\(/).exec((instance).constructor.toString()) for obtaining the type name at runtime.
    • The constructor parameter "message" has been mangled into "b". This breaks code that attempt to perform dependency injection by parsing constructor arguments.

    In summary, even in just this simple snippet of code TypeScript can improve on an external minifier, both by mangling names that should have been minified as well as optionally leaving names untouched.

  5. RyanCavanaugh commented on Aug 11, 2014

    @RyanCavanaugh
    MemberAuthor

    If I use the Closure Compiler, it goes from code like this:

    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;
    })();
    
    
    var x = new Greeter();
    x.greet();
    console.log(x.getMessage());

    to this:

    var b = new (function() {
      function a(a) {
        this.c = a;
      }
      a.prototype.b = function() {
        console.log(this.a());
      };
      a.prototype.a = function() {
        return "Hello, " + this.c;
      };
      return a;
    }());
    b.b();
    console.log(b.a());

    Here, the greeting field and getMessage names have been correctly minified. The "over-minification" of other variables leads to my next discussion point on this.

    Some people think class names, property names, parameter names, local names, etc are meaningful runtime metadata, other people think they're not. Some other people think only some of their property names, parameter names, local names, and so on are things that should be minified. Among those people, there's disagreement over how those names should be specified (globally? locally? in the code? in a config file? on a commandline?). Nearly everyone believes that their set of rules is the only one that makes sense.

    The only path forward that could beat existing minifiers also involves a ton of configuration for what the allowed set of minifications even is; implementing and testing all these rules would be very expensive. It's a lot of investment for what is probably a few percent improvement (especially after gzipping) over the state of the art here. That's why we want to see compelling examples for where only the TypeScript compiler could minify and do a meaningfully better job than any existing tool.

  6. NoelAbrahams commented on Aug 12, 2014

    @NoelAbrahams

    Ryan Cavanaugh (@RyanCavanaugh),

    Regarding the under-minification problem, it looks like the code in your post was minified using the "advanced" option of the closure "compiler". And yes, that appears to solve the problem in this simple case. However, consider this slightly more involved example:

    class Greeter {
    
        public greet() {
            console.log('greet');
        }
    }
    
    function createInstance<TType>(name:string): TType {
    
        return new window[name]();
    }
    
    var x = createInstance<Greeter>('Greeter');
    x.greet();

    Basically, we have introduced a class factory function. The closure compiler, with the advanced option, minifies this to

    (function() {
      function a() {
      }
      a.prototype.a = function() {
        console.log("greet");
      };
      return a;
    })();
    (new window.Greeter).a();

    Clearly this is going to fail at runtime. The solution is to export the "Greeter" symbol:

    window["Greeter"] = Greeter;

    Seems simple enough. But in large projects with hundreds of classes, this is not trivial. TypeScript understands this code better, because it knows that function Greeter is a class name.

    More importantly, the problem I have with the closure compiler is that it requires the entire code-base to be minified in one go. This is not feasible in large projects, where there is separation between library code and client code. In this situation it is necessary to declare externs, which ultimately results in maintaining one's public API in duplicate.

    With regard to the second issue that was raised, namely the problem of configuration, that, I guess, is an implementation detail: if there is demand for configuration then that would have to be dealt with. But it seems strange to decline an entire issue on that basis.

    A possible halfway solution would be for TypeScript to provide an option to generate closure-style extern files or to export symbols (class names).

  7. danquirk commented on Aug 12, 2014

    @danquirk
    Member

    The configuration is not just an implementation detail. The fact that we can go back and forth all day with different code examples that need differing amounts of minification is proof of the need for either a) very simple minification algorithms b) extremely customizable minification options. You've already identified multiple issues that absolutely demand customization to work at all.

    In addition, we're still talking about this nebulous concept of 'the TypeScript compiler understands the code better so it should just do this example correctly' (for some definition of correctly). There's still very little here that is actually stating proposed solutions to classes of problems which you could put a configuration over the top of.

  8. NoelAbrahams commented on Aug 17, 2014

    @NoelAbrahams

    To summarise the discussion so far:

    • The JavaScript generated by TypeScript is not optimal in terms of providing that output as input to a simple minifier. This is largely due to private methods going on the prototype. A developer wanting to write code that is optimal for a minifier would have written those private methods as simple functions within the class closure.
    • This problem can be solved by running the output through an advanced minifier (for example the closure compiler with the "advanced" option).
    • Unfortunately the closure compiler requires all symbols to be within sight of the minification run. If they are not then those symbols must either be exported (added using bracket notation) or an externs file must be created, declaring the symbols that should be preserved.
    • User preferences for minification can be diverse.
    • The TypeScript compiler has a superior information set with which to perform minification than a JavaScript minifier; this information set includes having access to the following modifiers "class", "private", "public", "constructor" and the API of external libraries through their declarations files.
    • The biggest advantage that a TypeScript compiler has over ordinary minifiers is being able to safely perform minification on any subset of the code-base. By "safely" I mean that, firstly, exported classes and their public API can be preserved, and, secondly, calls made from within a class into external libraries can also be preserved with recourse to the information in their respective declarations files.

    In the light of the above, I have three basic proposals listed in order of preference:

    A. A fully functional minifier provided by TypeScript.
    We provide two compiler options

    • --minify-simple - Preserves class names, constructor signature...
    • --minify-advanced - Performs a more aggressive minification.

    In both cases, private fields and methods would always be minified.

    B. TypeScript generates externs files for providing to the Closure Compiler if specified.

    C. TypeScript only minifies private fields and methods if specified.

    It is conceivable that with option A there will be many people who would like a halfway solution between "simple" and "advanced" minification, e.g. preserve class names but minify constructor signature. In this instance, it may be possible for those concerned to run the output generated by TypeScript through a third-party minifier that provides more specific options.

  9. bryanerayner commented on Aug 23, 2014

    @bryanerayner

    TL;DR
    Perhaps Data-Annotations could be added to Typescript's syntax to provide developers with an easy way to tell the compiler what should, and should not be overly minified.

    I have been writing a large Angular app using Typescript. I've been painfully watching the size of the JS grow and grow, and would personally LOVE to have some advanced compilation support that only Typescript can provide. IMO Typescript can do an incredibly good job at minifying code, better than closure because you're not limited to comments for specifying compiler directives.

    For an Angular app, there needs to be a high level of granularity of what functions and properties are minified (a la Closure Compiler's advanced mode) and what properties should be left as-is. They are referenced in the HTML and oftentimes used by Angular to correctly link the DOM to JS. You'd need a solution on an item by item basis in order to accomplish this in a way that would make it easy to use.

    For example, much in an Anuglar directive can be truly 'Javascript Only', and I would like advanced minification on it. However, there are some variables that need to be exposed to HTML, outside of the javascript engine.

    If we were able to use something similar to a C# data-annotation on class properties, that would be a much better method than Closure compiler's recommendations. I've seen a lot of Javascript written for the Closure compiler that uses array notation to reference unminified properties - This is a pain to write. Any time you reference a property by a string in Javascript, it just feels muddy to me.

    I've been using Newtonsoft.Json on a dot Net backend. We directly serialize our business logic models to the client side - Of course, we want to keep some things hidden from JSON serialization. For those without a C# background, a data annotation looks like this:

    Imports Newtonsoft.Json
    
    class Klass
    {
        [JsonIgnore]
        public string[] SomethingVeryLarge;
    
        public string SomethingMoreManageable;
    }
    

    The [JsonIgnore] data annotation instructs Json.Net to overlook this property when parsing an instance of Klass.

    Having something like data-annotations could provide the compiler with a really good system of flags, that could be used for this advanced minification support, or other compiler features. I could see this syntax eventually being usable Typescript programs to further extend the language.

  10. NoelAbrahams commented on Aug 24, 2014

    @NoelAbrahams

    Bryan Rayner (@bryanerayner),

    there are some variables that need to be exposed to HTML, outside of the javascript engine.

    Yes, that's a relevant point as well. This is also the case when using KnockoutJS, for example:

    class ViewModel {
    
      [minifyIgnore]
      public foo = ko.observable('bar');
    }

    because foo is referenced in the HTML:

    <div data-bind="text:foo" />
  11. eggers commented on Sep 11, 2014

    @eggers

    I would love a typescript aware minifier. Also, because I've had some issues with ng-min not working very well on tsc generated javascript. (I haven't tried ng-annotate yet)

  12. WanderWang commented on Sep 16, 2014

    @WanderWang

    I Agree with Ryan Cavanaugh (@RyanCavanaugh)
    I'm working for a Open Source HTML5 Game Framework called EGRET
    Many of our developers thinks that our game.min.js should be smaller and smaller .
    Now we compress our game.min.js with Google Closure Compiler SIMPLE_OPTIMIZATION , there are some reason we abandon ADVANCED_OPTIMIZATION

    • It's very hard to use ADVANCED in a complex project with Zero bug . We had to test the whole the test case carefully again . When we turned to SIMPLE mode , it has too many private long-named field not be optimized , C is a useful solution with SIMPLE mode
    • We should support our customer a lib.min.js and there are many public api inside . But ADVANCED_OPTIMIZATION delete almost all the apis because it thinks that the apis not be used . To solve this problem , we should provide a extern-file to Closure Compiler , but we have to many API . B solution with ADVANCED mode is a good idea , because of tsc can generate the extern files very easy ( such as .d.ts )

    So, I hope both B and C could be added to tsc .
    By the way ,forgot A ,please ^_^

  13. 176 remaining items

  14. added
    Out of ScopeThis idea sits outside of the TypeScript language design constraints
    and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    VS Code TrackedThere is a VS Code equivalent to this issue
    on Feb 14, 2023
  15. RyanCavanaugh commented on Feb 14, 2023

    @RyanCavanaugh
    MemberAuthor

    After pondering this one for cough a while, we've decided that minification is pretty clearly out of scope for the TypeScript project and will remain so for the foreseeable future.

    Broadly, this boils down to two key discussion points:

    1. Could TypeScript minify better than other minifiers in the space? If so, at what cost?
    2. Is putting a good-enough minifier "in the box" worth it?

    Regarding the first point, after looking at what modern minifiers can do, what the type system requires of us (which is to say, no type-directed emit), and what other systems which do semantic-informed minification (Closure) do, the cost-benefit ratio is simply not there. Type-directed minification is not desirable or feasible. With types off the table, there's really nothing that TypeScript can do (prior to stripping the types, or immediately after) that other tools can't do an equally good job of. Given our finite time and complexity budget, the best move in terms of increasing the overall goodness of the JavaScript ecosystem is to invest our resources in places where we can provide a value-add in this space.

    Regarding "good enough" minification, there's not a lot of meat left on that bone. We understand that people want fewer tools in the toolbox, but duplicating the work and competing on getting the same results with other minifiers seems like a net-negative for the ecosystem. If minification is worthwhile enough, then the extra build step is going to justify the cost over time. In fact, newer tools are so fast that the cost for good minification even lower than it was a decade ago when we first discussed this issue.

    When TypeScript came out, it assumed it was the only build tool in the chain, and that other build tools like minifiers had to come strictly before or after it. Nowadays, most modern JavaScript tools operate directly on TypeScript - including some bundlers and minifiers. The kind of JS people are generally writing today, using ES Modules and (maybe) #private members, is also much easier to statically analyze for the sake of DCE/tree-shaking and local identifier renaming, both of which can be done after types are removed from the original code.

    We know that there's been a lot of feedback on this issue, and as such believe that there is not much new ground left to cover with further comments on the topic. To prevent a flood of notifications on everyone's inbox, we're temporarily locking this issue for 2 weeks for some pre-emptive cooldown, but further discussion can pick up at that time if needed (2/28/2023).

  16. locked as resolved and limited conversation to collaborators on Feb 14, 2023
  17. unlocked this conversation on Feb 28, 2023
  18. hawkeye-sama commented on Mar 14, 2024

    @hawkeye-sama

    Is there any update on this?

  19. RyanCavanaugh commented on Mar 15, 2024

    @RyanCavanaugh
    MemberAuthor

    Update: This is still out of scope

  20. geekley commented on Apr 17, 2024

    @geekley

    I don't know if there's already a proposal for this (couldn't find in a simple search), but IMO better than minification would be the opposite.

    By that I mean an option for TypeScript to make every possible effort to keep JS line numbers equivalent to their TS source line numbers. Sometimes source maps are difficult to set up or just won't be properly supported. It would be great if I could look at a stack trace with a JS line number and go directly to its source on the same line.

    So it would be great if TypeScript could:

    • ensure transpiled code retains line numbers as much as possible (e.g. expand by adding extra lines, collapse by minified one-liners) specially the executable lines
    • e.g. "minify" in one-line code added at the top (e.g. for compatibility) to avoid changing subsequent line numbers
    • keep my indentation style in the output (e.g. tabs or 2 spaces)

    This is something you can't really do with an external tool (or at least it would be more complicated) I believe.

    Is it worth opening a feature request?

  21. RyanCavanaugh commented on Apr 17, 2024

    @RyanCavanaugh
    MemberAuthor

    This is something you can't really do with an external tool

    This would be straightforward-ish to do by using the emitted sourcemaps

  22. wstaelens commented on Oct 15, 2024

    @wstaelens

    originally asked in 2014, we are 2024 now.

    as a TypeScript n00b am I correct the tsconfig.json has no option to produce minified javascript output files directly?
    We have the option to "removeComments"

    Mariusz Kierski (@chaser92) suggest " TypeScript should better do what it's best at, one tool should serve one purpose."
    I don't agree, it already has options to "minify" the output --> "removeComments", so why not further optimize it with additional options or a generic "minify": true/false ?

    Not a fan of having yet another tool to maintain, update, (resolve conflicts- with while this could be in TypeScript itself.

    looking from a distance at this this looks like something so obvious and "normal" to have...

    Imagine the reduction of traffic and resources this could give on webservers if this would be on by default, globally. 💚 😃

  23. locked as resolved and limited conversation to collaborators on Oct 15, 2024
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

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions