なぜhtmxにはビルドステップがないのか

アレクサンダー・ペトロス

htmxの貢献者の一部からよく出る質問は、なぜhtmxがTypeScriptで書かれていないのか、あるいはそもそもなぜhtmxにビルドステップが全くないのか、ということです。htmxのソースコード全体は、3,500行のJavaScriptファイル1つです。htmxに貢献したい場合は、htmx.jsファイルを変更することで行います。このファイルは、本番環境でブラウザに送信されるファイルと同じもので、最小化と圧縮が行われている程度です。

私はhtmxプロジェクトの代弁者ではありませんが、いくつか重要な貢献をしており、このビルドなしの構成を維持することを、この問題が提起されるたびに声高に主張してきました。私の視点から、なぜhtmxにビルドステップがないのかを説明します。

#一度書けば、永遠に実行可能

ライブラリをプレーンなJavaScriptで書く最良の理由は、それが永遠に続くということです。これはおそらくJavaScriptの最も過小評価されている機能です。いくつか例外的なケースがあると思いますが、1999年にNetscape Navigatorで動作していたJavaScriptは、昨日ダウンロードされたGoogle Chromeで、現代のコードと並んで変更なしに動作します。これは、ごくわずかなプログラミング環境に当てはまるだけです。Python、Java、Cなどには当てはまりません。これらはすべて、新しい言語機能を選択すると、廃止されたAPIを強制的に使用することになるバージョン管理メカニズムを備えています。

もちろん、ほとんどの人がJavaScriptで経験することは、牛乳のように古くなるということです。3か月後にnodeリポジトリを再開すると、プロジェクトがセキュリティ警告、後方互換性のないライブラリの「アップグレード」、そしてプロジェクトを開始した瞬間に文化的ピークを迎えたフロントエンドフレームワークに苦しめられていることに気づくでしょう。今ではテクノロジーの負債と広く考えられています。この状況の責任は誰にあるかは他の誰かが決めることですが、いずれにせよ、JavaScriptランタイム以外の依存関係を持たないことで、この問題全体を排除できます。

今日のJavaScriptを書く一般的な方法は、TypeScriptからコンパイルすることです(TypeScriptはおそらくビルドシステムを使用する最良の理由なので、例として頻繁に使用します)。TypeScriptはWebブラウザでネイティブに実行されないため、TypeScriptコードは、後方互換性へのECMAの熱狂的な献身によって保護されていません。他の依存関係と同様に、新しいメジャーバージョンのTypeScriptは、前のバージョンとの後方互換性が保証されていません。そうである可能性はあります!しかし、そうでない場合は、最新の開発ツールチェーンを使用したい場合、メンテナンスを行う必要があります。

メンテナンスは労働力で支払われるコストであり、オープンソースコードベースは、その支払いを最も余裕がないプロジェクトです。ビルドステップを使用しないことを選択することで、htmxを最新の状態に保つために必要な労力を大幅に削減できます。この経験は、htmxの前身であるintercooler.jsによって裏付けられています。これは(私が理解している限り)ほとんど労力をかけずに無期限に維持されています。htmx 1.0がリリースされたとき、TypeScriptはバージョン4.1でした。intercooler.jsがリリースされたとき、TypeScriptは1.0よりも前でした。それらのTypeScriptバージョンで書かれたコードは、今日のTypeScriptコンパイラ(執筆時点ではバージョン5.1)で変更なしにコンパイルされるでしょうか?たぶん、そうではないかもしれません。

しかし、htmxは依存関係のないJavaScriptで書かれているため、Webブラウザが関連性を保つ限り、変更なしに実行されます。ブラウザベンダーに難しい作業を任せましょう。

#開発者体験

TypeScriptの開発者体験(DX)が、多くの点でJavaScriptの開発者体験よりも優れていることは事実です。TypeScript DXがあらゆる点で優れているわけではないことも事実であり、ソフトウェアエンジニアが進化を、トレードオフを伴う選択肢ではなく、能力のテレオロジーとして捉える傾向が、自分たちが気に入っているDXの側面のために支払われるコストを見えなくしてしまうことがあります。たとえば、TypeScriptを使用するための小さなトレードオフは、コンパイルに時間がかかることであり、変更をテストするために再コンパイルを待つ必要があることです。通常、このコストはごくわずかであり、支払う価値は十分にありますが、それでもコストです。

