Skip to content
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

Module design for optimal minification #86

Open
frankebersoll opened this issue Apr 13, 2016 · 12 comments
Open

Module design for optimal minification #86

frankebersoll opened this issue Apr 13, 2016 · 12 comments

Comments

@frankebersoll
Copy link

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!

@ToddThomson
Copy link
Owner

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

@ToddThomson ToddThomson added this to the TsProject Release 1.2.2 milestone Apr 13, 2016
@ToddThomson ToddThomson self-assigned this Apr 13, 2016
@ToddThomson
Copy link
Owner

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

@niemyjski
Copy link
Contributor

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

@ToddThomson
Copy link
Owner

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

@ToddThomson
Copy link
Owner

Reference to issue #66

@ToddThomson
Copy link
Owner

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

@ToddThomson
Copy link
Owner

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

@ToddThomson ToddThomson modified the milestones: TsProject Release 2.1.0, TsProject Release 2.0.0, TsProject 2.0.3 Nov 10, 2016
@ToddThomson
Copy link
Owner

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

@niemyjski
Copy link
Contributor

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.

@ToddThomson
Copy link
Owner

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

@niemyjski
Copy link
Contributor

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

@ToddThomson
Copy link
Owner

ToddThomson commented Nov 30, 2016

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

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

No branches or pull requests

3 participants