OpenLayers provides an easy way to create a custom build of the library to include in your page that contains only the things you need. Throughout this book, we've been including a file called ol.js (or ol-debug.js), a file that contains all the functionality (classes, functions, and so on) that OpenLayers provides. So far, this has been great—we've been developing up to this point, so we want to be sure that we have access to all the classes that we may use.
Well, the ol.js file we've been using is around 391 KB, and the debug version is 3.5 MB! Both are quite large files to load, especially since they contain only JavaScript code. Even though large file sizes aren't as much of an issue as they were years ago, when everyone was on slower connections, a large JavaScript file in a production environment is something we want to avoid if at all possible. This is especially true for mobile environments, where users pay for data plans and data speeds can be highly variable.
We want all our files in a production environment to be as small as they can absolutely be. This will allow users to download the files faster (since there is less to download), which decreases the page's loading time (saves on bandwidth expenses). Faster page loads (even if the speed is only perceived) will greatly enhance the user's experience. Fortunately, we can greatly reduce the size of the OpenLayers library file with a build tool provided by OpenLayers.
To optimize your application code for production, there are two approaches you can take:
The following table shows the advantages and disadvantages of each approach:
|
Approach |
Advantages |
Disadvantages |
|---|---|---|
|
Combined compilation | ||
|
OpenLayers and application code separate |
|
Before going further, you will need to follow the installation process for Python, Java, and Node following the guide in Appendix B, More details on Closure Tools and Code Optimization Techniques. These tools are required to create custom builds of OpenLayers.
The standard release archive for OpenLayers does not contain the build tools we require. Additionally, the build tools provided with OpenLayers work only with code cloned from a Git repository. The instructions for this book are designed for version 3.0.0 of OpenLayers. To make sure that you are using the correct version, you can check out the v3.0.0 branch of the repository using the following command:
git checkout -b v3.0.0
If you have already run the OpenLayers build tools on a version other than v3.0.0, it is recommended to remove the build and node_modules folders and start again.
We'll briefly cover how the closure compiler works to optimize your code, as we will need to understand it a little bit to use it properly. More detailed information can be found in Appendix B, More details on Closure Tools and Code Optimization Techniques.
The closure compiler optimizes code by applying a series of transformations to your code. Covering all of these is the topic for a whole book in itself, but for our purposes it does three basic things:
When the compiler rewrites your code, it will attempt to use language features that reduce the overall size of your code and execute more efficiently. This can include the following:
Generally, these changes are safe and do not change the logic or functionality of your code. They simply take advantage of language features to fit your code into less space.
The compiler will also attempt to remove the so-called dead code. Dead code is any function that the compiler determines will not be called by the application. It is sometimes difficult for the compiler to automatically determine what code your application will use, especially if you are creating a separate build. In these cases, we need to tell the compiler what things we need to keep even though they might be considered dead code. This is generally done in two ways depending on our strategy. If we are creating a combined build, the compiler can generally determine what functions we will need automatically, but it doesn't know what files to include in the first place. To help the compiler, we add code to register these dependencies. If we are creating a separate build, the compiler doesn't have our code to analyze; so, we have to use a different technique. In this case, we will provide an explicit list of functions to keep in the compiled library.
We will cover both techniques shortly.
The final compiler technique we need to discuss is the renaming strategy. Generally, when we are writing code, we use descriptive names for functions and variables. This makes code much more readable to ourselves and others, and it's much easier to remember names that mean something when writing our code. However, these long names take up space—1 byte per character—and when they are used repeatedly, it can really add up. The JavaScript engine in a browser doesn't really care about our descriptive names though. A name like X1 is just as good as GeoJSONParser, and as long as it is used in the right places, we can save 11 bytes for every use of GeoJSONParser by replacing it with X1. Using short names is not practical for application developers, but it is something that can be done really well by a compiler.
Unfortunately, this is one of the biggest gotchas of using the Closure Compiler. It renames objects, functions, and properties very aggressively and makes them effectively unusable by external code. There are two main problems we need to be aware of concerning this renaming.
When creating a standalone build of OpenLayers, we need to make sure that the objects, functions, and parameters we need are not renamed. This is done using exports, which will be covered later in this chapter.
When creating a combined build of OpenLayers with our application code, we no longer have to worry about exports because our use of the OpenLayers objects, functions, and properties will be taken into consideration by the compiler and renamed appropriately. However, if we use other third-party libraries (for instance, jQuery), the closure compiler does not know about them and will rename our use of their objects, functions, and properties. There are two things we can do to tackle the second problem. First, we can provide special JavaScript files, called externs, which define the objects, functions, and properties of external libraries. The compiler can then correctly avoid renaming parts of our code that we need to preserve. The second, is to use string values in key places, because the compiler will never rename a string value. This technique relies on the ability for JavaScript code to reference object properties using array-like syntax. For instance, if we have a property name on an object foo, normally, we would reference it like the following:
foo.name = 'test';
The compiler might rewrite this code like the following:
XB.XC='test';
If foo is an object coming from some external library that relies on name, our code may break in unusual ways. To prevent this, we can use the array-like notation with a string value to avoid renaming:
foo['name'] = 'test';
We'll see an example of this in our next example.
Exports and externs are quite similar. The first is for choosing the library code you want to expose the third-party code, whereas the second, is to stop the default renaming behavior for the third-party code. To fully grasp the difference, you should refer to the official topic on Do Not Use Externs Instead of Exports! at https://developers.google.com/closure/compiler/docs/api-tutorial3#no.