Prologue
最近よく耳にする Svelte, 気になったので一通り触ってみようと思ったことがきっかけです。
とりあえず component 間の binding, route 設定 がどうなっているのか触りながら調べてみました。
Svelte は公式のドキュメントがしっかり作られており、チュートリアルもわかりやすい、と言われています。そのため、ここでも都度ドキュメントのリンクを貼っていますが、そちらを確認しながら進めてください。
環境
- 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
初期ディレクトリは以下のような感じです。
% 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 と言う単語から察するに、ここから持ってきているぽいですね。
ドキュメントもありました。
起動してみます。
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です。
TS の追加
参考: https://svelte.dev/blog/svelte-and-typescript
Svelte で TS サポートする、とはどういうことをするのか、上記ブログから抜粋しました。
-
lang="ts"
を設定する。 -
svelte-check
コマンドでタイプチェックできる。 - コンポーネントを書いている際にヒントやタイプチェックをしてくれる。
- ts ファイルは Svelte コンポーネント API を解決してくれる。
以下のコマンドで TypeScript を追加します。
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 が作成されているので中身を確認します。
{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}
svelte-check してみる
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
型と値が異なるとエラーを出してくれるそうですが、今回は確認できず...
このように出るらしいです。
コードを見てみる
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
<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
<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 に値を渡してみます。
<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>
属性と名前が一致する場合は省略して書くことも可能です。
<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}
の形式で書きます。
<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 を持つこともできます。
<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:
ディレクティブを使うことにより、データを子から親へ流すことができます。
<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 にも追記します。
<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 のような役割です。
<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 も以下のように修正します。
<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 を以下のように変更。
<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
<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
の中身を移動させます。
さらに、リンクを追加してみます。
<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の画面も作成することができます。
ハッシュベースの routing
参考: https://github.com/ItalyPaleAle/svelte-spa-router#hash-based-routing
リンク先は http://localhost:5000/#/about
のような表示になります。
上記でも説明されているのですが、ここはいまいち理解ができず...
この辺りの記事もわかりやすいのですが、概念として理解ができず、自身の知識が足りなさそうなので、追って勉強します...
Epilogue
最低限の小さなサイトを作れるところまで行ってみようと思い、 Vue.js の知識をもとに、比較しながら進めてみました。
router にも色々な機能、API があり、今後機会があればもう少し触ってみようと思いました。
Vue.js の知識と照らし合わせながら進められたので、思ったより苦労はしなかったのですが、逆に props の扱い方などで混乱はしました。
軽量と言われている通り、シンプルなシンタックスが多い印象なので、学べば学ぶほど楽しくなると思います。
ドキュメントも充実しており、ブログなども多いので、学びやすい環境も魅力的と感じました。
今回できなかったこととして、 router で設定した Home.svelte に main.ts から props をどう渡せばいいのか...その辺りの取り扱いがよくわからなかったので、こちらは引き続きキャッチアップしていきたいと思います。
また、TS を入れた際、既存のファイルに自動的に lang="ts"
が挿入されていたので、JS→TSへの移行はもしや簡単なのでは?という検証もしてみたいと思います。
初めて触ったため、認識の不足、勘違い、code のミスなどありましたらご指摘ください。