node-fibersの廃止

2021年3月26日投稿 Natalie Weizenbaum

残念なお知らせですが、それほど驚くべきことではないnode-fibersパッケージが製品寿命に達し、Node 16との互換性のために更新されないことが最近判明しました。Dart Sassはこれまで、JavaScriptユーザーがnode-fibersを渡して非同期render()メソッドのパフォーマンスを向上させることを許可していましたが、今後はNode 16以降では残念ながらこのオプションは使用できなくなります。

この失われたパフォーマンスを取り戻すための代替オプションがいくつかあります。現在利用可能なもの、開発中のもの、理論的には可能だがユーザーからのプルリクエストで実現できるものなどです。残念ながら、現在すぐに利用できるオプションのいずれも、node-fibersと同じ使いやすさで直接置き換えられるものではありません。そのため、パフォーマンスが非常に重要な場合は、当面はNode 14を使い続けることをお勧めします。

何が起きたのか?何が起きたのか? permalink

ここまでの経緯を理解するには、2つの歴史的背景を知る必要があります。まず、Dart Sassがそもそもnode-fibersを使用する理由、そして次に、node-fibersが廃止される理由です。

このセクションはかなり技術的な内容なので、詳細に興味がない場合は先に進んでください

SassにおけるファイバーSassにおけるファイバー permalink

Dart Sassは、現在非推奨となっているNode SassからそのJavaScript APIを継承しました。このAPIには、Sassファイルをコンパイルするための2つの主要な関数があります。renderSync()はコンパイルされたCSSを同期的に返し、render()は代わりに、コンパイルされたCSSを非同期的に渡すコールバックを受け取ります。非同期プラグイン(webpackのsass-loaderなどの広く使用されているインポーターなど)を許可したのはrender()のみだったため、実際にはrender()が非常に広く使用されるようになりました。

Node Sassの場合、render()renderSync()のパフォーマンスの違いは無視できました。なぜなら、非同期の処理方法にほとんど制限のないC++コードに基づいて構築されていたからです。しかし、Dart Sassは純粋なJavaScriptとして実行されるため、JavaScriptの厳格な非同期ルールに従う必要があります。JavaScriptの非同期は伝染性があるため、任意の関数(インポータープラグインなど)が非同期である場合、それを呼び出すものすべてが非同期になる必要があり、プログラム全体が非同期になるまで続きます。

そして、JavaScriptの非同期は無料ではありません。非同期関数の呼び出しごとに、コールバックを割り当て、どこかに保存し、それらのコールバックを呼び出す前にイベントループに戻らなければならず、それらすべてに時間がかかります。実際、Dart Sassの非同期render()renderSync()よりも2~3倍遅い傾向があります。

ファイバーが登場します。ファイバーは、RubyやC++などの言語で使用できる非常に優れた概念であり、プログラマーは非同期関数により多くの制御権を与えられます。同期コード(Sassコンパイラなど)が非同期コールバック(webpackプラグインなど)を呼び出すことさえ可能になります。node-fibersパッケージは、V8仮想マシンで特殊な処理を行い、JavaScriptでファイバーを実装しました。これにより、Dart Sassは高速な同期コードを使用して非同期render() APIを実装することができました。そしてしばらくの間、それは素晴らしかったです。

ファイバーの死ファイバーの死 permalink

残念ながら、node-fibersが使用していた特殊な処理には、V8のパブリックAPIの正式な一部ではないいくつかの部分へのアクセスが含まれていました。それらが使用していたインターフェースがリリースごとに同じままであるという保証はなく、実際にはかなり頻繁に変更される傾向がありました。長い間、それらの変更は小さかったので、それらをサポートする新しいバージョンのnode-fibersをリリースすることができましたが、Node.js 16で運を使い果たしました。

最新バージョンのV8には、内部構造の大幅な見直しが行われています。これにより、最終的にはいくつかの優れた改善が実装されますが、その副作用として、node-fibersが使用していたAPIは、明確な代替手段なしに完全に削除されました。これは誰のせいでもありません。これらのインターフェースはV8のパブリックAPIの一部ではなかったため、それらを安定的に維持する義務はありませんでした。ソフトウェアでは、そういうこともあります。

パフォーマンスの回復パフォーマンスの回復 permalink

node-fiberssass.render()に渡すことができなくなったことによって失われたパフォーマンスを取り戻すためのいくつかのオプションがあります。短期的なものから長期的なものまで順に説明します。

非同期プラグインを避ける非同期プラグインを避ける permalink

これは今日からでもできることです。Sassに渡すプラグインを同期的にすることが可能であれば、高速化のためにファイバーを必要としないrenderSync()メソッドを使用できます。これには既存のプラグインの書き換えが必要になる場合がありますが、すぐに効果が見られます。

組み込みDart Sass組み込みDart Sass permalink

まだ本番環境向けには準備ができていませんが、Sassチームは「組み込みDart Sass」と呼ばれるプロジェクトに取り組んでいます。これには、ライブラリではなくサブプロセスとしてDart Sassを実行し、特別なプロトコルを使用して通信することが含まれます。これにより、既存の代替手段に比べていくつかの重要な改善が得られます。

  • コマンドラインからsassを実行する場合とは異なり、これはwebpackインポーターなどのプラグインでも動作します。実際、既存のJavaScript APIを可能な限り忠実に再現する予定です。これにより、非同期プラグインは同期プラグインよりもさらに高速に実行される可能性があります。

  • 既存のJSコンパイル版とは異なり、これはDart VMを使用します。Dart言語のより静的な性質により、Dart VMはNode.jsよりも大幅に高速にSassを実行し、大規模なスタイルシートでは約2倍の速度向上が得られます。

組み込みSassのNode.jsホストはまだ開発中ですが、ベータ版(機能は最小限)が利用可能なので、試してみることもできます。

ワーカー・スレッドワーカー・スレッド permalink

純粋なJS Dart SassをNode.jsワーカー・スレッドで実行する可能性についても検討しました。ワーカー・スレッドは、同期コードが非同期コールバックの実行を待つことを可能にするという点で、ファイバーと少し似ています。残念ながら、スレッド境界を越えて渡すことができる情報の種類に非常に制限があるため、Sassのような複雑なAPIをラップして使用することがはるかに困難になります。

現時点では、Sassチームは組み込みSassに重点を置いており、代替手段としてワーカー・スレッドに取り組むための帯域幅がありません。とは言っても、やる気のあるユーザーがこれを実装するのを喜んでお手伝いします。興味のある方は、GitHubのissueをご覧ください!

node-fibersの復活node-fibersの復活 permalink

現実のものにするには真の献身が必要ですが、もう1つの潜在的な解決策があります。原理的には、node-fibersに必要なフックを正式にサポートする新しいAPIをV8に追加することが可能です。これにより、パッケージは見事に復活し、Sassは将来にわたってrender()を高速化できます。

SassチームはV8チームとnode-fibersの所有者に連絡を取り、どちらもこのアイデアに原理的に賛成しています。どちらも自ら最後までやり遂げる時間はありませんが、挑戦しようとするエンジニアを喜んで支援する意思を示しています。

ただし、これは気弱な人には向かない貢献です。C++の知識、node-fibersのコードベースとV8のisolate APIの基礎を学ぶ意欲、そしてnode-fibersのニーズとV8チームが維持することに納得できる安定したAPIを交渉するためのAPI設計と人間関係のスキルが必要です。しかし、興味のある方は、お気軽にご連絡ください