The way Avatar/js project ( https://java.net/projects/avatar-js )
project implements CommonJS/require is as follows.
It creates a anonymous function code wrapping a module (say like
http.js). The anonymous function accepts 'exports' as argument. When you
eval that code at top level global scope, because of the anon function
wrapping around, all top level vars in a module code like http.js become
locals of that anon function. Only whatever is passed as 'exports' is
populated with exposed functions and variables. The caller can pass
suitable 'exports' object as argument when calling the anon functions.
-Sundar
On Wednesday 11 December 2013 06:34 PM, Tim Fox wrote:
On 11/12/13 12:53, Attila Szegedi wrote:
On Dec 11, 2013, at 1:13 PM, Tim Fox <[email protected]> wrote:
Confused...
I assumed that if two scripts where run with their own script
context, then they would already have separate globals, i.e. if I do
myglobal = 1
in module 1 that won't be visible in module 2.
That's true, but then you also end up with the need for
ScriptObjectMirrors between them, and that was what I suggested you
try to avoid.
So I'm not sure really what --global-per-engine really means, if the
modules have their own globals anyway. I guess my understanding must
be wrong somewhere.
Well, it will mean that modules won't have their own globals…
--global-per-engine will make it so that the Global object is
specific to the ScriptEngine, and not to the ScriptContext, e.g. even
if you replace the ScriptContext of the engine, when scripts are run,
they'll still see the same global object as before the replacement.
The gist of it is:
a) without --global-per-engine, the Global object is specific to a
ScriptContext, each ScriptContext has its own. ENGINE_SCOPE Bindings
object of the context is actually a mirror of its Global.
b) with --global-per-engine, the Global object lives in the
ScriptEngine. ENGINE_SCOPE Bindings object of the context is just a
vanilla SimpleBindings (or whatever you set it to), and Global will
delegate property getters for non-existent properties to it (but
it'll still receive property setters, so new global variables created
by one script will be visible by another; no isolation there).
What I was suggesting is that your module loading code would look
something like:
// The engine that you use
ScriptEngine engine = new NashornScriptEngine().getEngine(new
String[] { "--global-per-engine" });
...
// when loading a module
Bindings moduleVars = new SimpleBindings();
moduleVars.put("require", requireFn);
moduleVars.put("module", moduleDescriptorObj);
moduleVars.put("exports", exportsObj);
Bindings prevBindings = engine.getBindings(ENGINE_SCOPE);
engine.setBindings(moduleVars, ENGINE_SCOPE);
try {
engine.eval(moduleSource);
} finally {
engine.setBindings(prevBindings, ENGINE_SCOPE);
}
return exportsObj;
NB: your modules would _not_ run in isolated globals.
That's the problem. CommonJS modules need to run in their own globals
to avoid pollution.
I thought they do, but I just spoke to Sundar and he explained the
mechanism to me so now I see they won't -- see above the case b).
They could pollute each other's global namespace (since it's shared).
Hopefully they'd adhere to Modules/Secure recommendation and refrain
from doing so, but you can't really enforce it.
Just about every CommonJS module I have seen uses globals, e.g. pretty
much all the node.js ones (and the vert.x ones). Here's an example:
https://github.com/joyent/node/blob/master/lib/http.js
Notice the liberal use of top level vars, which are globals. This is
the norm in CommonJS AIUI. Any require() implementation that doesn't
provide global isolation isn't going to fly.
My require() implementation in Rhino could provide real isolation,
but this is unfortunately impossible in Nashorn. Nashorn makes an
assumption that the global object during execution of a script is an
instance of jdk.nashorn.internals.objects.Global; in Rhino, it could
have been anything so there I was able to run a module in a new scope
that had the actual Global object as its prototype, so it could catch
all variable assignments in itself and essentially make the Global
read only (albeit objects in it mutable - e.g. a module could still
extend Array prototype etc.).
In Nashorn, it's the other way round with "--global-per-engine" -
Global object is the one immediately visible to scripts, and
ENGINE_SCOPE Bindings object is used as source of properties that
aren't found in the Global. Here, Global catches variable assignments
and ENGINE_SCOPE Bindings object ends up being immutable (although
objects in it are obviously still mutable, so the module can build up
its "exports" object).
Basically, you have a choice between having shared globals (no
isolation) without mirrors (and then you won't run into any issues
with mirrors), or separate globals (with real isolation), but then
also mirrors, and then you might run into limitations of mirrors
(e.g. they can't be automatically used as Runnable etc. callbacks
from Java and so on.)
Mirrors also don't seem to work in the way required for a require()
implementation, as described in my previous experiments :(
So... the way it looks right now, I can't see anyway I can create a
working commonJS require() implementation with Nashorn as it stands
currently. This would be a showstopper for having Nashorn support in
Vert.x which is dissappointing as I was impressed by the good
performance :(
Having said that, apparently avatar.js does implement require() so I'm
puzzled how it manages that. I did look at the avatar.js code but
couldn't find their require implementation. Does anyone have any
pointers?
I'm not trying to justify or otherwise qualify any of the design
decisions here, just trying to help you understand its constraints.
Attila.