フロントエンドの「Svelte」とvueを比べてみる


<H2>はじめに</H2>
Qiitaをなんとなく眺めていたら、「SvelteはVueよりつよつよ」というようなことが書いてあった記事を見つけ、とりあえず試してみました。

<H2>TL;DR</H2>
Svelteは速い
Svelteはコード量が少ない
Svalteは書きやすそう
Svelteはドキュメントが少ない

<H2>Svelteとは</H2>
スヴェルトと読むようです。ライブラリというよりもコンパイラで、.svelte拡張子に記述したコードをCSS、javaScriptとして出力してくれます。

<H2>Svelteとvueを比べてみた</H2>
もともとvueで作っていたシンプルなアプリケーションをSvelteで作ってみて、どんな差があるのかを見てみました。

アプリケーションはフランス語で数字の0~30がランダムに再生されるので、それを答えるという大変ニッチなものです。
ページも1枚、ルーティングもなければコンポーネント分けもしていないシンプルなものです。

![スクリーンショット 2020-11-20 9.58.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562918/fe5b56ae-1f01-b29b-a5a0-662757651026.png)

<H2>Svelteは速い</H2>
コードを見る前に、実際にコンパイル、ビルドの所要時間を見てみます。  


vueでのビルド
> ⠸  Building for production...
>  DONE  Compiled successfully in 6461ms     


Svelteでのビルド 

> src/main.js → public/build/bundle.js...
> created public/build/bundle.js in 840ms

6,461ms対840ms秒ということで、7~8倍の差があります。
また、ローカルでの開発サーバー起動に関しても、vueでは1,800ms程度かかるところが、Svelteだと340ms程度でした。
小さいプロジェクトでの実施ですので絶対的な処理時間自体対して長くないですが、プロジェクトが大きくなったり、Docker上で動かしたりすると顕著に差が出てくるでしょう。

<H2>Svelteはコード量が少ない</H2>
今回用いたアプリケーションの実際のコードを比べてみます。

<H3>vueのソースコード(実装部分)</H3>

```js
<template>
  <div >
      <div>question Number {{questionNumber}}</div>
      <div v-if="isAnswerChecking">
          <span class="response"  v-bind:class="{ 'is-correct': isCorrectAnswer }">
              <span v-if="isCorrectAnswer">Correct!!!</span>
              <span v-else>Oops!!</span>
          </span>
      </div>
      <div v-else>
          <audio v-bind:src="questions[selected].audio" controls />
          <div>
          <label>input number</label>
          <input type="number" v-model="inputNumber"/>
          <button v-on:click="answer()" >Go</button>

          </div>
      </div>
  </div>
</template>

<script>

export default {
  name: 'NumberListening',
  data() {
      return {
          questionNumber: 1,
          selected: 0,
          questions: [
              {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/0.mp3",answer:0},

              {audio:"https://frenchtogether.com/wp-content/uploads/2018/03/30.mp3",answer:30},                       
          ],
          inputNumber: "",
          isAnswerChecking: false,
          isCorrectAnswer: false
      }
  },
  created: function() {
      this.generateQuestion();
  },

  methods: {
      answer: function(){
          this.isAnswerChecking = true;
          this.answerCheck();
          setTimeout(this.refreshResponse,1500);
      },
      answerCheck: function() {
          this.isCorrectAnswer = (this.inputNumber == this.selected)
      },
      refreshResponse: function() {
          if (this.isCorrectAnswer) {
              this.generateQuestion();
              this.questionNumber++;
          }
          this.inputNumber = "";
          this.isAnswerChecking = false;
          this.isCorrectAnswer = false;
      },
      generateQuestion: function() {
          this.selected = Math.floor(Math.random() * this.questions.length);
      }
  }
  
}
</script>

```


Svelteのコード

