[ACCEPTED]-Load "Vanilla" Javascript Libraries into Node.js-commonjs
Here's what I think is the 'rightest' answer 15 for this situation.
Say you have a script 14 file called quadtree.js
.
You should build a custom node_module
that 13 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 12 in your ./node_modules/quadtree/quadtree-lib/
directory are files from your 3rd 11 party library.
Then your ./node_modules/quadtree/index.js
file will just 10 load that library from the filesystem and 9 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 8 you can use your quadtree
module like any other 7 node module...
var qt = require('quadtree');
qt.QuadTree();
I like this method because 6 there's no need to go changing any of the 5 source code of your 3rd party library--so 4 it's easier to maintain. All you need to 3 do on upgrade is look at their source code 2 and ensure that you are still exporting 1 the proper objects.
There is a much better method than using 11 eval
: the vm
module.
For example, here is my execfile
module, which 10 evaluates the script at path
in either context
or the 9 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 8 example.js
contains:
function getSomeGlobal() {
return someGlobal;
}
The big advantage of this method 7 is that you've got complete control over 6 the global variables in the executed script: you 5 can pass in custom globals (via context
), and all 4 the globals created by the script will be 3 added to context
. Debugging is also easier because 2 syntax errors and the like will be reported 1 with the correct file name.
The simplest way is: eval(require('fs').readFileSync('./path/to/file.js', 'utf8'));
This works great for 1 testing in the interactive shell.
AFAIK, that is indeed how modules must be 14 loaded.
However, instead of tacking all 13 exported functions onto the exports
object, you 12 can also tack them onto this
(what would otherwise 11 be the global object).
So, if you want to 10 keep the other libraries compatible, you 9 can do this:
this.quadTree = function () {
// the function's code
};
or, when the external library 8 already has its own namespace, e.g. jQuery
(not 7 that you can use that in a server-side environment):
this.jQuery = jQuery;
In 6 a non-Node environment, this
would resolve to 5 the global object, thus making it a global 4 variable... which it already was. So it 3 shouldn't break anything.
Edit: James Herdman 2 has a nice writeup about node.js for beginners, which 1 also mentions this.
I'm not sure if I'll actually end up using 10 this because it's a rather hacky solution, but 9 one way around this is to build a little 8 mini-module importer like this...
In the 7 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 6 functionality you'll need to manually choose 5 which names to export.
So for a library 4 like the file ./lib/mylibrary.js
...
function Foo() { //Do something... }
biz = "Blah blah";
var bar = {'baz':'filler'};
When you want to use its 3 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 2 know how well this would all work in practice 1 though.
I was able to make it work by updating their 7 script, very easily, simply adding module.exports =
where 6 appropriate...
For example, I took their file 5 and I copied to './libs/apprise.js'. Then 4 where it starts with
function apprise(string, args, callback){
I assigned the function 3 to module.exports =
thus:
module.exports = function(string, args, callback){
Thus I'm able to import the library 2 into my code like this:
window.apprise = require('./libs/apprise.js');
And I was good to go. YMMV, this 1 was with webpack.
A simple include(filename)
function with better error messaging 5 (stack, filename etc.) for eval
, in case of 4 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 3 need to specify this:
export NODE_MODULE_CONTEXTS=1
nodejs tmp.js
Otherwise you cannot 2 use global variables in files included with 1 include(...)
.
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.