Subterranean Flower

Svelteで始める頑張らないフロントエンド生活 前編

Author
古都こと
ふよんとえんよぷよぐやま!!!

フロントエンドの世界も随分と様変わりしました。便利なツールが多数導入され、高度なフレームワークが整備され、言語には静的型検査が付き、より本格的なアプリケーションの制作に集中できるようになりました。

しかしそれらの環境は業務には適していても、必ずしも個人開発に適しているとは言えません。過剰な抽象化がほどこされ、混乱を招くこともあります。

そんな中で、最近話題のフレームワークとしてSvelteというものがあります。Svelteはシンプルかつ十分な機能を提供します。

対象とする読者

この記事は以下の読者を対象としています:

  • HTML/CSSはある程度わかる
  • JavaScriptはちょっと書いたことがある
  • React/Vue/Angularは自分には難しすぎた
  • でもフロントエンドで遊んでみたい

フロントエンドの参入障壁

フロントエンドと言えば、かつてはちょっと頑張れば誰にでもできるような平和な分野でした。しかし今は違います。ネイティブアプリに近い体験を求められ、外部ツールやフレームワークの導入が実質的に必須となっており、学習コストは段違いに高くなっています。

今はReact/Vue/Angularの3大フレームワークが有名ですが、これらは全て学習コストが高く、初心者が挫折するのに十分な複雑さを備えています。もちろんその複雑さは業務レベルのアプリを構築するのに必要なので、みんな頑張って勉強しているわけですが。

一方、個人開発などにおいては、無理をして抽象度の高いフレームワークを使う必要はありません。過剰なクオリティを追い求めると、勉強するべきことも膨れ上がり、負担は増加します。

しかし現実問題としては、素のJavaScriptでは機能が少なく、まともに何かを作ろうとするとフレームワークが必要になります。結局は個人開発や学習においても、かなり無理をしてReactなどを導入せざるを得ないというのが現状となっています。

そんな中に現れたのがSvelte(スヴェルト)です。Svelteはフロントエンド開発において極めてシンプルな枠組みを提供します。

Svelteとは何か

Svelte(スヴェルト)はフロントエンドフレームワークとそれを開発するツールのセットです。拡張子が「.svelte」のファイルにHTML/CSS/JavaScriptを記述し、それをブラウザ用に変換して動作させます。

SvelteでのHello Worldは以下のコードになります:

<script>
  const message = 'Hello World!';
</script>

<main>{message}</main>

見た目通り、Svelteは非常にシンプルで明快です。そう、ただのJavaScriptとHTML/CSS、あとはちょっとした独自の構文さえ覚えればSvelteを使うことができてしまいます。Svelteでは新たに覚えることは非常に少なく、かつ十分な機能も提供してくれます。

例えばクリックするたびに数字が増えるボタンを作るには以下のようにします:

<script>
  let count = 0;
  const increase = () => count++;
</script>

<button on:click={increase}>Click: {count}</button>

わずかこれだけです!難しいことは一切考えず、素直なJavaScriptコードを書けばあとは勝手にSvelteがやってくれます。

SvelteにReact/Vue/Angularほどの柔軟性はありませんが、小さなプロジェクトで使うには十分かつシンプルです。まずはSvelteから始めてみてはどうでしょう?

Svelte開発準備

エディタの準備

