Meteor += ECMAScript – How great? – Example: for-of loops



Meteor += ECMAScript – How great? – Example: for-of loops

0 2


devshop-july-2015

Slides for my talk about ECMAScript at the July 2015 Meteor Devshop

On Github benjamn / devshop-july-2015

Meteor += ECMAScript

{ github,
  twitter,
  instagram,
  facebook
}.com/benjamn
            

Have you ever tried describing Meteor without mentioning JavaScript?

The JavaScript App Platform

Meteor is a complete open source platform for building web and mobile apps in pure JavaScript

What are we talking aboutwhen we talk about JavaScript?

The language that runs in web browsers, the one that hasn't changed all that much since the early 2000s?

The language that puts the “JS”in Node.JS?

But which version of Node?

How much of the standard library actually works according to the specification?

Which specification are we talking about?

Can you run the same code in a web browser?

What kind of module system does Meteor support?

What is the deal with Fibers?

These questions have answers!

The most valuable property of any programming language:

Everyone can agree precisely what will happen when a computer runs a program written in that language.

“JavaScript” conveys much of the necessary information

Though “JavaScript” may be a little vague, a vast audience of programmers still know it by that name, so that's why we keep calling it that.

But programming languages can change, and JavaScript is changing faster than ever!

ECMAScript 2015

A big departure from sequential numbering (ECMAScript 4, 5, 6), since it signals an intention to release a new standard every year.

Now easier than ever to propose new language features, build consensus around those proposals, and get them implemented.

RegExp.escape revived on es-discuss last month, presented in this week's TC39 meeting.

Multiple levels of implementation:

transpilers (Traceur, Babel) native support in Node native support in (some) browsers native support everywhere

Our commitment:

The ecmascript package will provide any and all language features that can be faithfully compiled to code that runs natively in all JavaScript engines.

Two kinds of progress:

Meteor provides simulated support for features not yet implemented natively in all JavaScript engines. Native support catches up to the simulation.

Code you write in the first stage should not have to be rewritten when the second stage arrives.

The code behind the meteor command-line tool has been using ECMAScript 2015 features for some time now.

And it's great.

How great?

function JsFile() { InputFile.apply(this, arguments); } JsFile.prototype = Object.create(InputFile.prototype); JsFile.prototype.constructor = JsFile; JsFile.prototype.addJavaScript = function (options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); }; exports.JsFile = JsFile;

How great?

function JsFile() { InputFile.apply(this, arguments); } Meteor._inherits(JsFile, InputFile); JsFile.prototype.addJavaScript = function (options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); }; exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { constructor() { InputFile.apply(this, arguments); } } JsFile.prototype.addJavaScript = function (options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); }; exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { constructor() { super(...arguments); } } JsFile.prototype.addJavaScript = function (options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); }; exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { constructor() { super(...arguments); } addJavaScript(options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); } } exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { addJavaScript(options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); } } exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { addJavaScript(options) { this._minifiedFiles.push({ data: options.data, sourceMap: options.sourceMap, path: options.path }); } } exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { addJavaScript({ data, sourceMap, path }) { this._minifiedFiles.push({ data: data, sourceMap: sourceMap, path: path }); } } exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { addJavaScript({ data, sourceMap, path }) { this._minifiedFiles.push({ data, sourceMap, path, }); } } exports.JsFile = JsFile;

How great?

class JsFile extends InputFile { addJavaScript({ data, sourceMap, path }) { this._minifiedFiles.push({ data, sourceMap, path }); } } exports.JsFile = JsFile;

How great?

export class JsFile extends InputFile { addJavaScript({ data, sourceMap, path }) { this._minifiedFiles.push({ data, sourceMap, path }); } }

How great?

export class JsFile extends InputFile { addJavaScript({ data, sourceMap = null, path }) { this._minifiedFiles.push({ data, sourceMap, path }); } }

We're pretty addicted.

