Node.jsとブラウザ間でコードを共有するにはどうしたらいいですか?

私は、JavaScriptクライアント(ブラウザで実行)とNode.jsサーバーで、WebSocketを使用して通信する小さなアプリケーションを作成しています。

クライアントとサーバーの間でコードを共有したいのですが、どうすればいいですか?私はNode.jsを使い始めたばかりで、最近のJavaScriptの知識は、控えめに言っても、少し錆びついた状態です。そのため、CommonJSのrequire()関数について、まだ頭を抱えています。もし私が 'export' オブジェクトを使用してパッケージを作成しているなら、ブラウザで同じJavaScriptファイルをどのように使用できるのかがわかりません。

メッセージのエンコードとデコード、その他のミラーリング作業を容易にするために、両端で使用されるメソッドとクラスのセットを作成したいのです。しかし、Node.js/CommonJSのパッケージング・システムは、両側で使用できるJavaScriptファイルを作成することを妨げているようです。

また、よりタイトなOOモデルを得るためにJS.Classを使ってみましたが、提供されたJavaScriptファイルをrequire()で動作させる方法がわからず、あきらめました。何か見落としがあるのでしょうか?

ソリューション

クライアントサイドとサーバーサイドの両方で使えるモジュールを書きたい場合、手軽な方法を短いブログ記事で紹介しています。Writing for Node.js and the browser, 基本的には以下のようになります(ここで thiswindow と同じ意味です)。

(function(exports){

    // Your code goes here

   exports.test = function(){
        return 'hello world'
    };

})(typeof exports === 'undefined'? this['mymodule']={}: exports);

また、クライアント側にNode.jsのAPIを実装することを目的としたプロジェクトもあり、例えば Marak's gemini などが挙げられます。

また、DNodeはJavaScriptの関数を公開し、シンプルなJSONベースのネットワークプロトコルを使って他のマシンから呼び出せるようにするものです。

解説 (9)

JavaScriptの関数の文字列表現は、その関数のソースコードであることを忘れてはいけません。関数やコンストラクタをカプセル化して書けば、toString()'されてクライアントに送信されるようになります。

また、ビルドシステムを利用して、共通コードを別ファイルにまとめ、サーバーとクライアントの両方のスクリプトにインクルードする方法もあります。私はこの方法を、WebSocketを使った簡単なクライアント/サーバーゲームに使っています。サーバーとクライアントの両方が基本的に同じゲームループを実行し、クライアントは誰も不正をしていないことを確認するためにサーバーと毎カチ同期しています。

私のゲームのビルドシステムは、単純な Bash スクリプトで、ファイルを C プリプロセッサーで実行し、次に sed で cpp が残したゴミを掃除して、 #include, #define, #ifdef といった通常のプリプロセッサーをすべて使用できるようにしています。

解説 (1)

サーバーは単純にJavaScriptのソースファイルをクライアント(ブラウザ)に送ることができますが、クライアントがコードをexecしてモジュールとして保存する前に、ミニ "exports" 環境を提供しなければならない、というトリックがあります。

そのような環境を作る簡単な方法は、クロージャを使うことです。 例えば、サーバーが http://example.com/js/foo.js のような HTTP 経由でソースファイルを提供するとします。 ブラウザはXMLHttpRequestによって必要なファイルをロードし、このようにコードをロードすることができる。

ajaxRequest({
  method: 'GET',
  url: 'http://example.com/js/foo.js',
  onSuccess: function(xhr) {
    var pre = '(function(){var exports={};'
      , post = ';return exports;})()';
    window.fooModule = eval(pre + xhr.responseText + post);
  }
});

重要なのは、クライアントが外部コードをすぐに実行される無名関数(クロージャ)にラップできることで、これにより "exports" オブジェクトを作成してそれを返すので、グローバル名前空間を汚染するのではなく、好きな場所にそれを割り当てることができます。 この例では、ウィンドウの属性 fooModule に割り当てられており、ファイル foo.js からエクスポートされるコードが含まれています。

解説 (2)