Svelte Summit 2020 の感想
はじめに
Svelte は僕がかなり気に入っているフロントエンドフレームワークの一つだ。シンプルな構文と柔軟性、作者の理念、そして多彩なアニメーションやトランジションの使い勝手など、どれも非常に気に入っている。以前にも Svelte に対する僕の考えについて何本か記事を書いたことがある:
Svelte Summit は 2020 年 10 月 18 日に開催され、コロナ禍の影響により全編オンライン生配信という形で行われた。計 7 時間、17 のセッションがあった。ここでは僕が面白いと感じたいくつかのセッションと自分なりの感想を記録しておく。
The Zen of Svelte
このセッションでは Svelte の中にある様々な「哲学」について語られている。その設計思想は Python における哲学とよく似ている。
Svelte は構文からアプローチし、学習コストを極力下げるように作られているため、フロントエンド以外の領域の人や初心者であっても非常に簡単に始めることができる。公式ドキュメントを見てもそれがよく分かる。解説テキストを読むだけでなく、もう片側には REPL が用意されており、コードを直接操作して実際の結果をその場で確認できるようになっている。

作者の Rich Harris は Rethink Reactivity というセッションでかつてこう述べていた:

Frameworks are not tools for organizing your code, frameworks are tools for organizing your mind フレームワークはコードを整理するためのツールではなく、「マインド(思考)」を整理するためのツールである。 — Rich Harris
例えば、React の登場によって Component、Immutable、Reactivity、State といった概念がフロントエンドに革命をもたらし、その後の多くのフレームワークもこれを基盤として登場し、フロントエンドの開発手法を根底から変えた。フレームワークの「書き方」そのものよりも重要なのは、それらのフレームワークの背後にある伝えたい概念と設計哲学だ。
また作者は、jQuery のようなツールの登場によって、彼のような情報系出身ではない、プログラミングのバックグラウンドを持たない人間でもウェブ開発の一員になれたとも述べている。この点から、作者が Svelte に対してどのようなビジョンを抱いているのかが垣間見える。
注:僕はこちらの記事でも詳しく語っているので、興味があればあわせて参考にしてほしい

HTML、CSS、JavaScript が分かれば、Svelte が書けることを意味するし、その逆もまた然りだ。一般的なテンプレートエンジンと少し異なるのは、Svelte のコンパイル方式の違いにある。テンプレートエンジンはバックエンドと組み合わされることが多く、変数をテンプレートエンジンに渡した後に HTML コードへと変換される。一方 Svelte では、すべてのコードが対応する JavaScript に変換され(SSR モードでない場合)、動的に実行できる。
僕はこのような哲学が好きだ。コードを書くからといって必ずしもエンジニアになる必要はない。料理をするからといってシェフになる必要がないのと同じだ。日常生活の課題を自分の手で解決したいだけということもあるだろう。
別の視点から見ると、JavaScript の基礎を飛ばして直接 Svelte を学ぶのは良いことなのか?と疑問に思う人もいるかもしれない。僕の考えは「目的による」だ。
もし一人前のフロントエンドエンジニアを目指すのであれば、いずれかのタイミングでその裏側で動いている原理を理解せざるを得ない。しかし、自分自身の課題を解決するためにウェブページを作りたいだけなら、基礎をそこまで深く理解していなくても何ら問題はない。
このセッションでは The zen of just writting css という記事にも触れられていた。Svelte ではコンポーネント内に直接 style を書くことができ、HTML、CSS、JavaScript の三位一体を実現できる。Svelte がハッシュ化を行ってくれるため、命名の衝突問題を直接回避できるのだ。React ではこのような問題を避けるために CSS-in-JS のソリューションをよく使うが、styling を行うためにより良い方法はないのだろうか?
例えば styled-components ではこのように書くことができる:
const Component = styled.div`
padding: ${props => props.padding}px;
`
// theme と組み合わせる
const ThemeComponent = styled.div`
background-color: ${props => props.theme.mainColor};
`;
これは非常に便利で面白い書き方だ。コンテキスト内に theme を定義し、コンポーネント宣言時に動的に取得できる。しかし、動的宣言であるゆえに、これらのスタイルは必然的にランタイムで実行される必要がある。渡される可能性のある prop を事前に知る術がないためであり、これはスタイルを完全に静的生成できないことを意味する。
しかし Svelte では、SSR を使って独立したスタイルシートを追加で直接レンダリングすることができる:
result: {
js,
css,
ast,
warnings,
vars,
stats
} = svelte.compile(source: string, options?: {...})
コンポーネント内で宣言されたスタイルは別途 css に出力されるため、スタイルとコードを分離できる。正直なところ、これが良いか悪いかは意見が分かれるところかもしれない。定数のように共通化したい色やフォントなどを扱う場合、(styled-components などのソリューションと比べると)それほど便利ではないかもしれない。
Prototyping with Svelte
Svelte に組み込まれている transition や animation などのディレクティブは非常に扱いやすいため、デザイナーであってもすぐにプロトタイプを作成してアイデアを検証できる。REPL 上でも素早く結果を確認して検証可能だ。「実装できない」と言い訳するエンジニアを見返したいデザイナーにおすすめしたい。
How does Svelte’s crossfade function work
crossfade は非常に使い勝手の良い transition だ。crossfade とは何だろうか?このように要素が 2 つの領域間を移動することを crossfade と呼ぶ。
このトークでは、crossfade が Svelte 内でどのように実装されているのか、そして実際のプロダクトでどのように使われているのかが語られている。
crossfade 自体は Svelte の key の概念を利用しており、実行されるたびに対応する key のノードを探し、両者の位置を計算した上で transition を使ってトランジションアニメーションを行う。Svelte 組み込みのディレクティブである transition を適用できるため、非常に扱いやすい。(サンプルは公式チュートリアルドキュメントより)
Svelte Animation
Svelte には animate という組み込みのディレクティブがある。ループ処理の each を使用している際、リスト内の要素に変更があると animate がトリガーされる。使い方は以下の通りだ:
{#each list as item (item.id)}
<div animate:flip>
...
</div>
{/each}
Svelte には flip という組み込みの animate がある。これは First, Last, Invert, Play の頭文字を取ったもので、アニメーション作成時のテクニックの一つだ。まず要素の変化前と変化後の位置を記録し(First, Last)、両者の差分(幅や高さ、移動オフセットなど)を計算し、最後にアニメーションを実行する。詳細な説明は Tech Bridge のコラム記事 — FLIP 技巧總複習 を参考にしてほしい。
Svelte はこの一連の計算を flip として抽象化し、導入後すぐに使えるように提供している。crossfade と組み合わせた効果は以下のようになる:
すべての移動に transition が適用され、より滑らかで心地よく感じられるはずだ!
Unlocking The Power of Svelte action
このセッションでは Svelte における action について語られている。フック(hook)に近い感覚だ。Svelte ではこのように書くことができる:
<div use:keyboard={{shortcut}} class="player-container">
</div>
ここでの use が Svelte action のディレクティブだ。後ろの keyboard は自作の関数であり、その関数シグネチャは以下のようになる:
function keyboard(node, options) {}
この関数はオブジェクトを返すことができ、その中には update と destroy を含めることができる。それぞれパラメータ変更時の更新関数と、破棄時に実行される関数を表す。第 1 引数がノード自身であるため、DOM 関連の操作を行う際に非常に便利だ。
スピーカーは動画内で Svelte Action を使ってみた感想を解説しており、一見の価値がある。
Demystifying Svelte Transitions
このセッションは Svelte Transition がどのように実装されているかを語っており、ソースコードを一歩一歩紐解いて見せてくれる、非常に素晴らしい内容だ!Svelte の transition 機構はとても面白く、JavaScript で動的に操作しているわけではない。
jQuery のアニメーションを使う場合、jQuery は指定されたパラメータに基づいて要素にインラインスタイルを追加する。例えば:
$('.div').animate({
...
})
jQuery はアニメーションが停止するまでインライン CSS のプロパティを更新し続ける。つまり、内部の実装はおおよそ以下のようになる(疑似コード):
while (!stop) {
updateCSS()
}
setTimeout(() => stop = true, duration);
つまり、この間 JavaScript がコードを実行し続けることになり、アプリケーションが大きくなるとパフォーマンスに顕著な影響が出る。
では、一般的なフレームワークはどのように実装しているのだろうか?Vue や React では、このように書くことができる:
// vue
<transition name="fade">
<p v-if="show">hello</p>
</transition>
// React
<CSSTransition in={inProp} timeout={200} classNames="fade">
<div>
</div>
</CSSTransition>
このうち Vue は、対応するタイミングで fade-leave-active、fade-enter-active、fade-enter などのクラス名をそれぞれ挿入してくれる。React も同様の仕組みで、fade-enter、fade-active、fade-exit、fade-exit-active などのクラス名を挿入してくれる。ただし、transition の動作内容は、自分で CSS 内に定義する必要がある。
これによって JavaScript の計算によるパフォーマンス問題は回避できるものの、CSS だけでアニメーションを作るのは少々面倒なこともある。JavaScript の動的計算の特性と CSS アニメーションのメリットを組み合わせる方法はないだろうか?ある!Svelte はそれを実現した。
以下のコードを宣言したとき、Svelte はいくつかの処理を行う:
<div transition:scale={{duration: 1000}}>
</div>
transitionは、アウト時・イン時の両方に同じトランジションを適用することを表す。- Svelte はこのアニメーションに必要なフレーム数の計算を開始する。アニメーション時間が 1000 ミリ秒の場合、計算式は
1000 / 16で 62.5 フレームとなる。ここで 16 としているのは、リフレッシュレート 60Hz の環境で各フレームに必要なミリ秒数が約 16ms(1000 / 60)だからだ。 - Svelte はアニメーション開始時点を 0、終了時点を 1 とし、変数
tと命名して、イージング関数をtへの補間として使用する。 - Svelte は動的に CSS の keyframe を作成し、その animation を要素に追加する。
stylesheet.insertRuleを通じて、現在の document に動的に style を追加する。
実際のソースコードを見てみよう:
// src/runtime/internal/style_manager.ts
export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: number, duration: number, delay: number, ease: (t: number) => number, fn: (t: number, u: number) => string, uid: number = 0) {
const step = 16.666 / duration;
let keyframes = '{\\n';
for (let p = 0; p <= 1; p += step) {
const t = a + (b - a) * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\\n`;
}
const rule = keyframes + `100% {${fn(b, 1 - b)}}\\n}`;
const name = `__svelte_${hash(rule)}_${uid}`;
const doc = node.ownerDocument as ExtendedDoc;
active_docs.add(doc);
const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet);
const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});
if (!current_rules[name]) {
current_rules[name] = true;
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
}
const animation = node.style.animation || '';
node.style.animation = `${animation ? `${animation}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`;
active += 1;
return name;
}
この関数が実行されると、以下のような CSS が生成される:
@keyframes __svelte__dynamic_hash_name {
0% {
transform: scale(0);
}
1.6% {
transform: scale(0);
}
...
100% {
transform: scale(1);
}
}
上記はサンプルコードであり、実際の挙動は duration やイージング関数によって異なる。Svelte は transition を作成してくれるだけでなく、これらの transition の管理も担う。例えば transition の途中で中断された場合、Svelte は animation を削除して停止させ、様々なライフサイクルも面倒を見てくれる。
これにより、CSS Animation のパフォーマンスを享受しながら、同時に JavaScript で動的に制御できるというメリットも得られるのだ。
もっと詳しく知りたい場合は、ぜひ動画を直接観てほしい。スピーカーが非常に分かりやすく解説してくれている!
Futuristic Web Development
Rich Harris 本人が登壇し、次世代のウェブ開発ワークフローの変革について語っている。ただし現時点の話はまだ最終決定ではないため、期待を胸に見守るくらいがちょうどいいだろう。
従来、リソースをバンドルする際は rollup や webpack のようなツールを使い、それらの依存関係追跡メカニズムによって更新を行っていた。当時のボトルネックはブラウザが ES6 構文や import メカニズムを広く解釈できなかった点にあり、開発者がコードをより適切に管理できるようにするためにこれらのツールが誕生した。現在ではブラウザのサポートが整い、ネイティブの ES Module に直接依存するアプリケーションが増えてきている。例えば最近登場した VuePress や Snowpack などはネイティブの ES Module を活用しており、最大のメリットは結果を確認するまでに長い時間待たされる必要がない点にある。ブラウザの仕組みを通じて直接読み込むため、バンドラーがコードをコンパイルし終えるのを待つ必要もなくなった。Svelte でも今後 snowpack の採用が予定されているので、興味があればチェックしてみてほしい。
おわりに
僕は今回の IT 鐵人賽 で Svelte に関するチュートリアル動画シリーズを制作した。Svelte の各種機能、応用編、よくある UI の実装、Svelte の内部原理から、最後には簡易版 Svelte の実装まで解説しているので、興味があればぜひ見てみてほしい。
今後は動画の内容を読みやすい記事としてもまとめていく予定なので、ぜひ僕のブログを引き続きチェックするか、RSS を購読してもらえると嬉しい!
関連記事
- Three.js で僕の部屋を表現する 僕が React Three Fiber を使って実際の自分の部屋をブラウザ上に再現し、実世界のオブジェクトを目録に見立て、空間の記憶を通してここ数年の生活と仕事について語った話。
- フロントエンドで画像を扱う際に注意すべきこと Jake Archibaldの記事を起点に、現代のレスポンシブ画像の書き方を整理する。なぜwidth/heightを付ける必要があるのか、CSSのaspect-ratioはいつ使うべきか、AVIFとWebPの選び方、そしてpicture/source/srcsetを使ったモバイル向け画像の切り替えについて。
- CSS field-sizing — たった1行のCSSでフォーム要素を自動リサイズする かつてtextareaの自動高さ調整は、JavaScriptでscrollHeightを監視するしかなかった。しかしCSSのfield-sizing: contentなら、わずか1行で代替でき、textarea、input、selectに対応している。本記事では従来のやり方のペインポイントと、field-sizingの使い方をまとめる。
- リンクの下線をもっと見栄え良くする:text-underline-offset デフォルトでは下線と文字が近すぎて、このスタイルを好まないデザイナーもいるし、僕自身もあまり綺麗ではないと感じていた。