ブラウザ、Node.jsの両方で使えるJavaScriptの書き方
(2014/12/31追記)この記事は古いです。@kaiinuiさんの記事『最近の行儀のよい JavaScript の書き方』がよりモダンな書き方なのでそちらを参照するべきでしょう。この記事は記録のために残します。
最近MEANアプリケーションを書いています。サーバー側はNode.js + Express、ブラウザ上ではAngularJS、永続化にはMongoDBというやつです。
これらは全てJavaScript単一言語なので、これまでサーバーサイド言語とJavaScript間では不可能だったコード、ロジックの共有が可能です。ただし一工夫が必要です。
共通化に一工夫が必要になる理由
Node.jsでは他JSファイルを読み込む際var Foo = require('Foo');といった宣言が必要になります。一方でクライアントサイド(以下「ブラウザ上」という)ではこういった概念はなく、主にHTMLの<script>タグなどで順に読み込むだけです。
このときブラウザ上ではrequire()が解釈できません。そのため、Node.jsを意識して書いたJavaScriptファイルは本来ブラウザ上では動きません。また、同様にNode.jsではexportsという機構もありますが、これもブラウザ上では解釈できません。
この差を吸収するために一工夫が必要になるのです。
exportsの共通化
1行目と3行目が特殊な雰囲気です。helloやmymoduleの名称は自由です。ただしmymoduleはブラウザ上でグローバル空間に定義される変数名となるので、汚染への配慮が必要です。
(function (exports) {
exports.hello = function(){return 'Hello world';};
})(typeof exports === 'undefined' ? this.mymodule = {} : exports);
サーバーサイド記述例
パスは例なので適切なものを使用してください。
var mymodule = require('./mymodule');
mymodule.hello();
クライアントサイド記述例
<script src="path/to/mymodule.js"></script>
<script src="path/to/client-side.js"></script>
alert(mymodule.hello());
共有ソースファイルをどこに置くかは悩ましいですが、今回のMEANアプリケーションではapp/とlib/がある同階層にcommon/ディレクトリを作成、その中に保存し、app/scripts/common/からシンボリック・リンクを貼る方法をとりました。(まだ試行錯誤中です)
require()の共通化
exportsの共通化については調べればすぐに出たんですが、なかなか分からなかったのがrequire()の共通化でした。色々試したところ以下の書き方が使えそうでした。
declare var mymodule;
var mymodule = (typeof require === 'undefined') ? mymodule : require('./mymodule');
exportsでの書き方を応用したものです。TypeScriptで書いているのでアンビエント宣言declare var mymodule;をつけています。JavaScriptなら2行目のみで大丈夫。これでNode.jsではrequire()を実行し、ブラウザ上ではrequireを飛ばしてグローバル変数mymoduleを使うことができます。
HTML記述例
<script src="path/to/mymodule.js"></script>
<script src="path/to/my-other-module.js"></script>
<script src="path/to/client-side.js"></script>
出来るっちゃあ出来るけど
重ねてですが変数名mymoduleはグローバルなので、一意性を保証できる長めの名前が安心です。そもそもサーバーサイドとクライアントサイドでソースを無闇に共有しすぎる設計も考えものです。ご利用は計画的に。
ということで、じゃんじゃんJavaScript (TypeScript) を書いていきましょー。
Discussion