LinuxとWindowsにnode.jsをインストールしてみたので、少し使ってみました。
JavaScriptにおいて、機能が欠けていて困る事って何でしょう。クラスがないとかはさしたる問題じゃないと思うのですが、モジュール機構がないのはホントに辛いです。node.jsには、待望のモジュール機構が導入されています。どうやら、CommonJS(http://www.commonjs.org/)という仕様に従っているようですが、僕は仕様を読んだわけではなくて、実際のnode.jsを触って見当を付けただけです。それを以下に書きます。
$ echo $HOME
/c/Users/hiyama/Work$ which node
/c/Users/hiyama/Work/bin/node.exe$ node --version
v0.5.0-pre$
今のところ、Webでドウコウという気はなくて、V8のインタプリタとしてだけnode.jsを使っています。
内容:
- モジュールとrequire関数
- モジュールのサーチパス
- 環境変数NODE_PATH(思うようにいかない)
- モジュールの作り方
- モジュールの階層化
- 感想
モジュールとrequire関数
モジュールとは、単にJavaScriptファイルのことです。モジュールを読み込むにはrequire関数を使います。以下で、「>」はnode.jsのプロンプトです。
> require('util')
{ print: [Function],
puts: [Function],
debug: [Function],
error: [Function],
inspect: [Function],
p: [Function],
log: [Function],
exec: [Function],
pump: [Function],
inherits: [Function] }
>
require関数の引数にはモジュール名(の文字列)を渡します。モジュール名は、JavaScriptファイル名から拡張子".js"を除いた名前です。require関数は、変数や関数をプロパティとして持つオブジェクトを返しますが、その値を取っておかないと後で使えません(その場で使うことはできますが; require('util').puts('hello') とか)。
> var util = require('util')
>
require関数の返した値を仮にモジュールオブジェクトと呼ぶことにします。モジュールオブジェクトを保存する変数は何でもかまいませんが、モジュール名と同じ名前の変数に代入すると分かりやすいでしょう。変数に保存したモジュールオブジェクトの変数や関数は、util.puts("hello") のようにして使えます。
モジュールのサーチパス
モジュールはJavaScriptファイルなのですが、そのファイルはどこにあるのでしょう? あるいは、node.jsはどこでファイルを探すのでしょう? その答えは、require関数のpathsプロパティに入っています。
> require.paths
[ 'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node' ]
>
JavaScriptの関数はオブジェクトなので、プロパティを持てるのです。require.pathsの値は環境により変わるでしょうが、上の例は現在の僕の環境です。この配列に並んでいるディレクトリがモジュールをサーチする場所です。
モジュール・サーチパスにディレクトリを加えるには、require.pathsを直接操作する方法もありますが、環境変数NODE_PATHを使うほうが便利でしょう。NODE_PATHの話は次節として、直接加えるなら:
> require.paths.push('c:\\Users\\hiyama\\Work\\js')
4
> require.paths
[ 'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node',
'c:\\Users\\hiyama\\Work\\js' ]
>
環境変数NODE_PATH(思うようにいかない)
さて、環境変数NODE_PATHの話です。以下、MSYSのbashを使ってますがWindowsでのこと。まずは、環境変数NODE_PATHを設定。
$ NODE_PATH=~/js; export NODE_PATH$ env | grep NODE
NODE_PATH=/c/Users/hiyama/Work/js$
node.jsを起動して、確認してみます。
$ node
> require.paths
[ 'c',
'/Users/hiyama/Work/js',
'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node' ]
>
あんれ? /c/Users/hiyama/Work/js が、ドライブ名'c'と残りに分割されてしまいました。Windowsの本来のパス表記を使ってみましょう。
$ NODE_PATH='c:\Users\hiyama\Work\js'$ echo $NODE_PATH
c:\Users\hiyama\Work\js$ export NODE_PATH
$ node
> require.paths
[ 'c',
'\\Users\\hiyama\\Work\\js',
'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node' ]
>
ウーム、ダメだ。bashじゃなくてCMD.EXEにしてみます。
C:\Users\hiyama\Work>set NODE_PATH=c:\Users\hiyama\Work\jsC:\Users\hiyama\Work>echo %NODE_PATH%
c:\Users\hiyama\Work\jsC:\Users\hiyama\Work>node
> require.paths
[ 'c',
'\\Users\\hiyama\\Work\\js',
'C:\\Users\\hiyama\\Work\\.node_modules',
'C:\\Users\\hiyama\\Work\\.node_libraries',
'C:\\Users\\hiyama\\lib\\node' ]
>
これでもダメ。
複数のディレクトリをコロン「:」で区切って指定したらどうなるでしょう。
$ NODE_PATH=/c/js:/c/Users/hiyama/Work/js;export NODE_PATH$ node
> require.paths
[ 'c',
'\\js;c',
'\\Users\\hiyama\\Work\\js',
'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node' ]
>
なんだか奇妙なことになってます。セミコロンならどうかな?
$ NODE_PATH='/js;/Users/hiyama/Work/js'; export NODE_PATH$ node
> require.paths
[ '/js;/Users/hiyama/Work/js',
'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node' ]
>
あれれれれれ、セミコロンだと切れないや。Windowsではセミコロンがパス区切りなんだけどな。
とりあえず、次のようにしておきますわ。
$ NODE_PATH='\Users\hiyama\Work\js'; export NODE_PATH$ node
> require.paths
[ '\\Users\\hiyama\\Work\\js',
'c:\\Users\\hiyama\\Work\\.node_modules',
'c:\\Users\\hiyama\\Work\\.node_libraries',
'c:\\Users\\hiyama\\Work\\lib\\node' ]
>
モジュールの作り方
ディレクトリ~/js/(Windows本来のパスでは c:\Users\hiyama\Work\js\)がサーチパスに入ったので、~/js/hello.js というファイルを作りました。
// hello.js var util = require('util'); exports.greeting = "hello"; exports.say = function() { util.puts(exports.greeting); };
これが、helloモジュールとなります。とりあえず使ってみます。
> var hello = require('hello')
> hello.say()
hello
>
うまくいきました。helloはどんなモジュールオブジェクトになっているでしょう?
> hello
{ greeting: 'hello', say: [Function] }
>
greetingとsayというプロパティを持つオブジェクトです。
もう分かりますよね; モジュールファイル内でexportsという名のオブジェクトがモジュールオブジェクトとなり、requireにより返されます。モジュール定義時には見えている変数・関数でも、requireした後は見えなくなるので情報隠蔽ができます。例えば、hello.js内のトップレベル変数utilは見えなくなります。
> typeof require('util')
'object'
> typeof hello.util
'undefined'
>
モジュールの階層化
モジュールの集まりであるパッケージを表現する特別な方法はありませんが、ファイルシステムのディレクトリ階層をそのままモジュール階層として使えます。ディレクトリ ~/js/ の下にサブディレクトリ ~/js/my/ を作って、そこに hello.js を入れてみます。hello.jsのgreetingを"Hi!"に変えました。
// my/hello.js var util = require('util'); exports.greeting = "Hi!"; exports.say = function() { util.puts(exports.greeting); };
> var myhello = require('my/hello')
> myhello.say()
Hi!
>
感想
node.jsの(おそらくはCommonJSの)モジュールの扱いはとても簡単ですね。これだけのメカニズムでも、今までJavaScriptプログラムの構造化に難儀していた人には嬉しい贈り物です。モジュール機構があれば、大きな規模のJavaScriptプログラムも、扱いやすくきれいな構造に編成できるでしょう。