ToddThomson / tsproject

Typescript minifier and modular typescript bundle optimizer for gulp (Ts Vinyl Adapter).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Module design for optimal minification

frankebersoll opened this issue · comments

One question about minification in general: How can we design our modules so that only a well-defined public api surfaces unmangled? Our project has an ExceptionlessClient that can be configured with plugins and depends on several services. It might be desirable to have a minimal-size version of the library for users that have no interest in extending any of its inner workings, so most of the names could be mangled. But then, I couldn't figure out how to achieve that.

Currently, we have many typescript files that each export one or many classes, like this:

export class ContextData {
  public setException(exception: Error): void { ... }
}

export class EventBuilder {
  public pluginContextData: ContextData;
  public setType(type: string): EventBuilder { ... }
}

export class ExceptionlessClient {
  public config: Configuration;
  public createException(exception: Error): EventBuilder { ... }
}

A user would reference the client class like this var client = require("exceptionless").ExceptionlessClient and then use it. Everything public in ExceptionlessClient and EventBuilder belongs to the public API and obviously shouldn't be mangled. Now, the default Configuration instance adds many plugins that themselves use other components. All those things could easily be mangled, which would save us lots of code. They don't even need to be exported.

Which type names or member names should be mangled and which shouldn't could be manually specified in the tsproject.json. Or there could be some sophisticated analysis that starts with a defined class or namespace and walks all visible types recursively to find all public names. Maybe there already is some mechanism that I don't know about, yet.

I would also like to assist in building such a feature, maybe you can point me to the right direction.

Thanks for your help!

@frankebersoll Please see issue #71. I use this feature to build TsProject. Essentially, it provides a namespace to allow for exported (public) classes. Classes outside this "package" are treated as internal so that the minifier shortens pretty much all identifiers in these "internal" classes. This is a huge win for minifying TypeScript code.
The best way to see this is by turning off whitespace removal and then taking a look at the tsproject.min.js file ( you will need to modify the source directly as there is no configuration option - perhaps a friendly user will issue a PR for this one day! ).
There is another user who wants to see some changes to bundle "packages" so your timing is good.
I am not sure if I've ever documented the "packages" feature. You will need to look at the source - start with looking at the "bundlePackage.ts" source file and then expand from there. The minifier uses packageNamespace to see if identifiers (class properties and methods) can be shortened if the container class is internal.
The bundle packages feature may not be fully what you want, but it is most likely a starting point for you to think about what exactly you want.
I would say that keeping the bundler configuration slim and simple is the way to go.

@frankebersoll Additionally, by convention all classes that are not "exported" ( int the generated bundle source file ) are treated as internal by the minifier. The bundler uses the bundle package configuration settings to decide when to remove the "export" keyword from the project source files.

@ToddThomson, I haven't had a chance to take a look into this yet, but we mark everything as public / private / internal. Those that are not marked public should be mangled. From these discussions it sounds like that may not be the case. Is this true? Thanks for your time.

@niemyjski No that is not the case. All private method and property names are shortened.

The problem is that TypeScript does not have an internal modifier for classes. So the solution is to find a convention that allows for a package to have a pseudo set of internal classes. I chose to do this with a package "namespace". Anything inside the package namespace and TsProject will treat as external ( Essentially the API ). Note that anything non public will still be mangled.
For classes/objects that are treated as internal ( by convention ) everything public gets shortened too.

Reference to issue #66

@frankebersoll @niemyjski Let me know when you've got an understanding of how TsProject currently separates the public and internal API for a bundle. I'm pretty sure that TypeScript 1.8.9 (or greater) with namespace merging together with TsProject "packaging" should work for you. However, I can always update the feature if required.

@frankebersoll @niemyjski I am currently looking at using a comment transform directive to override when an identifier may be mangled. For example /* @minify( "argument" ) */ . This way I can utilize the TsProject package=component feature to expose a public API based on a namespace identifier and have the rest of the package be internal and thus mangle all identifiers except for those marked with the transform ( minifier ) directive. This will achieve what no build pipeline using uglify can achieve.
I need this to make Angular 2 component templates to work within my bundling scheme. It will also help with identifiers that utilize JSON.stringify(), etc.

@frankebersoll @niemyjski Please let me know if you are still interested in this issue. I am going to be making changes to the "packaging" feature of TsProject over the next couple of weeks and would welcome your input on what you require in this area.

Yes, this would be something I'd be interested in, ideally it would be controlled via the access modifiers and you wouldn't have to do anything.

@niemyjski The minifier utilizes the Typescript AST and examines identifier flags for access modifier flags. This yields a bit better results then uglifyjs. However, for optimal minification there are conventions that can be used to make more of the bundle/package "internal" and thus even public identifiers can be mangled. This results in much better minification than uglify.js. However, there are situations where this extra optimization runs into trouble. Angular bindings for example. This is where adding annotations to identifiers can work really well. Essentially we apply additional type information by using an annotation within a comment above the identifier. For example: /** @nomangle */will override the aggressive minification for "internal by convention" classes.
Anyway, I'd be happy to work with you and @frankebersoll to optimize expressionless.

BTW: Please give me your thoughts on issue #99 . This will effect you.

Yeah, I think an annotation within a comment would be good or maybe even an annotation (no comment).

I'm not sure @frankebersoll is available anymore :( I haven't heard from him in months. I'll take a look at #99

@frankebersoll I am interested in talking to you about your initial comment. I am now actively looking at at minification analysis. Although it will be applied to TsMinifier

TsProject used a basic convention to make non exposed API private and non exportable. This will change substantially in TsMinifier. I will detail the new feature in that repo.