ECMAScript 2015で導入されたモジュール機構、エクスポート/インポートの方法をBabelで試しながら調べていこう。
powered by Insider.NET
前回はBabelというJavaScriptトランスパイラーについて話をした。今回は、コマンドラインからこれを使いながら、ECMAScript 2015(以下、ES2015)のモジュール機構について見ていこう。C#コードと1対1ほどには対応しないので、今回もES2015の話題がほとんどとなるがご容赦願いたい。
説明するまでもないが、「モジュール」とは何らかの関連性を持ったコード群をひとまとまりの固まりとして集めたものだ。モジュールは何らかの機能を外部に提供(エクスポート)し、そのモジュールを利用する側はそれらの機能をインポートして使用する。また、モジュール内部でのみ使用する要素については外部に公開しないようにする。
C#では名前空間やアセンブリ、クラス、アクセス修飾子などを使用することで、論理的/物理的にモジュールを構成できる。
これに対して、JavaScript 5.x(以下、JS5)までのJSには、モジュール的なものを実現することを目的とした言語構造は存在していなかった。JSはもともとWebブラウザー内部でちょっとした処理を行うための言語であり、大規模なアプリ開発を想定していなかったことを考えると、言語仕様としてモジュールがなかったのはある程度は納得できることだ。
だが、JSの利用領域が広くなり、大規模なアプリ開発にも用いられるようになったことで、機能をモジュールに切り分けて、それらを必要に応じて取り込んで使用できるようにする必要性が高まってきた。
そこで、ES2015では、言語仕様としてモジュールが導入された。
ES2015では、一つのファイルが一つのモジュールを構成する。モジュールは、クラスや変数(定数)、関数などをexport宣言により外部にエクスポートできる。エクスポートされた機能を利用する側ではimport宣言によって、それらを取り込む。簡単な例を以下に示す。
// module1.es6
export function getAns2UltimateQ() {
return 42;
}
// main.es6
import { getAns2UltimateQ } from './module1';
console.log(getAns2UltimateQ());
このようにエクスポートする側では「export」キーワードに続けて、エクスポートしたいものを記述していくのが基本となる。インポートする側では「import」キーワードに続けてインポートするものを指定して、その後に「from」キーワードとインポート元のモジュールを指定する。
上のコードをBabelでコンパイル/実行すると次のようになる(「npm babel-preset-es2015」コマンドを実行してes2015プリセットインストールしている)。
> babel --presets es2015 module1.es6 -o module1.js
> babel --presets es2015 main.es6 -o main.js
> node main.js
42
ちなみに前回に紹介したbebel-cliをインストールすると、「babel-node」コマンドも利用できるようになる。これを使用すると、コマンドラインに指定したJSファイルの実行に必要なファイルのトランスパイルが行われた上で、指定したJSファイルをnodeコマンドで実行してくれる。そのため、上のコードは次のように実行できる。
> del *.js …… トランスパイル後の.jsファイルを削除
> dir /b …… ファイルを確認
main.es6
module1.es6
node_modules
> babel-node --presets es2015 main.es6
42
エクスポート/インポートの方法にはいくつかの種類がある。これについては以降で詳しく見ていこう。以下ではbabel-nodeコマンドラインの実行結果は頻繁には登場しないが、本稿ではWindows 10とbabel-nodeコマンドで動作を確認している。
エクスポートにはデフォルトエクスポートと名前付きエクスポートの2種類がある。前者はそのモジュールで一番重要なエクスポートであることを意味し、モジュールごとに一つだけ指定可能だ。モジュール内で複数のオブジェクトを公開したいのであれば後者を利用する(数学ライブラリのように多くの関数群を公開するのであれば、こちらを利用することが考えられる)。ちなみに上で示したエクスポートは実際には名前付きエクスポートとなる。
デフォルトエクスポートを指定するには「export default」宣言を行う。構文は次のようになる。上の例では「export function() {...}」と「default」がない点に注意しよう。「default」の有無が重要だ。
export default 関数宣言/クラス宣言/代入文など
以下に例を示す。
// 例1
export default function getAns2UltimateQ() { ... };
// 例2
export default function() { ... };
// 例3
export default class Foo {
…… 省略 ……
};
// 例4
export default 3.14;
// 例5
export default const PI = 3.14;
例1と例2は関数をデフォルトエクスポートとする例だ。「export default」では無名関数も記述可能である。ただし、例2のように無名関数を指定すると、モジュール内でこれを参照する方法がなくなるので注意しよう。
例3はクラスをデフォルトエクスポートとしている。あるモジュールが全体として何らかのクラスを実装しているという場合、これをデフォルトエクスポートとするのがよいだろう。上ではクラス名を指定しているが、これを省略することもできる。
例4と例5は定数をデフォルトエクスポートとしている。
これらをインポートするには以下のような構文を指定する。
import オブジェクト名 from 'モジュールファイル名';
「オブジェクト名」というのは、インポートしたオブジェクトを参照する名前のことだ。これはモジュールで指定した名前とは異なっていても構わない。ちなみに、エクスポートは「エクスポート名」を持ち、デフォルトエクスポートではこれは「default」に設定される。
例えば、上の例1〜5のデフォルトエクスポートをインポートするには次のようにする。
// 例1&例2
import getAns2UltimateQ from './module1';
console.log(getAns2UltimateQ());
// 例3
import Foo from './module1';
var f = new Foo();
console.log(f.someprop);
// 例4&例5
import PI from './module1';
console.log(PI * 1 * 1);
これらは全て上で述べた「default」というエクスポート名を持つオブジェクトをインポートして、それらを「getAns2UltimateQ」「Foo」「PI」などの名前で参照することを意味している。
なお、最初に見た「名前付きエクスポート」のインポートとの違いは「{}」でオブジェクト名が囲まれているかどうかだ。
Copyright© Digital Advantage Corp. All Rights Reserved.