Ben Newman (Facebook)
Fluent 2014
{ github, twitter, instagram, facebook }.com/benjamn
Why isn't every Python project using Python 3 yet?
Crazy idea: ease into the new language by simulating its most useful features in the current version of JavaScript (ECMAScript 5).
=>
function syntax
[3, 1, 10, 28].sort((a, b) => a - b)
[3, 1, 10, 28].sort(function(a, b) {
return a - b;
}.bind(this))
var recast = require("recast");
var types = recast.types;
var traverse = types.traverse;
var n = types.namedTypes;
var b = types.builders;
var ast = recast.parse(
"[3, 1, 10, 28].sort((a, b) => a - b)"
);
traverse(ast, function(node) {
});
traverse(ast, function(node) {
if (n.ArrowFunctionExpression.check(node)) {
}
});
traverse(ast, function(node) {
if (n.ArrowFunctionExpression.check(node)) {
var body = node.body;
if (node.expression) {
node.expression = false;
body = b.blockStatement([b.returnStatement(body)]);
}
}
});
traverse(ast, function(node) {
if (n.ArrowFunctionExpression.check(node)) {
var body = node.body;
if (node.expression) {
node.expression = false;
body = b.blockStatement([b.returnStatement(body)]);
}
var funExp = b.functionExpression(
node.id, node.params, body,
node.generator, node.expression
);
}
});
traverse(ast, function(node) {
if (n.ArrowFunctionExpression.check(node)) {
var body = node.body;
if (node.expression) {
node.expression = false;
body = b.blockStatement([b.returnStatement(body)]);
}
var funExp = b.functionExpression(
node.id, node.params, body,
node.generator, node.expression
);
var bindExp = b.callExpression(
b.memberExpression(funExp, b.identifier("bind"), false),
[b.thisExpression()]
);
}
});
traverse(ast, function(node) {
if (n.ArrowFunctionExpression.check(node)) {
var body = node.body;
if (node.expression) {
node.expression = false;
body = b.blockStatement([b.returnStatement(body)]);
}
var funExp = b.functionExpression(
node.id, node.params, body,
node.generator, node.expression
);
var bindExp = b.callExpression(
b.memberExpression(funExp, b.identifier("bind"), false),
[b.thisExpression()]
);
this.replace(bindExp);
}
});
console.log(recast.print(ast).code);
// Which prints:
[3, 1, 10, 28].sort(function(a, b) {
return a - b;
}.bind(this))
If you already have a build step for static resources, you can be cooking with arrow functions in a matter of minutes!
var recast = require("recast");
var ast = recast.parse(source);
transform(ast); // Anything goes.
console.log(recast.print(ast).code);
...rest
parameters
function max(a, ...rest) {
rest.forEach(x => { if (x > a) a = x; });
return a;
}
function max(a) {
var rest = Array.prototype.slice.call(arguments, 1);
rest.forEach(function(x) { if (x > a) a = x; }.bind(this));
return a;
}
...rest
parameters
traverse(ast, function(node) {
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
}
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
var sliceCallee = mx(mx(mx("Array", "prototype"), "slice"), "call");
}
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
var sliceCallee = mx(mx(mx("Array", "prototype"), "slice"), "call");
var sliceArgs = [
b.identifier("arguments"),
b.literal(node.params.length)
];
}
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
var sliceCallee = mx(mx(mx("Array", "prototype"), "slice"), "call");
var sliceArgs = [
b.identifier("arguments"),
b.literal(node.params.length)
];
var sliceCall = b.callExpression(sliceCallee, sliceArgs);
}
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
var sliceCallee = mx(mx(mx("Array", "prototype"), "slice"), "call");
var sliceArgs = [
b.identifier("arguments"),
b.literal(node.params.length)
];
var sliceCall = b.callExpression(sliceCallee, sliceArgs);
var restVarDecl = b.variableDeclaration("var", [
b.variableDeclarator(node.rest, sliceCall)
]);
}
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
var sliceCallee = mx(mx(mx("Array", "prototype"), "slice"), "call");
var sliceArgs = [
b.identifier("arguments"),
b.literal(node.params.length)
];
var sliceCall = b.callExpression(sliceCallee, sliceArgs);
var restVarDecl = b.variableDeclaration("var", [
b.variableDeclarator(node.rest, sliceCall)
]);
node.body.body.unshift(restVarDecl);
}
});
...rest
parameters
traverse(ast, function(node) {
if (n.Function.check(node) && node.rest) {
var sliceCallee = mx(mx(mx("Array", "prototype"), "slice"), "call");
var sliceArgs = [
b.identifier("arguments"),
b.literal(node.params.length)
];
var sliceCall = b.callExpression(sliceCallee, sliceArgs);
var restVarDecl = b.variableDeclaration("var", [
b.variableDeclarator(node.rest, sliceCall)
]);
node.body.body.unshift(restVarDecl);
node.rest = null;
}
});
class
syntax
Input (ES6):
|
Output (ES5):
|
Transform code much more involved than previous examples;
see es6-class-visitors.js
for all the gory details.
var Class = require('Class');
var copyProperties = require('copyProperties');
function Derived(value) {
this.parent(value + 1);
}
copyProperties(Derived.prototype, {
getValue: function() {
return this.parent.getValue() - 1;
}
});
copyProperties(Derived, {
getName: function() {
return "Derived";
}
});
Class.extend(Derived, Base);
jslint
warning that complains when you
try to commit new code that assigns
.prototype
properties
jslint
tells you that your code might
be bad after you've written it
We have lots of this (ES5):
|
We want more of this (ES6):
|
We want more of this (ES6):
|
So that we can ship this (ES5):
|
It's just a source tranformation! What's different?
Meaning: you should be able to change your mind as
often as you like, git reset --hard
, and rerun
the script.
parallel
to run the transform script in many processes simultaneously.
find ~/www/html/js/lib | \
grep "\.js$" | \
time parallel ~/www/scripts/bin/classify --update
228.03s user 12.25s system 1229% cpu 19.548 total
Killing off Class.extend
in favor
of extends
/super
only really
depended on the conversion of files
using Class.extend
.
github.com/{ facebook/jstransform, benjamn/ast-types, benjamn/recast, facebook/regenerator } code.facebook.com/projects