🍳

Svelte ・SvelteKit入門

2023/08/04に公開

今回のモチベーション

Svelte聞いたことあったけど、触ったことない。なんか学習コスト低くて簡単にreactiveにできるらしいけど、実際にどうなのか。
去年ぐらいにSveltekit出たみたいだけど、使いやすいのか。チュートリアルとか記事とかあさってみよう

2022のフロントエンドフレームワーク利用率は20%: 4位(React,Angular,Vueの次)、6月にversion4が出ましたよ。
マイナーの中ではいいけど、メジャーと比べるとまだまだ普及してない。

特徴

  • 仮想DOMを利用しない、事前コンパイル
    Svelteは、実行時にアプリケーションのコードを解釈するのではなく、ビルドに際しアプリをクリーンなJavaScriptコードに変換するということです。その結果、フレームワークの抽象化によるパフォーマンス低下が発生することなく、アプリの初回読み込み時のもたつきを心配する必要もありません。

Virtual DOM is pure overhead

  • 事前知識を必要としない学習コストの低さ
    素のHTML、CSS、JavaScript/TypeScriptを書いていくだけ。
    マークアップとJSをがかけたら調べながら構築できる。チュートリアルが親切。

  • 「1個のことを実現する方法は常に1個である」という考え方のもと記法がシンプル
    $: ラベルのみでリアクティビリティを実現する。加えてSvelteコンパイラには、このような無限ループを検知する機能があり、コンパイル時にこれに気づくことができるみたい。

  • 国内の利用企業を探したがパッと見つけられず。。ざっと確認したけどアプリケーションの一部だけ利用して、様子見しているところはありあそう。

メジャーなエコシステムの一覧

Home - Svelte Society

エコシステム ツール
言語サポート (Babel / CoffeeScript / TypeScript / PostCSS / SugarSS / Less / Sass / Pug / Stylus) https://github.com/sveltejs/svelte-preprocess
状態管理 https://svelte.dev/docs#run-time-svelte-store
Webフレームワーク(ルーティング含) https://github.com/sveltejs/kit
モジュールバンドラー(Vite) https://github.com/sveltejs/vite-plugin-svelte
モジュールバンドラー(Rollup) https://github.com/sveltejs/rollup-plugin-svelte
モジュールバンドラー(Webpack) https://github.com/sveltejs/svelte-loader
エディターサポート(VSCode) https://github.com/sveltejs/language-tools
エディターサポート(Atom) https://github.com/sveltejs/svelte-atom
UIコンポーネント https://sveltesociety.dev/recipes/testing-and-debugging/unit-testing-svelte-componentにたくさん掲載されています
UIコンポーネントテスト(Testing Library) https://testing-library.com/docs/svelte-testing-library/intro
UIコンポーネントテスト(Storybook) https://storybook.js.org/tutorials/intro-to-storybook/svelte/en/get-started/
UIコンポーネントテスト(Cypress) https://docs.cypress.io/guides/component-testing/quickstart-svelte
コード整形(Prettier) https://github.com/sveltejs/prettier-plugin-svelte
リンター(ESLint) https://github.com/ota-meshi/eslint-plugin-svelte
リンター(Stylelint) (プラグイン不要で使えます) https://github.com/stylelint/stylelint
Chrome Devtools (ただし改修が必要なようです) https://github.com/sveltejs/svelte-devtools
ホスティング(Vercel / netlify / cloudflare / cloudflare workersなど) https://kit.svelte.dev/docs/adapters
Denoサポート https://github.com/pluvial/svelte-adapter-deno
プレイグラウンド https://svelte.dev/repl/hello-world?version=3.52.0

Svelteの記法

状態管理

<script>
	let count = 0;

	function increment() {
		count += 1;
	}
</script>

<button on:click={increment}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

reactivity

以下のようにラベル付き文で宣言するとリアクティブ宣言(reactive declarations)となり
参照される値が変わるたびにこのコードを再実行する

リアクティブな  を宣言するだけでなく、任意の ステートメント をリアクティブに実行することもできます。

<script>
	let count = 0;
	$: double = count + 2;

	$: if (count >= 10) {
		alert('count is dangerously high!');
		count = 0;
	}
	
	function increment() {
		count += 1;
	}
</script>

<button on:click={increment}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

<p>{count} double id {double}</p>

変数への代入と状態変化の検知

cart.push(id) を呼び出していますが、'Add a number' ボタンをクリックしても今のところ何も起こりません。冗長な気もしなくはないですが、代入して追加することで、reactiveになります。

<script>
	let count = [];
  
  変数への代入文を書くとreactiveになる
	function addToCart(id) {
		cart = [...cart, id]
  }
  
  要素追加をするがreactiveにならない
  function addToCart(id) {
		cart.push(id)
  }
</script>

props

子コンポーネントでexport することで、親から渡されたpropsを受け取れる

# App.svelte
<script>
	import PackageInfo from './PackageInfo.svelte';

	const pkg = {
		name: 'svelte',
		speed: 'blazing',
		version: 4,
		website: 'https://svelte.dev'
	};
