Svelte で簡単なページを作成してみる

## Prologue

最近よく耳にする Svelte, 気になったので一通り触ってみようと思ったことがきっかけです。
とりあえず component 間の binding, route 設定 がどうなっているのか触りながら調べてみました。

Svelte は公式のドキュメントがしっかり作られており、チュートリアルもわかりやすい、と言われています。そのため、ここでも都度ドキュメントのリンクを貼っていますが、そちらを確認しながら進めてください。

公式: https://svelte.dev/

## 環境

- macOS: v10.15.6
- node.js: v12.18.2
- terminal: iTerm
- エディタ: VS Code
- パッケージマネージャ: `yarn`

# Svelte とは

参考: https://svelte.dev/tutorial/basics

- 高速な Web アプリケーションを構築するためのツール。
- ビルド時にアプリを JS に変換する。  
→ FW の抽象化のパフォーマンスコストは支払わず、アプリが最初に読み込まれた時にペナルティが発生しないことを意味する。
- 1つ以上のコンポーネントで構成される。HTML, CSS, JS をカプセル化し、再利用可能な自己完結型のコードブロックである。

# プロジェクトの作成

プロジェクトを作成して、 TypeScript を入れます。
ここではプロジェクト名は `svelte-ts-prj` としています。

参考: https://svelte.dev/blog/svelte-and-typescript 
https://github.com/sveltejs/template#svelte-app

``` 
npx degit sveltejs/template svelte-ts-prj
npx: 1個のパッケージを2.687秒でインストールしました。
> cloned sveltejs/template#master to svelte-ts-prj
```

初期ディレクトリは以下のような感じです。

``` Shell
% cd svelte-ts-prj 
svelte-ts-prj % ls -la
total 32
drwxrwxr-x   9 mi**  staff   288  2  2 18:46 .
drwxr-xr-x  29 mi**  staff   928  2  2 18:46 ..
-rw-r--r--   1 mi**  staff    41  1 27 20:43 .gitignore
-rw-r--r--   1 mi**  staff  2903  1 27 20:43 README.md
-rw-r--r--   1 mi**  staff   520  1 27 20:43 package.json
drwxrwxr-x   5 mi**  staff   160  2  2 18:46 public
-rw-r--r--   1 mi**  staff  1841  1 27 20:43 rollup.config.js
drwxrwxr-x   3 mi**  staff    96  2  2 18:46 scripts
drwxrwxr-x   4 mi**  staff   128  2  2 18:46 src
```

clone と言う単語から察するに、ここから持ってきているぽいですね。
ドキュメントもありました。

https://github.com/sveltejs/template

起動してみます。

```
svelte-ts-prj % yarn install // 必要なパッケージをインストール
svelte-ts-prj % yarn run dev // 起動
yarn run v1.22.4
warning package.json: No license field
$ rollup -c -w
rollup v2.38.4
bundles src/main.js → public/build/bundle.js...
LiveReload enabled
created public/build/bundle.js in 214ms

[2021-02-02 18:49:51] waiting for changes...
npm WARN lifecycle The node binary used for scripts is /var/folders/4f/n4nwljj15jgbgmws96bs1_h40000gn/T/yarn--1612259390190-0.998612066362979/node but npm is using /Users/mi**/.nodebrew/node/v12.18.2/bin/node itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.

> svelte-app@1.0.0 start /Users/mi**/mii_work/svelte-ts-prj
> sirv public "--dev"


  Your application is ready~! 🚀

  - Local:      http://localhost:5000
  - Network:    Add `--host` to expose

────────────────── LOGS ──────────────────

  [18:50:00] 200 ─ 3.97ms ─ /
  [18:50:00] 200 ─ 1.35ms ─ /global.css
  [18:50:00] 200 ─ 2.48ms ─ /build/bundle.css
  [18:50:00] 200 ─ 3.83ms ─ /build/bundle.js
  [18:50:00] 200 ─ 0.94ms ─ /favicon.png
```

このような画面が表示されたらOKです。

![スクリーンショット 2021-02-02 18.51.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558989/da32b8fc-4429-cb6a-bd6a-b0ecfaa77052.png)


## TS の追加

参考: https://svelte.dev/blog/svelte-and-typescript 

Svelte で TS サポートする、とはどういうことをするのか、上記ブログから抜粋しました。

- `lang="ts"` を設定する。
- `svelte-check` コマンドでタイプチェックできる。
- コンポーネントを書いている際にヒントやタイプチェックをしてくれる。
- ts ファイルは Svelte コンポーネント API を解決してくれる。

以下のコマンドで TypeScript を追加します。

``` Shell
svelte-ts-prj % node scripts/setupTypeScript.js
Converted to TypeScript.

You will need to re-run your dependency manager to get started.
svelte-ts-prj % yarn // 再度必要なパッケージをインストール
yarn install v1.22.4
warning package.json: No license field
warning svelte-app@1.0.0: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 3.86s.
```

