113

There are some third party Javascript libraries that have some functionality I would like to use in a Node.js server. (Specifically I want to use a QuadTree javascript library that I found.) But these libraries are just straightforward .js files and not "Node.js libraries".

As such, these libraries don't follow the exports.var_name syntax that Node.js expects for its modules. As far as I understand that means when you do module = require('module_name'); or module = require('./path/to/file.js'); you'll end up with a module with no publicly accessible functions, etc.

My question then is "How do I load an arbitrary javascript file into Node.js such that I can utilize its functionality without having to rewrite it so that it does do exports?"

I'm very new to Node.js so please let me know if there is some glaring hole in my understanding of how it works.


EDIT: Researching into things more and I now see that the module loading pattern that Node.js uses is actually part of a recently developed standard for loading Javascript libraries called CommonJS. It says this right on the module doc page for Node.js, but I missed that until now.

It may end up being that the answer to my question is "wait until your library's authors get around to writing a CommonJS interface or do it your damn self."

Chris W.
  • 37,583
  • 36
  • 99
  • 136
  • related question: http://stackoverflow.com/questions/22898080/how-to-create-an-enviroment-agnostic-javascript-library – Josmar Apr 06 '14 at 18:41

7 Answers7

81

Here's what I think is the 'rightest' answer for this situation.

Say you have a script file called quadtree.js.

You should build a custom node_module that has this sort of directory structure...

./node_modules/quadtree/quadtree-lib/
./node_modules/quadtree/quadtree-lib/quadtree.js
./node_modules/quadtree/quadtree-lib/README
./node_modules/quadtree/quadtree-lib/some-other-crap.js
./node_modules/quadtree/index.js

Everything in your ./node_modules/quadtree/quadtree-lib/ directory are files from your 3rd party library.

Then your ./node_modules/quadtree/index.js file will just load that library from the filesystem and do the work of exporting things properly.

var fs = require('fs');

// Read and eval library
filedata = fs.readFileSync('./node_modules/quadtree/quadtree-lib/quadtree.js','utf8');
eval(filedata);

/* The quadtree.js file defines a class 'QuadTree' which is all we want to export */

exports.QuadTree = QuadTree

Now you can use your quadtree module like any other node module...

var qt = require('quadtree');
qt.QuadTree();

I like this method because there's no need to go changing any of the source code of your 3rd party library--so it's easier to maintain. All you need to do on upgrade is look at their source code and ensure that you are still exporting the proper objects.

Chris W.
  • 37,583
  • 36
  • 99
  • 136
  • 3
    Just found your answer (making a multiplayer game and needed to include JigLibJS, our physics engine, on the server as well as the client) you saved me a LOT of time and hassle. Thank you! – stevendesu Mar 29 '12 at 17:36
  • 8
    If you follow this exactly, keep in mind that it's pretty easy to accidentally obliterate your node_modules folder using NPM, especially if you don't check it into SCM. Definitely consider putting your QuadTree library in a separate repository, then `npm link`ing it into your application. Then it's handled as if it were a native Node.js package. – btown May 10 '12 at 10:07
  • @btown, could you expand a bit for the newbies like me what SCM and npm link do exactly that prevents the potential issue you mention? – Flion Jan 22 '15 at 17:38
  • Is this really neccessary if I just want to include a script? – quantumpotato Sep 05 '15 at 21:03
  • 1
    @flion replying to the old comment for others ref as I'm sure you'll know you answer by now. SCM - Source Control Management (e.g. GIT) and a link to a quick but good demo of [npm link](https://egghead.io/lessons/node-js-using-npm-link-to-use-node-modules-that-are-in-progress) – delp Aug 10 '16 at 09:13
78

There is a much better method than using eval: the vm module.

For example, here is my execfile module, which evaluates the script at path in either context or the global context:

var vm = require("vm");
var fs = require("fs");
module.exports = function(path, context) {
  context = context || {};
  var data = fs.readFileSync(path);
  vm.runInNewContext(data, context, path);
  return context;
}

And it can be used like this:

> var execfile = require("execfile");
> // `someGlobal` will be a global variable while the script runs
> var context = execfile("example.js", { someGlobal: 42 });
> // And `getSomeGlobal` defined in the script is available on `context`:
> context.getSomeGlobal()
42
> context.someGlobal = 16
> context.getSomeGlobal()
16

Where example.js contains:

function getSomeGlobal() {
    return someGlobal;
}

The big advantage of this method is that you've got complete control over the global variables in the executed script: you can pass in custom globals (via context), and all the globals created by the script will be added to context. Debugging is also easier because syntax errors and the like will be reported with the correct file name.

Guy
  • 65,082
  • 97
  • 254
  • 325
David Wolever
  • 148,955
  • 89
  • 346
  • 502
  • Does `runInNewContext` use the global context if `context` (otherwise referred to as `sandbox`, in docs) is undefined? (this point was not made clear by any docs I found) – Steven Lu Jan 23 '14 at 21:43
  • It seems that, for the purpose of playing with a third-party library ignorant of Node or the CommonJS pattern, Christopher's eval method works well. What benefits can the `vm` module offer in this case? – Michael Scheper Feb 26 '15 at 00:00
  • 2
    See my updates for a description of why this method is better than eval. – David Wolever Feb 26 '15 at 00:12
  • 1
    this _totally_ rocks -- it allowed me to instantly re-use my web-based non-module code for a server-side implementation that emails the outputs [on a schedule] instead of displaying them on a webpage. All the web code used the loose-augmenting module pattern and script injection -- so this works so fine !! – Al Joslin Dec 07 '15 at 22:22
  • How can we use this in Node.js if example.js depends on example1.js library? – sytolk May 12 '17 at 05:12
  • The npm module rewire is really good for this. It's basically the same idea. Instead of: var dep = require('someDep') you do var context = rewire('someDep'); context.__get__('someDep'). We use it here, mostly as a way of mocking objects (you can override functions via the context), but same idea. – Robert Christ Nov 03 '17 at 16:45
31

The simplest way is: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); This works great for testing in the interactive shell.