And we want you to have the same development experience.

How?

meteor add ecmascript

Just a package that can be added to any app or api.used by any package, and it will be installed by default for all new apps and packages.

With many thanks to Babel

So what dialect of JavaScript does Meteor implement?

The latest one: ECMAScript 2015+

Whatever we can faithfully transpile.

Whatever will one day be implemented natively.

Whatever helps you write Meteor apps.

Example: for-of loops

let sum = 0; for (let x of arrayOfNumbers) { sum += x; }

gets transpiled to

var sum = 0; var _iter = arrayOfNumbers[Symbol.iterator](), _result; while (! (_result = _iter.next()).done) { var x = _result.value; sum += x; }

Symbol.iterator is a special kind of unique property name that cannot be simulated faithfully.

Example: for-of loops

var sum = 0; var _iter = arrayOfNumbers[Symbol.iterator](), _result; while (! (_result = _iter.next()).done) { var x = _result.value; sum += x; }

assumes a minimal Symbol implementation:

if (typeof Symbol === "undefined") { Symbol = function Symbol() {}; }

Example: for-of loops

var sum = 0; var _iter = arrayOfNumbers[Symbol.iterator](), _result; while (! (_result = _iter.next()).done) { var x = _result.value; sum += x; }

assumes a minimal Symbol implementation:

if (typeof Symbol === "undefined") { Symbol = function Symbol() {}; } if (! Symbol.iterator) { Symbol.iterator = "@@iterator"; }

Example: for-of loops

var sum = 0; var _iter = arrayOfNumbers[Symbol.iterator](), _result; while (! (_result = _iter.next()).done) { var x = _result.value; sum += x; }

assumes a minimal Symbol implementation:

