`Function("import(specifier)")`
The function constructor captures the active script or module when creating a function object, at step 15 of https://tc39.es/ecma262/#sec-ordinaryfunctioncreate.
This means that, given this a.js module:
Function("import('./b.js')")();
it will call the host hook with a.js as the referrer and ./b.js as the specifier.
I would thus expect it to call the importHook when running in a new Module already in layer 0. If instead it goes through the evaluators from layer 3, they should have a way to get the correct referrer module.
Note that the current layer 0 spec already calls the importHook in this case, but as far as I remembered we so far assumed that it wouldn't do it.
Honestly this seems like a benefit to evaluators as it means there doesn't need to be multiple copies of Function floating about the same realm.
i.e. In the current proposal the pattern would be to do this:
const evaluator = new Evaluator({
globalThis: {
...globalThis,
SOME_GLOBAL: "SOME_GLOBAL",
},
});
evaluator.globalThis.Function = evaluator.Function;
evaluator.eval(`
const fn = Function("return SOME_GLOBAL")
console.log(fn()); // SOME_GLOBAL
`);
however in doing this we wind up with multiple copies of Function floating around in the same realm:
const evaluator = new Evaluator({ globalThis: { ...globalThis } });
evaluator.globalThis.Function = evaluator.Function;
evaluator.eval(`
// Multiple Function constructors are floating around
console.log(parseInt.constructor === Function); // false
`);
However with the existing behaviour we need not bother create a new Function at all, it'll just work when run inside the associated evaluator:
const evaluator = new Evaluator({
globalThis: {
...globalThis.
someGlobal: "SOME_GLOBAL",
},
});
// With the current spec, this should just work
evaluator.eval(`
const fn = Function("return SOME_GLOBAL")
console.log(fn()); // SOME_GLOBAL
`);
// No Function discontinuity
evaluator.eval(`
console.log(parseInt.constructor === Function); // true
`);
The only thing lost is the ability to do new evaluator.Function(...) from the outside, but this could be fixed easily by providing a evaluator.createFunction or similar that changes what evaluator context the created function belongs to.
Frankly I think new Module(...) and eval should do the same so that we don't need to install these objects on the evaluator's globalThis at all. No power would actually be lost in such an approach, if we want to expose the outer eval/Function/Module we can just wrap it (just like we could do with import(...) today):
const evaluator = new Evaluator({ ...globalThis, SOME_GLOBAL: "SOME_GLOBAL" });
evaluator.eval(`
// Would just work as eval would capture current script/module and get it's associated
// evaluator
console.log(eval.call(null, "SOME_GLOBAL"));
`);
// But we can still customize eval by wrapping with a closure that ensures the nearest script/module record is our current level
const evaluator2 = new Evaluator({
globalThis: {
...globalThis,
eval: (arg) => eval.call(null, arg),
SOME_GLOBAL: "SOME_GLOBAL",
},
});
globalThis.SOME_GLOBAL = "NOT_IN_EVALUATOR";
evaluator2.eval(`
console.log(eval.call(null, "SOME_GLOBAL")); // NOT_IN_EVALUATOR
`);