Svelteでいきましょう!

こんにちは。 SaaS Product チームの板倉です。

SaaS Productチームでは開発運用しているサービスのマイクロサービス/マイクロフロントエンド化を進めています。
マイクロフロントエンドを実現する技術として採用しているフレームワークはAngular(TS, Dart)、Vue.js、Reactとなっています。
どれも素晴らしいフレームワークだと思いますが、私たちが進めているマイクロフロントエンドのやり方だと他にも良いフレームワークがあるんじゃないかと感じることがありました。
以前から気になっていたSvelteをブログにまとめながら自分でもキャッチアップしてみようと思ったのが今回のきっかけとなります。
(alpine.jsやpreactも考えてますが、フロントエンド大好きエンジニアがチームにはたくさんいますので、その方々のエントリーを待ちたいと思います)

Svelteって?

最近いろんなサービスでSvelteに関するエントリーを目にすることが多く今更感がありますが、紹介してみようと思います。
Svelteの最大の特徴はビルドすると素のJavaScriptが出力されるということだと思います。Svelte自身のランタイムは含まれません。バンドルサイズが小さくできるのは魅力です。Svelteがコンパイラと表現されることが多い理由ですね。

書いてみよう

実際にSvelteを使ってアプリを作ってみました。
今回はsnowpackを使ってプロジェクトを作っています。プロジェクトを作った際のコマンドは以下の通りです。

yarn create snowpack-app svelte-app --template @snowpack/app-template-svelte-typescript --use-yarn

作ったプロジェクトを見てみましょう。
SvelteはVue.jsと同じようにSFC(Single File Component)を採用しています。
以下は作成されたコードの一部です。

<script lang='typescript'>
   import {onMount} from 'svelte';
   let count: number = 0;
   onMount(() => {
     const interval = setInterval(() => count++, 1000);
     return () => {
       clearInterval(interval);
     };
   });
  </script>
  
  <style>
    :global(body) {
      margin: 0;
      font-family: Arial, Helvetica, sans-serif;
    }
    .App {
      text-align: center;
    }
  </style>
  
  <div class="App">
    <header class="App-header">
      <img src="/logo.svg" class="App-logo" alt="logo" />
      <p>Edit <code>src/App.svelte</code> and save to reload.</p>
      <p>Page has been open for <code>{count}</code> seconds.</p>
      <p>
        <a class="App-link" href="https://svelte.dev" target="_blank" rel="noopener noreferrer">
          Learn Svelte
        </a>
      </p>
    </header>
  </div>
  

SFCなのでVue.jsと似てるなという印象です。
違う所としてはデフォルトでは <template> 不要ということですね。templateタグで括らずUIの部分を書くことが出来ます。

Vue.jsと比較してみる

ここからは実際に触ってみて感じたVue.jsと同じ部分、違う部分を少しずつ紹介できたらなと思います。
今回作ったプロジェクトはこちらです。

Data

テンプレートからデータを参照するには、Svelteだとscriptタグ内に定義するだけです。

<script lang="ts">
let task: string = 'ブログを書く';
</script>

<div>{task}</div>

テンプレートでデータをレンダリングしたい場合は {} を使用します。Vue.jsは 2つですが、Svelteは1つです。

Method(Callback)

関数を呼び出したい時もDataと同様、scriptタグ内に定義するだけです。

<script lang="ts">

const onClick = (event: Event) => {
  console.log(event);
}
</script>

<button on:click={onClick}>click me</button>

テンプレート上でのコールバックの登録は on:<event> となります。Vue.jsは v-on:<event>@<event> ですね。
Vue.jsにあるイベント修飾子はSvelteにもあります。
Vue.jsでは v-on:click.prevent のように ドット(.)で設定しますが、Svelteの場合 on:click|preventDefault のように パイプ(|)で定義します。
修飾子は少しずつ違うのでドキュメントを参照して頂ければと思います。