5

AFAIK, that is indeed how modules must be loaded. However, instead of tacking all exported functions onto the exports object, you can also tack them onto this (what would otherwise be the global object).

So, if you want to keep the other libraries compatible, you can do this:

this.quadTree = function () {
  // the function's code
};

or, when the external library already has its own namespace, e.g. jQuery (not that you can use that in a server-side environment):

this.jQuery = jQuery;

In a non-Node environment, this would resolve to the global object, thus making it a global variable... which it already was. So it shouldn't break anything.

Edit: James Herdman has a nice writeup about node.js for beginners, which also mentions this.

Martijn
  • 13,225
  • 3
  • 48
  • 58
  • The 'this' trick sounds like a good way to make things more portable so that Node.js libraries can be used outside of Node.js, but it still means I need to manually change my javascript libraries to support the Node.js require syntax. – Chris W. Mar 02 '11 at 19:03
  • @ChrisW.: yes, you will have to manually change your libraries. Personally, I also would've liked a second mechanism to include external files, one that automatically converted the included file's global namespace to the imported namespace. Perhaps you could file a RFE to the Node developers? – Martijn Mar 03 '11 at 07:02
4

I'm not sure if I'll actually end up using this because it's a rather hacky solution, but one way around this is to build a little mini-module importer like this...

In the file ./node_modules/vanilla.js:

var fs = require('fs');

exports.require = function(path,names_to_export) {
    filedata = fs.readFileSync(path,'utf8');
    eval(filedata);
    exported_obj = {};
    for (i in names_to_export) {
        to_eval = 'exported_obj[names_to_export[i]] = ' 
            + names_to_export[i] + ';'
        eval(to_eval); 
    }
    return exported_obj;
}

Then when you want to use your library's functionality you'll need to manually choose which names to export.

So for a library like the file ./lib/mylibrary.js...

function Foo() { //Do something... }
biz = "Blah blah";
var bar = {'baz':'filler'};

When you want to use its functionality in your Node.js code...

var vanilla = require('vanilla');
var mylibrary = vanilla.require('./lib/mylibrary.js',['biz','Foo'])
mylibrary.Foo // <-- this is Foo()
mylibrary.biz // <-- this is "Blah blah"
mylibrary.bar // <-- this is undefined (because we didn't export it)

Don't know how well this would all work in practice though.

Chris W.
  • 37,583
  • 36
  • 99
  • 136
  • Hey, wow: a down-voted (not by me) and up-voted answer by the same user for the same question! There should be a badge for that! ;-) – Michael Scheper Feb 26 '15 at 02:49
2

I was able to make it work by updating their script, very easily, simply adding module.exports = where appropriate...

For example, I took their file and I copied to './libs/apprise.js'. Then where it starts with

function apprise(string, args, callback){

I assigned the function to module.exports = thus:

module.exports = function(string, args, callback){

Thus I'm able to import the library into my code like this:

window.apprise = require('./libs/apprise.js');

And I was good to go. YMMV, this was with webpack.

John Mee
  • 50,179
  • 34
  • 152
  • 186
0

A simple include(filename) function with better error messaging (stack, filename etc.) for eval, in case of errors:

var fs = require('fs');
// circumvent nodejs/v8 "bug":
// https://github.com/PythonJS/PythonJS/issues/111
// http://perfectionkills.com/global-eval-what-are-the-options/
// e.g. a "function test() {}" will be undefined, but "test = function() {}" will exist
var globalEval = (function() {
    var isIndirectEvalGlobal = (function(original, Object) {
        try {
            // Does `Object` resolve to a local variable, or to a global, built-in `Object`,
            // reference to which we passed as a first argument?
            return (1, eval)('Object') === original;
        } catch (err) {
            // if indirect eval errors out (as allowed per ES3), then just bail out with `false`
            return false;
        }
    })(Object, 123);
    if (isIndirectEvalGlobal) {
        // if indirect eval executes code globally, use it
        return function(expression) {
            return (1, eval)(expression);
        };
    } else if (typeof window.execScript !== 'undefined') {
        // if `window.execScript exists`, use it
        return function(expression) {
            return window.execScript(expression);
        };
    }
    // otherwise, globalEval is `undefined` since nothing is returned
})();

function include(filename) {
    file_contents = fs.readFileSync(filename, "utf8");
    try {
        //console.log(file_contents);
        globalEval(file_contents);
    } catch (e) {
        e.fileName = filename;
        keys = ["columnNumber", "fileName", "lineNumber", "message", "name", "stack"]
        for (key in keys) {
            k = keys[key];
            console.log(k, " = ", e[k])
        }
        fo = e;
        //throw new Error("include failed");
    }
}

But it even gets dirtier with nodejs: you need to specify this:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

Otherwise you cannot use global variables in files included with include(...).

kungfooman
  • 4,473
  • 1
  • 44
  • 33