Svelteフロントエンドフレームワークは先頃、Svelte 3をリリースした。Svelte 3では、ユーザーインターフェイスの実装に関連して、エンティティをリンクするインバリアントを宣言的に表現する、新たな方法を導入している。これによって開発者は、パフォーマンスを意識して再レンダリングを実施したり、状態依存の部分を意識的に同期するような処理が不要になる可能性がある。新しいロゴとキャッチフレーズ、Webサイトも用意された。Sapper(Svelteを搭載したNext.jsスタイルのアプリケーションフレームワーク)とSvelte Native (モバイルアプリケーション開発をターゲットとする)は、Svelte 3にアップグレードされている。InfoQは今回、Svelteの開発者であるRich Harris氏にインタビューして、Svelte 3の持つ意味と、開発者への影響について聞いた。
Svelte 3は、Svelteフロントエンドフレームワークの最新のメジャーイテレーションである。Rich Harris氏は、その背後にある目標について、次のように説明している。
バージョン3は大幅なオーバーホールです。過去5、6か月間、私たちの焦点は、優れた開発者エクスペリエンスの提供にありました。その結果、現在では、他の何よりも大幅に少ない定形コードで、コンポーネントを記述することが可能になっています。
今回のリリースの中核となっているのは、レンダリングプロセスに関わる変数間の永続的な関係を宣言的に表現する、新たな構文である。ドキュメントでは、"Reactive Assignment"、"Reactive Declaration"、"Reactive Statement"という3つのケースに区別されている。
Reative Assignmentの例として、Harris氏は、Svelteと他のフロントエンドフレームワークを使用した場合の、簡単な増分カウンタの実装を紹介している。
// With Svelte
const { count } = this.get();
this.set({
count: count + 1
});
// With React
const { count } = this.state;
this.setState({
count: count + 1
});
// With Vue
(...)
data: () => ({count: 0}),
(...)
this.count += 1;
この例では、組み込みフレームワークに状態の変化をトラップあるいは通知して、依存する計算や効果を起動する必要があるが、Svelte 3では、単に変数の値を設定すれば、
count += 1;
Svelteが対応する同期コードをビルド時に生成してくれている。
count += 1; $$invalidate('count', count);
Reactive Declarationは、さまざまな変数間の関係を扱うためのものだ。Svelte 3では、変数間の関係を式として書くことができる。Svelteコンパイラがスプレッドシートのように、式の右側の変数の変更が左側の変数に反映されるようにしてくれる。以下の例では、
<script>
let count = 1;
// the `$:` means 're-run whenever these values change'
$: doubled = count * 2;
$: quadrupled = doubled * 2;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Count: {count}
</button>
<p>{count} * 2 = {doubled}</p>
<p>{doubled} * 2 = {quadrupled}</p>
"$: doubled = count * 2;
"という構文で、doubled
変数をcount
変数にリンクする等式が導入されている。この場合もSvelteコンパイラが、count
の更新をdoubled
に反映するコードを生成してくれる。
let doubled, quadrupled;
$$self.$$.update = ($$dirty = { count: 1, doubled: 1 }) => {
if ($$dirty.count) { $$invalidate('doubled', doubled = count * 2); }
if ($$dirty.doubled) { $$invalidate('quadrupled', quadrupled = doubled * 2); }
};
Reative Statementは、従属変数の変化毎に評価されるステートメントである。ここでも$
構文が使用されている。
<script>
let count = 0;
$: if (count >= 10) {
alert(`count is dangerously high!`);
count = 9;
}
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
これはフレームワークによって、次のようにコンパイルされる。
$$self.$$.update = ($$dirty = { count: 1 }) => {
if ($$dirty.count) { if (count >= 10) {
alert(`count is dangerously high!`);
$$invalidate('count', count = 9);
} }
};
Svelteの開発者であるRich Harris氏に、Svelte 3を開発した動機と、開発者への影響について話を聞いた。
InfoQ: Svelteの開発経緯について教えてください。あなたはGuardian紙で使用されていた、もうひとつのテンプレートベースのフロントエンドフレームワークである、Ractiveの開発にも関わっていますが、Svelteを開発した理由は何だったのですか?
Ractiveは、2012-13年に使用されていたUIフレームワークの一部で、その時代に生まれたものです。パワフルで直感的ですが、当時すべてがそうであったように、宣言型のコンポーネントを実行時に解釈して動作する仕組みでした。その結果、手書きのコードよりもJSバンドルが大きかったり、パフォーマンスが遅いという形で、フレームワークの代償を払うことになっていました。
2016年までに、すべてが変わりました。デスクトップではなくモバイルが、突然、トラフィックの大部分を占めるようになり、バンドルサイズとパフォーマンスがそれまで以上に重要になったのです。BabelやTypeScriptといったプロジェクトを通じて、コンパイラやビルドステップに慣れ始めていたJSコミュニティは、宣言型コンポーネントをコンパイラが理解して、高度に最適化された命令型コードに変換することはできないだろうか、と考え始めました。この頃の私は、UIフレームワークの設計について何年も考えていて、モジュールバンドラ(Rollup)やコンパイラ(Bublé)を開発していたので、それを試すにはよい位置に自分がいると思ったのです。
InfoQ: Svelte 3では、コンポーネント内部(intra-component)の同期の問題を解決するために、新しい構文が導入されていますが、コンポーネント間(inter-component)の同期や通信は、どうなっているのでしょうか?2つのコンポーネントが共有状態の一部を読み書きしたり、あるいは2つのコンポーネントが通信する必要がある場合、Svelteで起こりうる同期の問題にどう対処していますか?
Svelteにはストア(store)と呼ばれるプリミティブがあって、組み込みのリアクティブシステムと連動しています。これは非常に単純なコントラクトで、 — パッケージにリファレンス実装がありますが、Reduxやxstateといった他のライブラリをラップすることも可能です — 構成性に優れています。TC39で提案されているObservable仕様(RxJSなどのライブラリで実装されています)も、Svelteのストアのコントラクトに準拠しているため、ネイティブにサポートしています。
InfoQ: SvelteのコンポーネントをWebコンポーネントにパッケージして、他のフレームワークで使用することは可能ですか?逆にSvelteでは、Webコンポーネントを簡単に使用できるのでしょうか?
コンパイラであることのメリットは、同じコンポーネントコードから異なる出力を生成できることです。Webコンポーネントの生成は、単にコンパイラオプションを設定するだけです。Webコンポーネントの使用も同じように簡単です — Svelteは、Custom Elements Everywhereテストスイートで30/30を獲得しています。
InfoQ: SvelteがWebコンポーネントにコンパイルできるということは、開発者はSvelteを、自分のコードベースに、段階的に導入可能だということでしょうか?それは現実的な方法ですか(既存のコードとの干渉やマークアップの競合、バンドルの問題などを考えた場合)?
そうです、Svelteは、段階的な導入を念頭に置いて設計されています。適切なPolyfillがあれば、カスタム要素(Custom Element)の利用が便利な方法になります。ただしそれは、'host'フレームワークに依存します — 例えばReactでは、カスタム要素のサポートが十分ではないので、移行中はSvelteコンポーネント用のReactラッパ(react-svelteなど)を使用した方がよいでしょう。
InfoQ: Svelteでは、ルーティングのネストをどのように扱うことができますか?
最も簡単な方法はSapperを使用することです。これは、Svelteコンポーネントフレームワーク上に構築されたアプリフレームワークです。Sapperはアプリのフォルダ構造に基づいて、完全に宣言的な、ネストされたルーティングシステムを提供します。Next.jsに似ていますが、多くの重要な機能強化が行われています。
コンポーネントベース、あるいはコンフィギュレーションベースのルーティングを好む開発者には、コミュニティの管理するプロジェクトがいくつかあります。
InfoQ: Svelteアプリケーションのテストは、どのように行うのでしょうか?
デフォルトのSapperテンプレートに、Cypressインテグレーションが組み込まれています。個々のコンポーネントのテストには、Puppeteerをよく使用しています。最も慣用的なテストストーリはまだ模索中ですが、まもなく何かが見つかると思います。
InfoQ: Svelteを採用すべき理由を3つ挙げて頂けますか?
第1に、コード量が少ないことです。より速く、よりバグの少ないソフトウェアを開発するためには、最も信頼できる方法です。第2には、一般的なフレームワークよりも、はるかに優れたパフォーマンスの保証が得られることです。これはつまり、アプリを拡張したり、低速なデバイスや通信が一般的な新たな市場に進出する場合、コードの書き直しに時間を費やす必要がない、という意味になります。
第3に、Svelteは、従来のフレームワークが範囲外と考えていた、多くの機能を備えています。これは、- コンパイラである — Svelteは、開発者であるあなたが実際に使用する機能を持てばよいからであって、他のユーザの使用するバイトサイズを節約することができます。つまり、宣言的なトランジションやアニメーションといった、一般のフレームワーク開発ではコード量を考慮しなければならない機能を、自由に導入することができるのです。
InfoQ: では、採用されない理由は何でしょうか?
成熟したフレームワークが持っているような、充実したエディタ統合や開発ツールがまだないことです。TypeScriptのサポートも十分ではありません。私たちはまだ、裕福な(deep-pocketed)企業ではありません — 信念を持った寄せ集め集団(ragtag crew)に過ぎないのです。
InfoQ: 次の目標は何ですか?
最も望まれている機能はTypeScriptのサポートですので、それに関しては、近いうちにある程度の進捗を図りたいと思っています。私自身は、Svelteを使ったWebGLアプリケーションの開発に関心を持っていて、初期段階の検討を行っています。最後に、(NativeScriptを搭載した)Svelteを使ってAndroidとiOSのアプリ開発を可能にする、Svelte Nativeというコミュニティプロジェクトがあって、驚くべき進歩を遂げています。
InfoQ: Svelteを使っている会社の例を教えてください。
私の勤務するNew York Timesや、他のいつかのニュース機関で、多数のプロジェクトに使用されています。私たちのWebサイトのホームページには、その他の例がたくさんあります。おそらく最も興味深いのは、ブラジルのStoneとロシアのMustlabが、POSシステムやSmart TVアプリなどの組み込み機器にSvelteを使っていることでしょう。多くのフロントエンド開発が、組み込みWebに移行しつつあります。これらの企業は、従来のJavaScript優先のフレームワークでは、その環境の要求を満たすことができないことを認識しているのです。