Closed samreid closed 6 years ago
Well, I noticed our "supported platforms" page says we still support IE11. What are our analytics for that, and when (if ever) do we expect to drop support for it?
Of course it may be better for us to stick with ES5 code "indefinitely"
Sticking with it indefinitely sounds like supporting IE11 indefinitely. Once IE11 support is dropped, we'd probably (somewhat incidentally) add code that would break in IE11 (even if it's not related to ES6).
I'd recommend to move forward with whatever ES6 features will help when it's supported by all of our supported browsers.
@jonathanolson or @oliver-phet, can you please report on current IE11 usage from our analytics?
Over the last year, IE 11 represents ~6.6% of the total session. (8.04% IE, of that 82.2% is IE11)
Should we add an IE-only banner "PhET support for IE is scheduled for termination, please use another browser", to see if we can drive those numbers even lower?
Many people can't switch due to using school computers, and presumably a banner wouldn't make a huge hit in usage due to situations like that? I'd generally prefer supporting IE11 with that usage.
@jonathanolson what is your threshold?
Not sure what the best threshold is. @ariel-phet and @oliver-phet, what thresholds have we used in the past?
@jonathanolson @samreid our threshold is more like 1%, so we are not very close to that yet.
Sounds good, we can check back in a year or so.
@oliver-phet pointed out that build an atom (one of our most popular sims) has around 4% IE, so not quite as much as our website.
We are using ES6 in chipper as of https://github.com/phetsims/chipper/issues/604 and discussing using it in simulations in https://github.com/phetsims/phet-core/issues/34
From https://github.com/phetsims/phet-core/issues/34
One of the reasons I wanted to stay on ES5 is so that no compilation step was necessary to test in the browser. But now many browsers support ES6 out of the box--we could develop in ES6, test directly in ES6 without a compilation step, then transpile for publication (to support older browsers).
To test this, I added the following code in createHTMLFiles.js:
var babel = require( 'babel-core' ); // eslint-disable-line require-statement-match
// ...
// Load the optimized code for the sim.
var mainInlineJavascript = grunt.file.read( 'build/' + buildConfig.name + '.min.js' );
console.log(mainInlineJavascript.length);
var b = babel.transform( mainInlineJavascript, { presets:['env']} );
mainInlineJavascript = b.code;
console.log(mainInlineJavascript.length);
I also had to do (not sure if essential in chipper/ or sim/):
npm install --save-dev babel-core
npm install babel-preset-env --save-dev
Then I was able to add ES6+ code in faradays-law-main.js and confirm that it transpiled properly to ES5 compatible code.
Here's a diff of one of the simpler model files in Farday's Law using ES6 classes:
I also neglected to mention, there are 2-3 usages of global.define
that fail after transpilation--patching them seems to get the sim working again:
I tested IDEA code navigation from FardaysLawModel.js in this code:
/**
* Restore to initial conditions
* @public
*/
reset: function() {
this.magnet.reset();
this.showTopCoilProperty.reset();
this.showMagnetArrowsProperty.reset();
this.bottomCoil.reset();
this.topCoil.reset();
},
Somehow, IDEA took me right to the correct (Magnet) definition of reset
. And clicking on showTopCoilProperty.reset, it showed this list of options:
Based on my preliminary investigation, I feel like we should add a Babel step to our compilation and start moving to ES6 in simulation code. We would need to pay the "up front costs" of:
Recurring costs:
I know it is like a lot of up-front costs, but it seems we stand to gain significant benefits from ES6+ such as:
this
I have not experimented with import/export, and I expect we would discover other benefits/issues if & when we proceed. I also noticed https://kangax.github.io/compat-table/es6/ reports that babel-core class extends
requires native support for Object.prototype.__proto__
, though MDN reports that it is available on all our supported platforms: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto. Other hitches like this could set us back.
However, my initial investigation gave me the feeling that ES6 would be beneficial for PhET and that moving to it sooner (even with a Babel step) would save us time in the long run. Let's chat and see what the rest of the development team thinks.
ironing out build tools (could be done after chipper 2.0), #551 (comment) is a step in the right direction but doesn't handle preloads
Already have transpilation set up so I can use it in chipper's 2.0 branch.
testing that transpiled code isn't too much larger and doesn't have any performance drawbacks testing that transpiled code behaves properly
We'll be doing a lot of testing for chipper 2.0 anyways. That could be included.
Builds will take longer
This is an acceptable trade-off for me (I'm usually the one who complains the most about build speed, and I think es6+ is worth it)
Maintaining Babel bindings, keeping up-to-date with another dependency
What is the cost here? We can just bump the babel version whenever we need something new (or a bugfix)?
As long as there aren't major performance drawbacks (which for many common usages, would not be anticipated), I feel this would be great.
I also neglected to mention, there are 2-3 usages of global.define that fail after transpilation--patching them seems to get the sim working again
My patch https://github.com/phetsims/chipper/commit/8abd5e7e61e240ca14ac099752c9070c8384af3c fixes this in the 2.0 branch. Basically localeInfo/mipmapDownscale use a trick to work in both Node.js and requirejs, passing this
to a function in "global" scope. Babel wraps everything with "use strict"
which then turns the this
reference to be undefined
. The recommended fix is replacing this
with ( 1, eval )( 'this' )
(see http://perfectionkills.com/unnecessarily-comprehensive-look-into-a-rather-insignificant-issue-of-global-objects-creation/#ecmascript_5_strict_mode).
In https://github.com/phetsims/chipper/issues/551#issuecomment-347087805, @samreid said:
I know it is like a lot of up-front costs, but it seems we stand to gain significant benefits from ES6+ such as: ...
A few of these gains are downright trivial (better options, arrow function, const vs var) compared to the costs. "Simpler/clearer/more maintainable code" is debatable - I would argue that those attributes are primarily a function of the programmer, secondarily a function of the language constructs. "Uniform language level across our codebase (chipper + sims)" will only be realized if PhET incurs the (massive!) cost of moving ES5 code to ES6. That said, if PhET wants to incur the costs, I'll be happy to come up to speed on ES6.
However, my initial investigation gave me the feeling that ES6 would be beneficial for PhET and that moving to it sooner (even with a Babel step) would save us time in the long run. Let's chat and see what the rest of the development team thinks.
Given the massive (there's that word again :) number of things on the PhET agenda (completing the PhET-iO API, instrumenting N sims, chipper:2.0, a11y, sonification, Rosetta, new sims,...) I find it hard to believe that "moving to it sooner" is currently a viable option for PhET. Does the PhET schedule have enough slack to accommodate the "up front" costs listed in #551 (comment)?
Finally... I do think that PhET needs to be more proactive/responsive to incorporating new technologies and updates, including ES6. This is not the Java world; things change more quickly in HTML5/JavaScript, and we'll get left behind (or miss out) if we don't stay current. But the time required to evaluate and adopt new technologies needs to be built into the schedule (which is already maxed out). What is PhET prepared to drop in order to incur the costs listed in https://github.com/phetsims/chipper/issues/551#issuecomment-347087805?
(EDIT: CM combined 3 comments into 1.)
I've read through https://github.com/phetsims/chipper/issues/551#issuecomment-347087805, and I'm even less convinced about the transpiler. The costs listed are massive.
Point-by-point:
ironing out build tools (could be done after chipper 2.0), #551 (comment) is a step in the right direction but doesn't handle preloads
This was pretty trivial, already implemented in chipper 2.0 branch.
testing that transpiled code isn't too much larger and doesn't have any performance drawbacks
Chipper 2.0 branch will need to be tested for this anyways. Only an additional cost if there is a significant performance cost.
testing that transpiled code behaves properly
Chipper 2.0 branch will need to be tested for this anyways up-front. Some legitimate chance we could add code that isn't transpiled to something compatible with a supported browser if we set up the wrong configuration initially.
moving es5 code to es6 where appropriate
We get to decide when to move things, and usually it would only be if there is an improvement that is worth it, or if the code is already being rewritten. I wouldn't suggest trying to move everything over, as that generally hasn't been our approach (we still have positional code that doesn't use 4-year-old HBox).
learning curves for developers
Agreed it is a cost. Hopefully reduced by the below examples of helpful es6+ things. The time I've spent learning it feels like it's been paid back already by its use in chipper/perennial.
cost of living with a mishmash of es5+es6 code for a period of time
Sounds similar to how we never used to use the built-in Array.forEach because it wasn't guaranteed on the browsers of the time (hasn't been a problem). Besides promises (for async code), I'm not sure how mixing es6+ and the es5 subset of es6+ would be a cost.
Builds will take longer, I recorded 5.3 seconds longer to build faraday's law.
Not ideal, but it's definitely a cost I'm willing to pay for the improvements.
Maintaining Babel bindings, keeping up-to-date with another dependency
Seems like it wouldn't be a major issue. We haven't had issues with having to upgrade grunt/require.js/etc., and I'm more than happy to maintain all of the code that interfaces with the api (currently without comments below):
const babel = require( 'babel-core' );
module.exports = function( grunt, jsInput ) {
return babel.transform( jsInput, {
compact: true,
presets: [ '../chipper/node_modules/babel-preset-env' ]
} ).code;
};
So my largest concerns are:
That does not seem "massive" to me. @pixelzoom, can you elaborate on what you thought the most significant costs where?
To offset those costs, I'd like to discuss some concrete JavaScript "problems" that I've run across in code that could be improved with es6+. Besides the promises/async/await (which are mostly for non-sim code), I agree nothing would be worth "massive" costs, but it does seem to prevent certain classes of bugs in new code, makes things more readable, and resolves some annoyances in the language. If we agree that the costs of as-we-want integration into our codebase is "minor", it seems beneficial to be able to solve the following problems.
Problem:
var self = this
for closures.
Solution:
Arrow functions have a more sensible this
(no need for self
variables)
function Something( foo, bar ) {
this.foo = foo;
this.something = function() {
// Points to the object above (same this as in this.foo)
this.bar = bar;
};
}
Problem:
Have multiple for loops or blocks of code, and want to reuse variable. Things get hoisted out of blocks, so you can only have one var
declaration. e.g.:
var i;
for ( i = 0; i < 10; i++ ) { /* ... */ }
// do something in-between
for ( i = 0; i < 20; i++ ) { /* ... */ }
Solution:
let
and const
are block-scoped, so you can use them as the variable for a for/if:
Problem: Creating closures in a loop, the outer variable gets re-used and set, giving usually undesired behavior, e.g.:
var funcs = [];
for ( var i = 0; i < 10; i++ ) {
funcs.push( function() { return i; } );
}
// almost never want an array full of 10s
var vals = funcs.map( function( f ) { return f(); } );
// vals: [ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ]
// fixed:
for ( var i = 0; i < 10; i++ ) {
(function( j ) {
funcs.push( function() { return j; } );
})( i );
}
Solution:
Again, let
and const
are block-scoped:
const funcs = [];
for ( let i = 0; i < 10; i++ ) {
funcs.push( () => i );
}
const vals = funcs.map( f => f() );
// vals: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Problem: Not great varargs support (with use strict)
trigger0: function( event ) { f( event ) }
trigger1: function( event, arg1 ) { f( event, arg1 ) }
trigger2: function( event, arg1, arg2 ) { f( event, arg1, arg2 ) }
Solution:
trigger: function( event, ...args ) { f( event, ...args ) }
Problem: Asynchronous things (especially loops) are a pain. Applies more to chipper/perennial, but this is a significant win.
function doWithLocales( locales, done, errorCallback ) {
var result = '';
var locales = someLocales.slice(); // copy needed, as we need to modify it
function loop() {
if ( locales.length ) {
// main loop body
var locale = locales.shift();
getSha( function( sha ) {
getBranch( function( branch ) {
getTitle( function( title ) {
getSomeString( sha, branch, title, function( str ) {
result += str;
loop();
}, errorCallback );
}, errorCallback );
}, errorCallback );
}, errorCallback );
}
else {
done( result );
}
}
loop();
}
doWithLocales( locales, function( result ) { /* ... */ }, errorCallback );
Solution: Promises and async-await
async function doWithLocales( locales ) {
var result = '';
for ( let locale of locales ) {
result += await getSomeString( await getSha(), await getBranch(), await getTitle() );
}
return result;
}
doWithLocales( locales ).then( result => /* ... */ ).catch( errorCallback );
Problem: Overly-verbose closures, e.g.:
function( a ) { return a.x; }
function( a, b ) { return a + b; }
function() { return 2; }
function( a ) { var tmp = f( a ); return tmp * tmp; }
{ a: function( b ) { return c; } }
Solution:
a => a.x
( a, b ) => a + b
() => 2
a => { const tmp = f( a ); return tmp * tmp; }
{ a( b ) { return c; } }
Problem:
Ugly string methods (indexOf for everything
Solution:
String methods: startsWidth
/endsWidth
/includes
Problem: Ugly default parameters:
function( width ) {
width = width === undefined ? 5 : width;
// ...
}
Solution: Default parameters:
function( width = 5 ) {
// ...
}
Problem: Overly verbose local variable assignment
function( locationProperty ) {
var x = locationProperty.value.x;
var y = locationProperty.value.y;
var z = locationProperty.value.z;
}
Solution: Destructuring
function( locationProperty ) {
var { x, y, z } = locationProperty.value;
}
// or (sometimes is helpful in duck-typed things, but is more confusing here)
function( { value: { x, y, z } } ) {}
Problem: Ugly usage of apply when you have an array of parameters
// example in Scenery logging
console.log.apply( console, Array.prototype.slice.call( arguments, 0 ) )
Solution:
console.log( ...argments )
Problem: Hard to read string concatenation
'+' + first + '-' + second + '.third;'
Solution: String interpolation
`+${first}-${second}.third`
Problem:
Reading other code, have a variable initialized to one thing that seems like it would be constant, but then gets changed later.
Solution:
const
instead of var
Problem: Ugly/asymmetric concatenation of arrays
[ 1, 2 ].concat( a ).concat( b );
Solution:
[ 1, 2, ...a, ...b ]
Problem: Overly verbose objects with the same property names.
{ a: a, b: b, c: c }
Solution:
{ a, b, c }
Problem:
Less-than-ideal binary literals (yes, used in Scenery for performance)
Solution:
Binary literals (e.g. 0b1101
)
@jonathanolson asked:
That does not seem "massive" to me. @pixelzoom, can you elaborate on what you thought the most significant costs where?
I said in https://github.com/phetsims/chipper/issues/551#issuecomment-347755076:
"Uniform language level across our codebase (chipper + sims)" will only be realized if PhET incurs the (massive!) cost of moving ES5 code to ES6.
I interpreted "uniform language level" as meaning we'd move everything to ES6. If I misinterpreted that, sorry. I'm fine with keeping a mishmash of ES5 and ES6, but I predict that some developers won't be able to resist the temptation of changing working code just for the sake of using ES6.
I appreciate the enumeration of problems solved by moving to ES6. I have no doubt that it is superior to ES5. I agree that PhET should eventually move to ES6. But again... I do not see room for that in the current schedule. I am barely making my milestones as it is. If you ask me to add ES6 to the mix, then all bets are off. So while I hate to be the party pooper here, I don't see this happening unless PhET management makes significant milestone adjustments.
I interpreted "uniform language level" as meaning we'd move everything to ES6. If I misinterpreted that, sorry. I'm fine with keeping a mishmash of ES5 and ES6
Currently we must have different linters set up for working with chipper vs simulations. Allowing (but not forcing) ES6 everywhere would resolve that.
@jonathanolson identified practical benefits in https://github.com/phetsims/chipper/issues/551#issuecomment-347836946 but I wanted to point out another benefit is the class syntax:
// Existing style
function Lion( name, maneColor ) {
Animal.call( this, name );
this.maneColor = maneColor;
console.log( 'created Lion: ' + name );
}
inherit( Animal, Lion, {
speak: function() {
Animal.prototype.speak.call( this );
console.log( this.name + ' roars.' );
}
} );
// Proposed style
class Lion extends Animal {
constructor(name, maneColor ){
super(name);
this.maneColor = maneColor;
console.log('Created lion: ',name);
}
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
I agree we should eventually move to ES6 and we'll need to plan for that in our scheduling.
Currently we must have different linters set up for working with chipper vs simulations. Allowing (but not forcing) ES6 everywhere would resolve that.
Yes, we have .eslintrc files under perennial/js and chipper/js/grunt with the basically one-line { parserOptions: { ecmaVersion: 8 }, env: { es6: true } }
. This would be moved into the main .eslintrc. This is not a major concern for me, and I wouldn't necessarily call it "different linters", but more "slightly different settings per-directory".
If you ask me to add ES6 to the mix
I'm really initially talking about just allowing people to use it, not forcing anyone to use any features.
I'm really initially talking about just allowing people to use it, not forcing anyone to use any features.
If we start using it in sims, then everyone needs to have some level of fluency in ES6. Unless you don't mind me passing on review because I can't understand code :)
This is not a major concern for me, and I wouldn't necessarily call it "different linters"
It's been a hassle to get IDEA eslint formatting to deal with the directories differently.
Executive decision, we will NOT use E6 code in sims for the time being. We can revisit this question after some big kahunas on the project get settled.
It would be inconvenient for me to not be able to run requirejs in IE11, I frequenly run sims in IE11 in requirejs to test with the JAWS screen reader. If it is a net benefit for the project I could live with building before testing with IE11. But this will be an annoyance for anyone working on IE11 specific issues.
It may be a hassle, but if we want to move forward with ES6 before we drop IE11 support, we could (as an interim approach) use an inline transpiler like https://www.npmjs.com/package/requirejs-babel-plugin
I totally misspoke in https://github.com/phetsims/chipper/issues/551#issuecomment-348289783, PhET no longer supports JAWS + IE11, so I redact that concern. I believe there were other reasons for deferring, but wanted to make this clear @ariel-phet.
We will still defer for the moment. Once all chipper 2.0 work is done, we will revisit this question.
Here's another argument for using ES6 in simulation code: https://github.com/phetsims/tasks/issues/931#issuecomment-382577385
Is IE11 still the main holdout? According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const IE11 does support const
In today's meeting, @jonathanolson @zepumph @jessegreenberg and @samreid expressed an interest in updating to ES6. One of the prior hurdles was the need to test requirejs mode on IE11, but @jessegreenberg informed us that IE11 is no longer a supported platform for a11y.
... @jessegreenberg informed us that IE11 is no longer a supported platform for a11y.
Please clarify. If a11y is being added to all sims, a decision not to support a platform for a11y is effectively a decision not to support the platform for PhET sims. Who made this decision?
@jessegreenberg reported that the accessibility team made that decision. But ES6 will still run on IE11 after compilation.
... @jessegreenberg informed us that IE11 is no longer a supported platform for a11y.
Just to be clear, a supported platform for a11y means that the accessibility features are not being tested with a screen reader, and so may sound weird for IE. The sim will still functionally run, because PhET QA is still fully testing it for IE.
I read https://jamie.build/last-2-versions recently, and I think it may be good for us (when we switch to using babel) to specify out the browsers we support, so babel only transpiles the features we need (in the future, when we drop browsers from being supported).
Added an es6
branch to chipper that would allow support for this.
I see the first main disadvantage is that we can't test require.js mode on IE11. That's not a problem for me, as we can use debug/normal builds (I do that anyways for things like iPads).
The second main disadvantage is that we generally need to stick to the ES6+ subset that babel knows how to rewrite for IE11 (which I believe is everything that I'd hope to use).
Presumably we'd want to run a quick "does it run" check across platforms for using babel (the main cost).
I see the benefits to our code in general to be worth the above costs.
I really like the idea of just having Babel in the build process, as I agree, I don't need to have IE11 work in requirejs. If we did need to support this, then Babel get's a whole lot harder.
I too think it is worth bringing the project up to es6.
At today's meeting we decided we would like to proceed with ES6 in simulation code. We will need to know more about how it relates to eslint (making sure let/const/var are OK).
We also want to wait until equality-explorer is published before moving this to master, but we could create dev versions for testing before that step.
@ariel-phet is hoping this will be in a month or so.
In general it looks like eslint is working well on the branch for me. Webstorm complains when I update the language level.
I'm sure that setting can be turned off, but it seems like I'd rather just replace all vars with let, or something like that. We could probably do that on a sim by sim basis to respect devs.
@zepumph will run a test, add some ES6 code (arrow function, const/let, etc) to a sim, checkout chipper branch, test in IDEA, test build and give to QA team for cross-platform testing.
I see no discussion of which ES6 features might be used. Does that mean all ES6 features are fair game? When we last discussed ES6, two big concerns where how/whether ES6 modules and classes could co-exist with requirejs and PhET's prototypal inheritance implementation respectively. Has someone investigated that?
Does that mean all ES6 features are fair game?
Maybe technically, but I would plan on not using ES6 modules/classes yet, since we picked require.js instead. Most other things seem like fair game.
When we last discussed ES6, two big concerns where how/whether ES6 modules and classes could co-exist with requirejs and PhET's prototypal inheritance implementation respectively. Has someone investigated that?
We haven't investigated that yet, but I don't think we would want to wait for that before using other things that make day-to-day work easier to read/write.
I would also be happy to fully specify the recommended subset and recommended places to use the new features in code.
When we last discussed ES6, two big concerns where how/whether ES6 modules and classes could co-exist with requirejs and PhET's prototypal inheritance implementation respectively. Has someone investigated that?
I tried changing SlitsScreenModel to use class/extends, and it worked in both requirejs and built mode. The skeleton of the file looks like:
// Copyright 2018, University of Colorado Boulder
/**
* Model for the Slits screen.
*
* @author Sam Reid (PhET Interactive Simulations)
*/
define( function( require ) {
'use strict';
// modules
// ... more imports
var WavesScreenModel = require( 'WAVE_INTERFERENCE/waves/model/WavesScreenModel' );
class SlitsScreenModel extends WavesScreenModel {
constructor() {
super();
var self = this;
// ... more constructor things
}
/**
* Returns the horizontal barrier location in integer coordinates.
* @public
*/
getBarrierLocation() {
// implementation
}
/**
* Set the incoming source values, in this case it is a plane wave on the left side of the lattice.
* @param {Lattice} lattice
* @override
* @protected
*/
setSourceValues( lattice ) {
// implementation
}
}
waveInterference.register( 'SlitsScreenModel', SlitsScreenModel );
return SlitsScreenModel;
} );
I added node.type='module';
to sherpa/lib/require-2.1.11.js line 1886 and was able to export/import in the browser in concert with requirejs. However, when trying a build, I received:
Error: Parse error using esprima for file: /Users/samreid/github/faradays-law/js/faradays-law/model/FaradaysLawModel.js
Error: Line 3: Unexpected token
Line 3 is: import myDefault from './Magnet.js';
It is unclear how we would want import/export/require to work together. import/export must be top-level and require statements must be nested.
Summary: I recommend we use ES6 classes (for new code) but not ES6 module loading because we already have a better solution for that part.
I investigated this more today. I used the branch es6
in chains and chipper, creating some es6 functionality in the chains screen view, and then checking how it worked in requirejs vs built mode on a few browsers.
For the most part, things are exciting and good.
https://phet-dev.colorado.edu/html/chains/1.18.0-es6.2/phet/chains_all_phet.html
I am finding that I get an error in IE because Promise
is not defined, but when I removed promised, other things like let
, template vars, and arrow functions work. The above link is transpiled using babel. @jonathanolson does babel support Promise
? Maybe we need to add a setting or something.
On top of that, eslint is working great; all that was needed was updating the master .eslintrc
to use es6 and ecma 8 parser options.
For the webstorm error I stated above in https://github.com/phetsims/chipper/issues/551#issuecomment-389964154, you can go to the inspector icon in the bottom right, then "Configure inspections" and search for/find JavaScript | ECMAScript 6 migration aids | 'var' used instead of 'let' or 'const'. You can turn off any inspections you don't want.
@jonathanolson can you think of why IE11 doesn't like Promise? Is this expected?
@jonathanolson pointed out in https://github.com/phetsims/axon/issues/118#issuecomment-283426535 that the caniuse table for ES6 Proxy looks like it covers the bases for our supported platforms:
While I was there, I noticed that ES6 Arrow Functions, const/let, and classes are also green for our platforms. Should we go for it? If so, how much a risk do we run of clients running browsers that do not support it? Would we put language level checks before starting up the simulation? Should we check our analytics to get a better understanding of how many clients we would lose if we started requiring modern ES6 constructs in our production code?
Of course it may be better for us to stick with ES5 code "indefinitely" to avoid questions like the above, but on the other hand, when the same question came up regarding moving from ES4, I believe we made the right decision to jump in to ES5, and it is has helped us immensely (get methods, use strict, array methods, json, etc) and has been worth the cost.
@jonathanolson can you comment on this before we bring it to the wider development team?