将“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。