The current version of JavaScript. We support "old" browsers, so we can't use it on the client side*, but we can totally use it server-side or in other environments where we have stricter control over the JS runtime.
ES6 has various new features:
Put it at the top of files to switch on "strict mode" which fixes various behaviours that have led developers over the ages to call JS "broken". For example, some comparisons, especially with NaN
, undefined
, and null
; also disables the performance-hurting with
keyword.
In Node v4 (previously io.js), 'use strict'
enables some extra behaviour like:
These are variable declarations with different semantics than var
. let
is block-scoped, consts
is block-scoped and cannot be redefined (if it's an array or an object, its inside can be modified, though). Generally you should use let
everywhere you used var
previously, except if you really want var
semantics.
In other languages, these are known as interpolated strings. PHP has "$foo"
, Ruby "#{foo}"
. ES6 has:
// Previously:
var url = base_url + "/auth";
// Now:
let url = `${base_url}/auth`;
Arrow functions are a shorter syntax for lambdas aka "anonymous functions":
// Previously:
$.get(base_url + "/" + token + "/projects", function(response) {
$('#result').text(response).addClass('success');
})
// Now:
$.get(`${base_url}/${token}/projects`, (response) => {
$('#result').text(response).addClass('success');
})
They also have an even shorter syntax for functions that return their entire body:
// Previously:
[1, 2, 3].map(function (n) { return n * 2; })
// Now:
[1, 2, 3].map((n) => n * 2)
Most importantly, they have lexical this:
// Previously:
function SomeClass() {
var self = this;
self.BASE_URL = 'http://example.com';
someInitialisation();
self.method = function(urls) {
return urls.map(function(url) {
return self.BASE_URL + url;
});
};
}
// Slightly better:
function SomeClass() {
this.BASE_URL = 'http://example.com';
someInitialisation();
this.method = function(urls) {
return urls.map(function(url) {
return this.BASE_URL + url;
}.bind(this));
};
}
// Now:
function SomeClass() {
this.BASE_URL = 'http://example.com';
someInitialisation();
this.method = function(urls) {
return urls.map((url) => this.BASE_URL + url);
};
}
JS now has native classes! Rewriting the previous example:
class SomeClass {
constructor {
this.BASE_URL ='http://example.com';
someInitialisation();
}
method(urls) {
return urls.map((url) => this.BASE_URL + url);
}
}
Traditional build systems are generally sequential, defined mostly following shell scripts:
# Rough translation of gulpfile.js in
# "shell-style" traditional build format.
# Of course the gulpfile does a LOT more!
cd src
cd images
pngoptim -input *.png \
-output ../out/
# Optimise all images
cd ..
cd tiles
mkdir -p ../intermediate
spritesheet -input *.png \
-output ../intermediate/tiles.png \
-less ../intermediate/tiles.less
# Create spritesheets
cd ../intermediate
pngoptim -input tiles.png \
-output ../out/tiles.png
# Optimise spritesheet
cd ..
cd less
cp ../intermediate/tiles.less .
lessc scadafarm.less \
> ../intermediate/scadafarm.css
# Compile LESS
rm tiles.less
cd ..
cd intermediate
csswring scadafarm.css \
> ../out/scadafarm.css
# Minify css
cd ..
mkdir -p ../websites/assets/{images,css}
cp out/*.png ../website/assets/images/
cp out/*.css ../website/assets/css/
Gulp uses streams of files that it applies transforms on:
gulp.src('images/*.png') // read a set of file
.pipe(pngoptim()) // apply optimisations
.pipe(gulp.dest('intermediate/')) // write to a directory
You wrap each set of transformations in a task:
gulp.task('images', () =>
gulp.src('images/*.png')
.pipe(pngoptim())
.pipe(gulp.dest('intermediate/'))
)
And specify dependencies:
gulp.task('images', () => {})
gulp.task('tiles', () => {})
gulp.task('style', ['tiles'] () => {})
gulp.task('copy-assets', ['images', 'style'], () => {})
Gulp then resolves the dependency graph and takes care of running all tasks with maximum concurrency. In this case, images
is probably going to take a long time, but in that time tiles
and style
will run.