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


108

我希望在Node.js服务器中使用某些具有某些功能的第三方Javascript库。(特别是我想使用找到的QuadTree javascript库。)但是这些库只是简单的.js文件,而不是“ Node.js库”。

因此,这些库不遵循exports.var_nameNode.js对其模块期望的语法。据我了解,这意味着当您这样做时,module = require('module_name');或者module = require('./path/to/file.js');最终将得到一个没有公共可访问功能的模块,等等。

然后我的问题是“我如何将一个任意的javascript文件加载到Node.js中,这样我就可以利用其功能而不必重写它就可以了exports?”

我对Node.js还是很陌生,所以请让我知道我对它的工作方式是否有明显的了解。


编辑:进行了更多的研究,现在我看到Node.js使用的模块加载模式实际上是最近开发的用于加载称为CommonJS的 Javascript库的标准的一部分。它在Node.js模块文档页面上说得很对,但是直到现在我都没想到。

我的问题的答案可能最终是“等到您的库的作者开始写CommonJS接口或自己做的那样做。”


Answers:


75

有比使用evalvm模块更好的方法。

例如,这是我的execfile模块,该模块pathcontext或全局上下文中评估脚本:

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。调试也更加容易,因为将使用正确的文件名报告语法错误等。


runInNewContext如果未定义(在文档中context称为),是否使用全局上下文sandbox?(我发现的任何文档都没有明确指出这一点)
Steven Lu

看起来,出于对不了解Node或CommonJS模式的第三方库的作用,Christopher的eval方法< stackoverflow.com/a/9823294/1450294 >效果很好。vm在这种情况下,模块可以提供什么好处?
Michael Scheper 2015年

2
有关此方法为什么比eval更好的描述,请参阅我的更新。
David Wolever,2015年

1
完全岩石-它让我立即重新使用我的基于Web的非模块代码的服务器端实现,电子邮件的输出[按计划],而不是在网页上显示它们。所有的Web代码都使用了松散扩展模块模式和脚本注入-因此,它工作得很好!
乔斯林

如果exam​​ple.js依赖example1.js库,我们如何在Node.js中使用它?
sytolk

80

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

假设您有一个名为的脚本文件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/目录中的所有内容都是来自第3方库的文件。

然后,您的./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();

我喜欢这种方法,因为无需更改您的第3方库的任何源代码,因此维护起来更容易。升级所需要做的只是查看其源代码,并确保您仍在导出正确的对象。


3
刚找到您的答案(制作多人游戏,并且需要在服务器以及客户端上都包含我们的物理引擎JigLibJS),您为我节省了很多时间和麻烦。谢谢!
stevendesu 2012年

8
如果严格按照此步骤操作,请记住,使用NPM意外删除node_modules文件夹非常容易,特别是如果您不将其检入SCM时。绝对可以考虑将QuadTree库放在单独的存储库中,然后将npm link其放入应用程序中。然后,将其视为原生Node.js包进行处理。
2012年

@btown,您能为像我这样的新手扩展一点吗?SCM和npm链接到底做了什么,以防止您提到潜在的问题?
弗利恩(Flion)2015年

如果我只想包含脚本,这真的有必要吗?
Quantumpotato 2015年

1
@flion回复了其他评论的旧评论,因为我确定您现在知道您已经回答了。
SCM-

30

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


1
队友的欢呼声!帮助很大
舍弃了

这也是最快的方法,有时您需要的是“肮脏”。在此和David的回答之间,此SO页面是一个很好的资源。
Michael Scheper

5

AFAIK,确实是必须加载模块的方式。但是,除了将所有导出的函数附加到exports对象上之外,您还可以将它们附加到this(否则将是全局对象)上。

因此,如果要保持其他库兼容,可以执行以下操作:

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

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

this.jQuery = jQuery;

在非节点环境中,this将解析为全局对象,从而使其成为全局变量……已经是。因此,它不应破坏任何内容。

编辑:詹姆斯·赫德曼(James Herdman)对于初学者有一个不错的关于node.js的文章,其中也提到了这一点。


“这个”技巧听起来像是使事情更具可移植性的好方法,以便可以在Node.js之外使用Node.js库,但这仍然意味着我需要手动更改我的JavaScript库以支持Node.js所需的语法。
克里斯·W

@ChrisW .:是的,您将必须手动更改库。就我个人而言,我还希望第二种机制包括外部文件,该机制会自动将所包含文件的全局名称空间转换为导入的名称空间。也许您可以向Node开发人员提出RFE?
Martijn

3

我不确定我是否最终会使用它,因为这是一个非常棘手的解决方案,但是解决此问题的一种方法是构建一个像这样的小微型模块导入器...

在文件中./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)

不过,不知道这在实践中将如何运作。


嘿,哇:同一用户对同一问题的投票否决(不是我本人),还是投票赞成!应该有一个徽章!;-)
Michael Scheper 2015年

2

我可以很容易地通过更新他们的脚本来使其工作,只需module.exports =在适当的地方添加...

例如,我拿走了他们的文件,然后将复制到“ ./libs/apprise.js”。然后从哪里开始

function apprise(string, args, callback){

我将功能分配给了module.exports =

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

因此,我可以像这样将库导入到我的代码中:

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

而且我很好走。YMMV,这是webpack的


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(...)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.