vm.js 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.