音声書き起こし
1. オープニング
@spring_raining
こんにちは。UITの玉田です。今回もUIT INSIDEを始めたいと思います。
UIT INSIDEは、ユーザーインターフェースとテクノロジーを愛する、開発するためのポッドキャストです。
最新のウェブ標準の動向や開発フレームワークの変遷、UIやUXに関することまで、毎週フロントエンドの情報を発信していくことを目的としています。
2. Engineering Blog記事の紹介
@spring_raining
さて、今回は、 えーと、LINEエンジニアリングブログに、1つ記事が公開されました。アプリケーションコードに変更を加えないNode.js Native ESMへの移行という 記事が、
@spring_raining
今回、鴻巣さんによって書かれましたので、えーと、この記事の内容について、もう少し、深掘りしてみたいなと思い、はい、今回、話そうかなと思います。
それでは、鴻巣さん、よろしくお願いします。
@kazushikonosu
お願いします。えっと、普段は、LINEスキマニとLINE Creators Marketを開発している鴻巣と言います。
@spring_raining
はい、よろしくお願いします。 では、えっと、ざっくり、記事の内容なんですけれども、えっと、Node.js上で、行動を実行する時にですね、あの、CommonJSと、Native ESMというものがあるかなと思うんですけれども、それを移行、えーと、Native ESMに移行した時の話ですね。
@spring_raining
で、ここの、内容が結構あんまり見ないやり方だなとは思ってたので、ここ、もう少しちょっと じゃあ、あの、先に記事の内容について軽く教えていただけますか。
@kazushikonosu
はい。えっと、LINEスキマニについてなんか、そもそもどういう開発を行ってるかっていうのを簡単に紹介すると、LINEスキマニ自体は、あの、いわゆるReactとTypeScriptで書かれたSPAなんですけども、 特徴のあるところとしてサーバサイドレンダリングをやっています。そのため、同じリポジトリ中に フロントエンドのモジュールバンドラーで処理されるコードと、Node.jsで実行することを想定して動くコードが両方あるような開発をしています。
@kazushikonosu
Node.jsの方は、今まではCommonJSで実行するために、tscなり、えっと、Babelだったりで変換をかけてたんですけども、今回、そのフロントエンドのモジュールバンドラーをViteに移行するにあたってと、せっかくだからこのタイミングで古いこの ビルドツールチェーンを一新したいなっていうことで、CommonJSからNative ESMに移行するっていうことを
@kazushikonosu
しました。で、えっと、その時に、なんかどういう手法が取れるかなっていうのを考えてたんですけども、そもそもこのViteに移行するメリットとして、開発時のパフォーマンスの高速化だったりとか、あとはそのバンドラ周りの設定をメンテナンスするコストを下げれる ところがあると思うので、えっと、Native ESMに移行するときには、なるべくビルドツールチェーンが重厚にならないように、開発時についてはNode.jsが提供している Custom ESM LoaderっていうAPIがあるんですけども、それを使って実際にプロダクション向けのビルドをするときは、tscにTransformerっていう、こう
@kazushikonosu
自前で変換コード変換することができるんですけども、それを使ってNative ESMの移行を実施することにを行いました。
@spring_raining
はい、ありがとうございます。 お話された通り、あの、前提条件として、あのー、サーバーサイドレンダリングをする部分があって、しかも、あの、あれですよね、よくある、例えばNext.jsとかそういうフレームワークではなく、えっと、自前でSSRの 基盤を持っているという前提があり、それをNative ESM、あのサーバーサイドレンダリングをする、Node.jsが実行する部分をNative ESMに移行するという話
@spring_raining
ですね。そうですよね。すごくモチベーションがよくわかります。クライアントサイドのコード、せっかくViteに移行したのに、以前だと、えっと、Babel。Babelです。はい。
これはあれですよね、その開発にもBabelでのJSの移行、変換があるっていうことですよね。
@kazushikonosu
あ、そうですね。開発の時も、えっと、プロダクションの時も、どっちもBabelで変換をかけて実行するみたいなことを。
@spring_raining
あー、なるほど。それは確かになんかちょっとモヤっとしますね。
@kazushikonosu
実はその前は、なんかさらにサーバーサイドの方もwebpackでバンドルするみたいなことをしていて
@spring_raining
はい。
@kazushikonosu
なんかそれをBabelだけにするっていうのでも、だいぶスッキリしたところはあったんですけども、今回、その、そもそもBabelとか一切通さないっていうので、よりスッキリできたかなって思っています。
@spring_raining
なるほど、そうだったんですね。じゃあ、あれですかね、そのファイルを変更して保存したら、あの、webpackが動いて、で、リロードされるっていう感じ ですか。
@kazushikonosu
そうです。
@spring_raining
あー、なるほど。それはちょっとしんどいかもしれないですね。そうですね。それが今やNative ESMで、もう ランタイムで変換されるという、すごいスッキリとした環境になったかなと。
@kazushikonosu
そうです。リロードにかかる時間とかもだいぶ減らすことができたので、開発体験もだいぶ良くなったかなと思っています。
@spring_raining
そこはめちゃくちゃ良さそうです。
3. Node.js Custom ESM Loader
@spring_raining
では、えっと、2つ、あのー、トピックがありますよね。えっと、Custom ESM Loaderと、TypeScript Custom Transformの、2つトピックがあると思うんですけども、 では、えっと、まず1つ目の、あの、開発環境での、あれですね、Custom Loader、Node.jsのCustom ESM Loaderを使った
@spring_raining
お話について、えっと、聞いていこうかなと思います。その前に、あれですかね、Node.jsのCustom ESM Loaderの話をした方がよかったりしますかね。
@kazushikonosu
はい。 ちょっと説明できる自信があんまりないんですけども。Custom ESM Loaderっていうのは何かっていうと、Node.js標準で、そのインポート文だったりとかのモジュール解決を行うアルゴリズムを提供してると ですけども、その部分に干渉して、インポート文の解決だったりとかに手を加えられる機能を提供するAPIです。なので、例えばts-nodeとかも、Custom Loaderを提供
@kazushikonosu
していて、TypeScriptのファイルを見つけたら、TypeScriptの変換をかけた上で、JSのコードとして解決するみたいなのも、Custom ESM Loaderで実現することができたりします。
@spring_raining
なるほど、ありがとうございます。これ、あの、結構最近なんですよね、記事に書いてあるんですけれども、あの、ノードのコード実行時に、あの、loaderというオプションがついていて、で、そこで、その、Custom Loaderを はい、読み込むという形ですよね。で、今回の例だと、そこに、えっと、ts-node、ts-nodeご存じかもしれないですけども、あの、ts-nodeの、TypeScriptのloaderを提供しているので、それを利用しつつ拡張したっていう形ですかね。はい、あってます。うん、ありがとうございます。
@spring_raining
そうですね、あの、大体30行ぐらいのコードが記事に書いてあって、ま、結構シンプルな感じがしますね。エイリアスが あるっていうところが、結構特徴的かなと思うんですけども、それもすごいあれですね、すごくシンプルに書かれています。
@kazushikonosu
そうですね、Custom ESM Loaderの例みたいなのが、Node.jsの公式のリポジトリーの中にあって、それをベースにすると、ほんとに簡単にできて、これぐらいだったら、全然メンテナンスできるなって感じになりました。
@spring_raining
そうですね、うん、結構ts-nodeの存在も結構大きそうですよね。あの、私、ts-node、大体あの、cliのts-nodeコマンドで 経由で実行するんですけれども、結構loaderがこんな感じで充実してるっていうのは 最近知って、結構便利そうだなって思いましたね。あの、ほんとにTypeScriptを直接インストールする必要がないケースとかも全然ありだなと、
@spring_raining
これを見て思いました。
@kazushikonosu
はい。実際なんかこう、Node.jsのファイルとして、このloaderを全部1つにまとめてしまうと、いくつもこうloaderがこう、Custom ESM Loaderで複数のこう機能を提供しなきゃいけない時とかも、ちゃんと自分が意図する通りにまとめられて、見通しも良くなるので、このぐらいのファイルサイズで済むなら全然 自分で書くのもありだなっていう風に思いました。
@spring_raining
うん、そうですよね。あとは、結構APIにこう、親近感があると。例えばViteのloaderとかRollupのloaderとか書いてたら、すごく あ、こういう感じなんですねっていう、すごい使いやすそうな
@kazushikonosu
ですね。もう雰囲気は完全にRollupのプラグインと同じような。あ、そうですそうです。なんかそういうものに馴染みがあればすぐに書けるかな。
@spring_raining
うん、確かに結構Node.jsのloader便利そうだなっていうのはすごい。あの、改めて思いました。
@kazushikonosu
何よりもこう、実行時に解決されて、Node.jsのスクリプトを走らせる前に何も変換しなくていいのが、開発時には特に魅力的だなっていう風に思います
@spring_raining
ですよね。いや、本当にこれは割と既存のフロントエンドツールに対する、こうNode.jsが自前でそういう機能を提供したっていうところは大きいですよね。
@kazushikonosu
すごいアンサーって感じがします。
@spring_raining
えーと、では、えー、もう1つのトピックにいこうかなと思います。
4. TypeScript Custom Transformer
@spring_raining
今のが、えっと、開発環境でランタイムで変換するために、Node.jsのCustom Loaderを使ったという話で、もう1つ、プロダクション環境で、コードを変換するために、えー、使用したものがあり、それがTypeScript Custom Transformer
@spring_raining
というのですね。はい。で、これは、Custom Transformerというのが、あれですかね、TypeScriptのCompiler APIというやつですかね。
@kazushikonosu
そうですね、TypeScriptのコンパイラを実行するときに、ま、ASTをこう操作できるみたいな、そんなものだとイメージしてもらえればいいかなと。
@spring_raining
なるほどなるほど。あれですね、あのー、普段、tscコマンドで実行するかなと思うんですけれども、それの裏側で動くものをカスタマイズができるようになったもの、という認識 ですね。はい、そうですね。こちらの、あの、サンプルコードも、あの、ブログの記事に載ってあります。で、あの、先ほどのloaderよりは若干長めなんですけれども、やってることは同じなんですよね。
@kazushikonosu
やってることはほとんど一緒で、Node.jsのCustom ESM Loaderの方は、そもそも、インポート文が最初から関数に渡ってきて、それを操作するみたいな感じな ですけども、TypeScriptのCustom Loaderの方は、ファイルの中からインポート文を静的解析で 見つけてきて、もしそのインポート文が、えっと、Native ESMの仕様では解決できないようなものだった時には、そのインポート文を書き換える、ASTそのインポート文のノードのASTノードを変換するみたいなことをやっています。
@spring_raining
なるほど。はいはいはい。あれですね、Transformerなので、基本的にコードの静的解析に関する設定で、そのインポート、isImportDeclarationとか、moduleSpecifierとか、そこらへんの条件を見て、で、読み込み先を、えっと、書き換えていくという感じなんですね。
なので、まあ、実質的にやっていることは同じ
@kazushikonosu
です。やってることはほとんど一緒だと思います
@spring_raining
でも、これができるっていうのは、あれですよね、あの、ESMのメリットというか、記事にも書かれてるんですけれども、こういう、あのインポートの、静的解析がやりやすいっていうのが、ESMのモチベーションの1つかなと思ってます。
@kazushikonosu
そうですね、これがもし、require文だったら、すごい見つけるのが大変だと思うので、ま、ESMならではなのかなっていうのは思います。
@spring_raining
こちらの、あのTypeScript Custom Transformerの、あの感想っていうか、書き心地というかはどうでしたかね。
@kazushikonosu
そうですね、これ、 うちの方はあんまり正直、TypeScriptのTransformer周りのドキュメントだったりとか、仕様とかがなかったので、Rollupのプラグインだったりとか、ESLintのプラグインとかを書くことに比べると、ちょっとハードルが高いかなとも。
ですけども、ま、そこまで複雑なことをやってないので、そんなに、そこまで難しいことでもないかなと思いました。
@spring_raining
なるほど。 そうですね、結構、用途によるかなと思うんですけれども。ちょっと話は外れるんですけれども、あのー、社内のJavaScriptの勉強会でも、最近、TypeScriptコードの静的解析がブームというか、
@kazushikonosu
そう、ちょっと流行ってますね、
@spring_raining
流行ってますよね。もしかしたら今後話すかもしれないんですけれども、ts-morphとか、そういうツールがありまして、 そうなんですよ、結構すごい、みんなTypeScriptというか、ESMというか、それのあのー、静的解析にアクセスする手段が整ってきたのかな っていうところがあって、あの、TypeScript、結構、あの、すごい、あれですよね、その言語に対する進化みたいなのに注目されがちなんですけれども、こういうツールも地味に整っている
@spring_raining
のはいいですね。あ、そうです。ちょっと、ドキュメントがあんまり整備されてないっていうのは、ちょっと、確かに私もちょっと感じてるんですけれども、
@kazushikonosu
ドキュメントが全然定義されてないんで、型定義ファイルとテストコードを読みながら、あ、こんなことだろうっていうの 想像する必要があるんですけど、ま、ある程度書き始めたら、そこまでこう、難しくないので、ま、書けるものではあるかと思ってます。なるほど。
@spring_raining
あーそうやってこう解釈していくんですね。すごいTypeScriptっぽいです。
@kazushikonosu
確かに型定義でなんとか解決できるって感じ。
@spring_raining
ありがとうございます。他に何かこう、結構すんなりと移行できた感がすごい感じるんですけども、これは割とあんまり困らなかったんですかね。
@kazushikonosu
元々は、ts-nodeだったりとか、あとちょっと記事でも紹介してるんですけども、Node.jsのオプションとして、そのCommonJSの時のモジュール解決の方法をNative ESMにも持ち込み、 experimentalなオプションがあったりとかして、そういうものをこう組み合わせて解決しようかなと思ってたんですけども、やっぱりそれはどうしてもうまくいかなくて。その、組み合わせて解決しようとした時にすごい時間かか かったんですけども、Custom Loaderと、えっと、Custom Transformerで解決するって決めてからは、割とすんなりといった印象です。
@spring_raining
なるほど、そうだったんですね。いや、うんうん、うんうん。
結構なんかすごい試行錯誤があったのかなと思ってたんですけど、結構、あの、周辺ツールが整っていうのもあってか、すごい いいですね。結構使える場面多そうだなと思いました。
@kazushikonosu
Custom ESM LoaderはまだexperimentalなAPIなので、プロダクションで使うのはちょっと心配なんですけども、 Custom ESM LoaderとTypeScirpt Custom Transformer、似たような書き味で こう使い分けることができたので、やっぱこううまく使い分ければすごい活用の幅があるなっていう風に思いました。
@spring_raining
ありがとうございます。この2つの組み合わせ、確かにすごく親和性が高そうだなってすごい今回見て思いました。ありがとうございます。
5. バンドルビルドをしないという選択
@spring_raining
では最後もう1つ、トピックは終わりなんですけれども、ちょっとメタ的な視点で 聞いてみたいところで言うと、今回、前提として、他の結構ランタイムでローラーを用意したりとか、
@spring_raining
TS Compiler APIで書き換えるっていうことをやっても、例えばコードバンドルするとか、そういったことを今回はやらずに解決したっていうところがあって、まあもちろん、なんかこのバンドルする選択っていうのもまああったけれども、ま、今回そういったLoader観点での書き換えでうまくいったっていうところは 結構面白いなと思ったところで、もしかしたら、あのバンドル、結構、あのしない理由みたいなのがもしあったらいいんですけれども、
@spring_raining
あの例えばソースコードのデバッグとか、そういったところで、こう、 あのー、Node.js上でコードを動かす上で、結構、今回の取った選択っていうのは良かったかなと思いますかね。すごいまとまりがない質問になって
@kazushikonosu
はい、
@spring_raining
すいません。
@kazushikonosu
すごいざっくりとした感想なんですけど、バンドルビルドをするのって疲れませんか、というのは。あ、はい。バンドルビルド を待ってる。特にSSRとかだと、SSRのサーバーに変更を加えるたびにバンドルビルドするのを待って、で、またリロードして、みたいなのもありますし、バンドルビルドをメンテナンスするために、こう、 例えば、Babelだったりとか、ま、webpackでバンドルする場合とかもそうですけど、webpackのバージョン変わったら追従しなきゃいけなかったりとか、すごい疲れるなと思ってて、
@kazushikonosu
やらなくていいなら、できるだけスリムにしたいなみたいなのはあったかもしれないです。
@spring_raining
それはそうですよね。 だって、あの、元々のモチベーションが、そのwebpackのバンドル、Node.jsで実行する行動のバンドルっていうのがしんどすぎるっていう話だったんで。ま、それは確かにそうですよね。
結構、今回はそのサーバーサイドでのコードの話なんですけれども、クライアントサイドのコードもそういう方針っていうのは、それこそ、あのViteの、あの開発サーバーの
@spring_raining
ファイルをバンドルせずに、都度、ブラウザのES loaderを、ESMの機能を使ってファイルを読み込ませるっていうところもそうですし、だんだんと、このコードを1つにまとめるっていう考えが、 をやめようっていう考えが、クライアントサイドから、今回はそのま、サーバーサイドの行動に移っていったっていうところは、すごい あれですよね、こう流れを感じます。
@kazushikonosu
そうですね。で、多分、 フロントエンド、クライアントサイドでバンドル、ビルドしないって選択肢があんまり普及しないのって、やっぱりプロダクション環境だとどうしてもネットワークのそのオーバーヘッドの部分があるからだと思うんですけども、サーバーサイドだとそれもないので、本当にやる理由がない ていうのが思っているところで。
@spring_raining
確かにそうですよね。そういう制約がないのに、逆になんでバンドルしてるんだって。
とはいえ、今回のソースコード、ソースコード書く必要があるっていうところで、まあちょっとハードル、ハードルっていうか、すごい良さそうではあるけれども、このトランスフォーマーのメンテナンスとかちょっと面倒だなみたいな というところもちょっとまああるところではあるので、今後、そういうクライアントサイドだけじゃなくて、そのバンドルしないビルドツール、すごいよくわかんない感じなんですけど、
@spring_raining
あの、そういうツールとかが出てきても面白いかもしれないですね。そういう、例えばあの、自動的にそのローダーのコードを吐くビルドツールとか、めちゃくちゃ面白そうですよね。
@kazushikonosu
なんか、それで言うと、あんまりスキマニにのリポジトリだと、このCustom ESM LoaderとTypeScriptのCustom Transformerを長期間にわたってメンテナンスするつもりはそこまでなくて。あー、 はい。なんか、というのも、なんか今はこのCustom ESM LoaderだったりとかTypeScriptのCustom Transformerが必要になってるのって、Native ESMに準拠してないES Modules風の書き方をしてるけど、実はES Modulesの仕様に準拠してないコードがいっぱいあるから
@kazushikonosu
と思ってて。Native ESMに移行する第1段階としてはこういうものが必要だと思うんですけど、どっちかっていうと今後はこういうものがいらなくても直接実行できるようなアプリケーションコードにしていくみたいなのが次のステップになるのかなっていう風に思ってい。
@spring_raining
なるほど。はいはいはいはい。確かにそっちの方が正しいですね。そうですね。それはそうだ。本当なら、なんて言うんですか、拡張子書かないといけないと、 そういうところとかをすごい、今のバンドルツールがすごい緩いルールでも動いてしまうっていうところがあるので、そういうローダーとかをどんどん強化していくっていうよりも、まー、そういうアプリケーションコード自体をそういう厳格な、厳格っていうほどでもないですけ
@spring_raining
ど、あの、ちゃんとESMの、ルールに従ってコードを書いていくっていう方が後々助かりそうですよね。
@kazushikonosu
そうですね。なんか、将来的にもそっちの方がいいのかなと思ってて、なんか、あと、その、サーバーサイドでバンドラを使うのモチベーションとして、こう、Babelだったりとかのプラグインを使いたい、こうプラグインが使いたいからバンドラを使うみたいなのもあったと思う ですけども、もうそういうのをなくしていくためにも、バンドラを使わずにこう最低限書いて、将来的にはこういうトランスフォーマーとかローダーを減らしていくみたいな方向性の方がいいのかなっていう風に個人的には考えてます。
@spring_raining
いや、正しくそうだと思います。うん。 いやでも、コードを一斉に書き換えようっていう選択肢じゃなくて、こういう選択肢が取れるっていうのはすごいNative ESMに移行する1つハードルが減ったなっていうのはいいところですね。
@kazushikonosu
そうですね。やっぱりもしこれでアプリケーションコードの例えば全てのインポート分に拡張子を追加しましょうとか なると、とてもじゃないけど差分が大きすぎてメンテナ移行が進まないと思うので、まずその第1歩として、とりあえずCustom ESM Loaderとか。えっと、Custom Transformerを使って、Native ESMの移行だけ進めて、後からアプリケーションコードの変更はちょっとずつ加えていくみたいな、なんかそういうアプローチがすごい現実的で、いい選択だったかなと。
@spring_raining
ありがとうございます。いや、ほんとに、そうですね、結構、割とこう、段階的な移行っていうところができる、 できるっていうか、まあ、今回、その記事でまあ示されているっていうところが、 そういういい点の1つだと思ってますので、あの、もちろん、こう、面白いローダーの使い方とかあるんですけれども、こういう、あの、本当に実用的な、めちゃくちゃ実用的なローダーの、使い方っていうところ、
@spring_raining
今回1つ例が知れてほんとによかったなと思ってます。ありがとうございます。
@kazushikonosu
なんか一方で、おっしゃるように、その、サーバーサイド向けのバンドルツールみたいなのが、もう少しこう、いい感じでいろんなソリューションがあったら、なんかこの、中間的なこういうソリューションもなんかこう、あって、もっと使いやすい、使いやすくできてたのにな、みたいなのは思います。
@spring_raining
そうですね、ほんとに、あの、結構極端ではありますよね、そういう、ほんとにこう、なんでもオールインワンなバンドルも、なぜかクライアントでもサーバーサイドでも動く、よくわからないけれども、 ブラックボックスで変換してくれるみたいな、そういうの、ほどではないけれども、より簡単にバンドル、バンドルではない、あの、そういうトランスパイルをしてくれる、そういう中間ツールがあっても良いかもしれないですね。
あのクライアントサイドではなくて、あのサーバーサイドの、こういう、そういうツールがあると、すごく今回のような、ま、結構、
@spring_raining
結構、やっぱり試行錯誤してここにたどり着いたということだったので、ま、ほんとにそういうツールがあると嬉しいなっていう。そうですね。うん。
@kazushikonosu
やっぱり今、Native ESMの移行って、ま、一般的にはこう、アプリケーションコードを全部変更して、Native ESMに準拠してない方のところは全部書き換えて、 みたいなのが必要で、すごいこう、重いプロセスだと思うんですけど、そういうツールがあったらもっと移行が進む。
逆に、そういうソリューションがあんまり普及してないから、Native ESMがへの移行が進んでないのかなっていう面はあると思うので、そういうツールの登場に期待したいなって思いました。
@spring_raining
いや、ぜひ、もし出てきたら、ぜひUIT INSIDEでも紹介しましょう。はい。では、えー、クロージングに移ります。
6. クロージング
@spring_raining
今回は、えーと、Native ESM移行と、まあそれの手段で、えーと、Custom Loader、 あとはTypeScirpt Custom Transformerのお話について聞いてきました。LINEのフロントエンド組織UITでは、
@spring_raining
このような技術的なキャッチアップを日々行っています。UIT INSIDE以外にも、毎週の社内勉強会でフロントエンドの情報交換を 行っています。今後もUIT INSIDEを通してこのような情報を外部に発信していけたらと思います。また、このエピソードのご意見やご感想はいつでもお待ちしております。#UIT_INSIDEでぜひツイートしてください。それでは、鴻巣さんありがとうございました。
@kazushikonosu
ありがとうございました。