-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: minification #8
Comments
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. |
@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. |
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. |
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:
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. |
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 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. |
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 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). |
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. |
To summarise the discussion so far:
In the light of the above, I have three basic proposals listed in order of preference: A. A fully functional minifier provided by TypeScript.
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. |
I like your B suggestion (TypeScript generates externs files for providing to the Closure Compiler). Not sure how it works but I like the sound of it when considering the points that everyone else is making. |
TL;DR 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:
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. |
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 <div data-bind="text:foo" /> |
I would love a typescript aware minifier. Also, because I've had some issues with |
I Agree with @RyanCavanaugh
So, I hope both B and C could be added to |
I'm noticing that the problem with using Closure is that there is information about visibility that TypeScript is aware of that Closure can't know. This is why the private members stick around when using TypeScript can make it easier to use Currently the Google Closure compiler doesn't use these hints for anything useful, but providing that information for it doesn't hurt (since it removes comments) and it gives minifiers more information to optimize with. |
If option A is fully efficient, I don't see why you would need B or C! I would love to have option A's |
I think the issue is that the compiler shouldn't necessarily be responsible for minification. I would much rather see all the information that TypeScript knows be emitted as jsdoc comments. That way JavaScript tools can utilize that information without needing to have a TypeScript parser. |
Don't compilers generally have a switch to optimize for file size? Why not this one? |
Compilers don't usually have a goal of near 1:1 mapping with source. The point is that generating the resulting code is completely different with optimizing for file size (and other optimizations) than what it currently produces. I'd suggest a separate tool is created for an optimizing compiler (one that optimizes for either file size or performance), that makes use of the API |
The goal of the TypeScript compiler could be near 1:1 mapping of source unless the /min switch is set, in which case the goal would be to minimize it to the hilt without changing its exports. |
Yes that's true, but then the back-ends are basically completely separate. I'd recommend implementing it as a separate module/project that uses the API, then if it does prove very useful it can be evaluated to be merged as part of the |
Meta: Is this the canonical issue tracking the proposal to create a path to an optimizing backend (be it built-in or via Closure annotations)? It appears to be at a glance, but if I'm missing a more appropriate spot, I'd appreciate a pointer to it. I'd just like to throw in my 2¢ in support of an optimizing backend. My company's using Typescript very heavily, and while using Closure's basic optimization mode on the output helps to a certain extent, it's quite obvious that we can do a lot better. Code size may not matter to everyone, but it should -- it's not just about over-the-wire size, which is (mostly) mitigated by gzip, but about memory use, parse time, and runtime performance (yes, you can still achieve significant improvement through static optimization). There's a reason that Google invested heavily in optimization for both the Closure and GWT stacks (full disclosure -- I worked a lot on GWT and to a limited extent on Closure when I was there). Just to be clear, this is what I mean when I suggest that we can do a lot better than just dropping whitespace and obfuscating local identifiers:
The above is a random chunk of GWT output from a Google app. The details don't matter -- the point being that aggressive optimization can dramatically reduce output size, make a non-trivial difference in parse time and runtime performance, and even amplify gzip compression by allocating identifiers in such a way as to reduce input entropy. As has been pointed out earlier on this thread, there are two obvious routes we can take:
I don't have a strong preference -- as long as it's possible to get good output, I don't care much how we get there. The output example above came from the GWT compiler, but Closure achieves similar results. Michael Bolin (who did a lot of the work on Closure at Google) created a proof-of-concept (http://bolinfest.com/typescript/) a couple of years ago, but didn't take it much further than that. It's not an entirely trivial exercise, because of the impedance mismatch between Typescript and Closure's inheritance mechanism in particular, but it doesn't seem like brain surgery either. The hardest design problem, as I see it, is dealing with exposed vs. optimizable symbols. Closure has annotations for dealing with "exporting" symbols, and Typescript would need something similar to make the optimizer useful. There are also important edge cases like dealing with externally defined objects (both importing third-party libraries, and dealing with the output of APIs like JSON.parse). The compiler must know about these things if it is to avoid breaking the output with an aggressive optimizer. I think it would be fairly easy to rally a few people to work on an optimizing backend for Typescript, but only if the team is bought into the idea. Trying to bolt such a thing onto the existing compiler, without the ability to tweak the compiler and language a bit, is probably a fool's errand. So I'd greatly appreciate any indication from the team as to their disposition on the subject. |
I really like the "random chunk of GWT output from a Google app". In addition to the benefits mentioned above, another objective is protection of intellectual property through obfuscation. If anybody has tried stepping through the code in Google Maps then they will know the protection that aggressive minification can provide in this regard. The annotation (for excluding minificaton) is not only relevant for externally defined objects, but also relevant when properties of JavaScript objects are bound to elements in the HTML. |
I decided not to say anything about obfuscation for the purpose of, well... "obfuscation", at least partially because I know that tends to raise a lot of hackles in the web world :) A bit more seriously, I'm less concerned about it simply because I've spent so much time reverse-engineering highly-obfuscated Javascript (e.g., http://www.j15r.com/blog/2005/02/09/Mapping_Google) that obfuscation only feels like a speed bump. But hey, a big speed bump can still be useful. Regarding annotation, right -- I forgot to mention that runtime-provided types are equivalent to external code. The (small, but important) distinction being that you tend to have relatively stable and available IDL for the former, whereas the latter's a bit of the wild west. If you have type information (e.g., via DefinitelyTyped), you're good to go, but if not you have a problem. Personally, I'd be fine with requiring explicit type information for all external libraries as a precondition for aggressive optimization, but I'm not sure that's a majority opinion. FWIW, Closure basically takes that approach. If you don't have type annotations for external code, Turing only knows what it will do under advanced optimization (actually, I don't even think Turing knows, because it's probably undecidable, and the compiler certainly can't tell you). With GWT it was a bit easier, because you needed explicit (lightweight, optimized away) Java interface wrappers to call into Javascript in the first place. So the compiler only has an aggressive mode, because it can prove that the output won't be broken (as long as your Javascript methods don't "cheat"). Requiring explicit types also works for dealing with parsed JSON (our rather large body of Typescript uses interfaces for all parsed values; otherwise we'd be breaking things left and right). I believe Closure allows for this, but the traditional method was to require string accessor syntax for any properties that weren't defined by the compiler (e.g., |
I agree. Just removing whitespaces would decrease my .js sizes by 10%~20%. This could really be an option at the tsconfig that isn't hard at all to implement (they already exist!). Later more complex optimizations could be added, using other options at the tsconfig. I don't think devs should necessarily use another tool for removing whitespaces. This is simple enough and a very very very common practice. Edit: here is one of my .js compiled files: 😬 |
I feel this discussion departs from the real reason this is important. Per Ryan's very early comment:
I think that really doesn't do justice for why this ticket is so important. It's important because it would save developer's time. I saw this thread years ago. Why? Because one of my first use cases was "Damn, this product is sweet. Can't wait to minify with it. Let's google how to do that." And I'm here now. Why? Because my first thought was "Sweet, I love this. Can't wait to minify it. Let's google how." And this is ticket #8. Why? Because every other developer that uses this project thinks the same thing. Yes, we COULD go download some additional third party software. But a lot of devs, like me, don't want to add yet another third party tool, don't want to learn another tool. We don't need it to be 10% faster than the alternative. We just want it to be there because it's simple, it saves us time, and it makes our lives a lot easier. I'm finding myself baffled that this ticket still exists when it's such an otherwise amazing project with such a mind-boggling choice to not include this when MS is typically so good at designing for simplicity and user experience. |
Your comment is perfect. I don't like bloating my programs with libraries and extra steps. I like simplicity and development speed. Life is too short to learn and use tons of stuff to get something working that could be way simpler. The main point here is that it could really be done easily, and by that i mean just removing the whitespaces, that would already be a very good functionality. As I said before, it isn't needed to implement all the possible minifiers at once. Having in the tsconfig a |
Hi guys! I made minimize-js to minimize my You can take a look at that. Usually, I launch it using the Cheers! 🍺 |
so its been almost 10 years, and typescript devs refuse to add a simple minifier? |
I assume nobody has any problem with that you @uzbeki just create a draft PR and we can start see how you implement this simple minifier. Please also maintain it the whole way in all future versions of TypeScript as this would then we a core part of TypeScript. Alternatively just use external tools like esbuild and go on 🤷 |
It's an unnecessary overhead to use Webpack (although config can be made by copy-paste), but @Randagio13 idea with postbuild is quite simple and can be implemented in seconds by anyone, when needed without changes in TypeScript |
Not to mention, it'd make it much easier to get a basic level of minification AND working source maps when the project and the person/team knowledge are primarily some non-JS/TS server-side language like Rust. (That's my biggest problem. I write stuff that sticks to progressive enhancement principles and don't want to spend all day trying out various different tools to confirm that, no, it won't break in weird ways like Webpack when I try to just write a few progressive enhancement hooks rather than a full SPA and, yes, it will output source maps that map from the original |
With all available tools and implementations out there, and given that this issue... is over 8 years old!! It's nice to have new TypeScript language features, but can we please have some feature complete implementation for the current more trivial issues? |
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:
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) 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). |
Is there any update on this? |
Update: This is still out of scope |
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:
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? |
This would be straightforward-ish to do by using the emitted sourcemaps |
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? @chaser92 suggest " TypeScript should better do what it's best at, one tool should serve one purpose." 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. 💚 😃 |
TypeScript should support emitting minified JavaScript.
There are several different things we could support:
The text was updated successfully, but these errors were encountered: