import.meta
はJavaScriptに追加予定の新機能です。すでにStage 3プロポーザル(内容がほぼ確定している状態)となってから2年近く経っている仕様で、Google Chrome, Firefox, Safariで実装済みです。また、node.jsではexperimentalな扱いで実装されています(import.meta
が前提とするESモジュールがそもそもまだnode.jsではexperimentalという事情がありますが)。
Stage 3になってから長いこともありすでに広く知られているこのプロポーザルですが、まとまった日本語資料が見当たらなかったのでこの記事を用意してみました。では、さっそくimport.meta
とは何かから解説していきます。ちなみに、import.metaのプロポーザルはこちらです。
import.meta
とは何か
一言で言えば、import.meta
はモジュールに固有の情報を持つメタプロパティです。
メタプロパティ
まずメタプロパティから解説します。これは、プロパティアクセスっぽく見えるけどそうではない構文のことを指します。実際、import.meta
という構文はimport
というオブジェクトのmeta
というプロパティに見えますが、そうではありません。なぜなら、import
は予約語であり変数名ではないからです。これはあくまでimport.meta
というひとつの構文であり、これに外れる形(例えばimport.foobar
など)は構文エラーとなります。
import.meta
は現在提案中のメタプロパティですが、実はすでに現在JavaScriptに採用されているメタプロパティが1つだけあります。それはnew.target
です。new.target
もあまり日本語記事がありませんが、こちらの記事が詳しいので読んでみるとよいでしょう。また、現在提案中(Stage 2)のfunction.sent
というメタプロパティもあります。いずれも、予約語.プロパティ名
という構文になっているのがメタプロパティの特徴です。
メタプロパティは、特定のコンテキストに固有の情報を表すのに使用される傾向があります。これは、仕様がグローバルに提供する何らかのAPIを使用するのに比べてコンテキスト依存であることが分かりやすいからだと思われます。そのため、メタプロパティは特定の状況でしか使用できないことがほとんどです。例えばnew.target
は関数がコンストラクタとして呼ばれたときに意味があるものですから、関数の中でしか使用できません。また、アロー関数はコンストラクタとして使用できないため、アロー関数の中でも使用できません。
モジュール
ではimport.meta
が使用できるのはどこかというと、モジュールの中です。
モジュールについての詳細な説明は省きますが、これはJavaScriptプログラムを複数のファイルに分割するための仕組みです。この分割された各ファイルが一つ一つのモジュールとなります。モジュールexport
文を用いて自身から何らかの値を他のモジュールに対して提供したり、import
文を用いて他のモジュールが提供する値を読みこんだりできます。モジュールでない従来のJavaScriptプログラムはスクリプトと呼んで区別されます。import.meta
はモジュールの中では使えますがスクリプトの中では使えないのです。
HTMLでは、script
要素を用いてモジュールを読み込む場合はtype="module"
という属性を付ける必要があります。例えば以下のようにすることで、module.js
がモジュール扱いで読みこまれます。
<script type="module" src="./module.js"></script>
一方、type="module"
を付け忘れるとmodule.js
はスクリプト扱いで読みこまれます。スクリプト扱いの場合はmodule.js
の中でimport
文やexport
文、そしてimport.meta
は使えません。
モジュールの動作例はこんな感じです。
/// module.js
import { fooFunction } from './foo.js';
fooFunction(123);
/// foo.js
export function fooFunction(arg) {
console.log(arg * 10);
}
上記のHTMLでmodule.js
が読みこまれると、module.js
がimport
文でさらにfoo.js
を読み込んで、結果としてコンソールに1230
が表示されることになります。
以上がモジュールの簡単な復習でした。
import.meta
の中身
では、いよいよ本題に入りましょう。import.meta
が持つモジュールに固有の情報とは何でしょうか。実を言えば、JavaScriptの仕様ではimport.meta
の中身は決まっていません(オブジェクトであるということは決まっています)。
そもそも、モジュールという概念自体、定義しているのはJavaScriptの仕様であるもののその具体的な仕組みは実行環境に委ねられています。例えばnode.jsの場合は、import './foo.js'
と書いた場合はファイルシステムからfoo.js
が読みこまれるでしょう。一方Webの場合は、import './foo.js'
はfoo.js
へのHTTPリクエストを発生させるでしょう。このように、import
文で指定された読み込み先モジュールが具体的にどのように解決されるかということは環境に依存します。
ですから、「モジュール」というものの実体が環境によって異なるために、import.meta
が提供すべき「モジュールに固有の情報」というのも環境によって異なるのです。
ここまで抽象的な話が続いてきたので、ここからは具体的にimport.meta
を使ってどんな情報が得られるのかを見ていきます。
Webにおけるimport.meta
先ほども述べたように、import.meta
によって提供される情報というのは環境ごとに異なります。まずは、Web(すなわちブラウザ上)におけるimport.meta
について解説します。
Webにおけるimport.meta
の挙動はHTML仕様書において定められています。それによれば、import.meta
(によって得られるオブジェクト)はurl
というプロパティをひとつだけ持っています。これは、そのモジュールのURLです。次の例で考えてみます。
console.log(import.meta.url);
<script type="module" src="./module.mjs"></script>
このHTMLファイルがhttp://localhost:8001/index.html
でアクセスできるとしましょう。ここからmodule.mjs
を読み込んだ場合、コンソールに表示されるのは"http://localhost:8001/module.mjs"
です。このように、モジュール内でimport.meta.url
を取得した場合はそのモジュールのURLが取得できるのです。これは特に、モジュールからの相対パスを絶対URLに変換したい場合に有用です。また、Webでは必然的にひとつのURLがひとつのモジュールに対応しているという事実は覚えておく価値があるかもしれません。
Webにおけるimport.meta
の拡張案
現在のところ仕様で定められているimport.meta
のプロパティはurl
だけですが、もうひとつscriptElement
というプロパティを入れようという議論がされています。ところが、これはさまざまな理由から難航しています。かなりうまく作らないと使い物にならなそうだということが分かってきたからです。
import.meta.scriptElement
というのは、そのモジュールが読みこまれるきっかけとなったscript
要素を得ることができるプロパティです(名前は変わるかもしれません)。上の例を少しいじってみましょう。
console.log(import.meta.scriptElement.id); // "abc"が表示される
<script id="abc" type="module" src="./module.mjs"></script>
index.html
はscript
要素を用いてmodule.mjs
を読み込みます。そうして実行されたmodule.mjs
がimport.meta.scriptElement
を見ると、index.html
内のscript
要素が得られるというわけです。
なんとなく便利そうに見えますが、すでに問題が見えています。index.html
を次のようにすると何が表示されるでしょうか。
<script id="abc" type="module" src="./module.mjs"></script>
<script id="def" type="module" src="./module.mjs"></script>
実は、この場合は"abc"
だけが表示されます。同じモジュールを2回読み込んでも2回実行されることはないからです。
これだけで使いやすさが半減していますね。モジュールを複数回実行できないという根本的な制限に加えて、わざわざimport.meta.scriptElement
を使うということは「自分の読み込み元」を詳細に(単にdocument
にアクセスするのでは不足するくらいに)知りたい場面のはずなのに、読み込み先をひとつしか取れないというのは問題です。
他にも、script要素がshadowRootの中にある場合などの問題が議論されていたようですが、とにかく難航しており標準化には至っていません。
既に仕様化されているurl
、そして議論されているscriptElement
くらいしか現状では見当たりません。もし他にこんな情報もimport.meta
に必要だろうと思うものがあれば提案してみるともしかしたら採用されるかもしれませんね。
node.jsにおけるimport.meta
Webとは別のJavaScript実行環境として有力なのがnode.jsです。node.jsも既にimport.meta
を実装しています。node.jsのimport.meta
にどんなプロパティがあるのかについては特に仕様化されていません。node.jsが自分で欲しい情報を好き勝手に実装すればいいだけです。議論したい場合、場所はGitHubのissueになります。
結論から言ってしまえば、node.jsに現在実装されているのはimport.meta.url
だけです。Webと同じですね。
node.jsにおいてはES Modules以前からCommonjsによるモジュールシステム(require
とかのやつですね)が広く利用されており、自身のファイル名の取得に関しては__dirname
や__filename
が(モジュールに対するローカル)変数として提供されるという独自仕様により行ってきました。node.jsにおいてはこれが結構便利で利用される場面も多かったのですが、JavaScriptの標準に準拠したモジュールを実装するにあたってこれらの独自仕様は排除される(スクリプトの中では依然使用可能だがモジュールの中では使用不可になる)こととなり、その代替としてimport.meta
が必要となりました。
では、import.meta.url
の例を見ましょう。
console.log(import.meta.url);
node.jsで動作を確認するには次のようにします。
node --experimental-modules module.mjs
結果はこんな感じになります。
file:///tmp/test-module/module.mjs
ポイントは、結果が単なるパスではなくfile:
スキームを持つURLとして得られることです。なんとなくWebの場合と一貫性があっていいですね。ちなみに、import
文でもこのようなfile:
スキームがサポートされるようです。
node.jsにおいてもimport.meta.url
以外のプロパティの議論はあまり活発ではないようです(全く無いわけではないようですが)。そもそもまだnode.jsにおけるモジュール自体がexperimentalということもあり、先にそちらに注力するのでしょう。
まとめ
この記事ではimport.meta
というプロポーザルを紹介しました。将来的な拡張は考えられますが現時点ではimport.meta.url
のみしかWeb・node.jsともに存在しないようです。
今後モジュールの利用が本格化するにつれてimport.meta
の利用は避けて通れなくなるでしょう。今後起こるかもしれないimport.meta
への拡張に目を光らせておくのも悪くはありません。
尤も、生のモジュールなど使わずにwebpack
でモジュールたちを1つのスクリプトにまとめる人ばかりの現状では実用化はしばらく先となりそうですが。
関連リンク
import.meta
への言及を含む日本語記事を探したところ以下の3つが見つかりました。