DOMテンプレートAPIを導入すべき好機
(justinfagnani.com)- Webプラットフォームは動的アプリケーションのランタイムとして定着したが、標準DOMにはデータ・イベント・プロパティ・XSS防御・効率的な更新をまとめて扱える 宣言的テンプレートAPI がない
- React、Vue、Angular、Lit、Svelte、SolidJS のような現代のフレームワークは、マークアップとデータを結び付ける テンプレートシステム を中核に据えており、これは可読性・セキュリティ・性能・静的解析・サーバーサイドレンダリングに影響する
- 過去の E4X、E4H、組み込みの
htmlテンプレートリテラルタグは DOM 生成には役立ったが、DOM 更新 は解決できず、今では tagged template literals とフレームワークの経験が標準化の土台を提供している - JavaScript ベースの API は JSX の標準化や HTML ベースのテンプレートより範囲が小さく、JSX 変換の コンパイルターゲット となり、その後 HTML テンプレートや宣言的カスタムエレメントへつなげられる可能性がある
- 提案の道筋は DOM Parts、template identity、Signals、DOM スケジューラのような下位メカニズムとかみ合い、vanilla 開発者・Web Components ユーザー・フレームワーク実装者に共通基盤を提供できる
標準DOMに欠けている宣言的テンプレート層
- Webプラットフォームは歴史上もっとも成功したアプリケーションランタイムであり、DOM API は静的な文書ビューアを動的で表現力のあるランタイムへ変えた基盤である
- Photoshop のような複雑なアプリも Web アプリとして提供され、UI 全体が Web Components で作られるほど、DOM は強力な API である
- ただし現在の標準 DOM API には、次の作業を一度に扱える便利な方法がない
- データから DOM ノード片を生成する
- イベントリスナーを追加する
- エレメントのプロパティを設定する
- XSS 攻撃に対する安全性を確保する
- 新しいデータでその DOM 片を効率よく更新する
フレームワークがすでに証明したテンプレートの価値
- 現代の Web フレームワークとレンダリングライブラリは、マークアップとデータを 宣言的に結び付ける テンプレートを中核機能として使っている
- React、Vue、Angular、Preact、Lit、Svelte、SolidJS、Stencil、Quik、Ember、FAST、Polymer、Marko などが含まれる
- 宣言的テンプレートは命令的 DOM API より書きやすく読みやすい
- 補間、イベントリスナー、プロパティ設定がテンプレートの近くにあるため、動作の局所性 を保ちやすい
- テンプレートシステムは補間値を自動的にエスケープできるため、XSS 防御 に有利である
- 優れたテンプレートシステムは、変更が必要な DOM だけを更新することで、多くの手書きコードより高速になりうる
- 多くのテンプレートシステムは、型検査や自動補完のような 静的解析 をサポートする
- 宣言的テンプレート定義はブラウザ環境全体の外でも解釈できるため、効率的なサーバーサイドレンダリングが可能である
テンプレート不在が生むコスト
- プラットフォームは、ほぼすべての Web アプリケーションの中核要件をまだ直接満たせていない
- 国際化 API、Temporal、Scheduler、Sanitizer のように、テンプレートも標準プラットフォーム機能として扱うに値する
- ユーザーはアプリのダウンロード時間、レンダリングオーバーヘッド、セキュリティの弱いアプリの影響を受ける
- テンプレートライブラリのコードは、最良でも数 kB、最悪では 100kB 以上 になることがある
- 数 kB でも、初期インタラクティブレンダリングの予算では大きな比重を占めうる
- 開発者は基本的な作業にもライブラリ、npm、CDN に依存しなければならない
- 単純な静的ファイルと DevTools の有用性が下がる
- スタック間で移植可能な基本テンプレート知識がない
innerHTMLのようなネイティブ DOM 生成 API は、デフォルトでは安全ではない
- フレームワークとテンプレートライブラリは、書きやすさ、レンダリング速度、更新速度、セキュリティ、コードサイズの間で難しいトレードオフを迫られる
- Web プラットフォームは Flutter、SwiftUI、Jetpack Compose のようなネイティブプラットフォームと競合している
- これらのプラットフォームはテンプレートに似た仕組みを内蔵し、アプリのインストールとコンパイル言語を利用するため、数 kB のバンドルサイズにそれほど敏感ではない
今あらためて試せる理由
- 過去にもテンプレートに近いプラットフォーム提案は存在した
- E4X は Firefox と Flash に実際に搭載された
- E4H は Ian Hixie が単純化したアイデアだった
- 2012 年ごろには組み込み
htmlテンプレートリテラルタグのプロトタイプもあった
- 過去の試みは DOM 生成には役立ったが、更新 を解決できなかったため、十分な解決策にはなりにくかった
- 現在ではフレームワークが十分なユースケースを生み出しており、ユーザー空間のテンプレート解決策も時間とともにますます似通ってきている
- フレームワークを使わない開発者や Web Components コミュニティでも、便利な DOM 操作とリアクティビティ API への需要がある
- DOM Parts のような低レベルな DOM 更新プリミティブの提案が進行中である
- より高レベルな宣言的テンプレート API は、フレームワーク実装の負担を減らし、低レベル API 提案を検証・補完できる
構文と意味論の共通部分
- 人気のクライアントサイドテンプレートシステムの多くは、マークアップとバインディング を結び付ける構造を共有している
- 式区切り、制御フロー、バインディング型の指定子には多少の違いがあるが、共通点のほうが大きい
- HTML ベースの Vue、Angular、Svelte と、JavaScript ベースの React、Lit、Solid の間にも類似性がある
- JavaScript ベースの API では、テンプレートは通常式であり、合成はテンプレート式のネストや参照で行われ、制御フローは JavaScript が担う
- 多くの JavaScript API ベースのシステムは、テンプレート式から DOM 記述を返し、その後に別個の
render関数呼び出しで適用する- 大半は同じ
render呼び出しで DOM を更新する - 一部はきめ細かなリアクティビティによりこの方式を避ける
- 大半は同じ
- E4X と E4H は JSX のように JavaScript にマークアップを埋め込む新構文を追加したが、DOM 記述ではなく DOM 片そのものを返す
- 初期 DOM 生成には向いていたが、更新は依然として命令的なままだった
今日の JavaScript で表現できる API
- JavaScript には HTML のような埋め込み DSL のための tagged template literals がすでにある
- 新しい JavaScript 構文を追加しなくても DOM API 上でテンプレートを表現できる
- 人気ライブラリは、このアプローチがうまく機能することを示している
- 新構文なしで提案を進められる点は、標準化の現実性にとって重要である
JSX とネイティブテンプレート API の関係
- tagged template literals は ES2015 の強力な機能だが、ユーザー空間のコンポーネントシステムでは JSX より構文オーバーヘッドが大きい
- JSX は人気が高いが、構文だけがあり、ランタイム意味論がない
- 標準化するには、単に新構文を追加するだけでなく意味論を定義しなければならない
- 周辺で定義された
createElement()呼び出しとして扱う方法は除外される - オブジェクトツリーを作る方法では、静的部分と動的部分が混在し、より効率的な更新パターンを妨げる可能性がある
- Records and Tuples 提案が進んでいたなら、JSX が box 付き Records を作る可能性もあったが、その提案は特に record identity と box の部分で停滞している
- JSX のような構文と意味論が完全な DOM テンプレートに適しているか検証するには、対象となるテンプレートシステムが必要である
- React のテンプレートには、DOM エレメントのプロパティやイベントへ明示的にバインドしたり、エレメントへディレクティブを適用したりする仕組みがない
- 現在の非標準 JSX はランタイム意味論のない純粋な構文なので、JSX を tagged template literal に変換するコンパイラを作ることができる
- JSX-to-Lit のような事例がすでにある
- ネイティブテンプレート API は、開発者が望む構文を選べる コンパイルターゲット になりうる
HTML ベースのテンプレートより JavaScript API を先に考える理由
- 多くの Web 開発者は、完全な機能を備えた HTML ベースのテンプレートシステムを望んでいる
- Web Components ユーザー、フレームワークユーザー、vanilla 開発者コミュニティに需要がある
<template>エレメント、declarative shadow DOM、Web Components がそうした機能を提供してくれると期待していた開発者もいる
- HTML ベースのテンプレートは JavaScript ベースの API よりはるかに大きな作業になる
- バインディング構文
- 式言語
- 制御フロー構文
- テンプレート更新メカニズム
- ネイティブに HTML ベースのテンプレート定義を読み込む方法もない
- Web Components v0 の HTML Imports は消え、HTML Modules はまだない
- HTML テンプレートを JavaScript モジュール内に置かなければならないなら、データと同じレキシカルスコープで JavaScript 式を書けるほうがよいという判断である
- Polymer 3 は Polymer 2 の HTML テンプレートを JavaScript へ移し、それが Lit へ直接つながった
- スクリプト的な仕事は JavaScript に任せたほうがコード量が少なく、体験もよかった
- JavaScript API は HTML ベース API の部分集合であり、足がかりにもなりうる
- JavaScript API が導入され検証されれば、HTML API にはバインディング、式、制御フロー、モジュールシステムを定義する課題が残る
リアクティビティモデルの選択肢
- 初期の DOM テンプレート提案には更新が含まれていなかったが、ユーザー空間のシステムはリアクティビティモデルと実装アプローチを十分に探ってきた
- 代表的なリアクティビティのアプローチは 3 つにまとめられる
- React の VDOM と diffing
- Lit の template identity
- Solid、Svelte、Vue、Angular などで使われる Signals ときめ細かなリアクティビティ
- diffing は理解しやすく、どんなデータにも動作するが、ほかのアプローチより遅い
- diffing アルゴリズムの違いが観測可能になることがある
- ひとつの diffing アルゴリズムを標準化し、プラットフォームエンジニアにそのオーバーヘッドを受け入れてもらうのは難しい
- template identity は、現在のテンプレートでレンダリングされた DOM なら更新し、そうでなければ置き換えるという方式である
- 高速で、多くのユースケースでは diffing と非常に近い結果を出す
- tagged template literals、JSX 変換、HTML ベースのテンプレートと相性がよい
- 意味論が単純で仕様化しやすい
- Signals は、新しいリアクティブ DOM API とテンプレート API に含める価値のあるモデルである
- JavaScript Signals 提案 はあるが、まだプラットフォームのネイティブ機能ではなく、採用も保証されていない
- Signals ベースのシステムは、すべてのデータを signal で包むときにもっともうまく機能する
- signal でないデータや、signal を作らないプラットフォーム API も多いため、再レンダリングはそうした変更を反映する簡単な方法である
- template identity と Signals は共存できる
- バインディングに signal だけを渡すテンプレートは、再レンダリングなしで signal のみで更新できる
- signal でないデータを参照できるテンプレートは再レンダリングが必要である
- 通常、再レンダリングはリアクティブなコンポーネントプロパティのようなコンポーネント層やフレームワーク層が自動で行う
標準化の議論と残された課題
- ネイティブな宣言的 JavaScript テンプレート API の議論は WICG webcomponents#1069 で始まっている
- この API は DOM 生成 API の論理的な次の一歩になりうる
- vanilla Web 開発者にとって、書きやすく安全な DOM 生成プリミティブになる
- Lit、FAST、HyperElement のような既存の Web Components ライブラリにすぐ役立つ
- 生の Web Components API 利用者にも有用で、より完全なリアクティブコンポーネント定義 API への足がかりになる
- 新しいフレームワークがこの API を基盤に作られうる
- 既存フレームワークはコンパイルターゲット、ランタイムバックエンド、直接サポート対象として利用できる
- 再レンダリング手法と Signals の両方に有用である
- 宣言的 HTML テンプレートと宣言的カスタムエレメントへの足がかりになる
- 合意し仕様化する API 表面が比較的小さい
- テンプレート意味論の定義に役立つ
- この作業は DOM Parts 提案の完成とも連動している
- DOM Parts の定義と有用性の実証にも寄与する
- 表面 API と構文は大きくないが、動作空間と下位の DOM Parts API は大きい
- きめ細かなリアクティビティには、何らかの形の DOM スケジューラ も必要になる
- 決めるべき項目、合意すべき項目、仕様化すべき項目、テストすべき項目が多く、協力が必要である
- 興味のある開発者は webcomponents#1069 にコメントするか、Bluesky で連絡できる
1件のコメント
Hacker News の意見
「テンプレートにとって良い構文がどんなものか分かっている」というのは、笑ってしまうほどの話です。まだまったく分かっていないと思います。良いテンプレートは記号的な構文というより、ほぼ間違いなく視覚的なツールに近く、かつて Dreamweaver のようなツールが成功し、デザイナーが Photoshop のようなツールで学ぶ理由もそこにあります
また、XSLT を作り直そうとしているように見えるのも残念です。誰かは結局、well-formed ではない断片をテンプレート化し、組み合わせると well-formed になる何かを作りたくなるはずです。ページ内で互いにつながっているものの同じツリーにはないエンティティも同様です。通常のマークアップにおける
labelとforが分かりやすい例ですできることなら、マークアップの標準的な文書レイアウトというルーブ・ゴールドバーグ装置に、すべてのテンプレートを無理やり合わせようとする試みが減ってほしいです。絶対位置指定を慎重に使えばかなりうまくいくことを再現しようとして、人々はあり得ないほど複雑な道を進んでいます。合わせるには計算が必要な場合もありますが、なぜ同じデータに対して機械にその計算を何度も繰り返すよう強制しなければならないのか分かりません
XML が設定やプロセス間通信のような不適切な場所で流行した一方で、XSLT という優れた変換能力を持つマークアップ言語として輝ける場所では衰退してしまったのが残念です
XSLT がつまずいた点は、本物の DSL であり、しかも宣言的・純粋・関数型の DSL だったことだと思います。人々は DSL を口では大きく持ち上げますが、実際には人気のあるホスト言語の手続き的セマンティクスを抽象化できない、単なる構文遊びにとどまることが多いです。難しいことを些細なものにしてくれる、よく設計された DSL に出会うと、人々は学ぼうとしません
そして、なぜ絶対位置指定が出てくるのかも疑問です。HN でこういう話を繰り返し見ますが、もちろん絶対位置指定には使いどころがあり、多くの場合必要です
しかしページやアプリのレイアウトに使うと、経験上、ほとんどの場合はコンテンツや画面サイズが少し変わっただけで崩れる悪夢でした。印刷新聞のレイアウトでさえタイポグラフィが絡むので、そのようにはできません
CSS を多く扱っていた頃、誰かが絶対位置指定で「ほぼ完成」したレイアウトを作った状態から作業を引き継ぐと、修正にものすごく時間がかかり、flex や通常フローのような方法で作り直す方がいつも速かったです。絶対位置指定を主なレイアウトツールとして使うと、問題は事実上修正不可能なレベルになるからです
calc()とビューポート単位を使えば筋の通った手法があるかもしれませんが、経験上、完全に静的なコンテンツとビューポートサイズでない限り、絶対位置指定はレイアウトに適していませんReact は、おおむね通常の JavaScript 関数が JSX 形式のマークアップの遅延表現を返す形で動作する、唯一の主要フレームワークです。React にはテンプレートレベルの反復や条件付きレンダリングという概念はなく、それは通常の JavaScript で処理します
そして今では、良いテンプレート構文が何かは分かっていると思います。基本的には JSX です。React があまり好きではない立場でも、そう言えます。JSX は Web 全体を席巻し、複数のフレームワークに合わせて変形されてきました。そして JavaScript のテンプレートシステムが、テンプレートを式として扱い、入れ子で合成し、特定のテンプレート構文ではなく JavaScript 自体で制御フローを処理するという共通の性質へ収束したことは、否定しにくいです
繰り返し学んできた基本的な教訓は、API/ABI は最終状態ではないということ。アプリケーションの要求は、安定した API ひとつで永久に満たされるわけではなく、将来のすべての問題をアプリレベルの問題に押し戻すこともできない
この提案は、プラットフォームの共通問題が React のような上位レイヤーで解決され、最終的に問題として認められると下へ降りていく過程をよく示している。ポリフィルも同じ例
こうした提案が成功すると一時的に注目されるが、有用な寿命の大半では、人々が回避しようとする古いものになってしまう。DOM API、ECMA のバージョン、古いブラウザ、システムの一部であるため触れられないあらゆる有用な技術がそうだった
エントロピー、拡張、後方互換性を主要なユースケースとして考えることはできないだろうか
Web プラットフォームには、ネイティブプラットフォームが持ち得るあらゆる能力を備えてほしい。もちろんプライバシー保護とサンドボックス化の制約は守るべきだ。そして Web 開発者の開発体験も優れたものになってほしい
しかしこれは、複雑性が増すことの結果とバランスを取る必要がある。この場合、ネイティブテンプレートが本当に開発体験を改善するのか確信が持てない。利益がコストを上回るようには見えない
getElementByIdは25年ほど安定していたのではないかと思う。「安定した API など存在しない」という言葉は、長続きするインターフェイスを作る能力がない、あるいは作る意思がない人たちの言葉だ。宇宙の法則ではなく、個人的な諦めに近い。反例は非常に多いアプリケーションの要求は、他の要求と同じく無限にある。その要求は、動いている API を壊すのではなく、新しい API を追加することで満たすものだ
だから20年前の決定のせいで、いまだに
smooshgateのような事態に巻き込まれる API がある: https://developer.chrome.com/blog/smooshgateWeb には本当にネイティブテンプレート、リアクティビティ、データバインディングが必要だ。何十億ものユーザーが React のようなものをダウンロードし、パースし、実行するために、どれだけの CPU と帯域幅が無駄になっているのか想像もつかない
Signals の部分は多くを物語っていて、アイデアは素晴らしいが実際には難しいことをよく示している
筆者がなぜ Signals を好むのかは理解できる。十分に大きなプロジェクトにはどれも、半分作りかけの DAG 計算ツリーがあり、言語がそのひとつを標準化するのは理にかなっている
しかしこうした抽象化は、認知コストと実装コストが大きい
たいていのエンジニアリングと同じく、問題はトレードオフだ。React のモデルはグローバル状態を更新してすべてを再レンダリングするので遅いが、頭では理解しやすい。Signals のモデルは速いが、あまりに多くの労力を要する
ほとんどのアプリは猛烈に速い必要はなく、人々はより単純な React を選ぶだろう
ただ、Signals はテンプレートとあまり関係がないのではないか。だとしたら、なぜ選ばなければならないのか。テンプレートと Signals を別々に持つことはできないのか
テンプレートについて考えていると、DOM のどこにテンプレートノードを差し込み、変更時にどう更新するかを知らせる方法が必要になる。まさにこの点で、こうした提案は失敗する
API はここでどちらか一方を選ばなければならない。技術的には両方を許容することもできるだろうが、ひどいものになる。開発者たちは、どちらであるべきかについて決して合意できないだろう
UI の大きな問題は常に、どう定義するかではなく、どう更新するかだった。Microsoft は MVC、MVP、MVVM など多くのモデルを定義しようとして失敗し、どれも苦痛を伴うものだった。そこへ imgui が「UI に状態がまったくなかったら?」と言い、それはかなり良いが CPU には負担が大きい
もしかすると、Web アプリ成功の最大の理由のひとつは、DOM がデータをビューに結び付ける方法を強制しなかったことかもしれない。だから私たちはフレームワーク地獄から抜け出しにくいのかもしれない
個人的に気に入っているのは ivi-js: https://github.com/localvoid/ivi
ベンチマークに勝つことだけを考えた末に到達し得る、最速で、最も醜く、命令的で、保守不能なバニラ JavaScript より、わずか8%遅いだけだ
https://krausest.github.io/js-framework-benchmark/2025/table...
ほとんどのアプリが猛烈に速い必要はないという話より、アプリは軽量であるべきだという点をもっと認めてほしい
人々が React をより単純だから選ぶという話には同意するが、まさにその理由で React が好きではない
JSXを採用するよりも、KotlinがレシーバーとビルダーによってDSL向けに汎用化された構文を提供し、その構文がコンポーネント階層の表現にもよく合っている、という形に近いものになってほしい
HTMLテンプレートだけでなく、設定やさまざまな種類の構造を表現するうえでも広く役立つはず
テンプレートとデータバインディングの実際の意味論は、Jetpack Composeに見られるように、そうした構文機能を使う標準関数群として用意すればよい
この記事が、この分野で最も経験豊富な人物の一人によって書かれている点も重要。Lit / Polymerの主要作者であり、GoogleでWeb Componentsに取り組み、Webプラットフォームの一部となった主要なDOM仕様に貢献した人物である
Safariは15年前から、ここには宣言的な方法があるべきだと主張していた
宣言的テンプレートは実はjQueryより悪いと感じている人がほかにもいるのか気になる。Reactをほぼ10年使ってきたが、SPAが複雑になるほど、DOMを命令的に直接制御したくなる
理由は、DOMが漏れのある抽象化であり、あるレベルでは最後の書き込みが勝つ方式のほうがよいと感じるから
宣言的テンプレートがそれを処理しようとしているのは分かるが、コンポーネント間で変更可能な状態を共有し始めると、この方式は非常に早く破綻する
refを取得し、あえて言えばidでコンポーネントを見つけて直接処理しても構わない。実際に「高速」で再レンダリングの少ないライブラリ、たとえばフォームライブラリはそうしているinnerHTMLやrefで好きなようにできる次に、DOMを命令的に制御すると、宣言的よりも面倒が少なくできることが何なのか分からない。
attachShadow()やshowModal()のような特定のメソッドくらいしか思い浮かばないが、それも10行のコンポーネントで包めば宣言的にできるRyan CarniatoとSolid JSは、まだSignalsで何が可能かを探索中だと理解している。この領域でのユーザーランドの探索が完全に終わったとは見ておらず、さらなる革新が可能かもしれない
方向性は気に入っているが、その前にブラウザにはもっと低レベルのAPIがいくつか先に入るべきだと思う
すべての人が標準テンプレートシステムに合意するのはほぼ不可能だろう。代わりにブラウザができることは、DOMに差分を高性能にネイティブな方法で適用する低レベルAPIを提供することだ
ブラウザにこういうものがネイティブであると本当にいい
element.applyDiff(DocumentFragment | string, { method: 'innerHTML' | 'outerHTML' })この方式なら、要素のフォーカス、入力値、オーディオ・ビデオプレーヤーの状態を維持し、属性を変更するなど、干渉の少ない方法で差分を適用できる。IdiomorphのようなJavaScriptライブラリがこうしたことをしているが、ネイティブな解決策ははるかに速い可能性がある
フレームワークなしのプロジェクトにも有用だろうし、Signals提案まで採用されれば特に役立つはず
Records and Tuples提案は停滞しているどころか、取り下げられている。https://github.com/tc39/proposal-record-tuple/issues/394
その位置はhttps://github.com/tc39/proposal-compositesが引き継いでいる