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

Posted

技术标签:

【中文标题】将“Vanilla”Javascript 库加载到 Node.js 中【英文标题】:Load "Vanilla" Javascript Libraries into Node.js 【发布时间】:2011-07-07 11:19:54 【问题描述】:

有一些第三方 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 使用的模块加载模式实际上是最近开发的加载 Javascript 库的标准的一部分,称为 CommonJS。 module doc page for Node.js 上就这么写了,但直到现在我都错过了。

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

【问题讨论】:

相关问题:***.com/questions/22898080/… 【参考方案1】:

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

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

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

我喜欢这种方法,因为无需更改 3rd 方库的任何源代码——因此更易于维护。升级时您需要做的就是查看它们的源代码并确保您仍在导出正确的对象。

【讨论】:

刚刚找到您的答案(制作多人游戏并需要在服务器和客户端上包含我们的物理引擎 JigLibJS),您为我节省了很多时间和麻烦。谢谢! 如果您完全按照此操作,请记住,使用 NPM 很容易意外删除您的 node_modules 文件夹,尤其是在您不将其检入 SCM 的情况下。绝对考虑将您的 QuadTree 库放在单独的存储库中,然后将其 npm linking 到您的应用程序中。然后将其视为原生 Node.js 包进行处理。 @btown,您能否为像我这样的新手扩展一下 SCM 和 npm 链接究竟做了什么来防止您提到的潜在问题? 如果我只想包含一个脚本,这真的有必要吗? @flion 回复其他人的旧评论,因为我相信你现在知道你会回答。 SCM - 源代码控制管理(例如 GIT)和一个快速但很好的 @987654321 演示的链接@【参考方案2】:

有一个比使用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;

这种方法的最大优点是您可以完全控制执行脚本中的全局变量:您可以传入自定义全局变量(通过context),脚本创建的所有全局变量都将被添加到context。调试也更容易,因为语法错误等会用正确的文件名报告。

【讨论】:

如果context(在文档中也称为sandbox)未定义,runInNewContext 是否使用全局上下文? (我发现的任何文档都没有明确这一点) 看来,为了玩弄不懂 Node 或 CommonJS 模式的第三方库,Christopher 的 eval 方法 ***.com/a/9823294/1450294> 效果很好。在这种情况下,vm 模块可以提供什么好处? 查看我的更新,了解为什么这种方法比 eval 更好。 完全 摇滚——它让我可以立即将我的基于 Web 的非模块代码重新用于服务器端实现,通过电子邮件发送输出 [按计划] 而不是在网页上显示它们。所有的网络代码都使用了松散增强模块模式和脚本注入——所以这很好用!! 如果 example.js 依赖于 example1.js 库,我们如何在 Node.js 中使用它?【参考方案3】:

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

【讨论】:

干杯伙伴!帮了大忙 这也是最快的方式,有时又快又脏是你需要的。在这和大卫的回答之间,这个 SO 页面是一个很好的资源。【参考方案4】:

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

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

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

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

this.jQuery = jQuery;

在非节点环境中,this 将解析为全局对象,从而使其成为全局变量......它已经是。所以它不应该破坏任何东西。

编辑: James Herdman 有一个 nice writeup 关于 node.js 的初学者,其中也提到了这一点。

【讨论】:

“this”技巧听起来是一种让事情更便携的好方法,以便可以在 Node.js 之外使用 Node.js 库,但这仍然意味着我需要手动更改我的 javascript 库支持 Node.js 需要语法。 @ChrisW.:是的,您必须手动更改您的库。就个人而言,我还希望使用第二种机制来包含外部文件,一种自动将包含文件的全局名称空间转换为导入名称空间的机制。也许您可以向 Node 开发人员提交 RFE?【参考方案5】:

我不确定我是否真的会最终使用它,因为它是一个相当老套的解决方案,但解决这个问题的一种方法是构建一个像这样的小型迷你模块导入器......

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

但不知道这一切在实践中的效果如何。

【讨论】:

嘿,哇:同一个用户对同一个问题投了反对票(不是我)和赞成票的答案!应该有一个徽章! ;-)【参考方案6】:

我可以很容易地通过更新他们的脚本来使其工作,只需在适当的地方添加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。

【讨论】:

【参考方案7】:

一个简单的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(...) 包含的文件中使用全局变量。

【讨论】:

以上是关于将“Vanilla”Javascript 库加载到 Node.js 中的主要内容,如果未能解决你的问题,请参考以下文章

将数据从 vanilla Javascript 传递到 React 的最佳方式? [关闭]

将动态数据从数组对象插入到引导模式中 - vanilla javascript

尝试在 Vanilla JS 中创建 Web 组件库,你会如何开始?

如何使用 vanilla javascript 通过 ajax 将多张照片上传到 Laravel 应用程序?

将对象列表打印为 vanilla javascript 中的链接

vanilla javascript图像缩放滚动效果