著作一覧 |
残念なことに、module.exports作戦はうまく行かなかった。
前提として、現在ブラウザー(Chromeだけど)で動いている結構でっかなシステムのうち、モデル相当の部分をNodeJSを使ってコマンドラインで実行したい。
実際は、requireがうまく働かないので、スクリプトファイルを直接読んでいるので、やろうと思えば、スクリプトを変形することはまったく問題ないのだが、気分の問題で、それはやらない(と、アーキテクチャを決めたおれが決めたのだから絶対なのだ)。
で、各スクリプトは以下のような形式で書かれている。
var res = res || {}; (function() { var .... ... res.foo = { // foo.jsは、res.fooを作り、bar.jsはres.barを作る。 ... }; })();
問題は、requireを使うと、NodeJSは新たなスコープ(モジュールスコープと呼べば良い?)を作ることにある。つまり、先頭のvar res
は、グローバル変数ではなく、ローカル変数だということだ。
意味としては以下に等しい。
var res = 'abc'; // requireしているスコープ var fn = function() { var res = res || '123'; // requireされたスコープ console.log(x); // => '123' }; fn();
というわけで、同じスコープでevalすれば良いというわけでそうやったわけだが、おもしろくないことがいろいろある。
一応、動けば良いとはいえ、globalを使うのはあまり良いことでもない(今回は、DOMParserだけ外部モジュールを使ったが、他にも使うかも知れないのでバッティングの可能性は減らせるだけ減らすべき)。それにローダー側(NodeJSで直接実行する部分)とは、環境は分かれているほうが良い。
もっと根本的な問題として、無理矢理ブラウザーで動いていたものを動かすうえに、でっかなスクリプト群の一部なので、依存関係が後から出てくる可能性もある(grepくらいはかけているが、ノーマークのres['abc']
にアクセスしているやつがいれば、abc.jsも含めなければまずい)。その他、動的な仕組みもいくつかあるので、動かして確認が必要だ。ということは、例外前提で動かすということになるのだが、evalをしたらファイル名も行番号も無名扱いで、辛うじて行ダンプから判断することになり、それは結構辛いものがある。
で、vmを使って、自分でコンテキストを与えて実行すれば良いということがドキュメントを眺めていてわかった。
というわけで、以下になった。
var res = {};
var window = {};
window.parent = window;
window.res = res;
window.DOMParser = require('domparser').DOMParser;
window.localStorage = { a: 'default', b: 'default' }, // 使っているやつがいた
var sandbox = { // 仮グローバル名前空間だが、関数ドキュメントの仮引数名を使
ってしまった。
res: res,
window: window,
localStorage: window.localStorage,
DOMParser: window.DOMParser, // window.でアクセスするやつとグローバルでアクセスするやつがいるのでしょうがない。
};
// むむ、今気づいたが、var sandboxではなく、windowそのものをコンテキストにしても良かったな。でも、window.window = window;
ってすごく気持ち悪い。
for (var k in global) {
sandbox[k] = global[k]; // これやらないと、consoleもsetTimeoutも使えない。シャローコピーで十分だ。
}
vm.createContext(sandbox); // 忘れるとvmに怒られる。
function load(s) {
var script = fs.readFileSync(s);
vm.runInContext(script.toString(), sandbox, s); // 第3引数でファイル名を与えると例外のバックトレースが正しく表示される
}
// ここまで用意して
load('foo.js');
load('bar.js'); // 元々fooと同じwindowで動いているのでグローバル環境は共有させる
...
console.log(res.foo); // ちゃんと入っている。(sandbox.resでもアクセスできるが、ガンガン使うので、先頭のようにこのスコープで定義してあるからこれで良いのだ)
console.log(res.bar); // 問題なし
これで、resだのwindowだのを通じてfooやらbarやらとインターフェイスできるし、かつ、こちらのグローバル環境と、fooやらbarやらのグローバル環境の分離ができた。
ジェズイットを見習え |