The vm
, or
Virtual Machine, module allows you to run arbitrary chunks of code and get a
result back. It has a number of features that allow you to change the
context in which the code runs. This can be useful to act as a kind of
faux sandbox. However, the code is still running in the same Node process,
so you should be cautious. vm
is
similar to eval()
, but offers some more
features and a better API for managing code. It doesn’t have the ability
to interact with the local scope in the way that eval()
does,
however.
There are two ways to run code with vm
. Running the code “inline” is similar to
using eval()
. The second way is to
precompile the code into a vm.Script
object. Let’s have a look at Example 5-40, which demonstrates running code inline
using vm
.
So far, vm
looks a lot like eval()
. We pass some
code to it, and we get a result back. However, vm
doesn’t interact with local scope in the same
way that eval()
does. Code run with
eval()
will behave as if it were truly
inline and replaces the eval()
method
call. But calls to vm
methods will not
interact with the local scope. So eval()
can change the surrounding context,
whereas vm
cannot, as shown in Example 5-41.
Example 5-41. Accessing the local scope to show the differences between vm and eval( )
> var vm = require('vm'), ... e = 0, ... v = 0; > eval(e=e+1); 1 > e 1 > vm.runInThisContext('v=v+1'); ReferenceError: v is not defined at evalmachine.<anonymous>:1:1 at [object Context]:1:4 at Interface.<anonymous> (repl.js:171:22) at Interface.emit (events.js:64:17) at Interface._onLine (readline.js:153:10) at Interface._line (readline.js:408:8) at Interface._ttyWrite (readline.js:585:14) at ReadStream.<anonymous> (readline.js:73:12) at ReadStream.emit (events.js:81:20) at ReadStream._emitKey (tty_posix.js:307:10) > > vm.runInThisContext('v=0'); 0 > vm.runInThisContext('v=v+1'); 1 > 0
We’ve created two variables, e
and v
. When
we use the e
variable with eval()
, the end result of the statement applies
back to the main context. However, when we try the same thing with
v
and vm.runInThisContext()
, we get an exception
because we refer to v
on the right side
of the equals sign, and that variable is not defined. Whereas eval()
runs in the local scope, vm
does not.
The vm
subsystem actually
maintains its own local context that persists from one invocation of
vm
to another. Thus, if we create
v
within the scope of the vm
, the variable subsequently is available to
later vm
invocations, maintaining the
state in which the first vm
left it.
However, the variable from the vm
has
no impact on v
in the local scope of
the main event loop.
It’s also possible to pass a preexisting
context to vm
. This context will be
used in place of the default context.
Example 5-42 uses
vm.runInNewContext()
, which takes a context object as a second argument. The scope
of that object becomes the context for the code we run with vm
. If we continue to pass it from object to
object, the context will be modified. However, the context is also
available to the global scope.
You can also compile vm.Script
objects (Example 5-43). These save a
piece of code that you can then run repeatedly. At runtime, you can choose
the context to be applied. This is helpful when you are repeatedly running
the same code against multiple contexts.
Example 5-43. Compiling code into a script with vm
> var vm = require('vm'); > var fs = require('fs'); > > var code = fs.readFileSync('example.js'); > code.toString(); 'console.log(output);\n' > > var script = vm.createScript(code); > script.runInNewContext({output:"Kick Ass"}); ReferenceError: console is not defined at undefined:1:1 at [object Context]:1:8 at Interface.<anonymous> (repl.js:171:22) at Interface.emit (events.js:64:17) at Interface._onLine (readline.js:153:10) at Interface._line (readline.js:408:8) at Interface._ttyWrite (readline.js:585:14) at ReadStream.<anonymous> (readline.js:73:12) at ReadStream.emit (events.js:81:20) at ReadStream._emitKey (tty_posix.js:307:10) > script.runInNewContext({"console":console,"output":"Kick Ass"}); Kick Ass
This example reads in a JavaScript file that
contains the simple command console.log(output);
. we compile this into a
script
object, which means we can then
run script.runInNewContext()
on the script
and pass in a context. We deliberately
triggered an error to show that, just as when running vm.runInNewContext()
, you need to pass in the
objects to which you refer (such as the console
object); otherwise, even basic global
functions are not available. It’s also worth noting that the exception is
thrown from undefined:1:1
.
All the vm
run commands take a
filename as an optional final argument. It doesn’t change the
functionality, but allows you to set the name of the file that appears in
a message when an error is thrown. This is useful if you load a lot of
files from disk and run them because it tells you which piece of code
threw an error. The parameter is totally arbitrary, so you could use
whatever string is meaningful to help you debug the code.
Get Node: Up and Running now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.