1. kkeethのエンジニア雑談チャンネル
  2. No.202 朝活「Speeding up the..
2023-03-27 29:24

No.202 朝活「Speeding up the JavaScript ecosystem - module resolution」をダラダラ読む回

はい.第202回は


Speeding up the JavaScript ecosystem - module resolution

https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-2/


を読みました💁

今回も前回に負けず劣らず良記事でした.知らないこともあり勉強になりまして,とてもありがたかったです.ぜひ皆さんも読んでみてくださいー!


ではでは(=゚ω゚)ノ



See Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.

00:06
3月21日、火曜日。今日は祝日ですね。
はい、地獄は朝9時10分になりました。
おはようございます。mimeiのkeethこどくわはるです。
はい、では本日も朝活を始めていきたいと思います。
本日はですけどもですね、タイトルになります。
Speeding up the JavaScript ecosystem。
今回はですね、第2章というか、パート2のモジュールリソリューションのブログを読もうと思っておりますが、
今朝見たら、ちょうどパート4ですね。
NPMスクリプトの記事が出ていまして、これもすごく読みたくなったので、
今日、モジュールリソリューションの記事、多分今日終わると思うので、
終わったらパート1のポストCSSとかSVG語とか、
いきたいと思ったんですけど、NPMスクリプトでいくかちょっと悩ましいですね。
そのざっと概要だけ見て決めようと思いますけど、
もうちょっとこのシリーズでですね、
Speeding up the JavaScript ecosystemのシリーズは読んでいこうかなと思っております。
はい、では早速今日のパート2のところを入っていこうかなと思います。
じゃあ最初はTLDRからですね、いきたいと思います。
JavaScriptのビルド、テスト、リントン、いずれかにおいてですね、
おいてもモジュール解決ってのは常に全ての核となるものになりますと。
私たちのツールの中で中心的な位置を占めているにも関わらず、
この側面を高速化することにあまり時間が割かれてきませんでした。
このブログポストで説明する変更により、
ツールを30%も高速化することが一応できますよというお話ですね。
今日はそういうところを見ていきたいと思いますということでした。
もうちょっと概要が続いてますね。
このシリーズの第1回では、JavaScriptツールで使用される、
様々なライブラリを高速化する方法をいくつか紹介しました。
このような低レベルのパッチによってビルド時間の合計が大幅に改善された一方で、
私たちのツールにはもっと根本的な改善点があるのではないかというふうに考えていました。
バンドルだったりテストだったりそのリンティングというような、
一般的なJavaScriptタスクの操縦感に対して、
もっと大きな影響を与えるものがあるんじゃないかなというふうに関心があったと。
そこで数日間かけて私たちの業界でよく使われている様々なタスクやツールの
CPUプロファイルを12個ほど集めましたと。
これらのタスクの操縦効時間に30%もの影響を及ぼしているということが分かりました。
これは私たちのインフラストラクチャーにとって非常に重要で影響力のある部分であり、
独自のブログ記事を書く価値があるというふうになりますと。
その重要な部分とはモジュール解決と呼ばれるものですね。
そして私が見た全てのトレースでは、
ソースコードの解析よりも多くの時間を要していたというふうになります。
これが一応概要ですね。
では早速本体の方に入っていきたいと思いますが、
まずセクション1はスタックトレースをキャプチャするためのコストですね。
はい、いきたいと思います。
03:01
そのきっかけはエラーオブジェクトにスタックトレースを添付するノード内部の関数、
キャプチャーラージャースタックトレースというものがあるんですね。
というメソッドにあって、
トレースの中で最も時間がかかっていることに気がつきました。
この2つのタスクはエラーが投げられた形跡がないまま成功していることから、
ちょっと普通では考えられないことだというふうに思いました。
一応画像がペタられていて、
トレース1とトレース2の実際のプロファイリングのデータが出ています。
トレース1はトータルで1.62秒かかっています。
実に全体の27%ですね。
これはキャプチャーラージャースタックトレースというメソッドの実行時間ですね。
その後はリードですね。
読み込みの時間が発生して、それが387.21ミリ秒ですね。
ミリ秒かかっています。
次にトレース2ですね。
トレース2もやはりキャプチャーラージャースタックトレースというメソッドが実行されて、
こちらが1.22秒ですね。かかっています。
これも全体の29%かかっています。
続いてこっちですね。
トレース2の方ではプロバイドシンクというのが走っているそうですね。
こちらにも1.52秒かかっていて、これは全体の40%近くに行きます。
最後にリードの時間がかかっていて、
こっちが9.88秒ですね。
これは3.5%ということで、
本当に3.5%で9.88秒はおかしいでしょ。
どういうものの比較に対して。
何に対しての3.5%かちょっとわからないですけど、
それぐらい実際に時間がかかっているということですね。
結構トレース2時間がかかっているということですね。
では本文に戻りましょう。
プロファイリングデータの発生箇所というのをクリックすると、
何が起こっているのかというのが明確になりました。
ほぼ全てのエラーはノードのネイティブなfs.statSync関数の呼び出しによるものです。
その関数はisFileという関数内で呼び出されていました。
ファイルかどうかというのを判定しながらこれは。
ドキュメントによるとfs.statSync関数というのは基本的に
POSIXのfs.statコマンドと同等。
POSIXって何だろう。
全部大文字でPOSIXというのがあるんですね。
これちょっと僕は不勉強でわからないんですけど。
というもののfs.statコマンドと同様に、
ディスク上にパスが存在するか、
ファイルかディレクトリーかをチェックするための
一般的に使用されるということです。
このことを考慮するとファイルが存在しない、
読み込み権利がないなど、
例外的な使用方法の場合にのみエラーが発生するそうです。
そろそろそのisFileの操作を覗いてみることにしましょうということで、
関数でfunction isFileでファイルというのが引き継がされるらしいですね。
全体Try-Catchで挟まれています。
Tryの方はfs.statSyncでそのファイルですね。
引数に受け取ったファイルを引数に渡してあげて、
そのままですね。
それを実行した結果を変数で持っていると。
conststatという変数に持っています。
returnでstat.isFileというのをもう一回実行しますと。
これメソッドですね。
stat.isFileを実行する。
それがtrueかどうかフォルスかというのを返しますと。
またはstat.isFIFoと言っていますね。
06:01
これもメソッドですね。
stat.isFIFoメソッドを実行して、
どちらかtrueであればtrueを返しますと。
そうじゃなければフォルスだということですね。
何かしらエラーが発生したときのキャッチですけど、
キャッチの中ではifのerror.codeが、
毎回読み方が合っているのか分からないけど、
enotっていうものですかね。
enotですね。
enotか分からないですけど。
もう一個はerror.codeかenotdirectoryですね。
enotdirですね。
であるかどうかというのを見ています。
error.codeか、
enotかenotdirectoryのどっちかですか。
これまたはですね。
どっちかですかというのを見て、
それがtrueであればフォルスを返しますと。
じゃなければもうスローエラーをしますということですね。
はぁはぁはぁ。
なるほど。
さっきのerror.codeが返ってくるときは、
少なくともファイル系のエラーなんだろうな
ということが分かるので、
そのときは単純にファイルじゃないよというので
フォルスを返すんですけど、
それ以外の場合はもう、
完全に何かエラーが発生してますねということで、
スローしてしまうという感じですね。
ちょっと音読で申し上げないですけど、
分かりづらくてごめんなさい。
一見すると、
何の変哲もない関数になりますが、
それにも関わらず、
トレースで表示されていましたと。
注目すべきはその特定のエラーケースを無視して、
エラーを転送する代わりにフォルスを返すことですと。
先ほど見ました2つのerror.codeですね。
enotとenotdirectoryの両error.codeというのは、
最終的にディスク上にパスが存在しないということも
意味しますと。
もしかしたらこれがオーバーヘッドになってるんでしょうか。
つまり、ここではこれらのエラーを即座に無視しています。
確かにそうですよね。
このエラー自体、
来たらとりあえず一旦フォルスって返して、
処理を何もしてないですからね。
この仮説を検証するために、
try-catchブロックというのがキャッチした
すべてのエラーをログアウトしてみましたと。
すると驚いたことに、
投げられたエラーはすべてenot.codeかenot.directoryの
どちらかでしたと。
nodeのfs.statsyncのドキュメントをちょっと覗いてみましたと。
すると、ファイルシステムのエントリーが存在しないときに
エラーが投げられないようにする
throwIfNotEntryというオブジェクト、
オブジェクションを渡すことがサポートしているという風に分かりますと。
この場合、代わりにundefinedというのが返されるようになりますと。
これnodeの公式ドキュメントのリンクも貼られているんですけど、
ちょっとピックアップされていて、
function.isFileという関数ですね。
こっちを見てみますと、
同じようにfs.statsyncというメソッドが実行されて、
引数にファイルが渡されますと。
第2引数のオブジェクトですね。
これはオプションがオプショナルですけど、
オプションとしてはそのthrowIfNotEntryというのがあって、
デフォルトフォルスになっていますと。
statがundefinedじゃない、またはstat.isFileとstat.isFIFOですね。
メソッドを実行してどちらかがtrueですかみたいなのを見てますと。
それの&条件でリターンをしているというような感じですね。
まあ結局nodeの本体と似たようなことをやっているということですね。
このオプションを適用することで、
キャッチブロックのif文がなくなって、
try-catchが冗長になり、
09:00
関数をさらにシンプルにすることができますと。
単純に最初からthrowIfNotEntry、
エントリーがなければ、つまりファイルじゃなければ、
そのままフォルスを返すというようなオプションを渡してしまえば、
try-catchということをしなくてよくなるということですね。
このたった一つの変更で、
プロジェクトのリントンにかかる時間が7%も短縮されました。
さらにすごいのは、
同じ変更でテストも同様に拘束されたことです。
単純にtry-catchの分のリントンにかかる時間って、
バカにならないですね。
結構詰め重なっていくんですね、これ。
また、この人の実行結果でいくと、
絶対にキャッチに入るケースがないと言っていますね。
一つもなかったと言っているんですけど、
この人のやった実行結果というのは、
どういう結果なのかちょっとわからないですけど、
このESLintですかね、
何かの対象としているモジュールのテストを実行した結果、
全テストケース、全パターンで、
実はisfileメソッドのキャッチ分に入らなかった、
キャッチ区間に入らなかったのかなと思いますね。
であれば確かにもういらないよねというのはそう思いますので、
このisfileメソッドを自分の作った独自の方で置き換えると、
7%短縮されますよという話をしていますね。
これはすごくありがたいお話でした。
では続いていきましょう。
続いてはセクション2ですけど、
ファイルシステムが結構高価であります。
高価でも価値が高いって書くんですけど、
これは多分高価っていうのは価値ではなくて単純に時間がかかってますよってことですよね。
その関数のストックトレースのオーバーヘッドがなくなったことで、
まだ何かあるような気がしていたんです。
2、3回のエラーは数分間で取得したトレースでは全く表示されないはずですよと。
そこでこの関数に簡単なカウンターで注入してみて、
どれくらいのヒントで呼び出されているか調べてみました。
その結果この関数は約15K回、15K、1万5千回呼ばれていまして、
このファイル内、プロジェクト内のファイル数の約10倍もあることが分かりました。
これは改善の余地がありそうですね。
それはすごいですね。
この関数1万5千回も呼ばれてるの。
本題ですね。
この記事のあるとおりですけど、
モジュールの話にちょっと入っていきたいと思います。
はいでは続いて、今のが相対的なモジュールですね。
続いて2つ目が絶対的、絶対パスですね。
アブソリュートモジュールインポートですね。
これはスラッシュ風だったり、スラッシュ風バー、スラッシュボブみたいなやつですね。
最後3つ目はパッケージインポートですね。
直接インポートしてるやつですけど。
これは単純に風だったり、アット風スラッシュバーみたいな感じ。
以上3つですね。
とりあえずツールのインポートの識別値というのは3つあって、
相対パス、絶対パス、パッケージ直接のインポートですね。
この3つのうちパフォーマンスの観点から最も興味深いのは最後の1つです。
パッケージインポートですね。
これは裸のインポート指定し、つまりドットやスラッシュ等で始まらないものは
通常NPMパッケージを参照する特別な種類のインポートになります。
このアルゴリズムはノードのドキュメントで詳しく説明されていますので、
詳しいのはノードのドキュメントで見てみてください。
これはこれで読んでいきたいですね。
その要点はパッケージ名を解析し、ファイルシステムのルートに到達するまで、
12:01
モジュールを含む特別なノードモジュールズディレクトリが存在するかどうかをチェックするために
情報にトラバースしようとすることです。
それをRAID.jsを説明してみましょう。
例えば、スラッシュユーザーズですね。
いわゆる僕らのPC、パソコンの中のパスですけど。
スラッシュユーザーズ、スラッシュユーザー名、スラッシュマイプロジェクト、スラッシュソース、
フィーチャー、ディテールページ、コンポーネンツ、レイアウト、インデックス.jsという
絶対パスで書かれていますけど、すごく長いんですけど、
特定のプロジェクトのところまで行って、
そのソースの下のフィーチャーズディレクトリの下のディテールページの下にある
コンポーネンツディレクトリの下のレイアウトの下のインデックス.jsですね。
というようなファイルを見ると、とりあえずしましょう。
このファイルにある中で、モジュール風というのを
インポートをしようとしたとしましょう。
はい、OKです。
そしてアルゴリズムは以下の場所をチェックしますので、
1、2、3、4、5、6、7、8個のパスがリストで書かれていますけど、
最初は、これ全部絶対パスなんですけど、
プロジェクト内のレイアウトディレクトリの下のノードモジュール風を見に行きますと。
そんなものはないと。
続いて、同ディレクトリの1個上ですね。
レイアウトの1個上はコンポーネンツディレクトリです。
コンポーネンツディレクトリの下でノードモジュール風を見に行くと。
これをずっと繰り返していくわけですね。
どんどんどんどん繰り返して、
最終的にはもうユーザーの下まで行くということですね。
このプロジェクトの外まで、
ノードモジュール風を最終的には探しに行こうとしているということですね。
途中で見つかればそのチェックはやめるんでしょうけど。
というのをやっていくと。
それは知りませんでしたね。
まさかのシステムのところのユーザーズまで戻ろうとするんですね。
最終的にはスラッシュまでは行かないですね。
パソコンの中の絶対バスで、
オリジンオブオリジンのところまでは見に行かないということですね。
これは多くのファイルシステムが呼び出されるということになりますと。
一言で言えば、
全てのディレクトリがモジュールディレクトリを含んでいるかというのをチェックします。
チェックの量は、
インポートするファイルがあるディレクトリの数に整比例します。
それはそうだよね。
そして問題は、
これがフーインポートされる全てのファイルに対して起こるということです。
つまり、
フーが他の場所にあるファイルでインポートされた場合、
モジュールを含むノードモジュールディレクトリを見つけるまで、
ディレクトリツリー全体を再び情報にクロールすることになりますと。
このように解決されたモジュールをキャッシュすることは非常に有効です。
それはそうだよね。
というわけで、
もちろんパスですね。
ネストをどこまで深くするかですけど、
深ければ深いほど、
読みに行く情報へのクロールの量も増えていくし、
数も増えていくということなので、
ネストする量もできれば、
あんまり深くしていないほうがいいよということになりますけど、
それでも、
するときは、
ちゃんとキャッシュするとかなり有効ですよねということでした。
しかしでも、
キャッシュもいいけど、
さらに良いことがあります。
多くのプロジェクトでは、
パスマッピングエリアを利用して、
タイピングの手間を省くことができるようになっています。
これは、
通常、タイプスクリプトのパスコンパイラーオプションだったりとか、
15:00
バンドラーのResolveエリアによっても行われます。
この場合、
パッケージのインポートと見分けがつかないという問題があります。
あ、はいはい、そうですね。
features-directory-users...
プロジェクト直下のソース・フィーチャーズに
パスマッピングを追加して、
インポートなんちゃら、
from features-details-pagesのように
インポートを宣言する。
使えるようにすれば、
全てのツールはこのことを知ることができるようになると。
これは、
絶対パス・相対パスという、
自分で作った独自のパスのところを
設定するみたいな感じじゃないですかね。
パッケージのときにも同じことができるのであればそうなんでしょう。
では、ちょっとバッドで言葉が続きますね。
しかし、そうでない場合はどうでしょう。
全てのJavaScriptツールが使用する
一元的なモジュール解決パッケージは存在しないため、
様々なレベルの機能がサポートされた
複数の競合するものが存在します。
私の場合、プロジェクトはパスマッピングを対応しており、
TypeScriptのtsconfig.jsonで定義されている
パスマッピングを意識していない
リンティングプラグインというのが含まれていました。
当然、そのフィーチャーズスラッシュのディテールページが
ノードモジュールを参照していると思い込んでしまい、
そのモジュールを見つけるために
再期的に情報探索のダンスをすることになりました。
しかし、結局見つからずさらにエラーになりました。
実は下にあったのかな。
上に見つけようとしてなくてエラーになっているけど、
実は下にあったのかですかね。
何かしらエラーが発生したということですね。
依存するライブラリとかにもやっぱりこういう話が出てくるので、
結構使うものは慎重に選ばなきゃいけないかもしれないですね。
まあいいやっていうところで続いてのセクションですね。
あらゆるものをキャッシュ化するという話に入ります。
じゃあ次にロギングを強化してこの関数が呼び出されたときに
ユニークなファイルパスがいくつあるか、
また常に同じ結果が返されるかどうかというのを
ちょっと確認しましたと。
はい。
適当性も一応見てみたんですね。
また、ISファイルの呼び出しでユニークなファイルパスを持つのは
約2.5回、5Kですね。
2500回だけで渡されたファイルの引数と戻り値の間には
強い1対1のマッピングがありました。
これはプロジェクト内のファイル数よりはまだ多いんですけど、
合計15000回呼び出されたことに比べればはるかに少ないですと。
実は5分の1ですからね。
もしファイルシステムにアクセスするのを避けるために
その周りにキャッシュを追加したらどうなるでしょうかというので
そのキャッシュを追加する関数、参考に1個貼られています。
const cache="new map",ですね。
マップオブジェクトからのインスタンスを1回作って
cacheという変数名で保てますと。
function resolveという関数を1個作っておいて
引数に先程の場所にファイルというのを渡しますと。
cache.getでそのファイルを見てみますと。
引数に渡されたファイルという引数をそのまま
getの引数にも渡してあげます。
戻ってきたものがconst cacheという変数にとりあえず入れておきます。
if cacheがundefinedだったらreturn cacheを返せばいいと。
undefinedじゃなければ。
つまりキャッシュの中身があるのであれば
そのままキャッシュを返してあげれば良いと。
そうじゃなければconst resolve="isfile",ですね。
isfileメソッドを実行します。
その時にこの関数、本体の方に渡されたファイルという
18:02
引数オブジェクトをそのまま渡してあげますと。
戻ってきたresolveというのが
モジュール解決されたレスポンスが返ってくるので
それを持っておきます。
それをcache.setですね。
マップのインスタンスですからね、cacheは。
setでファイルとそのresolveという
モジュール解決したレスポンスも一緒に渡して
キャッシュしてしまうと。
その結果をreturnファイルで返してあげればOKですよ、
ということですね。
内部的にキャッシュをしておくということを
これをすることによって
キャッシュを追加したことで
リント処理にかかる時間はさらに15%を短縮されました。
15%は悪くないですね。
15000回さっきのisfileとか呼び出されたりしているので
結構時間かかってますね。
その中でこれをやることで15%も短縮されました。
しかしキャッシュの危険な点は
キャッシュが古くなる可能性があるということです。
そりゃそうだよね。
リフレッシュのタイミングどうか
ちゃんとリフレッシュしますかというのを
処理入れないと危険ですからね。
通常キャッシュはある時点で無効化されなければならないんです。
念のためにキャッシュされたファイルが
まだ存在するかどうかをチェックする
より保守的なアプローチを選択することにしました。
さっきの関数にさらにもうちょっと手を加えましたよと。
これはツールがウォッチモードで実行されることが多く
可能な限りキャッシュし
変更されたファイルだけを無効にする
ということが期待されている。
そういうツールが多いですね。
そういうことを加味すると
珍しいことじゃないよねということを言ってます。
さっきのnewmapのところですね。
キャッシュというインスタンスを作っているんですけど
function.resolveも作っています。
そこは大体一緒なんですけど
そのキャッシュの中からですね
cache.getで引数に渡されたファイルの中身を見ていますけど
その中身をレスポンスでキャッシュで持っています。
そのキャッシュがundefinedじゃなければ
もしかしたらisfileでファイルという引数を渡してあげて
それがtrueであれば
キャッシュをそのまま返せばいい
キャッシュをそのまま返せばいいということをしています。
つかずキャッシュが存在することを意味していますからね。
最初の判定条件キャッシュというところだけではなくて
isfileのところも一緒にセットでチェックをして
okであればキャッシュを返しますと。
さらにexisting-resolution-logic hereですね。
ああ、分かりました。
さらにですね、extensionsを見てますと
extensionsをfor-ofで分回して
一個一個のext
これはあれですね、識別子
拡張子ですね。拡張子を見ていきます。
ファイルプラスext拡張子ですね。
文字列結合をしてファイルパスを作っていますと。
そのファイルパスをまたif文ですね。
isfileメソッドで実行して
もし存在するのであればcache.setで
ファイルとそのファイルパスをキャッシュにしておいて
リターンファイルパスをしてあげれば良いと。
じゃなければ完全にslowですね。
エラーを発生させてしまえば良いという事をしています。
より保守的ですね。
正直なところですね、キャッシュされたシナリオでもファイルシステムに到達しているため
21:01
そもそもキャッシュを追加するメリットが無くなってしまうのではないの?
という風には思っていました。
キャッシュされたシナリオでも結局ファイルシステムに
行っちゃっているのであればキャッシュのメリットはあまりないような気がしましたけど
しかし実態としての数字を見てみますと
リント処理にかかる時間は
0.5%しか悪化していません。ほぼ変わらない。
しかしリントのファイルシステムコールを追加する事は
もっと重要な事ではないの?という風に
この人は次の疑問に思いました。
では次のセクションは
ファイル拡張子のあてゲーム
ザファイルエクステンションゲッシングゲーム
と言っていますね。
ゲームという例え方が面白いですね。
スクリプトにおけるモジュールの問題というのは
この言語が最初からモジュールシステムを持っていなかったという事です。
ノードJSが登場した時にコモンJSというモジュールシステムが付与しました。
このシステムには読み込むファイルの拡張子を省略する機能など
いくつかの可愛い機能がありました。
いわゆるリクワイヤー括弧で
文字列で
ドットスラッシュフみたいなように読み込みの記述をしますと
自動的に拡張子がドットJSというのが追加されて
いわゆるドットスラッシュフードットJSというファイルを読み込もうとしています。
これは相対パスですね。
もしそれがなければ、JSONファイルドットスラッシュフードットJSをチェックし
それもなければ、インデックスファイルフースラッシュインデックスドットJSとチェックしますと。
フーのディレクトリの下の
インデックスJSを読み込みにくく
自動で勝手にやってくれます。
こういうことをやってくれるので
エクステンションズを最初から引数に持っておくと
コンストエクステンションズイコール配列で
ドットJS、JSX、CJS、MJS
TS、TSX、MTS、CTSですね。
この8つですけども、これ拡張子ですね全部。
これはチェックすべき8つの拡張子の可能性になります。
しかもこれだけではありません。
これらの拡張子を解決する可能性のあるインデックスファイルを行為するために
このリストを2倍にする必要がありますと。
この拡張子をまずバーッと見ていてありますかって見るんですけど
なければスラッシュ
その名前がディレクトリかもしれないので
スラッシュインデックスという風に見に行くようにしますから
単純に2倍ですよね。
つまり私たちのツールにはディスク上に存在する拡張子を見つけるまで
拡張子のリストをループする以外の選択肢はないのです。
./.fooを解決したいのに実際のファイルがfoo.tsである場合は
次のようなチェックが必要ですと言って
5回のチェックが走っているよと言ってます。
まずはfoo.jsを見てダズンディグリスト
次はfoo.jsxを見てダズンディグリスト
みたいなことを繰り返していくんですね。
さっきのエクステンションとのリストを上から順番に舐めていって
5つ目が.tsでこれありましたからビンゴということですね。
こんなことをやってくれますよということでした。
これは4つの不要なファイルシステムを呼び出していることに他なりません。
合計5回実行してますからね。
24:00
もちろん拡張子の順番を変えてプロジェクトで最もよく使われる拡張子を
先頭に置くことももちろんできます。そうすれば正しい拡張子が早く見つかる可能性が
高まりますけど問題は完全に解消できるわけではないです。
ES2015ですね。
2015の仕様の一部として
新しいモジュールシステムというのが提案されました。
詳細はまだ決まっていませんでしたが当時は公文は決まっていました。
インポート文はツールとしてはコモンJSよりも非常に優れているため
すぐに採用されました。
その静的な性質からツリーシェイクのようなより多くのツールの
強化機能のためのスペースが開かれて
未使用のモジュールやモジュール内の関数さえも
簡単に検出し本番ビルドから取り除くことができるようになりました。
当然誰もがこの新しいインポート公文に飛びつきました。
その結果が今の感じですね。
モジュールシステムとかリソリューションの環境になってきたという感じですね。
しかし一つ問題がありました。
それは公文だけが確定しており
実際のモジュールの読み込みや解決はどのように行われるべきかということではなかったということですね。
このギャップを埋めるためにツールはコモンJSの
既存のセマンティックスを再利用しました。
このツールはほとんどのコードベースを移植する際に必要なのは
公文の変更だけであり、これらはコードモッズによって
自動化できているため採用に非常に
高都合だったということですね。
これは採用の中で素晴らしい観点でした。
しかしそれは同時にインポート指定詞がどのファイル拡張子に解決すべきかという
推測ゲームを引き継ぐことも意味します。
結局根本解決にはなっていないということです。
ただ動作することを前提として
再利用したということですね。
この曖昧さの原因を取り除き常に拡張機能を追加することで
一連の問題回避することはできます。
ツールもずっと早くなりますし、いろんなことが解決できます。
しかしツールが曖昧さに対処するために
適応しているためエコシステムが前進するまで
あるいは前進したとしても結構時間がかかるでしょうということです。
では最後のセクションです。
ではここから我々はどうすればいいのかというので
閉められています。
今回の調査を通してモジュール改造との最適化に対して
これほどまでに改善の余地があることに少し驚きました。
この記事で紹介したいくつかの変更によりリント処理にかかる時間は
合計すると30%ほど短縮されました。
こうなったいくつかの最適化は実はJavaScriptに限ったことではありません。
他のプラグラミング言語のツールでも見られるような最適化です。
他の言語でも似たようなことがあるわけですよね。
パス問題の解決は他の言語でもありました。
サーバーサイトだとネームスペースの話もあったりするので
そこが結構回帰してくれるのかもしれないですけどね。
とはいえ結局パス問題はどの言語であろうと
宿命的には逃れられない話ですね。
モジュール解決に関して言えば4つの主要な観点があります。
1つ目はファイルシステムからの呼び出しはできるだけ避けましょう。
2つ目はファイルシステムへの呼び出しを避けるため
できる限りキャッシュをしましょう。
3つ目はfs.statまたはfs.statsyncを使用している場合は
常にslowifnoentryをfalseというのを設定しておけば
速くなりますよと。
これだけで確か15%ほど解決しますよね。
27:00
最後、情報へのトラバースを可能な限り制限しましょうと言っています。
私たちのツールの遅さは
JavaScriptという言語が原因ではなく
物事が全く最適化されていないという環境の方が原因でした。
モジュール解決のために標準的なパッケージが1つもないため
JavaScriptのエコシステムが分離されていないことも
助けにはなりません。
このモジュールリソリューションの話は
ノード.jsだけではないですからね。
全体通して他の言語だったり
環境でも同じような問題が起きたり
単一のライブラリーが存在しないのであれば
それがもし存在するんだったら
それは多分ないんでしょうね。
なので皆さんが毎回苦しんでいたり
パフォーマンス改善をどうするかというのを議論しているわけだと思います。
もちろんこれが一挙に解決することができれば
いろんな僕らのプログラミング環境のところが解決すると思うので
その一瞬間
プログラマーの世界での歴史の名前を残す感じはしますけど
かなり問題は難しいんだろうなと思います。
今も一刻一刻ライブラリーが増え続けていますからね。
なかなか難しいのでしっかり中身のソースコードを読んでいって
自分たちができる限りのことをやっていきましょう
という風に僕は感じました。
というところで朝勝はちょっと長くなりましたけど以上になります。
いかがだったでしょうか。
毎回パート3のLinting with ES Lintonの記事もそうなんですけど
とても資産に富んでるかつ
ものすごいファクトベース
ノードジェス本体のソースコードを見たりとか
そういう風に書かれた記事でもあってめちゃくちゃ勉強になりますね。
いかに自分の技術力が低いかと思わされるわけですけど
こういうところを読んでしっかり盗んでいって
技術力を向上させていきたいなと思いました。
では朝勝これで終了したいと思います。
日曜日ですね。日曜日なかったですね。火曜日でした。
宿日なんですけど休んでいただいて
残り3日間頑張っていけたらと思います。
じゃあ明日はですね。パート1に行くかパート4に行くかちょっと悩ましいですけど
パート1に行ったほうがいいなと思いました。
メニューも書いてあるのでいろんな解決の話が出てくる
パフォーマンスもありいろんなライブラリもどうせ使いますので
その辺も見ていきたいなと思っております。
それが終わったらパート4NPMスクリプトもせっかく出てるので
このまま読み切っていきたいと思います。
じゃあ本日の朝勝はここで締めたいと思います。
お疲れ様でした。
29:24

コメント

スクロール