Introduction
ES2015 is the latest version of the ECMAScript standard released in June 2015. Commonly known as ES6 (ECMAScript revision 6), the name officially changed to reflect the more granular nature of future updates and prompt faster adoption. This table shows the current adoption of features across various environments: https://kangax.github.io/compat-table/es6/.
An excellent introduction of all the new features are available here: http://www.2ality.com/2015/08/getting-started-es6.html. It has great comparisons between how things used to be done in ES5, and how they should be done now in ES6. Most of the additions are just syntactic sugar, like string templates, class
, const
, let
and () => {}
functions. However, addition of native modules
changes everything, and lets entirely new things become possible.
The easiest way to get started writing in ES2015 is to use the Babel REPL (read-eval-print loop) here: https://babeljs.io/repl/. A good exercise is to copy/paste the examples from the above link here to see exactly how it can be re-written in ES5.
The following code shows how a great deal of new syntax doesn’t add functionality that wasn’t available before, although that functionality may not have been obvious. Try running this through the REPL above.
import fetch from 'fetch';
const defaultUser = 'maxmalynowsky';
class UserRepos {
constructor(user) {
this.user = user;
}
getRepos() {
const url = `https://api.github.com/users/${this.user}/repos`;
fetch(url).then(response => this.repos = response.json());
setTimeout(() => console.log(this.repos), 1000);
}
get user() {
return this._user;
}
set user(user) {
this._user = user || defaultUser;
}
static print() {
{let exists = false}
console.log(exists);
}
}
export default UserRepos;
All very interesting, but what does the build environment for ES2015 code look like? The main point of this article is assembling the pieces of that environment bit by bit. The two core technologies are SystemJS and Babel. SystemJS is a module loader, and Babel is a compiler. Each can be used independently of the other, but really comes together nicely when you integrate the two. Let’s take a look at installing these both from scratch before moving into an automated install so we understand what’s going on.
Installation
To understand what the ES2015 development environment looks like, we first need to know where we are. The hello world of cartography is a basic leaflet map, so lets start there. The following links show the gradual progression towards a fully modern environment, explained step-by-step.
- http://bl.ocks.org/maxmalynowsky/edbe38c7dc2d3a5b677b
- http://bl.ocks.org/maxmalynowsky/89a96d985424b31d019e
- http://bl.ocks.org/maxmalynowsky/bc8cf2646d743c6888b6
- http://bl.ocks.org/maxmalynowsky/6c3f30c1f252b1be713c
- http://bl.ocks.org/maxmalynowsky/1032a4af73997665fee9
- http://bl.ocks.org/maxmalynowsky/13b663fac1f906c30aed
Step 1
This is our basic HTML + JS combination that hasn’t really changed much since the mid 2000’s when it became popular to move script tags to the bottom of the body. In the Javascript code, the entire function is wrapped inside of a immediately-invoked function expression (iife) to avoid polluting the global name space. Try serving these files locally, and in your browser’s dev tools, run console.log(map)
. You shouldn’t be able to access the map object. Now remove the first and last lines, and try this again, you should now be able to access it.
Step 2
In this step, we don’t change our JS, but make a subtle modification to how its loaded. Notice in the HTML, the script tag for index.js
is removed, and replaced instead with System.import()
. By itself, this is all SystemJS is capable of. Although SystemJS is a module loader, try adding the following line to the top of index.js
.
import nothing from 'default';
This nonsensical statement should result in a 404 error, since it is equivalent to adding the following script tag and exposing the global variable nothing
.
<script src="default.js"></script>
However, this import feature isn’t possible without an external runtime. By default it looks for traceur since it was the original ES6 to ES5 compiler, but babel is a much more mature and feature-rich library, so we are going to use that.
Step 3
To really take advantage of SystemJS, it needs to be integrated with Babel. For a development environment, there is a browser runtime available that will compile on-the-fly. For production we will pre-process everything, but for now, this avoids having to run builds with file watchers in the background. Since we are now loading files through SystemJS, we have to make it aware of babel by setting it as a compiler option using System.config()
, otherwise it will keep looking for traceur.
Even though this configuration is complete, we can’t quite use the new ES2015 syntax yet. Try modifying the JS to use const
instead of var
, or add the following line.
setTimeout(() => console.log(map));
Chrome and Firefox wont have any issues with it, but older browsers such as IE or Safari will produce errors. This is because babel isn’t turned on yet, its just that newer browsers already support that new syntax natively. To enable it, we need to write our javascript in a different way.
Step 4
In this step, we see a fundamental change in how our code is organized. The single point of entry to the app is index.js
, and everything else is mapped to it through relative path imports from there. The functions for creating a map and layer have been split into separate files, to be assembled in index.js
. In case the export format is a bit confusing, map.js
can be re-written as the following variable export.
const map = L.map('map', {
center: [51.42, -116.22],
zoom: 13,
});
export default map;
There is another change we’ve made that may not be so obvious at first. Babel has been turned on now, so it is safe to use any of the new syntax in Safari or IE. This happens because there is at least one import
statement in the main point of entry file. Notice our iife and use strict
have disappeared, this is because they are assumed in this mode too.
Step 5
This last step is not something that should ever be done, but it provides a good transition to using JSPM, which will automate a lot of what was done above. We want to write our application code in a way that makes no assumptions about global variables, but leaflet is only accessible through this way.
To bring in leaflet into the SystemJS/Babel environment we’ve created, we use the map
option in System.config()
. This tells SystemJS how to map library files to globally accessible names, without attaching to window
. Because of CORS, we need to store leaflet.js
locally. Leaflet also requires to be attached to window
to function properly, so its going to do this however we call it.
There doesn’t seem to be any benefits of doing things this way, but its good practice for using JSPM, which would automatically download and map leaflet and other libraries for us. For libraries written modularly, like those commonly found on npm
, this also has the advantage of pulling in only what actually gets used for production. This makes it possible to pick out only one or two functions from a library like jQuery or lodash, without incuring the size penally adding their script tag would.
Step 6
Rather than exporting variables or functions, its much more useful to export classes. For those familiar with AngularJS services, this functionality can be mimicked by returning a new
instance of a class, so that all modules importing from it have a shared data store.
class LeafletMap {
constructor() {
this.canvas = L.map('map', {
center: [51.42, -116.22],
zoom: 13,
});
}
}
export default new LeafletMap();