Reputation:
I've got some function that allows to merge namespace, very similar to import
when the module contains lot's of function (I expose an API with dozens of combinators)
It generates lots of var f = target.f;
for every item from the export
function getNamespace(name, exports){
var output='';
for(var item in exports){
output += 'var ' + item + ' = '+name+ '.'+item + ';';
}
return output;
}
and usage:
var paco = require('./paco.js');
eval(paco.getNamespace('paco', paco));
// instead of paco.between(paco.start(),paco.content(),paco.end())
between(start(), content(), end())
Question:
I there a way to 'hide' the eval into the some function ? I don't want neither to mutate global namespace nor to call vm.runInThisContext
, just need to add some local variables into the calling context after call function similar to require
.
I mean I need something like
import('./paco');
// this should work like this
// var paco = require('./paco.js');
// var between = paco.between;
but without mutation of global and without eval in the calling scope.
Upvotes: 4
Views: 5159
Reputation: 144912
tl;dr: No.
In order to understand why this is impossible, it's important to understand what Node is doing behind the scenes.
Let's say we define a function in test.js:
function foo() {
var msg = 'Hello world';
console.log(msg);
}
In traditional browser JavaScript, simply putting that function declaration in a file and pulling the file in with a <script>
tag would cause foo
to be declared in the global scope.
Node does things differently when you require()
a file.
First, it determines exactly which file should be loaded based on a somewhat complex set of rules.
Assuming that the file is JS text (not a compiled C++ addon), Node's module loader calls fs.readFileSync
to get the contents of the file.
The source text is wrapped in an anonymous function. test.js will end up actually looking like this:
(function (exports, require, module, __filename, __dirname) {
function foo() {
var msg = 'Hello world';
console.log(msg);
}
});
This should look familiar to anyone who has ever wrapped their own code in an anonymous function expression to keep variables from leaking into global scope in a browser. It should also start making sense how "magic" variables in Node work.
The module loader eval
s1 the source text from step 3 and then invokes the resulting anonymous function, passing in a fresh exports
object. (See Module#_compile
.)
1 - Really vm.runInThisContext
, which is like eval
except it does not have access to the caller's scope
After the anonymous wrapper function returns, the value of module.exports
is cached internally and then returned by require
. (Subsequent calls to require()
return the cached value.)
As we can see, Node implements "modules" by simply wrapping a file's source code in an anonymous function. Thus, it is impossible to "import" functions into a module because JavaScript does not provide direct access to the execution context of a function – that is, the collection of a function's local variables.
In other words, there is no way for us to loop over the local variables of a function, nor is there a way for us to create local variables with arbitrary names like we can with properties of an object.
For example, with objects we can do things like:
var obj = { key: 'value' };
for (var k in obj) ...
obj[propertyNameDeterminedAtRuntime] = someValue;
But there is no object representing the local variables of a function, which would be necessary for us to copy the properties of an object (like the exports
of a module) into the local scope of a function.
What you've done is generate code inside the current scope using eval
. The generated code declares local variables using the var
keyword, which is then injected into the scope where eval
was called from.
There is no way to move the eval
call out of your module because doing so would cause the injected code to be inserted into a different scope. Remember that JavaScript has static scope, so you're only able to access the scopes lexically containing your function.
The other workaround is to use with
, but you should avoid with
.
with (require('./paco.js')) {
between(start(), content(), end())
}
with
should not be used for two reasons:
To be honest, I'd recommend that rather than doing something tricky with eval
, do your future maintainers a favor and just follow the standard practice of assigning a module's exports to a local variable.
If you're typing it that often, make it a single-character name (or use a better editor).
Upvotes: 2
Reputation: 25091
No. It's not possible to modify the local scope from an external module. Reason being, when eval is called in the external module, its context will be the external module, not the scope requiring the module.
In addition, vm.runInThisContext does not have access to the local scope, so that wont help you either.
Upvotes: 0
Reputation: 66488
According to this answer Global variables for node.js standard modules? there is global
object the same as in browser there is window
. So you can add key to that object
function getNamespace(exports) {
for(var item in exports){
global[item] = exports[item];
}
}
and use it as:
paco.getNamespace(paco);
no need for eval at all.
Upvotes: 0