Sample Code
const vm = require('vm');
const sandbox = {};
vm.runInNewContext('class Foo{}', sandbox);
vm.runInNewContext('function Bar(){}', sandbox);
console.log(sandbox.Bar); // Prints "[Function: Bar]"
console.log(sandbox.Foo); // Prints "undefined"
Expected result:
console.log(sandbox.Foo); // Prints "[Function: Foo]"
Actual result
console.log(sandbox.Foo); // Prints "undefined"
Can someone please explain why I am unable to access the class Foo within the specified sandbox context?
Is my only option to use function instead of class? This really doesn't look right to me.
I thought it was something to do with the iterable nature of classes but the following shows that's not the case:
> Object.getOwnPropertyNames(sandbox);
[ 'Bar' ]
/cc @AnnaMag @fhinkel @ofrobots
This behavior is also seen in the REPL with regards to tab completion. function behaves like var, and class behaves like let and const.
What Colin said, a class is block-scoped, it's not global. To illustrate:
$ node
> vm.runInNewContext('class C {} function F() {} this')
{ F: [Function: F] } // F but not C
Not a bug, IMO. Close?
Adding to the above: since it is not available in the global context, it won't be set on a sandbox.
It appears that function declarations are also block scoped, but create properties on the global object while class declarations don't create properties on the global object.
This is a significant nuisance IMO - we have some isomorphic code that needs to be loaded on the server into a sandbox, and now every class will have to be associated with a var or exported from a "namespace". ... oh well.
It appears that function declarations are also block scoped
Not always. Functions are block-scoped in strict mode but function/global-scoped in sloppy mode. Classes are always block-scoped, regardless of 'use strict'.
$ node -e '{ function f() {} } f()'
# no error
$ --use_strict -e '{ function f() {} } f()'
[eval]:1
{ function f() {} } f()
^
ReferenceError: f is not defined
# <snip>
I'll go ahead and close this out.
Is there a workaround for this?
I have a language that is compiled to Javascript classes at runtime and I am trying to eval the code like vm.runInNewContext("class Foo {}", {}) but cannot figure out how to access that new class in the parent context.
if you're transpiling it you have access to the class name right? could you emit globalThis.Foo = class {}; instead of class Foo {}?
I do indeed have access to the class name, and that approach works. Thanks @devsnek
Another approach that doesn't pollute the global object:
const Foo = vm.runInNewContext(`
class Foo { /* ... */ }
Foo // return Foo
`);
Thanks @bnoordhuis
I gave that a try but did not seem to work: https://github.com/breck7/jtree/blob/master/src/GrammarLanguage.ts#L1483
I started getting "Identifier 'X' has already been declared" errors.
If you're reusing the context, wrap the code in a block:
const Foo = vm.runInContext(`
{
class Foo { /* ... */ }
Foo // return Foo
}
`, ctx);
Thanks @bnoordhuis! That worked and seems cleaner. (https://github.com/breck7/jtree/blob/master/src/GrammarLanguage.ts#L1455)
I didn't know that the last line of block would be returned.
Most helpful comment
If you're reusing the context, wrap the code in a block: