ExecJS::Xtrn

Build Status Gem Version

Drop-in replacement for ExecJS. The latter spawns separate process for every JavaScript compilation (when using external runtime), while ExecJS::Xtrn spawn long running JavaScript process and communicates with it via stdin & stderr.

This is just proof of concept, not suitable for production.

When not on MS Windows, one definitely should use ExecJS with excellent therubyracer gem instead.

Installation

Add this line to your application's Gemfile:

gem 'execjs-xtrn'

And then execute:

$ bundle

Or install it yourself as:

$ gem install execjs-xtrn

Usage

Just add/require this gem after/instead (implicit) execjs gem. The latter will be monkey-patched.

Engines

ExecJS::Xtrn uses two external JavaScript runners:

* Windows Script Host
* Node.js in two modes:
- Simple (1 execution context = 1 external process)
- Nvm (all execution contexts share single external process using [vm API](http://nodejs.org/api/vm.html))

So, there exist four engines:

* Engine - absctract engine (smart enough to execute blank lines)
* Wsh - engine using WSH (CScript)
* Node - engine using Node.js (separate process for every execution context)
* Nvm - engine using Node.js and vm API (single process)

All engines autodetect their availability at startup (on require 'execjs/xtrn') and sets Valid constants. Eg on MS Windows ExecJS::Xtrn::Wsh::Valid = true, on Linux - false

One of available engines is made default engine for ExecJS. If Node.js is available it is Nvm. Else, if running on Windows it is Wsh. Else it is Engine, so ExecJS is made unusable.

Default engine can be shown/changed at any moment with ExecJS::Xtrn.engine accessor, eg

ExecJS::Xtrn.engine=ExecJS::Xtrn::Node

API

ExecJS::Xtrn is primarily designed to power other gems that use popular ExecJS.

But it has his own API (similar to ExecJS' API) and can be used itself.

In general one should create instance of an Engine and then feed it with JavaScript code:

ctx=ExecJS::Xtrn::Wsh.new
ctx.exec 'fact = function(n){return n>1 ? n*fact(n-1) : 1}'
puts "10!=#{ctx.call 'fact', 10}"

Every execution context has three methods:

* exec('`code`') -  executes arbitrary JavaScript code. To get result `return` must be called.
* eval('`expression`') - evaluate JavaScript expression. `return` is not needed
* call(`function`, arguments...) - special form of eval for function call

Engine class also has exec and eval methods, they just create brand new execution context, pass argument to it, destroy that context and return its result. Using these class methods is not recommended, since it's just what ExecJS does (except for Nvm engine).

Engine class also has compile method that combines new and exec and returns execution context. This is how ExecJS is used in most cases.

ctx=ExecJS::Xtrn::Wsh.compile 'fact = function(n){return n>1 ? n*fact(n-1) : 1}'
puts "10!=#{ctx.call 'fact', 10}"

And finally ExecJS::Xtrn patches ExecJS and installs those 3 class methods (exec, eval, compile) in it. So, ExecJS.compile is ExecJS::Xtrn::Nvm.compile if Nvm engine is available.

Overriding ExecJS

Sometimes ExecJS is required after ExecJS::Xtrn. In that case you can call ExecJS::Xtrn.init and it will overwrite ExecJS' methods again.

To test whether JavaScript is served by ExecJS::Xtrn, it's convenient to look at ExecJS::Xtrn statistics.

Statistics

Every engine gathers it's own usage statistics. Eg:

> ExecJS::Xtrn::Node.stats # or ExecJS::Xtrn::Nvm.stats or ExecJS::Xtrn::Wsh.stats
=> {:c=>2, :n=>2, :o=>8, :i=>6, :t=>0.131013}

Here:

* c = number of child processes spawned (for Nvm c should always be 1)
* n = number of request made
* o = bytes sent to child process(es)
* i = bytes got from child process(es)
* t = seconds spent communicating with child process(es)
* m = number of VMs created (Nvm only)
* x = number of VMs destroyed (Nvm only)

ExecJS::Xtrn.stats combines statistics for all its engines, even unused.

ExecJS.stats shows statistics for current engine only.

Every execution context has his own statistics too. Eg

s=ExecJS::Xtrn::Nvm.compile '...'
s.exec '...'
s.stats

but c (and m, x) fields are omitted there.

Compatibilty

Not every JavaScript code behaves identically in ExecJS and ExecJS::Xtrn. In most cases it depends on how global JavaScript variables are used. For most modern code it is the same though.

As a rule of thumb, JavaScript code must survive after wrapping in anonymous function ((function(){...})()).

For instance, old versions of handlebars_assets didn't work in ExecJS::Xtrn (and worked in ExecJS).

The following packages have been tested to run under ExecJS::Xtrn out-of-box:

* [CoffeeScript](http://coffeescript.org/) via [coffee-script](https://rubygems.org/gems/coffee-script) and [coffee-rails](https://rubygems.org/gems/coffee-rails) gems
* [UglifyJS2](https://github.com/mishoo/UglifyJS2) via [uglifier](https://github.com/lautis/uglifier)
* [Handlebars](http://handlebarsjs.com/) via [handlebars_assets](https://github.com/leshill/handlebars_assets) gem

CoffeeScript since v1.9.0 introduces new incompatibility: it uses Object.create that is missing from WSH. To fix it, Object.create was manually defined in ExecJS::Xtrn::Wsh.

Testing

After git checkout, required NPM modules must be installed. Simply run:

bundle install
bundle exec rake npm

The testing itself is

bundle exec rake

And bundle exec may be ommited in most cases.

Credits

* [ExecJS](https://github.com/sstephenson/execjs)
* [therubyracer](https://github.com/cowboyd/therubyracer)
* [Node.js](http://nodejs.org/)
* [Windows Script Host](http://en.wikipedia.org/wiki/Windows_Script_Host)