Using ECMAScript 6 Today – Type of Features – Abstract Syntax Trees



Using ECMAScript 6 Today – Type of Features – Abstract Syntax Trees

0 0


es6-midwest-io


On Github impatient / es6-midwest-io

Using ECMAScript 6 Today

Scott Dillender (@smimp)

Interest in this after listening to some Javascript Jabber episodes on it Mention your version grunt traceur

Overview

  • Why you should use it today
  • How it works(AKA a brief intro to transpilers)
  • Feature Summary
  • Features in Code
  • ES6 tools
  • Spec Status
  • Questions?
Goal of the talk: Already loads of information out there. I aim to distill it and provide access to the better content. Mention dilemma of lots of talks out there, not necessarily with many views Also mention that there's one JS talk at the conference.

Should you use it today?

YES

We're at a point where we can replicate a large chunk of the functionality inside of current ECMAScript version (ES 5). The last vestige of pre-ES5 environment is IE8.

ES6 is the next iteration of javascript. Members of the TC39 working group review proposals and decide on what should be included in the next version of the language. The group is comprised of members from various tech companies. Bring up bytecode for the web and how transpilers are the future. DSL's for javascript.

Current Browser Share

ES5 Compatibility Chart (via kangax)

Additionally, we're to the point where browsers are released more frequently.

Evergreen Browsers

We're finally to a point where all of the major browsers will be on a relatively frequent release cycle.

  • Firefox - 7 releases in the past year
  • Chrome - 8 releases in the past year
  • IE - last two major releases were in October of 2012 and 2013.
Evergreen browser phrase was coined by: The big point is that everyone is automatically updated. I'm not trying to actively say that we have Evergreen and ES5. It's more like hedging your bets

We have the technology

With features added in ES5, most of the features in ES6 can be implemented with a polyfill or transpiling.

  • Traceur - Google
  • Regenerator/Recast/JSTransform - Facebook
  • ES6 Transpiler
  • ES6-Shim
  • Harmony Collections
  • Polymer's Shims

Many more depending on what you're actually looking for. https://github.com/addyosmani/es6-tools

Play along

Slides at:

Traceur Repl

                        
var es6 = 'es6';
console.log(`hello ${es6} world`);
                        
                    

Summary of New Features

  • Arrow Functions
  • Classes
  • Generators/Iterators
  • Comprehensions/Spread/Destructure
  • Map/WeakMap/Set/WeakSet
  • Proxies
  • Reflection
  • Object function
  • Import/Export
  • Math functions
  • String functions
Dilemma - Lots of really good articles/videos out there. With 600 views. So, how much have people really seen.

Type of Features

  • Syntactic Sugar

    Expressivity - Fewer words, more meaning

  • Features that are completely new and can't be replicated.

Rest Parameters - Syntactic Sugar - ES6

function rest(initialParameter, ...restOfParameters) {
  console.log(initialParameter,restOfParameters);
};

rest(1,2,3,4);
//1,[2,3,4]

Rest placement must be at end, but can have preceding params

ES5 Version

function unrest(a,b) { console.log(a,b); }

function restEnd(primaryFunction) {
  return function() {
    var argSize = arguments.length;

    return primaryFunction.call(this,
        arguments[0],
        [].slice.call(arguments,1)
    );
  }
}

restEnd(unrest)(1,2,3,4);
//1,[2,3,4]

The function restEnd decorates unrest to get the same behavior as the ES6 version.

Proxy - New Functionality

var a = Proxy({}, {
    get: function(obj, prop) {
        console.log("get arguments", arguments);
        return obj[prop];
    },
    set: function(obj, prop, newVal) {
        console.log("Calling set with ", arguments);
        obj[prop] = newVal;
    }
  }
);
a.eek = 'bam';

Returns: "Calling set with " Arguments
   {0: Object, 1: "eek", 2: "bam", 3: Object, 2 more…}

Presently Firefox is the only browser with support for this. Traceur does not implement.

Generators - Borderline

var numberator = function*(base) {
  while(base < 4) {
    yield ++base;
  }
  return ++base;
};

