good and eval

eval is Evil.

Douglas Crockford

There are many issues with eval. Some obvious, some less obvious. And if you don't know what you're doing, just avoiding it completely is a good rule of thumb.

But...

If used carefully and correctly, it becomes less bad.

And it's an incredibly fun metaprogramming tool that allows you to do things no other mechanism can.

direct eval

There is a difference between indirect eval and direct eval.

A direct eval call is a call of the eval() function without anything fancy. It can be made indirect through a few mechanisms. E.g. calling it from an object window.eval(), using optional chaining call eval?.(), etc.

Tools executing/compiling JavaScript hate direct eval.

Normally, these tools analyze code to optimize execution.

When introducing direct eval, a few of these optimizations become impossible.

The reason for this is one incredible feature of direct eval:

It can access the callee's local scope.

{
	let x = "hello";
	// this actually works
	eval("console.log(x);");
}

Just with this concept, you can build pretty fun things already: https://gitlab.com/datasha.re/htpl

with proxy

There are two other awesome (horrible) metaprogramming features of JavaScript.

And when you combine all three: direct eval, with and Proxy, the world becomes your oyster.*

*The world stops being your oyster real fast when you have to debug something in this mess but it will have been fun while it lasted.

The with statement is deprecated, but all major browsers and runtimes still support it (except in strict mode).

It allows you to modify/manipulate the scope of a part of the program.

const obj = {x: "hello"};
with (obj) {
	// this works as well
	eval("console.log(x);");
}

Now we are ready to add the final piece of the puzzle: a Proxy.

A Proxy object allows you to intercept member accesses to a given object.

While intercepting you can execute code and return an arbitrary result for the member access, e.g. a result based on your execution.

This means you can "pretend" that certain members exist "just-in-time" when they are being accessed without needing to declare them in advance anywhere.

Combining this with the with statement allows you to "pretend" that certain variables are in the scope.

const obj = {};
const proxy = new Proxy(obj, {
	has(_obj, prop) {
		if (prop === "x") {
			return true;
		}
		return false;
	},
	get(_obj, prop) {
		if (prop === "x") {
			return "hello";
		}
	},
});
with (proxy) {
	// now this is crazy
	eval("console.log(x);");
}

What is the moral of this story?
Maybe it's this:

Disregard the advice of the giants whose shoulders you are standing on. Whatever do they know about having fun?

(That is a joke. Be careful and knowledgeable when using powerful tools. They can be used for great good and great evil.)

async code

The world is not yet your oyster.

const obj = {};
const proxy = new Proxy(obj, {
	has(_obj, prop) {
		if (prop === "x") {
			return true;
		}
		return false;
	},
	get(_obj, prop) {
		if (prop === "x") {
			// you cannot run async code here to fetch the value "hello" from a website :(
			await fetch("https://hello-provider-service.example.net/get-hello");
		}
	},
});
with (proxy) {
	// now this is crazy
	eval("console.log(x);");
}

How can you run async code while intercepting a member access with a proxy?

That is left as an exercise for the reader.

Note: The solution does not involve just using a synchronous XMLHttpRequest as that would be too easy.