Node.jsサーバーで使用したい機能を備えたサードパーティのJavascriptライブラリがいくつかあります。 (具体的には、見つけた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が初めてなので、それがどのように機能するかを理解する上で大きな穴があるかどうかを教えてください。
[〜#〜] edit [〜#〜]:さらに調べてみると、Node.jsが使用するモジュール読み込みパターンが実際にあることがわかりました CommonJS と呼ばれるJavascriptライブラリをロードするために最近開発された標準の一部。 Node.jsのモジュールドキュメントページ でこれが正しいと言われていますが、今までそれを逃していました。
私の質問に対する答えは、「あなたのライブラリの作者がCommonJSインターフェースの作成に取り掛かるまで待つか、自分でやるまで待つ」ということになるかもしれません。
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
に。構文エラーなどが正しいファイル名で報告されるため、デバッグも簡単です。
この状況に対する「正しい」答えは次のとおりです。
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();
サードパーティのライブラリのソースコードを変更する必要がないため、この方法が気に入っています。したがって、保守が容易です。アップグレード時に行う必要があるのは、ソースコードを見て、適切なオブジェクトをエクスポートしていることを確認することだけです。
最も簡単な方法は次のとおりです。eval(require('fs').readFileSync('./path/to/file.js', 'utf8'));
これは、対話型シェルでのテストに最適です。
私の知る限り、それが実際にモジュールをロードする方法です。ただし、エクスポートされたすべての関数をexports
オブジェクトに追加する代わりに、this
(グローバルオブジェクトになります)に追加することもできます。
したがって、他のライブラリとの互換性を保ちたい場合は、次の操作を実行できます。
this.quadTree = function () {
// the function's code
};
または、外部ライブラリが既に独自の名前空間を持っている場合、例えばjQuery
(サーバーサイド環境でthatを使用できるわけではありません):
this.jQuery = jQuery;
非ノード環境では、this
はグローバルオブジェクトに解決されるため、グローバル変数になります...したがって、何も壊してはいけません。
Edit:James Herdmanには初心者向けのnode.jsについて Nice writeup があり、これについても言及しています。
これはかなりハックなソリューションであるため、実際にこれを使用するかどうかはわかりませんが、これを回避する方法の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)
しかし、これがどれだけ実際に機能するかはわかりません。
適切にmodule.exports =
を追加するだけで、非常に簡単にスクリプトを更新することで機能させることができました...
たとえば、theirファイルを取得し、「./ 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 でした。
エラーが発生した場合のeval
のエラーメッセージ(スタック、ファイル名など)が改善された単純なinclude(filename)
関数:
_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(...)
に含まれるファイルでグローバル変数を使用できません。