//Array comprehension
console.log([for ( x1 of numberator(0)) x1]); //no longer lazy
//[1,2,3,4]
var numberator = $traceurRuntime.initGeneratorFunction(function $__22(base) {
return $traceurRuntime.createGeneratorInstance(function($ctx) {
  while (true)
    switch ($ctx.state) {
        case 0:
            $ctx.state = (base < 4) ? 1 : 5;
            break;
        case 1:
            $ctx.state = 2;
            return ++base;
        case 2:
            $ctx.maybeThrow();
            $ctx.state = 0;
            break;
        case 5:
            $ctx.returnValue = ++base;
            $ctx.state = -2;
            break;
        default:
            return $ctx.end();
            }
            }, $__22, this);
            });

What we've seen so far

  • Rest parameters (...)
  • Proxies
  • Generators
  • Array Comprehension

Transpilers in Action

  • A brief look at syntax trees
  • Arrow Function
Start the conversation as the language as the library.

Abstract Syntax Trees

  • Representation of a program that can be acted on by language
  • Statements
  • Declarations
  • Expressions
  • Operators
  • Literals

Esprima repl can be found at http://esprima.org/demo/parse.html

Trivial Example

                            
                                ;
                            
                         

yields

                            
                                {
                                "type": "EmptyStatement"
                                }
                            
                        

Marginally Less Trivial Example

                            
                                var a = function(one) { console.log(1); };
                            
                         

yields

                            
                                {
                                "type": "VariableDeclaration",
                                "declarations": [
                                {
                                "type": "VariableDeclarator",
                                "id": {
                                "type": "Identifier",
                                "name": "a"
                                },
                                "init": {
                                "type": "FunctionExpression",
                                "id": null,
                                "params": [
                                {
                                "type": "Identifier",
                                "name": "one"
                                }
                                ],
                                "defaults": [],
                                "body": {
                                "type": "BlockStatement",
                                "body": [
                                {
                                "type": "ExpressionStatement",
                                "expression": {
                                "type": "CallExpression",
                                "callee": {
                                "type": "MemberExpression",
                                "computed": false,

                            
                        
                            "object": {
                                "type": "Identifier",
                                "name": "console"
                                },
                                "property": {
                                "type": "Identifier",
                                "name": "log"
                                }
                                },
                                "arguments": [
                                {
                                "type": "Identifier",
                                "name": "one"
                                }
                                ]
                                }
                                }
                                ]
                                },
                                "rest": null,
                                "generator": false,
                                "expression": false
                                }
                                }
                                ],
                                "kind": "var"
                                }
                        

Or, more sensibly displayed

We'll eschew some of the information in favor displaying it more compactly.

VariableDeclaration
VariableDeclarator
FunctionExpression + Param Identifier
BlockStatement
ExpressionStatement
CallExpression
MemberExpression+Identifier
Argument

If you go down this path, it appears unwieldy, but with the proper tools/libraries you can handle it, within reason.

Arrow Function

Arrow Function

 var sum = (a,b) => {return a + b; }
 //or
 var sum = (a,b) => a+b;

Is Roughly Equivalent To(ES5)

   var sum = function(a,b) { 
    return a+b;
   }

Is Equivalent To:

   var sum = function(a,b) { 
       return a+b;
   }.bind(this);

AST Arrow

VariableDeclaration
VariableDeclarator
ArrowFunctionExpression Parameters (a,b)
BinaryExpression

Note the implicit return

ES5 Equivalent

VariableDeclaration
VariableDeclarator
CallExpression
MemberExpression
FunctionExpression Parameters (a,b)
BlockStatement
ReturnStatement BinaryExpression
Param - Identifier - Bind
Argument - ThisExpression

Recast - Ben Newman - Facebook

Tidbits:

  • It allows rewriting source code programmatically
  • Uses AST-types for ES6 functionality (May no longer be needed with latest Esprima/Parser API)
  • Preserves comments
  • Only updates parts that are changed ( preserves formatting )

Use:

  • Builder for all of the different statment/expression types. Will likely need to visit recursively.
  • If you're missing a param, will alert you with name.
  • Visitor Pattern is the easiest way to use.

Code to Transform (Recast):

var recast = require('recast');
var Visitor = recast.Visitor;