tsconfig.json が作成されているので中身を確認します。


``` tsconfig.json
{
  "extends": "@tsconfig/svelte/tsconfig.json",

  "include": ["src/**/*"],
  "exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}
```


### svelte-check してみる

``` Shell
svelte-ts-prj % yarn svelte-check
yarn run v1.22.4
warning package.json: No license field
$ /Users/mi**/mii_work/svelte-ts-prj/node_modules/.bin/svelte-check

Loading svelte-check in workspace: /Users/mi**/mii_work/svelte-ts-prj
Getting Svelte diagnostics...
====================================

====================================
svelte-check found 0 errors, 0 warnings and 0 hints
```

型と値が異なるとエラーを出してくれるそうですが、今回は確認できず...
このように出るらしいです。

https://svelte.dev/blog/svelte-and-typescript#3_CI_Checks


## コードを見てみる
VSCode の 拡張機能があるのでインストールします。
https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode

以下気になるところを抜粋してみていきます。

### export を使って 変数を property や props として扱う

component 間の値の受け渡しには export を使います。

参考: https://svelte.dev/docs#1_export_creates_a_component_prop

```App.svelte
<script lang="ts">
	export let name: string;
	let email = 'hoge@hoge.com'
</script>
<main>
	<h1>Hello {name}!</h1>
	<p>{email}</p>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>
```

`name` は `main.ts` から値をうけとっているため、export が必要ですが、 `email` は固定の値で `App.svelte` 内でセットしているため `undefined` にはなりません。
また、 `export const name = 'Japan'` と書くと readonly となるため、プロパティとしての受け取りではなくなります。


### component 

template 内 で大文字始まりで記載し、import した component を表示できます。

参考: https://svelte.dev/docs#Template_syntax

 ``` App.svelte
<script lang="ts">
	import Input from './Input.svelte' // 追加

	export let name: string;
	let email = 'hoge@hoge.com'
</script>

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

component に値を渡してみます。

```App.svelte
<script lang="ts">
	import Input from './Input.svelte'

	export let name = "Japan"
	let email = 'hoge@hoge.com'
	let placeholder = 'hogehoge' // 追加
</script>

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

属性と名前が一致する場合は省略して書くことも可能です。

``` App.svelte
<main>
	<h1>Hello {name}!</h1>
	<p>{email}</p>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
	<Input {placeholder} /> // placeholder={placeholder} を {placeholder} に変更
</main>
```


### Element directive

参考: https://svelte.dev/docs#Element_directives

event は `on:eventname={handler}` の形式で書きます。

``` Input.svelte
<script lang="ts">
  export let placeholder = 'hoge'
  const addText = () => {
      console.log('click')
  }
</script>
<div>
    <input placeholder={placeholder} />
</div>
<button on:click={addText}>add</button>
```

また、同じ event に対して複数の listener を持つこともできます。

``` Input.svelte
<script lang="ts">
  export let placeholder = 'hoge'
  const addText = () => {
      console.log('click')
  }

  const sendMessage = () => {
      console.log('send message')
  }
</script>
<div>
    <input placeholder={placeholder} />
</div>
<button on:click={addText} on:click={sendMessage}>add</button> // click event をもう一つ追加
```

ボタンをクリックすると、同時に console.log が吐き出されました。

### 子から親へのバインディング

`bind:` ディレクティブを使うことにより、データを子から親へ流すことができます。

``` Input.svelte
<script lang="ts">
  export let placeholder = 'hoge'

  export let value = '' // バインディングするプロパティ
  
  const addText = () => {
      console.log('click')
  }

  const sendMessage = () => {
      console.log('send message')
  }
</script>
<div>
    <input placeholder={placeholder} bind:value={value} /> // 追加
</div>
<button on:click={addText} on:click={sendMessage}>add</button>
```

親 component にも追記します。

``` App.svelte
<script lang="ts">
	import Input from './Input.svelte'

	export let name = "Japan"
	let email = 'hoge@hoge.com'
	let placeholder = 'hogehoge'
	let data = '' // 追加
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>{email}</p>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
	<Input {placeholder} bind:value={data} /> // 追加
	<p>{data}</p>
</main>
```

input フォームに入力された値が、親 component App.svelte の template にリアクティブに表示されます。  

### dispatcher する

参考: https://svelte.dev/docs#createEventDispatcher

event が発火したタイミングで値を親へ送ってみます。

`createEventDispatcher()` で `eventDispatcher` を作成します。
引数には name と detail の2つを渡すことができます。
Vue.js でいう emit のような役割です。

``` Inputs.svelte
<script lang="ts">
  import { createEventDispatcher } from "svelte";

  export let placeholder = 'hoge'

  export let value = ''

  const dispatch = createEventDispatcher() // eventDispatcher の作成
  
  const addText = () => {
      dispatch('emitAddText', value) // name と detail の2つの引数をとることができる
  }

  const sendMessage = () => {
      console.log('send message')
  }
</script>
<div>
    <input placeholder={placeholder} bind:value={value} />
</div>
<button on:click={addText} on:click={sendMessage}>add</button>
```