エディタを準備しましょう。なんでもいいですが、迷ったらVisual Studio Code(https://azure.microsoft.com/ja-jp/products/visual-studio-code/)がオススメです。VS Codeを使うなら以下のSvelte対応拡張も入れてください。

https://marketplace.visualstudio.com/items?itemName=JamesBirtles.svelte-vscode

ビルド環境の準備

Svelteを利用するにはNode.jsというツールが必要になります。まずはこれをインストールしましょう。

Windowsの方は公式サイト(https://nodejs.org/ja/)からダウンロードしてインストールするだけです。LTS(長期サポート版)と最新版がありますが、どちらでもいいです。

macOSの方はHomebrew(https://brew.sh/index_ja)を導入し、brew install node を実行すればインストールできます。

Debian/Ubuntuの方は https://github.com/nodesource/distributions/blob/master/README.md#debinstall を参考にインストールしてください。

インストールが済んだらコマンドプロンプト/ターミナルから以下のコマンドを叩きます:

node -v

Node.jsのバージョンが表示されれば成功です。

プロジェクトの初期化

プロジェクトを作るための適当なフォルダに移動します。次に、以下のコマンドでSvelteプロジェクトのフォルダが作成されます:

npx degit sveltejs/template svelte-project

npxコマンドはNode.jsをインストールしたときに一緒についてきます。「svelte-project」の部分はプロジェクト名なので、自由に変更して構いません。

プロジェクトフォルダができたら、cdコマンドでフォルダの中に移動し、npm install というコマンドを実行します。

cd svelte-project
npm install

npmもNode.jsに付属するツールで、外部パッケージ(モジュール)の管理をしてくます。npm installでSvelteの開発に必要なパッケージをインストールしてくれます。いくつかWARNINGが出ると思いますが、無視してください。

Svelteプロジェクトの実行

npm installが済んだら、以下のコマンドを実行してください:

npm run dev

これでSvelteプロジェクトがビルドされ、サーバが立ち上がります。ターミナルに「http://localhost:5000/」が表示されるはずなので、ブラウザでそこにアクセスしてみてください。

ページが表示されたでしょうか?おめでとうございます。あなたはSvelteへの第一歩を踏み出しました!

なおサーバを止める時はCtrl+Cを押せば止まります。

テンプレートの構成を見てみる

本格的にSvelteをいじる前に、現在のテンプレートの中身を見てみましょう。このテンプレートではpublicフォルダとsrcフォルダがメインになります。

まずはpublicのほうから。publicフォルダには以下のファイルが含まれています:

  • ウェブページとなる index.html
  • そのスタイルを指定する global.css
  • 単なるfaviconである favicon.png
  • svelteファイルから変換されたJavaScriptが入るbuildフォルダ

index.htmlをちらっと見てみましょう。大したことは書かれておらず、bodyタグに関しては空っぽです。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset='utf-8'>
	<meta name='viewport' content='width=device-width,initial-scale=1'>

	<title>Svelte app</title>

	<link rel='icon' type='image/png' href='/favicon.png'>
	<link rel='stylesheet' href='/global.css'>
	<link rel='stylesheet' href='/build/bundle.css'>

	<script defer src='/build/bundle.js'></script>
</head>

<body>
</body>
</html>

これは実際のHTMLの構築はSvelteのJavaScriptファイルが行うためです。もちろんここにHTMLを書くこともできますが、Svelteに任せた方が楽です。

また、langやtitleが気に入らない人はここでさくっと書き換えておきましょう。

あとは、cssファイルはただのcssファイルですし、faviconもただのfaviconです。

buildフォルダにはsvelteファイルから変換されたJavaScritpファイル、CSSファイルが入ります。このフォルダを手動でいじることはないですし、中身を見ることもほとんどないです。あまり気にしなくていいでしょう。

次にsrcフォルダを見ていきましょう。以下のファイルがあると思います:

  • App.svelte
  • main.js

App.svelteはまさに今回の主役となるsvelteファイルです。中身を開いてみると、JavaScript、HTML、CSSが書かれています。

<script>
	export let name;
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

<style>
	/* 省略 */
</style>

SvelteではこのようにHTML/CSS/JavaScriptをひとつのファイルにまとめて記述します。

次にmain.jsを開いてみましょう。

import App from './App.svelte';

const app = new App({
	target: document.body,
	props: {
		name: 'world'
	}
});

export default app;

main.jsは実際にスクリプトを実行するときのスタート地点です。App.svelteを読み込み、body要素をターゲットにApp.svelteを描画しています。

大雑把にSvelteの構成は掴めたでしょうか?次は実際にコードを書いていきましょう。

Svelte入門

Hello Svelte!

まずは簡単なメッセージを表示させてみましょう。src/App.svelteの中身を全て消し、以下のように書き換えてください:

<script>
  const message = 'Hello!';
</script>

<main>
  {message}
</main>

<style>
</style>

<script>はJavaScriptを書く部分で、<style>はCSSを書く部分です。真ん中の<main>はHTMLのmainタグです。HTMLならなんでもよく、mainタグである必要性はありません。divとかでもいいです。

そして以下のコマンドでサーバを起動します。もし起動しっぱなしならそのままで大丈夫です。

npm run dev

ブラウザで「http://localhost:5000/」を開くと「Hello!」という文字が表示されるはずです。

SvelteではHTML中で{}で囲むことによってJavaScript側の変数を参照することができます。今回の場合はJavaScript側でmessageという変数を宣言し、HTML側でmessage変数を参照しています。

イベントの設定

アプリを作成する上で、「クリックしたら何かをする」という動作は非常に重要になります。当然、Svelteにもその機能はあります。App.svelteを以下のように書き換えます:

<script>
  const messages = ['Hello!', 'Svelte!'];
  let current = 0;

  const toggle = () => current = (current+1) % 2;
</script>

<main>
  {messages[current]}
  <button on:click={toggle}>Toggle Message</button>
</main>

<style>
</style>

メッセージの他に、ボタンを設置しました。Svelteでは「on:イベント名」という属性でイベントを設定します。今回は「on:click」に、JavaScript側で作成したtoggle関数を設定しています。

これでページを開いてみると、ボタンが増えているはずです。クリックすると値が変化し、画面に変更が適用されます。「Hello!」と「Svelte!」が交互に表示されます。

ここで驚くべきことは、変数の変更が自動的に画面へ反映されている点です。私たちは変数を変更しただけであって、変更を画面へ適用するコードを書いたわけではありません。このあたりは、Svelteが自動的にやってくれるのです。

コンポーネントとprops

アプリの規模が大きくなってくると、コードをコンポーネント(部品)に分割したくなってくるでしょう。Svelteにはコンポーネント化の機能がついています。

srcフォルダの下に、新しいファイル「Greeting.svelte」を作成してください。そして以下の内容を書き込みます:

<script>
  export let username;
</script>

<div>
  Hello, {username}!
</div>

内容としては、変数の値を使って挨拶を表示しているだけですね。

ここでexportというキーワードが出てきました。JavaScriptではexportは値を外部から読み込めるするようにためのものですが、Svelteでは外部から変数の値を指定するために使います。使用方法は以下のようなイメージです:

<Greeting username="Koto Furumiya">
</Greeting>

これを使うようにApp.svelteを書き換えてみましょう:

<script>
  import Greeting from './Greeting.svelte';
</script>

<main>
  <Greeting username="Koto Furumiya"/>
</main>

<style>
</style>

まず作成したsvelteファイルをimportします。これでこのsvelteファイルの中でGreeting.svelteが使えるようになります。

次にGreetingを普通のHTMLタグのように使用します。ここで<Greeting/>は<Greeting></Greeting>の省略系です。

このときコンポーネント(ここではGreeting)に渡す値のことをpropsと言います。Greetingコンポーネントではusernameというpropsを持っていますね。ここではpropsはひとつですが、実際は複数exportできます。

これをブラウザで表示してみると、挨拶が表示されるはずです。

コンテナコンポーネント

コンポーネントは普通のHTMLタグのように使えるので、<Greeting>Hello!<Greeting/>のような書き方もできます。ですがこのとき「Hello!」は消えてしまいます。いったいどこへ?

実はこの「要素の中身」はコンポーネント側で<slot>要素を設置することで拾うことができます。試しに「Card.svelte」というコンポーネントを作ってみましょう:

<div>
  <slot></slot>
</div>

<style>
  div {
    box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.1);
    border-radius: 3px;
    padding: 1em;
  }
</style>

Card.svelteは、slot要素をdiv要素で囲んだだけのシンプルなコンポーネントです。ただしちょっとスタイル付けはしています。これをApp.svelte側から使ってみます:

<script>
  import Greeting from './Greeting.svelte';
  import Card from './Card.svelte';
</script>

<main>
  <Card>
    <Greeting username="Koto Furumiya"></Greeting>
  </Card>
</main>

<style>
</style>

Cardコンポーネントの中にGreetingコンポーネントを入れています。本来なら中のものは消えるのですが、Card.svelteのslot要素で拾っているため、そのまま表示されます。

このようにしてslot要素を使うことで、タグの中身をコンポーネントに反映させることができます。

バインディング

Svelteでは変数とフォームの入力値を紐付けできます。つまり入力値が変わったら変数の値も変わる、ということが簡単に実現できます。

入力値の紐付けにはbind:value属性を使用します。App.svelteを以下のように書き換えてみましょう:

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

<main>
  <input placeholder="Enter your name" bind:value={name}>
  <div>Hello, {name}</div>
</main>

<style>
</style>

input要素にbind:valueを使ってname変数を紐付けています。これでinput要素の入力値が変わるとname変数も変わるようになります。

実際にページを開いてみると、入力値がリアルタイムで反映されていくのがわかると思います。

なお、チェックボックスの場合は同様にbind:checkedを使います。

再計算

Svelteでは変数に値を代入(またはバインド)すれば自動的に画面にも反映されるという仕組みがありました。しかしここに罠があります。代入された変数については反映してくれますが、その他の変数については面倒を見てくれません。

つまり以下のようなコードは、正しく動きません:

<script>
  let firstName = '';
  let lastName = '';
  let fullName = `${firstName} ${lastName}`;
</script>

<main>
  <input bind:value={firstName}>
  <input bind:value={lastName}>
  <div>Hello, {fullName}</div>
</main>

<style>
</style>

ファーストネームとラストネームを入力してもらって、結合してフルネームを作って表示しようとしているコードになります。

ここでfirstNameとlastNameは正しく更新されます。しかしfullNameは代入もされないしバインドもされていないので、最初の1回しか処理が走りません。つまりfullNameは常に空文字列です。

これを解決するためには、Svelteでは「$:」ラベルを付与します。$:ラベルが付与された式や文は、変数の更新のたびに再計算されるようになります。このときletは外しておきます。以下のようになります:

<script>
  let firstName = '';
  let lastName = '';
  $: fullName = `${firstName} ${lastName}`;
</script>

<main>
  <input bind:value={firstName}>
  <input bind:value={lastName}>
  <div>Hello, {fullName}</div>
</main>

<style>
</style>

これで代入やバインドと関係のない変数でも強制的に再計算させることができます。

分岐処理

SvelteではHTML中でifやif-elseを使うことができます。

<script>
  let checked = false;
</script>

<main>
  <label>
    <input type="checkbox" bind:checked={checked}>
    Check Me!
  </label>
  {#if checked}
    <div>Checked!</div>
  {/if}
</main>

<style>
</style>

開始は{#if condition}で、終了は{/if}です。elseは間に{:else}を、else-ifは{:else if condition}を挟みます。

<script>
  let checked = false;
</script>

<main>
  <label>
    <input type="checkbox" bind:checked={checked}>
    Check Me!
  </label>
  {#if checked}
    <div>Checked!</div>
  {:else}
    <div>Hurry!</div>
  {/if}
</main>

<style>
</style>

繰り返し処理

Svelteでは繰り返し処理を行うこともできます。繰り返し処理は{#each array as item}と{/each}で挟みます。

<script>
  const persons = [
    { name: 'Alice' },
    { name: 'Bob'},
    { name: 'Carol' }
  ];
</script>

<main>
  {#each persons as p}
    <div>{p.name}</div>
  {/each}
</main>

<style>
</style>

Svelteでアプリを作ろう

ポケモンステータスアプリ

ここまででSvelteの基礎について学ぶことができました。次は実際に簡単なアプリを作ってみましょう。ポケモンのステータスを表示するだけの簡易的なアプリを作ります。

これを実現するためには、まずステータス値のバーを作ります。「Bar.svelte」ファイルを作ってください:

<script>
  export let label = '';
  export let value = 0;
</script>

<div class="container">
  <div class="label">{label}</div>
  <div class="bar" style="--value: {value}px"></div>
</div>

<style>
  .container {
    display: flex;
    align-items: center;
    height: 1.5em;
    padding: 0.2em 0;
  }

  .label {
    margin-right: 0.5em;
    width: 4em;
    text-align: right;
  }

  .bar {
    background-color: white;
    height: 100%;
    width: var(--value, 0);
  }
</style>

Bar.svelteでは、ラベルと数値を受け取り、画面に表示します。簡易的な作りにするため、valueの値が100なら100px、200なら200pxでそのまま表示します。

また、ラベルとバーを横並びにするために、display: flexというのを使っています。詳しく知りたい方は「flexbox」で調べてみてください。

次に「Pokemon.svelte」を作ります。Pokemon.svelteは画面での緑色や赤色や青色の部分です。

<script>
  import Bar from './Bar.svelte';

  export let pokemon;

  const typeToClass = {
    'くさ':  'grass',
    'ほのお': 'fire',
    'みず': 'water'
  };

  let open = false;
  const toggle = () => open = !open;
</script>

<div class="pokemon {typeToClass[pokemon.type]}" on:click={toggle}>
  <h2>{pokemon.name}</h2>
  {#if open}
    <div class="stats">
      <h3>ステータス</h3>
      <Bar label="HP" value={pokemon.hp} />
      <Bar label="攻撃" value={pokemon.atk} />
      <Bar label="防御" value={pokemon.def} />
      <Bar label="特攻" value={pokemon.sAtk} />
      <Bar label="特防" value={pokemon.sDef} />
      <Bar label="素早" value={pokemon.spd} />
    </div>
  {:else}
    <div>クリックで詳細を見る</div>
  {/if}
</div>

<style>
  h2,
  h3 {
    font-weight: normal;
  }

  .pokemon {
    color: white;
    padding: 1em;
    cursor: pointer;
  }

  .grass {
    background-color: rgb(96, 184, 69);
  }

  .fire {
    background-color: rgb(226, 82, 82);
  }

  .water {
    background-color: rgb(62, 104, 240);
  }
</style>

Pokemon.svelteでは、pokemonのデータをexport(外部から入力してもらう)しています。

また、openという変数も持っています。open変数を切り替えることで開閉を実現しています。openはtoggle関数によって反転させます。コンポーネントのどこをクリックしても開閉したいので、全体を囲っているdivに対してon:click={toggle}を設定しています。

各ポケモンには名前の他に、「くさ」「ほのお」などのタイプと、「攻撃」「防御」などのステータスがあります。名前は常に表示しておきますが、タイプは背景色で表示し、ステータスはopenがtrueのときのみ表示します。openの判定には{#if}を使っています。

各ステータスはBarコンポーネントを使用して表示しています。ラベルと、数値を渡してやることであとはBar.svelteが処理してくれます。

最後にこれらをApp.svelte内で使用します。ポケモンのデータを用意して、Pokemonコンポーネントに渡してやれば完成です。

<script>
  import Pokemon from './Pokemon.svelte';

  const pokemons = [
    {
      name: 'フシギダネ', type: 'くさ',
      hp: 45, atk: 49, def: 49, sAtk: 65, sDef: 65, spd: 45
    },
    {
      name: 'ヒトカゲ', type: 'ほのお',
      hp: 39, atk: 52, def: 43, sAtk: 60, sDef: 50, spd: 65
    },
    {
      name: 'ゼニガメ', type: 'みず',
      hp: 44, atk: 48, def: 65, sAtk: 50, sDef: 64, spd: 43
    },
]
</script>

<main>
  {#each pokemons as p}
    <Pokemon pokemon={p}/>
  {/each}
</main>

<style>
</style>

繰り返し処理をするために、{#each}を使用しています。処理内容としては、素直にPokemonコンポーネントにデータを渡しているだけです。

これを実行してみましょう!

無事表示されたでしょうか。表示されたら、クリックで開閉してみてください。

できましたか?おめでとうございます!あなたはSvelteを使って、ウェブアプリを作ることができました!

本番ビルドする

実はもう少しだけ続きがあります。今まで実行していたコマンドは「npm run dev」でした。これは開発用のコマンドで、簡易的な変換しかしてくれないので、容量的にも無駄が多いです。

配布用にちゃんとしたビルドをするには、以下のコマンドを実行します。

npm run build

これでしっかり最小化されたjsファイルが出力されます。本番ビルドが成功したら、どこかのサーバにpublicフォルダ以下のファイルを全てアップロードすれば公開できます。ぜひ友達に自慢しましょう!

まとめ

Svelteを使うことで、難しいフレームワークの学習無しにウェブアプリの開発が可能になります。業務アプリの開発などには向かないかもしれませんが、個人でちょっとしたプロジェクトを進めるのには十分すぎるほどの機能を備えています。

React/Vue/Angularを学ぶことももちろん大事ですが、まずはSvelteという小さなところから始めてみてはどうでしょう。簡単なところから始めていけば、小さな経験をたくさん積めますし、十分に習熟したら他のフレームワークに乗り換えるのも容易になります。

フロントエンドの世界は難しいですが、だからといって全てを理解しないといけないわけではありません。自分のできるところからコツコツやっていけば、いつかきっと立派なエンジニアになれます。

フロントエンド、楽しんでいきましょう!

後半へ続く

Svelteで始める頑張らないフロントエンド生活 後編」へ続きます。後半では、より本格的なアプリケーションの開発をします。