var Arrow = Visitor.extend( {
   visitArrowFunctionExpression: function(arrow) {
     return b.callExpression(
       b.memberExpression(
         b.functionExpression(null, 
          arrow.params, 
          this.visit(fixBody(arrow.body)
          )
       ),
         b.identifier('bind'),false),
       [b.thisExpression()]);

   }

});

 var ast = recast.parse(code);
 new Arrow().visit(ast);

 var output = recast.print(ast).code;

 console.log(output);

Fix Body

    var fixBody = function(body) {
      if(body.type !== Syntax.BlockStatement) {

        return b.blockStatement([b.returnStatement(body)]);
      }
      return body;

    };

Quick Samples

Default Params

  var test = function(a=[1,0],b=1) { 
    console.log(a,b);
  };

  test()
  //[1,0] 1

Destructuring Object

   var {username, password} = { password: '123456', username: 'elpmaxe' };

   console.log(username);
   //elpmaxe
   console.log(password);
   //123456

Destructuring Arrays

var [first, second, third] = ['zero','one','two','three'];

 console.log(first);
 //zero

 console.log(second);
 //one

 console.log(third);
 //two

Generator

var generator = function*(initial) {
  var individual = initial.split('');
  var i =0;
  while( i< individual.length -1)
  {    
    yield individual[i++];
  }
  return individual[i];

};

var a = generator('testMe');

console.log(a.next()) //Object {value: "t", done: false}
console.log(a.next()) //Object {value: "e", done: false} 
console.log(a.next()) //Object {value: "s", done: false} 
console.log(a.next()) //Object {value: "t", done: false} 
console.log(a.next()) //Object {value: "M", done: false} 
console.log(a.next()) //Object {value: "e", done: true}

Let and Const

   const always= 'always';
   if(1 === 1) {
      let x = 2;
   }
   always = 'not-always'; //traceur lets you do this
   console.log(x);  //reference error undefined

Caveat, under experimental in traceur.

This is where a linter would be better than this behavior.

Comprehensions

var a = [1,2,3]

//Array comprehensions
console.log([ for (x of a) x ]);

//Generator comprehension
var b = ( for (x of a) if(x %2) x );

console.log(b.next()) //Object {value: 1, done: false}
console.log(b.next()) //Object {value: 3, done: false}
console.log(b.next()) //Object {value: undefined, done: true}

Spread Operator

var a = [1,2,3,4];

var sum = function(first,second, third,fourth) {
  console.log(fourth); //4
  return Array.from(arguments).
  reduce( 
    (a, b) => a+b
  );

};      

console.log(sum(...a)); //10

Template Strings

var buildFlightUrl = function({flightType,departure }) {          
  return `http://flights.com/${flightType}/date/${departure}`
}

console.log(buildFlightUrl({flightType:'roundTrip', departure: '2014-07-16'}));
//http://flights.com/roundTrip/date/2014-07-16

Practical Example Accessibility - Aria

Managing tabindex/visibility

Setup

var isFocusable = (elem) => elem &&  elem.tabIndex >= 0;

var timeFunction = function(toTime) { return () => { 
  console.time('ListFocus');
  var result = toTime.call(this,...Array.from(arguments));
  console.timeEnd('ListFocus'); 
  return result;  };
}; 

isHidden(domElement) {
    if(!domElement) {return true; }
    return domElement.offsetWidth <= 0 && domElement.offsetWidth <= 0 ||
     domElement.getAttribute('aria-hidden') === "true";
  }

Find Focusable Elements

class ParseFocusable {
  constructor({domElement, filter=isFocusable, timeFocus=false}) {
    this.domElement = domElement;
    this.isFocusable = filter;
    this.focusArray = timeFunction.bind(this)(this.focusArray); 

  } 
  *focii(domElement, ignoreSelf) {
    var isHidden = isHidden(domElement);
    if(!ignoreSelf && this.isFocusable(domElement) && !isHidden) {
      yield domElement
    }    
    if(!isHidden) {//if not hidden traverse
      yield *this.focii(domElement.firstElementChild)
    }    
    if(!ignoreSelf){//if not initial object, get siblings
      yield *this.focii(domElement.nextElementSibling);
    }
  }
  focusArray() {
    return [for(n of this.focii(this.domElement,true)) n];
  }

}