親 component App.svelte も以下のように修正します。

``` App.svelte
<script lang="ts">
	import Input from './Input.svelte'

	export let name = "Japan"
	let email = 'hoge@hoge.com'
	let placeholder = 'hogehoge'
	let data = ''

	const callbackFunctionFromInput = (event) => { // event を追加し、受け取った detail を表示
      console.log(event.detail)
	}
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>{email}</p>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
	<Input {placeholder} bind:value={data} on:emitAddText={callbackFunctionFromInput} /> // event を追加
	<p>{data}</p>
</main>
```

ボタンを押下したときに value を表示させるように修正しました。

Input.svelte はそのままで App.svelte を以下のように変更。

``` App.svelte
<script lang="ts">
	import Input from './Input.svelte'

	export let name = "Japan"
	let email = 'hoge@hoge.com'
	let placeholder = 'hogehoge'

	const callbackFunctionFromInput = (event) => {
	  console.log(event.detail)
	  name = event.detail // 受け取った detail を name に格納
	}
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>{email}</p>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
	<Input {placeholder} on:emitAddText={callbackFunctionFromInput} /> // dispatcher で受け取るため bind を削除
</main>
```

input フォームに入れた値が HELLO の後に表示されればOKです。

### routing について

SPA の開発をするためにはパッケージを入れる必要があります。

参考: https://github.com/ItalyPaleAle/svelte-spa-router

パッケージを追加します。

```
yarn add svelte-spa-router 
```

ルーティングを作成するため、App.svelter を以下のように書き換えます。

参考: https://github.com/ItalyPaleAle/svelte-spa-router#define-your-routes


``` App.svelte
<script lang="ts">
	import Router from 'svelte-spa-router'
	import Home from './Home.svelte'
	import About from './About.svelte'

	const name = 'world'

    const routes = { // ここで route と表示させる component を記載
	  '/': Home,
	  '/about/': About,
	  '*': Home
	}
</script>

<main>
	<Router {routes}></Router> // 上で設定した compoent が routing に合わせて表示されます。
</main>

```

`App.svelte` はシンプルにしておきたいので、`Home.svelte` を作成して、`App.svelte` の中身を移動させます。
さらに、リンクを追加してみます。

``` Home.svelte
<script lang="ts">
import Input from './Input.svelte'

import {link, push} from 'svelte-spa-router' // 追加

export let name = "Japan"
let email = 'hoge@hoge.com'
let placeholder = 'hogehoge'
let data = ''

const callbackFunctionFromInput = (event) => {
	console.log(event.detail)
	data = event.detail
}

</script>

<main>
	<h1>Hello {name}!</h1>
	<p>{email}</p>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
	<Input {placeholder} on:emitAddText={callbackFunctionFromInput} />
	<p>{data}</p>
	<button on:click={() => push('/about')} >LINK</button>
	<a href="/about" use:link>LINK</a> // リンクを追加
</main>

```

a タグの場合は `use:link`, button 等の場合には `<button on:click={() => push('/about')} >LINK</button>` と記載するようです。

CSS 等で整えると以下のようなSPAの画面も作成することができます。

![スクリーンショット 2021-02-09 10.34.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558989/7dcdc444-972b-4925-345b-841c08e7717d.png)



#### ハッシュベースの routing

参考: https://github.com/ItalyPaleAle/svelte-spa-router#hash-based-routing

リンク先は `http://localhost:5000/#/about` のような表示になります。
上記でも説明されているのですが、ここはいまいち理解ができず...  
この辺りの記事もわかりやすいのですが、概念として理解ができず、自身の知識が足りなさそうなので、追って勉強します...

https://krasimirtsonev.com/blog/article/deep-dive-into-client-side-routing-navigo-pushstate-hash

## Epilogue

最低限の小さなサイトを作れるところまで行ってみようと思い、 Vue.js の知識をもとに、比較しながら進めてみました。
router にも色々な機能、API があり、今後機会があればもう少し触ってみようと思いました。

Vue.js の知識と照らし合わせながら進められたので、思ったより苦労はしなかったのですが、逆に props の扱い方などで混乱はしました。
軽量と言われている通り、シンプルなシンタックスが多い印象なので、学べば学ぶほど楽しくなると思います。
ドキュメントも充実しており、ブログなども多いので、学びやすい環境も魅力的と感じました。

今回できなかったこととして、 router で設定した Home.svelte に main.ts から props をどう渡せばいいのか...その辺りの取り扱いがよくわからなかったので、こちらは引き続きキャッチアップしていきたいと思います。

また、TS を入れた際、既存のファイルに自動的に `lang="ts"` が挿入されていたので、JS→TSへの移行はもしや簡単なのでは？という検証もしてみたいと思います。

初めて触ったため、認識の不足、勘違い、code のミスなどありましたらご指摘ください。