TypeScriptを使用するためのより大きなコストは、ブラウザで実行されているコードが、あなたが書いたコードではないため、ブラウザの開発者ツールが使いにくくなることです。TypeScriptコードが例外をスローすると、スタックトレース(JavaScriptの行番号、JavaScriptの関数シグネチャなどを含む)が、あなたが書いたTypeScriptコードにどのように対応しているかを把握する必要があります。JavaScriptコードが例外をスローすると、ソースコードを直接クリックし、書いたものを読み、デバッガーにブレークポイントを設定できます。これはとてつもないDXです。このように作業したことのない若いWeb開発者にとって、それは啓示的な経験となる可能性があります。

ビルドステップの支持者は、TypeScriptがソースマップを生成できると指摘します。これは、ブラウザにどのTypeScriptがどのJavaScriptに対応するかを伝えるものです。その通りです!しかし、今度は追跡する必要のあるものがもう1つ増えました。あなたが書いたTypeScript、それが生成したJavaScript、そしてこの2つを接続するソースマップです。現在依存しているホットリロード開発サーバーは、localhostでこれらを最新の状態に保ちますが、ステージングサーバーではどうでしょうか?本番環境ではどうでしょうか?これらの環境で発生するバグは、どこから来たのかに関する多くの情報を失っているため、追跡するのが難しくなります。これらは解決可能な問題ですが、あなたが作り出した問題です。これらはコストです。

htmx DXは非常にシンプルです。ブラウザは単一のファイルをロードします。これは、あらゆる環境であなたが書いたものとまったく同じファイルです。そのエクスペリエンスを維持するために必要なトレードオフは現実的ですが、このプロジェクトにとっては理にかなったトレードオフです。

#強制された明確さ

モジュール化は、ソフトウェアの最も素晴らしいアイデアの1つです。モジュールを使用すると、コードを、より小さな問題を解決する、適切に閉じ込められたサブ構造に分割することで、非常に複雑な問題を解決できます。モジュールは非常に便利です。

ただし、単純な問題、または少なくとも比較的単純な問題を解決したい場合があります。そのような場合、より複雑なソフトウェアの構成要素を使用しないことが役立つ場合があります。そうしないと、それに見合う価値を創出することなく、その複雑さをエミュレートしてしまう可能性があります。その核となる部分で、htmxは比較的単純な問題を解決します。それは、ハイパーテキストの宣言的な特徴を使用してDOM要素を簡単に置き換えることができる、いくつかの属性をHTMLに追加することです。htmxが単一のファイル(繰り返しますが、約3,500 LOC)に残ることを要求すると、ライブラリに一定の意図が強制されます。htmxソースに取り組む際には、新しいコードの追加を正当化する必要があるという現実的なプレッシャーがあり、これは相対的な単純さの均衡を維持します。

DXのコストは明らかですが、驚くべきDXの利点もあります。ソースファイルで関数名を検索すると、その関数のすべての呼び出しをすぐに確認できます(これにより、より高度なコードのイントロスペクションの必要性も軽減されます)。機能が隠れる場所がないため、htmxでの作業がはるかに簡単になります。はるかに複雑なプロジェクトでも、このアプローチの側面を使用しています。SQLite3は、単一ファイルのソースアマルガメーションからコンパイルされます(開発には個別のファイルを使用しますが、狂ってはいません)。これにより、ハッキングが大幅に簡単になります。Linuxカーネルをこのように構築することは決してできませんが、htmxはLinuxカーネルではありません。

#コスト

他のテクノロジーの決定と同様に、ビルドステップを省略することを選択することには、長所と短所があります。情報に基づいた意思決定を行い、利点またはコストの一部が適用されなくなった場合に、その決定を再検討できるように、これらのトレードオフを認識することが重要です。プレーンなJavaScriptで書く利点を念頭に置いて、それがもたらすいくつかの問題点について考えてみましょう。

#静的型なし