v3.ja.vuejs.org

svelte.dev

Props

Componentに値を外から渡したい時は、定義したものに対して export をつけます。

Component.svelte

<script lang="ts">
  export let tasks: Task[] = [];
</script>

利用する側は属性に利用する値を設定します。 App.svelte

<script lang="ts">
  import Component from './Component.svelte';
  
  const tasks: Task[] = [];
</script>

<Component tasks={tasks}></Component>

プロパティと値が同名の場合、<Component {tasks}></Component> と省略して書くこともできます。
こちらは、Shorthand attributes という箇所に記載されています。
Introduction / Dynamic attributes • Svelte Tutorial

Custom Event

Vue.jsでは emit 関数を使用することでイベントをdispatchしました。
Svelteではイベントディスパッチャーを生成した上で利用します。

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

  const dispatch = createEventDispatcher();

  const register = () => {
      dispatch('input-complete', {task: 'somthing'})
  }

</script>

Event Propagation

イベントの伝搬に関しては、明確に違いがありました。
Vue.jsでは発火したイベントはルートへ向かって再起的に伝搬していきました。
Svelteではイベントは親にのみ通知されます。
ドキュメントにも明確に Unlike DOM events, component events don't bubble と記載されています。
以下のリンクに記載されているように、親の親へ通知されるにはイベントのフォワードが簡単に出来るようになっています。

Events / Event forwarding • Svelte Tutorial

Componentのレイヤリングが出来ていればそこまでforwardingを利用することはないかも知れませんが、忘れているとハマってしまうポイントです。

Logic

Vue.jsでは属性として書きますが、Svelteでは式を独立して書きます。

<script lang="ts">
let tasks: Task[] = [];
</script>

{#each tasks as task} 
  <TaskComponent {task}></TaskComponent>
{/each}

if 文についても同様です。

{#if empty}
<div > タスクを登録してみよう </div>
{/if}

Reactive Statement

Vue.jsで言うところの computedwatch にあたるものです。
Svelteでは $: を 使うことで宣言したものやブロックがReactiveに作用します。

let date = new Date(); // <- 画面で操作されることによって変更される
$: dateString = formatDate(date); // Vue.jsでcomputedにあたるもの
$: {
  api.putXXX(date);
} // <- 日付を変更したらAPIヘPUTリクエストしたいのように値が変わったら処理を実行して欲しい処理。Vue.jsでwatchにあたるもの

表現が同じなので、混乱せずに実装できそうですね。

さわってみて

Vue.jsに触れたことがある方であれば、そこまで違和感なく開発を進めることができるのではと思います。素のVue.jsよりComposition APIを利用した際のコードにより似てるなという印象です。
ビルドした際にランタイムを意識する必要がないので、近い将来WebComponentsへ移行していく際の選択肢になりそうかなと思いました。
話は逸れますが、WebComponentsは最近LitElementがLitになり、より洗練されてきているので、そちらについても今後エントリーを書いてみたいと思います。

最後に

SaaS Productチームでは、採用する技術はチームの意思を重要視しています。
まだSvelteに関しては誰にも話していませんが、プロジェクトで使っていきたいなと思っています。
誤解の無いようにお伝えしますが、採用する技術をCTOやシニアエンジニアが決めると言うことはありません。どうしてもその技術を使いたいというパッションが大事だと思っています。

自分で言うのもアレですが、現状に満足することなくチーム全体でより良い技術を模索する貪欲なチームです。
より良い技術を模索し、切磋琢磨したいエンジニアにはとても良い環境だと思います。

チームの文化は以下の記事が参考になると思いますので、興味をお持ちの方はお読み頂ければと思います。

journal.uzabase.com

話を聞いてみたいと言う方は、是非以下のリンクからエントリーをお願いします。
カジュアルな形で一度お話させて頂ければと思います。

B2B SaaS - ソフトウェアエンジニア(サーバー/フロント) - Uzabase, Inc.