本セッションの登壇者
セッション動画
「Module Harmonyについて」ということで発表を始めさせていただきます。「フロントエンドアーキテクチャーの現状と未来」というイベントタイトルからは少し外れますが、モジュールと言われるJavaScriptの1つの仕様の中で、こういうことが起きてるんだよという、少し角度を変えた話をさせていただければと思います。
Xでは@yosuke_furukawa、GitHubではyosuke-furukawaというアカウントで活動しています。主にNode.jsの話をしていて、Node学園やJSConf JPなどのカンファレンスを主催しています。
依存関係や競合を整理する
今、Module Harmony という提案が行われています。JavaScript(ECMAScript)の仕様は合議制で、プロポーザルという提案を出して、話し合いで決めます。特に、普通は実装する前に仕様が決まりますが、一度実装してから仕様が決まるのが特徴的です。モジュールとして結構いろいろな提案が行われています。
今回のこのModule Harmonyというのは、プロポーザルを新しく提起するものではありません。どちらかというと、依存関係や競合を整理するものです。
モジュールという1つの仕様に対していろいろな議論が起きているので、横断で議論する、汎用的にどういうものがモジュールとして理想なのかというのを議論する、個々のプロポーザルの違いを明確にしていきましょうといった、この3つが目的です。
複雑な依存関係
では、今どれだけモジュールにプロポーザルがあるのかということですが、提案されたのは2023年の終わり頃なので増減はあるかもしれませんが、ざっくり10個ぐらいあります。
矢印は依存関係を表します。「Module Source」、「Module Instance」と言われているものは、強く依存されているので、早くコンセプトを決めないと他が進められないという状態になっています。
逆に、右下の「Deferred import」 や「import attributes」には依存がないので、独立して話が進められます。他には、なぜか相互依存している「imports from local bindings」や「Module Declaration」といったものもあるので、これはどちらかを先に決めないと進みません。
こういったものを整理しようというのがModule Harmonyの流れです。今回は、提案されている仕様を紹介して「へー!」ってなったら終了です。これを解決するところまでは我々はまだ至っていません。
依存が少なめのモジュールが先
まずは今、一番依存がないところから話します。1つ目は「import attributes」です。
昔は「import assertions」と呼ばれていて、その時に触ったことがある人はご存知かもしれません。インポートする時に、どんな形式のものをインポートするかを宣言できるもので、昔はコードの with
が assert
になっていました。assertは検証の意味なので、もう少し広く扱えるように、属性の意味で with
に代わりました。今はJSONをインポートする時に with {type: “json”};
と書くのがメインのユースケースになっています。そのうち、たとえばJavaScriptからCSSや画像をimportすることが一般的なユースケースになってくれば、もしかしたらそれも同様に書けるようになるかもしれません。「import attributes」はもうかなりステージが進んでいます。
2つ目の「Deferred import evaluation」も依存関係が少ないもので、インポート時に遅延評価できるシンタックスを追加する仕様です。await import()
とimport関数のような書き方で遅延評価を行う方法もありますが、ロードする時に async
で囲む必要があります。それを宣言的にできるようにするのがこのモジュールです。import defer
と書けば、実行するタイミングでインポート元のファイルを評価できるようになっています。
依存が多めのモジュールの実装は遠い
次からが結構激しく依存されているもので、難しくなってきます。おさらいしながら話しますね。
インポートは流れとして5つのフェーズを経るのですが、最初がインポートに書かれたURL、ファイルのパスなどの識別子を解決するResolveフェーズです。次に、Fetch/compileで取得した後にそのファイルが何なのかを簡単な評価をします。その後、Attach contextでコンテンツを評価するのに必要なものの前準備が入ります。具体的には GlobalThis
などの評価を付け加えます。続いてLinkでリンクして、Evaluateで検証するというこの5つのフェーズを経ることになっています。
4月ぐらいに新しく行われた会議では、ここは統合してもいいんじゃないかといった話も出ていますが、インポートするフェーズをそれぞれ選べるように、修飾子を用意して評価できるようにしようとしています。
次に「Module Source」です。例にある <specifier>
でソースを提供すると、ファイルをソースからファイルとして持ってきた感じになります。WebAssembly.instatiateStreamingも同様にWasmをファイルとして持ってきて、そこからロードするということをやりますが、それを使わずに、インポートでJavaScriptと統合されたような形でソースを取ってこられるようにしようという概念です。ソースをworkerに読み込ませれば、この例ではそのworkerをfooというソースとしてそのまま実行できるようになります。概念として分かりにくいかもしれないですね。
普通はJavaScriptでインポートした時にEvaluate済みのものがもらえるので、オブジェクトだと思うんですけど、そうではないんですよね。ファイルの中身がもらえるだけで、それをworkerに渡すと、コンテキストが反映されてworkerとして実行できるようになるという考え方です。
モジュールのインスタンスをロードするという考え方も、概念として難しいです。
そもそもインポートやエクスポートするのがモジュールですが、「Module Declarations」で定義することができます。インポートやエクスポートではファイルやURLなどの宛先を識別子として指定しますが、そうではなく、ファイル内部でモジュールを書いて定義できるようにします。バンドラーなどでそのまま使えて、1ファイルにできるので便利ですね。
declareしたモジュールは式として評価できます。Module expression はclass expressionに近いかなと思います。
モジュール式なので、もちろん中でimport文も書けます。
そうやって定義したモジュールで、今度はインスタンスだけをインポートするためのimport文も用意されています。これは先ほど統合されるかもと言っていたもので、具体的な例はあまり書かれていません。
自分のファイル内で定義したものをローカルでもインポートできるようにするというのが「Module Instance Imports」です。ローカルにモジュールを持って来ることができます。使い勝手はまだ不明ですが、これもバンドラーの中で使うことがあるかと思います。
あとは「Module loader hooks」。これはソースレベルで有用なモジュールをロードする時に検知して、メタプログラミングみたいなことをしたい時に使うものです。コンテキストを新しく加えたり、新しい関数を追加したりする時に使うためのものとして今、提案されています。
他に実装まで遠そうなものとしては、モジュールのソースを解析した段階でできるimport/exportの依存グラフを処理するための情報を提供する、「Module Static Analysis」などがあります。
あとは「Virtual Module Source」。これはもう一番遠いですね。仮想のModule Sourceを作れるようにする仕様になります。新しく提供される new Module
を使用して、元となるコードがなくてもobjectから仮想モジュールを作れる機能です。ただ、先ほどのようにModuleをどう定義するかという考え方がまだないので、その定義がすべて済んだ後にこの話が出てくるのでまだ先になります。
必要なのは「調和」
まとめです。Module Harmonyは、たくさん出てしまったモジュールの仕様を一旦整理しようという試みです。
実は昔も同じようなことがあって、それがECMAScript4の悲劇です。その後に「いがみあってしまったけれど調和していこうよ」ということでECMAScript Harmonyという取り組みがありました。
同じようなことが今、起きようとしていて一回整理しようよといった感じです。OSSはキラキラしているように見えて、憧れている方も多いと思いますが、実は中ではこういう実社会でもありそうな話が結構起きていて、だからこそ、普段から言動について心がけて話をする必要があります。一般のアプリケーションの開発活動でもハーモニーというのを重視して話ができるといいかなと思います。
参考文献はこちらです。僕からは以上です。