1

I'm developing an application with node.js and socket.io. I have built a small-scale project with some variables and functions inside a connection-specific block. These functions have access to the variables declared within that block without the need to pass the values in specifically.

This works fine, and is acceptable for a project of this size. However, as I was trying to clean the code up a bit, I looked into factoring those functions out into their own file and found modules declared by using exports.functionname as described here: http://nodejs.org/docs/v0.3.2/api/modules.html

However, these functions do not have access to the variables within the same block as they normally do when being require()'d in instead of actually being declared in the file.

Is there some way to make functions in an external file behave as if they were declared locally in nodejs?

3 Answers 3

2

There isn't without hacking the module system, which is exactly what I did. https://github.com/Benvie/Node.js-Ultra-REPL/blob/master/lib/ScopedModule.js

I can't say I recommend it for production. Basically the issue is more with JavaScript itself. Node wraps modules in a function so they have their own private scope. The only way to share in that scope is to be executed inside that function which wouldn't really work for a module (modular...) system. The only other scope is global which also isn't desirable.

The trick I used to get around it is in changing that wrapper function to have dynamic properties based on an externally defined set of module imports so that from inside the module wrapper all those parameters look like they're magically defined but aren't global.

Node's module wrapper looks like this:

(function (exports, module, require, __filename, __dirname){
  /**module**/
})

Where mine simply adds more parameters and ensures they're resolved before executing the module wrapper.

I have it read a file named ".deps.json" in a given folder before loading a module. An example one would be like this

[{
    "providers": [ "./utility/",
                   "./settings/" ],
    "receivers": [ "*" ]
}]

So it will load the modules in those subfolders and then expose each one as a parameter in the wrapper, named based on the filename.

Sign up to request clarification or add additional context in comments.

Comments

1

The usual and clean way would be to define a function in your module, that takes the required variables as parameters:

// extracted_file.js
exports.handler = function(param1, param2) {
  return param1 + param2;
}

// caller file
var extractedHandler = require('./extracted_file').handle;

var localVar1 = 1300,
    localVar2 = 37;
console.log(extractedHandler(localVar1, localVar2));

Comments

0

You can change function scope using toString() and the dreaded eval.

If your functions were in say lib.js as follows:-

var lib = {}
lib.foo = function() { console.log(var1) }
lib.bar = function() { console.log(var2) }
module.exports = lib

In main.js you could have this:-

var var1 = 1
var var2 = 2
var ob1 = require('./lib')
ob1.foo()  
ob1.bar() 

When you run it the ob1.foo() line gives a ReferenceError, var1 is not defined. This is because the scope of foo() comes from lib.js and not your main js file, and var1 is invisible to it. ob1.foo is a reference to a function with lib.js scope. You need to make a new reference with main.js scope.

To do this convert each function to string and eval it. That will create a new function with the same code but with main.js scope. Here's one way using a setMainScope method which loops through all the functions changing their scope to main.js.

var var1 = 1
var var2 = 2
var ob1 = {}

ob1.setMainScope = function(ob2) {
for(var prop in ob2) {
    if( !ob2.hasOwnProperty(prop) ) {continue}
    if( typeof ob2[prop] !== 'function' ) {continue}
    this[prop] = eval( "(" + ob2[prop].toString() + ")" )
}
}

ob1.setMainScope( require('./lib') )
ob1.foo()  // 1
ob1.bar()  // 2

Now it all works nicely except eval was used. The scope of foo() and bar() is now main.js and so they 'see' var1 and var2.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.