Faster, smaller, better

Compiling your application together with OpenLayers 3

 

 

Tobias Sauerwein - Guillaume Beraudo

Camptocamp

 

Camptocamp

How do you like your OpenLayers?

 

 

full build
(ol.js)
custom build compiled
with
application

 

JavaScript development today

Challenges of writing large JS applications

  • namespaces/modules?
  • visibility control?
  • type checking?
  • static checking?
  • testing?

Google Closure Tools

Google Closure Compiler

Google Closure Library

Google Closure Compiler

compiles JavaScript to better JavaScript

 

  • code checks (syntax, variable references)
  • checks for common pitfalls
  • static type checking
  • Transpiler (ES 6 > ES 5)
  • advanced optimizations (inlining, dead-code)

Dead-code removal + Inlining

goog.provide('app');

app.printHello = function() {
    console.log('Hello');
};

app.someUnusedFunction = function() {
    console.log('Unused function');
};

app.run = function() {
    app.printHello();
};

app.run();

Compiled

(function(){console.log("Hello");})();

Renaming

goog.provide('app');

function hello(o) {
    alert('Hello, ' + o.firstName + ' ' + o.lastName);
}

app.run = function() {
    var person = {firstName: 'New', lastName: 'user'};
    hello(person);
    console.log(person);
};

app.run();

Compiled (pretty_print)

(function(){var a = {a:"New", b:"user"};
alert("Hello, " + a.a + " " + a.b);
console.log(a);
})();

Exports

<button onclick="app.doSomething()">...</button>

 

How to prevent that app.doSomething is renamed or removed?

 

/**
 * @export
 */
app.doSomething = function() {
    ...
};

Type annotations

/**
 * @constructor
 * @param {number} x X.
 * @param {number} y Y.
 * @param {number=} opt_z Z.
 */
app.Point = function(x, y, opt_z) { ... };


/**
 * @param {!app.Point} other An other point.
 * @return {number} The distance.
 */
app.Point.prototype.distanceTo = function(other) { ... };

JSDoc Tags: A selection

@const @constructor @enum @export

@extends @final @implements @interface

@nosideeffects @param @private

@protected @return @throws @type ...

 

Reference

Why bother with types?

Why types?

Understanding code

entries.forEach(function(entry) {
  entry.data.validate();
});

What does this code do?

Why types?

Static type check

var point = new app.Point([0, 0]);
ERR! compile src/main.js:14: WARNING - Function app.Point:
  called with 1 argument(s). Function requires at least
  2 argument(s) and no more than 3 argument(s).
ERR! compile     var p5 = new app.Point([0, 0]);
ERR! compile              ^
ERR! compile
ERR! compile src/main.js:14: WARNING - actual parameter 1 of
  app.Point does not match formal parameter
ERR! compile found   : Array
ERR! compile required: number
ERR! compile     var p5 = new app.Point([0, 0])

ERR! compile 0 error(s), 2 warning(s)
ERR! compile 95.5% typed

Why types?

IDE integration, refactorings

Using external libraries

Compile with application

library must be compatible to Closure Compiler

Reference library outside compilation

compilation requires an externs file

<script type="text/javascript" src="libs/jquery.min.js"></script>
<script type="text/javascript" src="build/app.js"></script>

Externs files

var map = L.map('map').setView([0, 0], 13);
ERR! compile src/main.js:4: ERROR - variable L is undeclared

externs file

/** @const */
var L = {};

/**
 * @param {string} div
 * @return {LeafletMap}
 */
L.map = function(div) {};


/** @constructor */
var LeafletMap = function() {};

...

And OpenLayers?

Simple example with ol3

goog.provide('app');

goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.layer.Tile');
goog.require('ol.source.OSM');

var map = new ol.Map({
    target: 'map',
    layers: [
        new ol.layer.Tile({source: new ol.source.OSM()})
    ],
    view: new ol.View({center: [0, 0], zoom: 4})
});

Compiler configuration

{
  "lib": [
    "node_modules/openlayers/src/**/*.js",
    "node_modules/openlayers/build/ol.ext/**/*.js",
    "src/**/*.js"
  ],
  "compile": {
    "closure_entry_point": "app",
    "externs": [
      "node_modules/openlayers/externs/bingmaps.js",
      ...
    ],
    "define": ["ol.ENABLE_DOM=false", "ol.ENABLE_WEBGL=false"],
    "compilation_level": "ADVANCED",
    "output_wrapper": "(function(){%output%})();",
    ...
  }
}

Tooling

compiler.jar

Closure Compiler as Java CLI tool

closure-util

npm package, wrapper around compiler.jar, dev. server

closure-util build config.json dist/app.js

closure-util serve config.json

Why compile with OpenLayers?

  • Only pay for what you use (build size)
  • Easier to extend OpenLayers
  • Benefit from the advantages of the Closure Compiler (static/type checking, efficient code, ...)

Build sizes

Example projects

Learning curve?

Resources: How to get started

Future / Alternatives

  • Support for ES 6 modules in OpenLayers 3 (module bundlers: tree-shaking)
  • Closure Compiler is a transpiler (ES 6 > ES 5)
  • TypeScript

This talk

Slides
bit.ly/ol3-closure

 

Find us on GitHub/Twitter
Tobias: @tsauerwein

Guillaume: @gberaudo

Credits for these great photos!