将“Vanilla”Javascript库加载到Node.js中

有一些第三方的Javascript库有一些我想在Node.js服务器中使用的function。 (具体来说,我想使用我find的QuadTree JavaScript库)。但是这些库只是简单的.js文件而不是“Node.js库”。

因此,这些库不遵循Node.js对其模块所期望的exports.var_name语法。 据我所知,这意味着当你做module = require('module_name'); 或者module = require('./path/to/file.js'); 你最终将得到一个没有公共function的模块等。

那么我的问题是“如何将一个任意的javascript文件加载到Node.js中,以便我可以利用它的function,而不必重写它,以便它能够exports

我对Node.js非常陌生,所以请让我知道是否有一个明显的漏洞,我了解它是如何工作的。


编辑 :研究更多的东西,我现在看到Node.js使用的模块加载模式实际上是最近开发的加载Javascript库CommonJS标准的一部分。 它在Node.js的模块doc页面上说了这个,但是直到现在我才意识到这一点。

最终可能的结果是,我的问题的答案是“等到你的图书馆的作者开始写一个CommonJS接口或者做你该死的自己”。

有一个比使用eval更好的方法: vm模块。

例如,这里是我的execfile模块,它在context或全局上下文中对path进行评估:

 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; } 

它可以像这样使用:

 > 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 

其中example.js包含:

 function getSomeGlobal() { return someGlobal; } 

这种方法的一大优点是可以完全控制已执行脚本中的全局variables:您可以传递自定义全局variables(通过context ),脚本创build的所有全局variables将被添加到context 。 debugging也更容易,因为语法错误等将以正确的文件名报告。

这是我认为这种情况的“最正确的”答案。

假设你有一个名为quadtree.js的脚本文件。

你应该build立一个具有这种目录结构的自定义node_module

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

您的./node_modules/quadtree/quadtree-lib/目录中的所有内容都是来自第三方库的文件。

然后你的./node_modules/quadtree/index.js文件就会从文件系统中加载这个库,并且正确地执行导出工作。

 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 

现在,您可以像使用其他节点模块一样使用您的quadtree模块。

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

我喜欢这种方法,因为不需要更改第三方库的任何源代码,所以维护起来更容易。 所有你需要做的升级是看他们的源代码,并确保你仍然导出适当的对象。

最简单的方法是: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); 这对于在交互式shell中进行testing非常有用。

AFAIK,这确实是如何加载模块。 但是,不要将所有导出的函数粘贴到exports对象上,而是将它们粘贴到this (否则将是全局对象)上。

所以,如果你想保持其他库兼容,你可以这样做:

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

或者,当外部库已经有自己的命名空间,如jQuery (不是你可以在服务器端环境中使用):

 this.jQuery = jQuery; 

在非节点环境中, this将parsing为全局对象,从而使其成为一个全局variables。 所以它不应该破坏任何东西。

编辑 :詹姆斯·赫德曼有一个关于node.js的初学者很好的写作 ,也提到了这一点。

我不确定是否最终会使用这个,因为这是一个相当冒险的解决scheme,但解决这个问题的方法之一就是build立一个像这样的小型模块导入器。

在文件./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; } 

然后当你想使用你的库的function,你需要手动select要导出的名称。

所以对于像./lib/mylibrary.js文件这样的库…

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

当你想在你的Node.js代码中使用它的function时…

 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) 

不知道这样做在实践中会有多好。

我能够简单地将module.exports =添加到它们的文件中的函数的脚本中。

例如, 他们的代码放在我放在'./libs/apprise.js'的文件中,以function apprise(string, args, callback){ apprise function apprise(string, args, callback){开头function apprise(string, args, callback){我把它改成了:

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

然后我的代码读取:

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

我很高兴去。 YMMV,这是与webpack 。

一个简单的include(filename)function,更好的错误信息(堆栈,文件名等)为eval ,在出现错误的情况下:

 var fs = require('fs'); // circumvent nodejs/v8 "bug": // https://github.com/PythonJS/PythonJS/issues/111 // http://perfectionkills.com/global-eval-what-are-the-options/ // eg 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"); } } 

但是它甚至会让nodejs变得更加肮脏:你需要指定这个:

 export NODE_MODULE_CONTEXTS=1 nodejs tmp.js 

否则,您不能在包含include(...)文件中使用全局variables。