Meteor 1.3でJavaScriptのmodulesを使う
Meteor 1.3ではmodulesのサポートが追加されます。本記事では、package/modulesのREADME.mdに従ってmodulesの使い方を説明します。
執筆時点のREADME.mdのバージョンはこちらです。
はじめに
Meteor 1.2でES2015のサポートが追加されましたが、期待の大きかったimport/export構文は入りませんでした。Meteor 1.3ではこの標準のモジュールシステムがサポートされるようになります。これはサーバサイドとクライアントサイドの両方で使えることができ、Meteorで長らく懸案事項だったファイルロード順の問題が解決されます。それでいて後方互換性を持ち、今までのコードもそのまま動きます。
モジュールを有効にする
Meteor 1.3ではmodulesパッケージはデフォルトでインストールされています。しかし、このパッケージは必須ではありません。これを既存アプリや既存パッケージに追加するかどうかは開発者の自由です。
アプリの場合は、meteor add modules
とすると有効にできます。もしくは、よりおすすめはmeteor add ecmascript
とすることです。ecmascript
にはmodules
が含まれているからです。
パッケージの場合は、package.js
ファイルのPackage.onUse
やPackage.onTest
のセクションにapi.use("modules")
を追加すると有効にできます。
ところで、ecmascript
パッケージを使わずにmodules
パッケージを使う意味があるかについて補足します。modules
パッケージのみを使った場合、NodeでおなじみのCommonJSのrequire
とexports
が提供されます。ecmascript
は単にimport
とexport
の構文をCommonJSに変換しているだけになります。require
とexports
を使ってMeteorアプリでNodeモジュールを使うこともできます。modules
を別パッケージに分けておくことで、ecmascript
が使えない場合でもrequire
とexports
が使えます。実際に、ecmascript
パッケージの実装はそのようになっています。
modules
パッケージ単体でも便利ですが、ecmascript
パッケージを使ってimport
とexport
を使うことがおすすめです(require
とexports
ではなく)。より説得材料が必要であれば、http://benjamn.github.io/empirenode-2015をご覧ください。
基本文法
import
とexport
の文法には様々な種類がありますが、まずは誰もが知っているべき基本の形式について述べます。
最初に、宣言の前にexport
とつけることでその宣言の名前でエクスポートすることができます。
|
|
上記の宣言では、a
, b
, c
といった変数がこのexporter.js
モジュールのファイルのスコープだけでなく、exporter.js
をimport
した他のモジュールからも利用できるようになります。
もしくは、宣言の前につけるのではなく、分けてexport
と変数名を書くことでエクスポートもできます。
|
|
これらすべてのエクスポートは「名前つき」であり、つまり他のモジュールからはその「名前」でインポートできることになります。
|
|
もし別の名前を使いたい場合は、export
やimport
で名前を変えることができます。
|
|
|
|
CommonJSのmodule.exports
と同様に、一つのデフォルトのエクスポートを定義することもできます。
|
|
このデフォルトのエクスポートは、波括弧なしてインポートすることができ、その際に名前をつけることになります。
|
|
CommonJSのmodule.exports
とは異なり、デフォルトエクスポートを使う場合でも名前つきエクスポートを使うこともできます。下記は両方を混在させた例です。
|
|
実は、デフォルトエクスポートは単に”default”と名づけられたエクスポートにすぎません。
|
|
これらの例をもとに、import
とexport
の文法を使い始めることができるでしょう。より詳しい情報は、Axel Rauschmayerによる詳細な説明に載っている様々なimport
とexport
の文法を参照してください。
モジュールアプリケーション構造
Meteor 1.3がリリースされる前は、アプリケーション内でがファイル間で値を共有するにはグローバル変数を割り当てるか、Session
のような共有変数(一応、グローバルではない)で伝達するしかありませんでした。モジュールの導入により、あるモジュールから別のモジュールのエクスポートされた値を参照できるようになるため、グローバル変数はもう使わなくてよくなります。
Nodeでのモジュールに慣れていると、モジュールはインポートされた時に初めて評価されるものと期待するかもしれません。しかし、Meteorの以前のバージョンではすべてのコードがアプリケーション開始時に評価されました。そこで後方互換性を維持するため、開始時評価がデフォルトのふるまいとなります。
もしモジュールを遅延評価(Nodeと同じように初回インポート時に評価)したい場合は、そのモジュールをimports/
ディレクトリ(アプリ内のルートディレクトリ以外のどこでも)に入れ、インポートするときにそのパスを指定します。例えば、import {stuff} from "./imports/lazy"
のようにします。node_modules/
内のファイルは常に遅延評価されます(後述)。
Meteorの将来のバージョンでは、遅延評価はデフォルトの動作になる可能性が高いです。もし現時点からその方式を採用したいのであれば、おすすめの方法はすべてのモジュールをclient/imports/
ディレクトリやserver/imports/
ディレクトリに格納し、それぞれclient/main.js
とserver/main.js
に単一のエントリーポイントを作ることです。main.js
ファイルはアプリ起動時に最初に評価され、そこからimports/
ディレクトリ内のモジュールが評価されます。
モジュールパッケージ構造
パッケージを作成する場合は、package.js
ファイルのPackage.onUse
にapi.use("modules")
やapi.use("ecmascript")
を追加するのに加えて、メインエントリポイントを指定するためにapi.mainModule
という新しいAPIを使用することもできます。
|
|
ここでserver.js
とclient.js
はパッケージの他のディレクトリにあるファイルをインポートできます。この際、それらのファイルはapi.addFiles
によって追加されている必要はありません。
api.mainModule
を使う場合、メインモジュールでエクスポートされたものはPackage["my-modular-package"]
でグローバルにアクセス可能になります。加えて、api.export
でエクスポートされたものはそのパッケージをインポートすることで自動でインポートされます。言い換えると、メインモジュールはFoo
のエクスポートをapi.export
で決めますが、他のプロパティも明示的に指定することでインポートはできる状態になります。
|
|
ここでの注意点は、import
はfrom "my-modular-package"
ではなく、from "meteor/my-modular-package"
とすることです。MeteorパッケージはNodeパッケージと区別するために、meteor/...
で始まるようにします。
最後に、このパッケージは新しいmodules
パッケージを使っており、Npm.depends
によって、”moment”というNodeパッケージに依存しているため、このパッケージ内ではimport moment from "moment"
とすることで、クライアントサイドでもサーバサイドでもこのモジュールをインポートできます。以前のMeteorではNpm.require
でNodeパッケージをサーバサイドでしかインポートできなかったため、これはすばらしいことです。
ローカルのnode_modules
Meteor 1.3より以前では、Meteorアプリケーションのnode_modules
ディレクトリの中身は完全に無視されていました。modules
を有効にすると、この無意味だったnode_modules
ディレクトリが突然すばらしく意味を持つことになります。
|
|
このアプリを実行すると、moment
ライブラリがクライアントサイドでもサーバサイドでもインポートされ、両方のコンソールでToday at 7:51 PM
のようなログが出力さ出るでしょう。このようにnpmモジュールを直接インストールできるようになると、https://atmospherejs.com/momentjs/momentのようなnpmラッパーパッケージを減らせることが期待されます。
Meteor 1.3のベータリリースでは、npmモジュールはクライアントサイドでもサーバサイドでも同じエントリーポイント(package.json
ファイルのmain
フィールドか、index.js
)でインポートされます。そのため、ブラウザで動くことが分かっているnpmモジュールだけを慎重にクライアントサイドにインポートする必要があります。今後は、package.json
ファイルのbrowser
フィールドをサポートすることを検討する予定ですが、ベータリリースには入っていません。
ファイルロード順序
Meteor 1.3以前では、どのファイルが先に評価されるかの順番はStructuring Your ApplicationのFile Load Orderに書かれているルールによって決まっていました。このルールは、あるファイルが別のファイルの変数に依存しているのに読み込みの順が逆になる場合など、制御にしくいものでした。
モジュールのおかげで、いかなる依存関係によるロード順もimport
文で解決できます。例えば、a.js
がb.js
より先に読み込まれる(ファイル名の順序で)としても、a.js
でb.js
の何かが必要な場合は、a.js
でb.js
の値をimport
すればいいだけになります。
|
|
|
|
場合によってはモジュールが別のモジュールの値をインポートする必要がなくとも先にロードして欲しいことがあるかもしれません。その場合は、より簡単なimport
文を使うこともできます。
|
|
この場合、どのモジュールが先にインポートされても、console.log
の呼び出し順は常に次のように一定になります。
|
|
まとめ
モジュールのサポートはMeteor 1.3の目玉機能の一つです。これまでのMeteorでは暗黙的だった動作を明確に記述することができ、コード量は増えるもののそれを上回るメリットがあると期待できるでしょう。