TypeScriptはJavaScriptの厳密なスーパーセットであり、追加する機能の一部は非常に役立ちます。TypeScriptには…型があり、IDEがコードを提案したり、メソッドを誤って使用した可能性のある場所を指摘するのに役立ちます。コードを自動的に名前変更およびリファクタリングするためのツールは、JavaScriptよりもTypeScriptの方がはるかに信頼性が高くなります。ただし、ブラウザはJavaScriptを実行するため、htmxコードはJavaScriptで記述する必要があります。そして、JavaScriptが動的に型付けされている限り、htmxソースで真の静的型を取得するために必要なトレードオフは、それだけの価値がありません(htmxユーザーは、.d.tsファイルで宣言された型付きAPIを引き続き利用できます)。

将来のバージョンのhtmxでは、ビルドステップなしで同じ保証の一部を得るためにJSDocを使用する可能性があります。 Svelteのような他のライブラリも、TypeScriptファイルが導入するデバッグの摩擦のために、この方向に進んでいます。

#ES6なし

htmxはInternet Explorer 11のサポートを維持しており、ビルドステップがないため、htmxのすべての行はIE11互換のJavaScriptで記述する必要があります。これは、ES6がないことを意味します。私のような人が、JavaScriptが現在かなり優れていると言うとき、通常はasync/await、匿名関数、関数型配列メソッド(つまり、.map.forEach)などのES6で導入された言語機能を指しています。これらは、htmxソースで使用することはできません。

これは非常に厄介ですが、実際には大きな障害にはなりません。いくつかの優れた言語機能がないからといって、関数型パラダイムでコードを書くことができないわけではありません。カスタムforEachメソッドを書かない方が良いでしょうか?もちろんそうです。しかし、htmxが対象とするすべてのブラウザがES6をサポートするまでは、ES5をいくつかのヘルパー関数で補完することは難しくありません。ES6に慣れている場合は、自動的により優れたES5を書くことができます。

IE11のサポートはhtmx 2.0で削除される予定で、その時点でES6がソースコードで許可されます。

#コアにモジュールなし

この点は明らかですが、改めて述べておく価値があります。htmxソースをモジュールに分割できれば、よりすっきりするでしょう。コードの品質に影響を与える他の要素はすっきりさだけではありませんが、htmxソースが高品質である限り、それはすっきりしているからではありません。

これにより、htmxで特定のことを行うのが非常に困難になります。 idiomorphアルゴリズムは、htmx 2.0のコアに含まれる可能性がありますが、htmxを使用せずにDOMモーフィングアルゴリズムを使用できるように、個別のパッケージとしても保守されています。コアが複数のファイルを含めることができる場合、gitサブモジュールなど、任意の数のミラーリングスキームでこれを簡単に実現できます。しかし、コアは単一のファイルであるため、idiomorphコードもそこに存在する必要があります。

#最終的な考察

このエッセイは、「なぜhtmxには現時点ではビルドステップがないのか」というタイトルの方が適切かもしれません。前述したように、状況は変化し、これらのトレードオフはいつでも見直すことができます!現在検討中の問題の1つは、リリースに関連するものです。 htmxがリリースをカットする場合、いくつかの異なるシェルコマンドを使用して、distディレクトリにhtmx.jsのミニファイおよび圧縮されたバージョンを入力します(難癖をつけたい人は、これが明らかに、ある意味でビルドステップであることを指摘しても構いません)。将来的には、そのスクリプトを拡張して、Universal Module Definitionを自動生成するかもしれません。または、より複雑な設定が必要な新しい配布ニーズが発生する可能性もあります。誰にもわかりません!

htmxの核となる価値観の1つは、過去10年間、ますます複雑化するJavaScriptスタックに支配されてきたWeb開発エコシステムにおいて、選択肢を提供することです。フロントエンドのJavaScriptの巨大なコードベースがなくなると、バックエンドでJavaScriptを採用するプレッシャーははるかに少なくなります。Python、Go、さらにはNodeJSでバックエンドを記述でき、htmxにとっては問題ありません。すべての主流言語には、HTMLをフォーマットするための成熟したソリューションがあります。これがHypermedia On Whatever you’d Like (HOWL)の原則です。

ビルドプロセスなしでJavaScriptを記述することは、SPAフレームワークの複雑化を管理するためにNextJSやSvelteKitが不要になった場合に利用できるオプションの1つです。この選択は、今日のhtmx開発には理にかなっており、あなたのアプリにとっても理にかなっている場合もそうでない場合もあります。

</>