```.js
<script>
	import { onMount } from 'svelte';
    let questionNumber = 1;
    let selected =  0;
    let questions = [
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/0.mp3",answer:0},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/1.mp3",answer:1},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/2.mp3",answer:2},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/3.mp3",answer:3},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/4.mp3",answer:4},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/5.mp3",answer:5},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/6.mp3",answer:6},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/7.mp3",answer:7},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/8.mp3",answer:8},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/9.mp3",answer:9},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/10.mp3",answer:10},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/11.mp3",answer:11},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/12.mp3",answer:12},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/13.mp3",answer:13},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/14.mp3",answer:14},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/15.mp3",answer:15},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/16.mp3",answer:16},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/17.mp3",answer:17},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/18.mp3",answer:18},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/19.mp3",answer:19},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/20.mp3",answer:20},     
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/21.mp3",answer:21},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/22.mp3",answer:22},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/23.mp3",answer:23},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/24.mp3",answer:24},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/25.mp3",answer:25},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/26.mp3",answer:26},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/27.mp3",answer:27},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/28.mp3",answer:28},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/29.mp3",answer:29},
		{audio:"https://frenchtogether.com/wp-content/uploads/2018/03/30.mp3",answer:30},                       
	];
	let inputNumber;
	let isAnswerChecking = false;
	let isCorrectAnswer = false;

	onMount(async () => {
		generateQuestion()
	})
	function answer(){
		isAnswerChecking = true;
		answerCheck();
		setTimeout(refreshResponse,1500);
	}
	function answerCheck() {
		isCorrectAnswer = (inputNumber == selected)
	}
	function refreshResponse() {
		if (isCorrectAnswer) {
			generateQuestion();
			questionNumber++;
		}
		inputNumber = "";
		isAnswerChecking = false;
		isCorrectAnswer = false;
	}
	function generateQuestion(){
		selected = Math.floor(Math.random() * questions.length);
	}	  
</script>

<main>
  <div>
      <div>question Number {questionNumber}</div>
	{#if isAnswerChecking}	  
		<span class="response">
			{#if isCorrectAnswer}
				<span class={ isCorrectAnswer ? 'is-correct': '' }>Correct!!!</span>
			{:else}
				<span>Oops!!</span>
			{/if}
		</span>
	{:else}
          <!-- svelte-ignore a11y-media-has-caption -->
          <audio src={questions[selected].audio} controls />
          <div>
          <!-- svelte-ignore a11y-label-has-associated-control -->
          <label>input number</label>
          <input type="number" bind:value={inputNumber}/>
          <button  on:click={answer} >Go</button>
		  </div>
	{/if }
  </div>
</main>
```


文字数にして、3,763 vs 3,735です(インデント、スペースを排除して測定)。
言うほど変わらなかったので、適当な切り口として、`<script>`内で、固定値で長ったらしく宣言している配列の部分を除いた部分で文字数の比較をしてみたところ、741 vs 636ということで、それなりに良さそうな差が出ました。
この差が出た一番大きな理由はおそらく `this`の有無でしょう。(その程度の差しかでないアプリケーションでした)。
とはいえ、読みにくくなるわけでもなく文字数が減ることに対して異論のある人はあまりいないでしょう。

<H2>Svalteは書きやすそう</H2>
先ほどのコードを比べてみた時に、目立った差としては以下のあたりでしょうか
 - thisの有無(Svelteでは不要)
 - 変数の宣言方法(vueは data(){return} とか独特の記法あり)
 - 条件付きレンダリング(if-else)の書き方(vueはタグ内に、Svelteはタグとは別の{}くくり)

個人的にはいずれも、Svelteの書き方の方が読みやすく、書きやすい印象があります。jsそのままに近くて、フレームワーク特有記法ではないと感じます(条件付きレンダリングについてはその限りではないですが...)。


<H2>vueの代わりにSvelteを使うか？</H2>
現時点でまだそれはできないかなと思います。まだドキュメントも少なく、(おそらく)こなれていないため今後大きく変更があってもおかしくないことを思うと、この程度の小さいコードが置き換えられたくらいではさすがに踏み込めないですね。
とはいえ、今メジャーな３大フロントエンドフレームワークvue,React,Angularよりも取っつきやすいのは間違いないので、シンプルなフロントエンド開発をちゃちゃっとやりたい人たちを取り込んでいくことが期待できると思います。

今後のSvelteに期待しましょう。

<H2>参考</H2>
 - [Vue・Reactを超える！？ 近年話題の Svelte に入門してみよう！ | Qiita](https://qiita.com/yassun-youtube/items/3faee13f87aa63e33ef5)
 - [ReactとVueを改善したSvelteというライブラリーについて | Qiita](https://qiita.com/so99ynoodles/items/a8144ced1a21467c6300)