「バニラ」JavaScriptライブラリをNode.jsにロードする


108

Node.jsサーバーで使用したいいくつかの機能を持つサードパーティのJavaScriptライブラリがいくつかあります。(具体的には、見つけたQuadTree javascriptライブラリを使用したいと思います。)しかし、これらのライブラリは単純な.jsファイルであり、「Node.jsライブラリ」ではありません。

そのため、これらのライブラリはexports.var_name、Node.jsがモジュールに期待する構文に従っていません。私が理解する限り、それはあなたがそうするとき、module = require('module_name');またはmodule = require('./path/to/file.js');あなたが公にアクセス可能な関数などのないモジュールで終わることになるということを意味します。

私の質問は、「Node.jsに任意のjavascriptファイルをロードして、その機能を利用できるように書き直す必要がないようにするにはどうすればよいexportsですか?」です。

私はNode.jsを初めて使用するので、それがどのように機能するかについて私の理解に明白な穴があるかどうかを知らせてください。


編集:もっと物事を研究していて、Node.jsが使用するモジュール読み込みパターンが、CommonJSと呼ばれるJavascriptライブラリを読み込むために最近開発された標準の一部であることがわかりました。Node.jsのモジュールドキュメントページでこのように書かれていますが、今まで見逃していました。

私の質問に対する答えは、「あなたのライブラリの作者がCommonJSインターフェースの作成に取り掛かるか、それをあなた自身の気の利いたものにするまで待つ」ということになるかもしれません。


回答:


75

モジュールを使用するよりもはるかに優れた方法がevalありvmます。

たとえば、次のコードは、いずれかのコンテキストまたはグローバルコンテキストで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ベースの非モジュールコードをすぐに再利用できました。すべてのWebコードはルーズ拡張モジュールパターンとスクリプトインジェクションを使用したため、これは非常にうまく機能します。
Al Joslin、2015

example.jsがexample1.jsライブラリに依存している場合、Node.jsでこれをどのように使用できますか?
sytolk 2017年

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/ディレクトリ内のすべては、サードパーティライブラリのファイルです。

次に、./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
答えを見つけただけで(マルチプレイヤーゲームを作成し、サーバーとクライアントに物理エンジンであるJigLibJSを含める必要がありました)、時間と手間を大幅に節約できました。ありがとうございました!
stevendesu

8
これに正確に従っている場合、特にSCMにチェックインしない場合は、NPMを使用してnode_modulesフォルダーを誤って消去してしまう可能性が非常に高いことに注意してください。QuadTreeライブラリを別のリポジトリに配置し、それをnpm linkアプリケーションに組み込むことを検討してください。その後、ネイティブのNode.jsパッケージであるかのように処理されます。
btown 2012年

@btown、私のような初心者のために、SCMとnpmリンクがあなたが言及する潜在的な問題を防ぐために正確に何をしているのかを少し拡大していただけませんか?
Flion、2015年

スクリプトを含めるだけの場合、これは本当に必要ですか?
Quantumpotato

1
@flionは、他の人の古いコメントに返信してrefに返信します。
SCM-

30

最も簡単な方法は次のとおりです。eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); これは、対話型シェルでのテストに最適です。


1
歓声メイト!多くを助けた
13:21の

これも最も簡単な方法であり、必要なのは簡単で汚れている場合もあります。これとDavidの答えの間で、このSOページは優れたリソースです。
Michael Scheper、2015

5

AFAIK、それは確かにモジュールがロードされなければならない方法です。ただし、エクスポートされたすべての関数をexportsオブジェクトに追加する代わりに、それらをオブジェクトに追加することもできます。this(それ以外の場合はグローバルオブジェクトになります)。

したがって、他のライブラリとの互換性を維持したい場合は、次のようにします。

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

外部ライブラリが既に例えば独自の名前空間を持っている場合、または、jQuery(あなたが使用することができないということを、サーバー側の環境で):

this.jQuery = jQuery;

非ノード環境では、 thisは、グローバルオブジェクトに解決され、グローバル変数になります...すでにそうでした。だから何も壊してはいけません。

編集:James Herdmanは、これについても言及している初心者向けのnode.jsに関するすばらしい記事があります。


「これ」のトリックは、Node.jsライブラリをNode.jsの外で使用できるように、移植性を高める良い方法のように聞こえますが、それでも、Node.jsの構文をサポートするようにJavaScriptライブラリを手動で変更する必要があることを意味します。
クリスW.

@ChrisW .:はい、ライブラリを手動で変更する必要があります。個人的には、外部ファイルを含める2番目のメカニズムも必要でした。これは、含まれるファイルのグローバル名前空間をインポートされた名前空間に自動的に変換するメカニズムです。おそらく、ノード開発者にRFEを提出できますか?
Martijn

3

これはかなりハッキーなソリューションなので、実際にこれを使用するかどうかはわかりませんが、これを回避する1つの方法は、このような小さなミニモジュールインポーターをビルドすることです...

ファイル内./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(...)いるファイルでグローバル変数を使用できません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.