console.clear(); 
var allElements = new ParseFocusable({domElement:document.body,timeFocus:true}).focusArray();
console.log(allElements);

Continued...

Adding O(1) Lookup

Sliding Generator

var sliding = function *(arr, numFields) {
  if(numFields <= 0) { numFields = 1; }
  var idx=0;

  while(idx + numFields < arr.length) {
    yield arr.slice(idx,idx+numFields);
    idx+=1;
  }

};

console.clear();

console.log( [for (x of sliding([1,2,3,4],2)) x]); //[1,2][2,3][3,4]

Now with Weak Maps

class ParseFocusable {
  constructor() {
    this.forward = new WeakMap();
    this.reverse = new WeakMap();
  }

  buildTabOrder() {
    for(var [a,b] of sliding(this.focusArray())) {
      this.forward.set(a,b);
      this.reverse.set(b,a);
    }
  }

  next(domElement) {
    this.forward.get(domElement);   
  }
  previous(domElement) {
    this.reverse.get(domElement);
  }

}

The One with the Long Distance Relationship

class HttpResolver { //class
  constructor({url, method, data}) {
    this._xmlHttpRequest = new XMLHttpRequest();    
    this._promise = new Promise(this.resolve.bind(this)); //context is important here es5
    this._xmlHttpRequest.open(method, url);
    this._xmlHttpRequest.send(data); 
  }
  getData() {
       return this._xmlHttpRequest.responseText;
  }
  resolve(resolve,reject) {

    this._xmlHttpRequest.onreadystatechange = () => { 
      if(this._xmlHttpRequest.readyState==4 && this._xmlHttpRequest.status==200) {
          resolve(this.getData());
      }
    }
    this._xmlHttpRequest.ontimeout = () => { throw new Error('timeout'); }
  }
  get promise() { return this._promise; } //get modifier (Use .promise instead of .promise() es5
}

Continued

class JSONResolver extends HttpResolver { //extends
  getData() {
   return JSON.parse(super.getData());
  }
}


var url = 'http://taginfo.openstreetmap.org/api/4/search/by_keyword?query=fire&page=1&rp=10',
  method = 'get';   

//thanks to openstreetmap for the easily accessible cors api.
var act = new HttpResolver({url, method});  
var act2 = new JSONResolver({url, method});  

act.promise.then((data) => console.log(typeof data));
act2.promise.then((data) => console.log(typeof data));

Modules

  • TC39 focused on static verification
  • Worked to make the module system workable for everyone (Node vs Web)
  • Finalization still underway (module vs import * from fs as 'whatever')

Syntax

                            

//Lexer.js
export  {Lexer};

//valid, Lexer is an export
import {Lexer} from 'Lexer';

//valid, 'Lexer' has an export.
//Lexer.Lexer is available
module Lexer from 'Lexer';

//same as line above.  only one will win
import * as Lexer from 'Lexer' ;