</script>

<PackageInfo {...pkg} />

# PackageInfo/svelte
<script>
	export let name;
	export let version;
	export let speed;
	export let website;

	$: href = `https://www.npmjs.com/package/${name}`;
</script>

<p>
	The <code>{name}</code> package is {speed} fast. Download version {version} from
	<a {href}>npm</a> and <a href={website}>learn more here</a>
</p>

logic

# の文字は常に ブロックの開始 タグと/ の終了 タグをつけて、{}if,else等々が利用できます。

## if ##
<script>
	let count = 0;

	function increment() {
		count += 1;
	}
</script>

<button on:click={increment}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

{#if count > 10}
	<p>{count} is greater than 10</p>
{:else}
	<p>{count} is between 0 and 10</p>
{/if}

## each ##
<div>
	{#each colors as color, i}
		<button style="background: {color}"> {i + 1}</button>
	{/each}
</div>

## await ##
<script>
	import { getRandomNumber } from './utils.js';

	let promise = getRandomNumber();

	function handleClick() {
		promise = getRandomNumber();
	}
</script>

<button on:click={handleClick}>
	generate random number
</button>

{#await promise}
	<p>...waiting</p>
{:then number}
	<p>The number is {number}</p>
{:catch error}
	<p style="color: red">{error.message}</p>
{/await}

Binding

<script>
	let name = 'world';
</script>

<input bind:value={name} />

<h1>Hello {name}!</h1>

Lifecycle

  • onMount コンポーネントが最初に DOM にレンダリングされた後に実行されます
  • beforeUpdate 関数は DOM が更新される直前に作業をスケジュール
  • tickは非同期関数で、次のDOM更新サイクルが完了を待機したいときなどに使用されます。

SvelteKit

去年の12月にversion1がリリース
Announcing SvelteKit 1.0
上記の内容を以下に要約すると、

How is it different?
サーバーでレンダリングされた最初のページがロードされた後、クライアントサイド・ナビゲーションがデフォルトになります。 (nextと同じ)
HTMLの生成もできて、一つのアプリケーションでサーバーサイドの処理もできるので、従来のNodeサーバーとしてアプしょ

What can I use with SvelteKit?
viteでビルドして、TypeScriptESLintPrettierとか使えるし、Vitest(ユニットテスト用)を追加するかどうか尋ねられます。TailwindやSupabaseなど、多くの人気プロジェクトにはすでに統合ガイドが存在する。コンポーネントストーリーにはStorybookとHistoireを使うことができるし、npmが提供しているものにももろん全てアクセスできます。

NuxtやNextと特に大きな差分ななさそう。SvelteKitでも同じようなことが簡単な記法で高速でできます的なことが言いたいのかも

Routingとディレクトリ構成

Nextと一緒で、slugで動的なページも対応できるし、ディレクトリベースのルーティングを使用しているので、特に違うところはなさそう。
.server.js ファイルを置き、そこで load 関数を宣言するとサーバーでのみ実行モジュールとして定義される

API Routeについて

ページ以外にも+server.js ファイルを追加し、そこでHTTP メソッド GETPUTPOSTPATCHDELETE に対応する関数をエクスポートすることで、 API ルート(API routes) を作成することもできます。

# +server.js
import { json } from '@sveltejs/kit';

export function GET() {
	const number = Math.floor(Math.random() * 6) + 1;

	return json(number);
}

# +page.svelte
<script>
	/** @type {number} */
	let number;

	async function roll() {
		const response = await fetch('/roll');
		number = await response.json();
	}
</script>

<button on:click={roll}>Roll the dice</button>

{#if number !== undefined}
	<p>You rolled a {number}</p>
{/if}

チュートリアルで作ってコード眺めてみたが、読み慣れてないのもあるが、かなりコードが追いづらいので、色々できるjquery感がある。

npm create svelte@latest my-app
cd my-app
npm install
npm run dev -- --open

感想

確かに書きやすいし、数時間チュートリアルを確認しただけで、なんとなく書けそうなイメージは持てるが生のHTMLを書きたい!というモチベーションもないので、業務で利用するのはなかなか辛い気がする。

簡単に書けすぎて、中規模以上になるとコードが荒れて、負債化しないかなーという気もする。実際にやってみないとわからない。TSとかTailwindも使えるので、コンポーネントをきれいに分ければ、いい感じになるのかも知れれないが、あえてReactを選択しない理由としては弱い気がする。

パッと思いついた用途としては、Svelteはコンパイル時にVanilla JSでDOMへの変更処理なども記述されるから、GitHub Actions経由でbuildして、Svelte(SPA) on S3 + CloudFrontとかで運用するとかなら読み込み速度が早い動的なwebサイト簡単に作れそうなので、一度作ったらあまり保守しなくて良さげなサービスとかなら、サクッと作れて、早いみたいなメリットを受けれる気がする。

Discussion