1

I believe this is technically by design (see discussion on expression vs. declaration for classes here), but I cannot find a way to get global class declarations to work as expected using eval, though there are no issues with global function declarations. This is actually the first time I've seen eval(...) produce a different system state than just embedding the code directly into the file.

For example:

// SHOULD declare global window.GlobalTest
class GlobalTest {}

// no problem!
console.log({ GlobalTest });

// SHOULD declare window.A, and window.B
eval('function A(){} function B(){}');

// no problem!
console.log({ A, B });

// SHOULD declare window.EvalTest
eval('class EvalTest {};');

// uh-oh! EvalTest is undefined =(
console.log(`typeof EvalTest: ${typeof EvalTest}`);


/* THINGS THAT WORK */

// single expression + assignment (boo!)
let SingleExpression = eval('(class SingleExpression {})');
console.log({ SingleExpression });

// assignment (boo!)
eval('AssignmentExpression = class {}');
console.log({ AssignmentExpression });

Running eval in the global scope, we can declare multiple top-level functions without issue. Classes, however, work differently - it only seems possible to declare a global class using eval by assigning it (i.e., EvalTest = class {}), but this requires processing the code rather than it just... working.

The other approach is to isolate the class completely, wrapping it in parentheses to make it an expression, and then assigning the results of the eval(...) call (see SingleExpression in example). This seems strange, because in the global scope, we have no problem declaring a class with class GlobalTest {} in any environment that I have tested. Yet inside eval, it falls apart. In any case, this also requires preprocessing of the code.

I have a script that contains multiple classes and functions, and there seems to be no way to run it as expected and declare multiple global classes with one eval call unless I preprocess the code just so that class MyGlobalClass {} works as expected. Global function/object declarations are not an issue.

Am I missing something obvious here? Any advice would be appreciated. I know - "it's not a bug, it's a feature" - but it just seems weird that class declarations do not actually declare anything inside eval, whereas every other variable declaration does. Embarrassingly enough, until I bumped into this issue, I thought classes were just syntactic sugar for functions, but it's clearly more complex than that.

Lewis
  • 4,285
  • 1
  • 23
  • 36
  • As an update, I have been able to get this pipeline to work by Closure Compiling everything before it goes into `eval` (which naturally renames `class test {}` to `var test = function(){...}`, but this is (naturally) extremely computationally expensive. – Lewis Jun 12 '20 at 06:50
  • 1
    `class` declarations are lexical declarations just like `let` or `const`, and `eval`ed code does have its own lexical scope (in strict mode, even its own `var`iable scope) iirc. – Bergi Jun 12 '20 at 07:38
  • @Bergi Word. I added an answer, but it looks like outside of NodeJS this would be impossible. – Lewis Jun 13 '20 at 00:06

1 Answers1

0

I have made some progress on this issue - as @Bergi pointed out:

The problem is rather that eval does create a new scope for lexical declarations (just like a block would), so that let, const and class become local variables inside the evaled code.

http://www.ecma-international.org/ecma-262/#sec-performeval

eval("var a = 1; let b = 2; class c {}");
console.log(typeof a, typeof b, typeof c);

So it looks like in most traditional runtimes, there is no way to do this without Closure Compiling the code first (which will naturally redefine lexicals in the global scope to the form var c = class {}.

However, I am working in NodeJS, and it seems like vm.Script.runInThisContext can be used to compile the code as expected:

new Script("var a = 1; let b = 2; class c {}").runInThisContext();
console.log(typeof a, typeof b, typeof c);

> 1 2 [Function: c]
Lewis
  • 4,285
  • 1
  • 23
  • 36
  • 1
    That [lexical variables don't create *properties* on the global object](https://stackoverflow.com/q/28776079/1048572) doesn't really matter, they still create global variables. The problem is rather that [`eval` does create a new scope for lexical declarations](http://www.ecma-international.org/ecma-262/#sec-performeval) (just like a block would), so that `let`, `const` and `class` become *local* variables inside the `eval`ed code. – Bergi Jun 13 '20 at 14:31
  • Thank you @Bergi, I edited. Between us, doesn't this literally defeat the purpose of `eval`? I had never run into an issue with `eval`ed code resulting in a materially different output than just pasting the code right into the parent script, until lexicals came along. – Lewis Jun 13 '20 at 19:18
  • Actually, the behaviour is the same for `var` and `function` declarations in strict mode, as this defeats various compiler optimisations otherwise. I never saw it as a problem. I'm not sure what you are needing the `eval()` for, but you can always just create an inline ` – Bergi Jun 14 '20 at 15:28
  • @Bergi I was actually working in Node, trying to set up a `window === global` state and allow for the importing of independent scripts in the global context *as if* they had been dropped in a ` – Lewis Jun 14 '20 at 21:14