if (typeof Symbol === "undefined") { Symbol = function Symbol() {}; // Fake! } if (! Symbol.iterator) { Symbol.iterator = "@@iterator"; // Fake! }

Since the original for-of loop works today in translation, and it will continue to work natively once it no longer needs to be translated, for-of loops meet our criteria for inclusion in the ecmascript package, while Symbols do not.

Example: Array.prototype

Array methods like .forEach, .map, and .reduce are now available in almost every JavaScript engine, except Internet Explorer 8 and earlier.

We use a popular polyfill library called es5-shim to implement these methods, but there is no way to make them non-enumerable in IE8.

Example: Array.prototype

If you ever use a for-in loop to iterate over an array in IE8, you'll get all those method names, too!

let sparseArray = []; sparseArray[0] = "a"; sparseArray[2] = "c"; for (let index in sparseArray) { console.log(index); // Should log 0 and 2. }

Normally you would be out of luck, but Meteor can actually fix this for you.

Example: Array.prototype

If you ever use a for-in loop to iterate over an array in IE8, you'll get all those method names, too!

let sparseArray = []; sparseArray[0] = "a"; sparseArray[2] = "c"; for (let index in runtime.sanitizeForInObject(sparseArray)) { console.log(index); // Should log 0 and 2. }

Normally you would be out of luck, but Meteor can actually fix this for you.

Example: Array.prototype

If you ever use a for-in loop to iterate over an array in IE8, you'll get all those method names, too!

let sparseArray = []; sparseArray[0] = "a"; sparseArray[2] = "c"; for (let index in runtime.sanitizeForInObject(sparseArray)) { console.log(index); // Should log 0 and 2. }

In most browsers, the sanitizeForInObject helper function simply returns its argument.

Example: Array.prototype

If you ever use a for-in loop to iterate over an array in IE8, you'll get all those method names, too!

let sparseArray = []; sparseArray[0] = "a"; sparseArray[2] = "c"; for (let index in runtime.sanitizeForInObject(sparseArray)) { console.log(index); // Should log 0 and 2. }

In IE8, when the argument is an array, it returns a new object containing only the enumerable keys.

Example: Promise.Fiber

// Replace Promise.prototype.then with a wrapper that ensures the // onResolved and onRejected callbacks always run in a Fiber. var es6PromiseThen = Promise.prototype.then;

Example: Promise.Fiber

// Replace Promise.prototype.then with a wrapper that ensures the // onResolved and onRejected callbacks always run in a Fiber. var es6PromiseThen = Promise.prototype.then; Promise.prototype.then = function then(onResolved, onRejected) { };

Example: Promise.Fiber

// Replace Promise.prototype.then with a wrapper that ensures the // onResolved and onRejected callbacks always run in a Fiber. var es6PromiseThen = Promise.prototype.then; Promise.prototype.then = function then(onResolved, onRejected) { var Promise = this.constructor; };

Example: Promise.Fiber

// Replace Promise.prototype.then with a wrapper that ensures the // onResolved and onRejected callbacks always run in a Fiber. var es6PromiseThen = Promise.prototype.then; Promise.prototype.then = function then(onResolved, onRejected) { var Promise = this.constructor; if (typeof Promise.Fiber === "function") { } };

Example: Promise.Fiber

// Replace Promise.prototype.then with a wrapper that ensures the // onResolved and onRejected callbacks always run in a Fiber. var es6PromiseThen = Promise.prototype.then; Promise.prototype.then = function then(onResolved, onRejected) { var Promise = this.constructor; if (typeof Promise.Fiber === "function") { return es6PromiseThen.call( this, wrapCallback(onResolved, Promise), wrapCallback(onRejected, Promise) ); } };

Example: Promise.Fiber

// Replace Promise.prototype.then with a wrapper that ensures the // onResolved and onRejected callbacks always run in a Fiber. var es6PromiseThen = Promise.prototype.then; Promise.prototype.then = function then(onResolved, onRejected) { var Promise = this.constructor; if (typeof Promise.Fiber === "function") { return es6PromiseThen.call( this, wrapCallback(onResolved, Promise), wrapCallback(onRejected, Promise) ); } return es6PromiseThen.call(this, onResolved, onRejected); };

Good Promise tutorials: HTML5Rocks, 2ality

Example: Promise.Fiber

function wrapCallback(callback, Promise) { var fiber = Promise.Fiber.current; var dynamics = fiber && fiber._meteorDynamics; return callback && function (arg) { return fiberPool.run({ callback: callback, args: [arg], // Avoid dealing with arguments objects. dynamics: dynamics }, Promise); }; }

Fibers are recycled using a pool, so a new Fiber does not have to be created every time a Promise callback fires.

Example: Promise.Fiber

function wrapCallback(callback, Promise) { var fiber = Promise.Fiber.current; var dynamics = fiber && fiber._meteorDynamics; return callback && function (arg) { return fiberPool.run({ callback: callback, args: [arg], // Avoid dealing with arguments objects. dynamics: dynamics }, Promise); }; }

Even better, Fiber._meteorDynamics is saved and restored when the callback runs, so you never have to call Meteor.bindEnvironment with Promises!

What about source maps?

No source maps?

We believe...

You shouldn't have to wait for universal native support before using the latest ECMAScript features.

Nor should you to have to wonder when it's safe to transition from simulated to native support.

Nor should it be your job to find the best way to integrate libraries like es5-shim with tools like Babel.

Meteor can be the place where these solutions accumulate

Because Meteor provides a complete platform out of the box, including compilation, packaging, and delivery of scripts, libraries, source maps, and other assets, we are in an ideal position to make the transition to ECMAScript 2015+ as seamless as possible.

That, more than anything, is why I left my job at Facebook

Facebook has these posters up everywhere that say things like “FOCUS ON IMPACT,” so that's what I did.

I believe Meteor has the best chance of providing the best experience for developing apps with the latest version of JavaScript.

And there are a whole lot more of you than there are engineers at Facebook.

Thanks!

Meteor += ECMAScript Ben Newman 30 July 2015 { github, twitter, instagram, facebook }.com/benjamn