ObfuscatePerLibrary
From APIDesign
In contrast to other efforts that try to bring Java back to browser, the Bck2Brwsr VM supports per JAR ahead of time compilation, including obfuscation (via Closure compiler). This keeps the dynamic nature of Java, when you compile units independently and then link them into final application on application start. I believe this is way better than classic static obfuscation mode when whole application is converted into a gigantic static JavaScript (like GWT and currently also TeaVM does).
Contents |
Exported Packages
Right now Bck2brwsr requires presence of OSGi headers in manifest. Only packages enumerated in Export-Package get exported and can be used by other JAR files (since Bck2Brwsr 0.12 also NetBeans specific OpenIDE-Module-Public-Packages tag is recognized). All other classes remain internal to the JAR file - they can be used from other classes in the JAR, but are hidden externally. This allows us to use full obfuscation mode for these classes - e.g. completely rename their class, method or field names. This leads to much shorter code and faster download times.
In case one needs to use a non-OSGi library JAR, there is a simple way to create an OSGiWrapper around it.
Faster Compilation
Per library obfuscation may also speed up compilation. Your libraries can be compiled just once and when you build your application you can re-use them and compile only classes of your application. This is possible now when invoking the Bck2Brwsr compiler manually (see ahead-of-time section in Bck2BrwsrJavadoc), and you can also configure your project to prepare Bck2BrwsrLibraries for others.
Fast and Parallel Downloading
When one sets up the Bck2Brwsr VM by calling
var vm = bck2brwsr(['library1.jar', 'library2.jar', 'app.jar']) vm.loadClass('app.MainClass').invoke('main');
the VM loads appropriate 'library1.js' & co. on background and in parallel. Once they are all available specified application class is loaded and its method is invoked.
Per library obfuscation is a way to minimize download time, as popular libraries can be hosted on a single website and shared between different applications. Then browsers can cache them and minimize the time needed to start Bck2Brwsr any application. Bck2Brwsr Maven archetype does not automate usage of such central repository of Bck2BrwsrLibraries (as of May 2015).
Design
Browsers are optimized for consuming JavaScript (compressed by GZip). We tried to use ZIP, but it was not the most effective solution. That is why our current Maven archetype generates alternative .js file for per JAR file. If the JavaScript file is found, let bck2brwsr use it. If not, Bck2Brwsr downloads and uses the JAR file.
There is a special @Exported annotation one can use to annotate packages, classes, methods. This exported elements need to stick to fixed identifier. In addition to that the Maven plugin recognizes OSGi manifest and exports all public classes in packages exported via Export-Package manifest header as well as OpenIDE-Module-Public-Packages header.
As much calls as possible inside a library is done with obfuscated names. Private, package private methods are obfuscated (unless @Exported). Classes in non-exported packages can have everything obfuscated (unless implement exported symbol). Exported classes generate JavaScript of following form:
function org_nb_test_Clazz() { } vm['org/nb/test/Clazz'] = org_nb_test_Clazz;
public or protected non-final methods need to be exported in an overridable style:
function org_nb_test_Clazz() { var p = ...; org_nb_test_Clazz.impl_myFunction_V = function () { ... }; p['myFunction_V'] = org_nb_test_Clazz.impl_myFunction_V; org_nb_test_Clazz.myFunction_V = function() { return this['myFunction_V'](); } }
Need to generate JavaScript which can be loaded into Bck2Brwsr VM. E.g. the actual vm object is already created and need to be passed to the generated script. The script assumes existence of method registerLibrary in the calling context:
registerLibrary(/*['dep','anotherDep','moreDep']*/, function (vm) { // when invoked register all own classes and their methods into the vm object });
As soon as one does var vm = bck2brwsr(...) all the provided libraries will be loaded.
Checklist
- Introduce @Exported annotation and modify obfuscation to also mange package private members (integrated in Bck2Brwsr 0.9)
- Encode resources from a JAR into JavaScript. Probably via wikipedia:base64 (also done in Bck2Brwsr 0.9)
- Modify Maven task to process single JAR in a batch - can precompile libraries since Bck2Brwsr 0.12
Conclusion
The way Bck2BrwsrLibraries work shows one can have modularity in a single classloader (as Bck2Brwsr loads all classes in a system ClassLoader). Yet, it is possible to isolate non-public packages between individual modules and achieve sufficient level of modular encapsulation. In addition to that one can build such kind of new modularity on top of existing concepts (NetBeans Runtime Container and/or OSGi manifest metadata), increase interoperability between those systems and reuse their existing tooling.