有一些第三方Javascript库具有我想在Node.js服务器中使用的一些功能。 (具体来说,我想使用我发现的QuadTree javascript库。)但这些库只是简单的.js
文件,而不是“Node.js库”。
因此,这些库不遵循Node.js对其模块所期望的exports.var_name
语法。据我了解,这意味着当您执行module = require('module_name');
或module = require('./path/to/file.js');
时,您最终会得到一个没有可公开访问功能的模块等。
我的问题是“我如何将任意javascript文件加载到Node.js中,以便我可以利用其功能而不必重写它以便它确实exports
?”
我对Node.js很新,所以如果我对它的工作原理有一些明显的漏洞,请告诉我。
编辑:研究更多内容我现在看到Node.js使用的模块加载模式实际上是最近开发的用于加载名为CommonJS的Javascript库的标准的一部分。它在module doc page for Node.js上说得对,但直到现在我都错过了。
最终可能是我的问题的答案是“等到你的图书馆的作者开始编写一个CommonJS界面或者做你自己该死的自己。”
答案 0 :(得分:78)
对于这种情况,我认为这是“最正确”的答案。
假设您有一个名为quadtree.js
的脚本文件。
您应该构建具有此类目录结构的自定义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();
我喜欢这种方法,因为无需更改第三方库的任何源代码 - 因此更容易维护。您在升级时需要做的就是查看源代码并确保仍在导出正确的对象。
答案 1 :(得分:73)
有一种比使用eval
更好的方法:vm
模块。
例如,这是我的execfile
模块,它评估path
中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;
}
它可以像这样使用:
> 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;
}
此方法的最大优点是您可以完全控制已执行脚本中的全局变量:您可以传入自定义全局变量(通过context
),并且脚本创建的所有全局变量都将被添加到context
。调试也更容易,因为语法错误等将以正确的文件名报告。
答案 2 :(得分:29)
最简单的方法是:eval(require('fs').readFileSync('./path/to/file.js', 'utf8'));
这非常适合在交互式shell中进行测试。
答案 3 :(得分:5)
AFAIK,确实是必须加载模块的。
但是,不是将所有导出的函数都添加到exports
对象上,而是将它们添加到this
(否则将是全局对象)。
因此,如果您想保持其他库兼容,您可以这样做:
this.quadTree = function () {
// the function's code
};
或者,当外部库已经拥有自己的命名空间时,例如jQuery
(并非您可以在服务器端环境中使用 ):
this.jQuery = jQuery;
在非Node环境中,this
将解析为全局对象,从而使其成为全局变量......它已经存在。所以它不应该破坏任何东西。
修改强>: 詹姆斯赫德曼有一个nice writeup关于初学者的node.js,也提到了这一点。
答案 4 :(得分:3)
我不确定我是否真的会最终使用它,因为它是一个相当hacky的解决方案,但是解决这个问题的一种方法是构建一个像这样的小型模块导入器......
在文件./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;
}
然后,当您想要使用库的功能时,您需要手动选择要导出的名称。
对于像文件./lib/mylibrary.js
...
function Foo() { //Do something... }
biz = "Blah blah";
var bar = {'baz':'filler'};
如果要在Node.js代码中使用其功能......
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)
不知道这在实践中的效果如何。
答案 5 :(得分:2)
我能够通过更新脚本轻松完成工作,只需在适当的位置添加module.exports =
即可...
例如,我拿了他们的文件,然后我复制到'./libs/apprise.js'。然后从
开始function apprise(string, args, callback){
我将功能分配给module.exports =
因此:
module.exports = function(string, args, callback){
因此,我可以将库导入 my 代码,如下所示:
window.apprise = require('./libs/apprise.js');
我很高兴。 YMMV,这是webpack。
答案 6 :(得分:0)
一个简单的include(filename)
函数,如果出现错误,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/
// 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");
}
}
但是对于nodejs来说它甚至更脏:你需要指定:
export NODE_MODULE_CONTEXTS=1
nodejs tmp.js
否则,您无法在include(...)
附带的文件中使用全局变量。