Общо показвания

април 07, 2012

JavaScript optimizations

I have been working the last year and a half on large in-browser applications and the last few months I have been looking into automated method of code optimization. The two most significant options out there currently are Closure compiler and Uglify-js.

I have used both and the following text is about my experience.

I will start with the few per-conditions: The code is modularized, i.e. different components are written as modules in separate files already and not all components should be loaded for the application to run. The application should be able to load additional dependencies/components as reaction to user actions or other events. The code should be minified  if possible and concatenated into small number of files, the lower the number of requests - the better.

Starting with code base of 1.8MB the task at hand was to squeeze it to something that can be loaded via the Internet on a low performance STB device. The application is the main device interface, so it should indicate activity to the UI asap.

The code was initially developer with requirejs so making a build of it with node requirejs module was not very difficult. The code needed some tweaking but in one day work the whole project compiled to 279KB. The resulting javascript file is only one (i.e. monolithic build) and once downloaded was able to start the UI in 2.79 seconds (as opposite to 9 seconds when un-compiled, due to more file loading mostly). This however, combined with the network lag of downloading 280KB  resulted in a black screen for almost 6 seconds and was not fast enough for a home entertainment equipment.  What we needed was 'load indication' module, that loads first and then indicate the load progress for the next few seconds.

Next we decided to try and use closure compiler. The compiler however forces the developer to use limited set of javascript patterns and it took a few days to stip the incompatible code or replace it with one that is. Some modules were not ported, for example the settings module. We used the closure-script to ease the development cycle. The result was impressive. The compiler is able to inline lot of the code ( for example property getters/setters are inlined, which lowers the scope creation load) and the tool we used is able to include the needed files per modules and modules are defined with namespaces. One caveat is that all modules need additional code for initialization, which we needed to write in addition to the logic code we already had. Another caveat (or one can think of it as advantage) is the renaming of method names, which means that you get much smaller size of the final built but you loose the names. The structure of your code might change beyond recognition, but the payoff often worth it. However the additional code that need to be written and the required changes should your code base is written without the compiler in mind can make the process long and tedious.

Finally we decided to use the requirejs whole project build capabilities. Once again it required some getting used to it and a bit testing, but we managed to separate the monolithic build to 4 modules. While testing the closure compiler we really liked the defines ( i.e. variables that can be defined at compile time and allow the compiler to strip code that is branched out based on the value of the defines) and we wanted to used it in the project. It was a nice surprise for us the fact that uglify-js supports defines as well. Both uglify and closure compiler require special syntax for this to work but the overhead is very small compared to the benefits.  We use it now to strip the debug modules and debug statements from the build. As requirejs works in node we were able to produce a small development server that can run the build process based on the query string submitted with the index request in less than 15 minutes. This frees us from the java virtual machine (as opposed to the closure tools). Uglify also makes its magic faster compared to closure compiler. Uglify however does not do function inlining for methods (getters/setters) nor does overwrite method names ( as this is not safe transformation in the context of non-restrictive javascript style ), it also does not make namespace squashing, which means that you need to make some extra effort to make sure your code does not use excessive scope creation/nesting.

The final thought on this project are as follow:
  1. You need to first decide which compiler you would you as both require different code style to work properly. 
  2. You need to go an extra step to make sure the code is structured suitably for modularization in both compilers.
  3. Do not forget that it is simply a tool, there are more than one ways to get to the destination, do not get obsessed!
As a last point I would suggest closure tools for projects that want to work across all browsers and have the latest abstractions (for example they already have abstraction for the transitionend event as it differs in firefox and chrome) and you do not want to collect the abstractions yourself. Closure library can save you lot of time for this.

On the other hand if you are targeting only newer browsers you can go with the faster and easier to use uglify-js/requirejs.

Няма коментари: