スヴェルトサミット 2020 ノートとエクスペリエンス

作成者:カランカラン
💡

質問やフィードバックがありましたら、フォームからお願いします

本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください

前言

Svelteは私がかなり好きなフロントエンドフレームワークの一つです。シンプルな文法と柔軟性、作者の理念、さらにさまざまなアニメーションやトランジションの使い方がとても魅力的です。以前にSvelteについての自分の見解をいくつか書いたこともあります:

Svelte Summitは2020年10月18日に開催されました。パンデミックの影響で全編オンラインでライブストリーミングされ、合計7時間、17の講演が行われました。ここでは、私が面白いと思った講演と自分の感想を記録します。

The Zen of Svelte

この講演では、Svelteのさまざまな「哲学」について語られています。設計理念はPythonの哲学に似ています。

Svelteは、文法からアプローチし、学習コストをできるだけ低く抑えています。そのため、非フロントエンド分野の人や初心者でも非常に簡単に始めることができます。公式ドキュメントを読んでみると、教育的な文章を読むことに加えて、REPLを通じて直接コードを操作し、実際の効果を見ることができます。

Svelte公式チュートリアルドキュメント

作者のRich HarrisはRethink Reactivityという講演で次のように述べました:

Rich Harris - Rethinking 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に対するビジョンが垣間見えます。

注:私はこの文章でさらに詳しく話していますので、興味があれば参考にしてみてください。

harris-jquery

あなたがHTML、CSS、JavaScriptを理解しているなら、Svelteを書くことができるということです。逆もまた然りです。一般的なテンプレートエンジンとは異なり、Svelteはコンパイル方式が異なります。テンプレートエンジンはしばしばバックエンドと連携し、変数をテンプレートエンジンに渡した後、HTMLコードに変換します。一方、Svelteではすべてのコードが対応するJavaScriptに変換され(SSRモードでない場合)、動的に実行されます。

私はこの哲学が好きです。プログラミングをすることは必ずしもエンジニアになる必要はなく、料理をすることも必ずしもシェフである必要はないのです。日常生活の問題を解決するために手を動かしたいだけかもしれません。

別の視点から見ると、JavaScriptの基礎を飛ばして直接Svelteを学ぶことは良いことなのでしょうか?私の考えは「目的次第」です。

もしあなたが今日、立派なフロントエンドエンジニアになりたいと思っているのなら、どこかのタイミングで背後にある原理を理解する必要があります。しかし、単に自分の問題を解決するためにウェブページを作りたいだけなら、基礎をそれほど理解していなくても問題ありません。

この講演では、The zen of just writing cssという記事にも触れています。Svelteでは、スタイルをコンポーネント内に直接書くことができ、HTML、CSS、JavaScriptの三位一体の効果を実現します。Svelteがハッシュを生成して、命名衝突の問題を直接回避できます。Reactでは、CSS-in-JSのソリューションを使用してこの問題を回避することがよくありますが、スタイリングを行うためのより良い方法があるのでしょうか?

例えば、styled-componentsでこのように書くことができます:

const Component = styled.div`
  padding: ${props => props.padding}px;
`

// テーマと組み合わせて
const ThemeComponent = styled.div`
  background-color: ${props => props.theme.mainColor};
`;

これは非常に便利で面白い書き方で、themeをコンテキストの中で定義し、コンポーネントを宣言するときに動的に利用することができます。しかし、動的に宣言するため、これらのスタイルは必然的にランタイムで実行される必要があります。なぜなら、渡される可能性のあるプロパティが何であるかを予測できないからです。つまり、スタイルは完全に静的に生成することができません。

しかし、SvelteではSSRを使用して独立したスタイルシートを追加でレンダリングすることができます:

result: {
	js,
	css,
	ast,
	warnings,
	vars,
	stats
} = svelte.compile(source: string, options?: {...})

コンポーネント内で宣言されたスタイルはcssに追加され、スタイルとコードを分離できます。正直に言うと、これは良いことか悪いことかは人それぞれの見解でしょう。共通の色やフォントなどの定数を使う場合、styled-componentsのようなソリューションと比べて便利さが失われるかもしれません。

Prototyping with Svelte

Svelteに内蔵されているトランジションやアニメーションなどのディレクティブは非常に便利で、デザイナーでもすぐにプロトタイピングを行ってアイデアを検証できます。REPL上でもすぐに結果を確認できますので、エンジニアができないデザインを実現したいデザイナーにはおすすめです。

How does Svelte's crossfade function work

crossfadeは非常に便利なtransitionです。crossfadeとは何でしょうか?2つの領域間で要素が移動することを指します。

このトークでは、講演者がSvelteにおけるcrossfadeの実装方法と、実際に彼の製品での使用方法について説明しました。

crossfadeはSvelteのkeyの概念を利用しており、毎回実行する際に対応するkeyのノードを探し、両者の位置を計算してからトランジションを使用してアニメーションを行います。transitionというSvelte内蔵のディレクティブを適用できるため、非常に便利に使えます。(例は公式チュートリアルドキュメントからの引用です)

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との効果はこのようになります:

すべての移動にトランジションがあり、より快適に感じられますよね!

Unlocking The Power of Svelte action

このセッションでは、Svelteのactionについて話します。これはフックに似た感覚です。Svelteではこのように書くことができます:

<div use:keyboard={{shortcut}} class="player-container">
</div>

ここでのuseはSvelteアクションのディレクティブです。次のkeyboardはカスタム関数で、その関数のシグネチャは次のようになります:

function keyboard(node, options) {}

この関数はオブジェクトを返すことができ、その中にはupdateとdestroyが含まれます。それぞれはパラメータが変更されたときの更新関数と、destroy時に実行される関数を意味します。最初の関数はノード自体なので、DOM関連の操作を行う際に非常に便利です。

講演者はビデオの中で彼がSvelte Actionを使用した感想を説明しており、参考になると思います。

Demystifying Svelte Transitions

このセッションでは、Svelteのトランジションがどのように実装されているかを解説し、原始コードを一歩一歩分解して見せてくれます。非常に興味深いです!Svelteのトランジションメカニズムは非常にユニークで、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-activefade-enter-activefade-enterなどのクラス名を自動的に追加します。Reactも同様の原理で、fade-enterfade-activefade-exitfade-exit-activeなどのクラス名を自動で追加します。しかし、トランジションの方法はCSS内で自分で定義する必要があります。

この点は、JavaScriptによる計算によるパフォーマンス問題を回避できますが、時にはCSSだけでアニメーションを作るのは少し面倒です。JavaScriptの動的計算機能とCSSアニメーションの補助を組み合わせる方法はあるのでしょうか?あります!Svelteはそれを実現しました。

以下のコードを宣言すると、Svelteは以下のような処理を行います:

<div transition:scale={{duration: 1000}}>
</div>
  1. transitionは出場と入場の両方に同じトランジションを適用します。
  2. Svelteはこのアニメーションに必要なフレーム数を計算します。アニメーション時間が1000ミリ秒の場合、計算は1000 / 16で62.5フレームになります。ここでは16を取る理由は、更新率60Hzの場合の各フレームの必要ミリ秒数は約16ms(1000 / 60)だからです。
  3. Svelteはアニメーション開始の時点を0、アニメーション終了の時点を1として変数tを命名し、easing関数を用いてtの補間を行います。
  4. Svelteは動的にCSSキーフレームを作成し、このアニメーションを要素に追加します。
  5. stylesheet.insertRuleを通じてスタイルを現在のドキュメントに動的に追加します。

では、原始コードを見てみましょう:

// 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やeasing関数によって異なります。Svelteはトランジションを作成するだけでなく、これらのトランジションを管理します。たとえば、トランジションが途中で停止した場合、Svelteはアニメーションを削除して停止し、さまざまなライフサイクルを管理します。

このように、CSSアニメーションのパフォーマンスを組み合わせながら、JavaScriptを使って動的に制御する利点を得ることができます。

もっと詳しく知りたい方は、実際にビデオを見てみると、講演者が非常に分かりやすく説明しています!

Futuristic Web Development

Rich Harris本人が登場し、次世代のウェブ開発のプロセスの変化について語ります。しかし、現時点での話はまだ最終的な決定ではないので、期待を持って見守るのが良いでしょう。

これまで私たちはリソースをパッケージする際、rollupやwebpackのような方法を用いてパッケージ化し、彼らの依存関係トラッキングメカニズムを通じて更新してきました。この時のボトルネックは、ブラウザが一般的にES6構文やimportメカニズムを理解できなかったことにあります。プログラマーがプログラムコードをより良く管理できるようにするために、これらのツールが生まれました。現在ではブラウザのサポートが徐々に整ってきており、ますます多くのアプリケーションがネイティブESモジュールに直接依存しています。最近登場したVuePressSnowpackは、ネイティブESモジュールアプリケーションを直接利用しています。最大の利点は、結果を待つ時間が短縮され、ブラウザのメカニズムを通じて直接インポートできるため、バンドラーがコードをコンパイルするのを待つ必要がなくなることです。Svelteも今後snowpackを使用する予定ですので、興味がある方はぜひご覧ください。

後記

私は今回のIT鉄人賽でSvelteに関する一連の教育ビデオを撮影しました。Svelteのさまざまな機能、応用、一般的なUI実装、Svelteの原理について説明し、最後に簡単なバージョンのSvelteを実装しています。興味があればぜひご覧ください。

今後、ビデオの内容を記事として読みやすくする予定ですので、皆さんは私のブログをフォローするか、直接RSSを購読してください!

この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨

Buy me a coffee