//invalid, because of no default
import Lexer from 'Lexer';
                            
                        
//tokenClasses.js
export var NEWLINE = new RegExp(/(\n+)/);
export var SPACE = new RegExp(/([^\n][\s\t]+)/);
export var WORD = new RegExp(/(\w+)/);
export var PUNCT = new RegExp(/([\^\[\]\!\*\+\-`#])/);

//import without any var request gets everything
export default {SPACE, NEWLINE, WORD,PUNCT};

//main.js
// war with {SPACE, NEWLINE, WORD,PUNCT};
import * as tokens from 'tokenClasses'
module tokens from 'tokenClasses';

//works. there is a default
import tokenDefault from 'tokenClasses';

//works
import {SPACE} from 'tokenClasses';
                            

Available tools - Traceur

  • grunt-traceur
  • gulp-traceur

Steps to use grunt-traceur

npm install grunt-traceur --save-dev Update your gruntfile Profit??
Will need to add watch as well and choose the destination of the js files. In addition, you should choose a module type. CommonJS and AMD(require) are also supported as well as an option that compiles everything into one file.

Current Spec Discussion

  • Generator Comprehensions
  • ES6 modules
  • TC39 vs the Community
  • Node modules
  • Object.observe - Definitely in ES7, but no one got the message
Generator comprehensions: concerns about parallelization (Perhaps automatic) Modules: what is the this. When doing modules, what is the equivalent of __dirname. module command, defaults Observe: Moved to ES7. People are already moving forward with implementation/use, which makes it somewhat risky, more in regard to divergent implementations

Questions?

Outtakes

The One Where Phoebe Forgets Something

   var wm = new WeakMap();
   var m = new Map();
   var s = new Set();
   (function() { 
     var c = {'not':'me'};      
     m.set(c,{'o':'k'});
     wm.set(c,{'o':'k'});
     s.set(c);
     console.log(wm.get(c));
   }
   )()

   wm.has(m.keys().next().value); 
   s.has(m.keys().next().value);
   m = undefined;
   s = undefined;
   //after next gc wm.size (if it existed) would be 0

  //ES6 Spec..an ECMAScript implementation must not provide any means to observe a key of a 
  //WeakMap that does not require the observer to present the observed key.
Hey you. Go line by line. Make sure to go into how yield pauses execution until the next time data is needed.

Shorthand Object

Shorthand Sample

var title = 'El presidente';
var name = 'Walter';

var short = {title,name};
var long = {title:title, name:name}

AST Shorthand

  type: "ObjectExpression",
  properties: [
    {
      type: "Property",
      key: {
        type: "Identifier",
        name: "title",
        loc: null
      },
      value: {
        type: "Identifier",
        name: "title",
        loc: null
      },
      kind: "init",
      method: false,
      shorthand: true
      }
    },
    {
      type: "Property",
      key: {
        type: "Identifier",
        name: "name",
        loc: null
      },
      value: {
        type: "Identifier",
        name: "name",
        loc: null
      },
      kind: "init",
      method: false,
      shorthand: true
      }
    }
  ]

ES5 AST

                    "init": {
                        "type": "ObjectExpression",
                        "properties": [
                            {
                                "type": "Property",
                                "key": {
                                    "type": "Identifier",
                                    "name": "title"
                                },
                                "value": {
                                    "type": "Identifier",
                                    "name": "title"
                                },
                                "kind": "init"
                            },
                            {
                                "type": "Property",
                                "key": {
                                    "type": "Identifier",
                                    "name": "name"
                                },
                                "value": {
                                    "type": "Identifier",
                                    "name": "name"
                                },
                                "kind": "init"
                            }
                        ]
                    }

Transform

   var Shorthand = Visitor.extend( {
      visitObjectExpression: function(objectExpression) {
        var newProperties = objectExpression.properties.map(function(prop) {
          return b.property(
            prop.kind,
            prop.key,
            this.visit(prop.value),
            prop.method,
            false);//is shorthand 
        }.bind(this));

        return b.objectExpression(newProperties);

      }

   });

    new Shorthand().visit(ast);

   var output = recast.print(ast).code;

The One With the Kitchen Sink

//arrow + destructuring + default params
var fibNext=({last=0,current})=>{ return last + current};
var gen = function *(...two ) { //generator + rest params
  var [last,current] = two;  //destructuring
  var next = fibNext({last,current}); //shorthand object syntax
  while(next < 1000) {
    next = fibNext({last,current});
    [last, current] = [current,next]; //destructuring
    yield next; //yield keyword (for generators)
  }
}
for(let z of gen(0,1)) { //generator + let
  var q = [ for (n of gen(...[0,1])) if (n=== z) n ]  //iterators + array comprehension + spread operator
  console.log(`Runtime complexity ${q}`); //string templating
}

export gen //modules
Test

More Classes

class Parent {
constructor(eek) {
this.eek = eek;
}
height(h) {
if(h) {
this.h =h;}
else {
return this.h;
}
}
}
class Child extends Parent {
constructor() {
super(10);
}
height(h) {
super.height(h);
this.h2=h;
}

static defaultKid() {
var toBe = new Child();
toBe.height(32);
toBe.name = 'Shakespeare';
return toBe;
}

}