vm.js

vm.js (GitHub: tarruda/vm.js, License: MIT) is a pretty impressive fully
functional ECMAScript virtual machine that can be used from any
ECMAScript3-compatible environment. Eventually it will provide a
complete ECMAScript 6 environment (for now only some features are
supported).

1
npm install vm.js

It’s written in CoffeeScript and has plenty of tests. The most
impressive thing is that the tests themselves run inside vm.js.

Here are some possible use cases:

  • Simple in-process javascript sandbox
  • Async-to-sync API adapter using fibers (lightweight in-process threads, not node.js fibers)
  • Use new ECMAScript features in very old browsers

The main API can be accessed through vm instances. Each vm is indirectly
associated with a global object (through a “realm”) and is isolated from
other vms.

Start by creating a new instance:

1
2
> Vm = require('vm.js')
> vm = new Vm()

Evaluate simple expressions:

1
2
3
4
5
6
7
8
9
10
> vm.eval('40 + 2')
42
> vm.eval('[a, b, c] = [1, 2, 3]') // Harmony destructuring assignment
[1, 2, 3]
> vm.realm.global.a
1
> vm.realm.global.b
2
> vm.realm.global.c
3

Compile programs and run later

1
2
3
4
// pass filename as second argument for stack traces/debugging
> script = Vm.compile('2 + 2', 'sum.js')
> vm.run(script)
4

Compiled scripts can be serialized/deserialized to/from JSON-friendly
structures:

1
2
3
4
> scriptObj = script.toJSON()
> serializedScript = JSON.stringify(scriptObj)
> deserializedScript = Vm.fromJSON(JSON.parse(serializedScript))
> vm.run(deserializedScript)

Expose objects to be used by code running inside the Vm

1
2
3
> vm.realm.global.factorial = function factorial(n) { return n > 1 ? factorial(n - 1) * n : 1 }
> vm.eval('factorial(5)')
120

The inverse also works:

1
2
3
> vm.eval('function factorial(n) { return n > 1 ? factorial(n - 1) * n : 1 }')
> vm.realm.global.factorial(5)
120

Return values asynchronously using fiber pause/resume:

1
2
3
4
5
6
7
8
9
10
11
12
13
// created a paused fiber from compiled code
fiber = vm.createFiber(Vm.compile('user = null; user = fetchAsync("/users/1");'))
vm.realm.global.fetchAsync = function(url) {
  fiber.pause() // pause execution
  $.get(url, function(data) {
    // user === null
    fiber.setReturnValue(data)
    fiber.resume()
    // user === data
  });
}
// start fiber
fiber.run()

There are plenty of examples on the
github page.

Post navigation

Leave a comment

Leave a Reply

Your email address will not be published.