简体   繁体   中英

Namespace import in node.js

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.

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.

  1. First, it determines exactly which file should be loaded based on a somewhat complex set of rules .

  2. 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.

  3. 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.

  4. The module loader eval s 1 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

  5. 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:

  1. It absolutely kills performance because V8 cannot perform name lookup optimizations.
  2. It is deprecated, and is forbidden in strict mode.

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).

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM