Help us understand the problem. What is going on with this article?

SvelteでWebサイトを作ってみて感じた魅力

The State of JavaScript 2019を見ていて気になったSvelteを触ってみる延長でサイトを作りました。作るついでに、現実的な使いどころと気になったことをまとめたので、Svelteを使いたくなったときの参考にしてもらえたら。Sapperというフレームワークも使ってみたので、軽めに触れてます。

作ったサイト → 電影予告集会

要点

Svelte

  • UIを最小限のJSに変換するコンパイラ
  • rollupRich Harrisさんがメインで開発
  • Svelte = KonMari(React + Vue)

利点

  • バンドルサイズが小さい
  • コード量も少ない
  • ビルドが速い(体感)

欠点

  • TypeScriptに公式には対応してない
  • 開発環境の快適さはReactやVueに劣る
  • シェアが少ないので採用するリスクが大きい

使いどころ

  • ファイルサイズに制約がある環境
  • SPAじゃないWebサイトの一部
  • 興味本位 / 趣味

まだ快適に開発できるとは言いがたい状態なので試行錯誤は必要ですが、短いコードでシンプルに書ける気持ちよさと、バンドルサイズを抑えられる利点は大きいです。特に、ファイルサイズに制約がある環境では有力な選択肢になると思います。

あとは、SPAじゃないWeサイトの一部にコンポーネントを埋め込みたい時などは、大きなランタイムが不要なSvelteの最大限に活かせる。ReactやVueが築き上げたエコシステムの恩恵を考えると、Svelteを採用する利点が上回るケースは限られると思いますが、ポイントで使うのであれば現実味がある。

実務で使う機会はなさそうなので、しばらくは趣味で触っときます。

DEMO

Svelte 3.17.1
Sapper 0.27.9

※2020年1月20日現在のバージョン

Svelteの特徴

ReactやVueはコンポーネントを各ランタイム上で動くようにコンパイルしますが、Svelteは最小限のヘルパーを含むシンプルなJavaScriptにコンパイルするため、大きなランタイムを読み込む必要がありません。

リソースが限られたブラウザ上での最適化に注力するのではなく、コンパイル時にやれる事はやってしまえという発想の転換で、バンドルサイズを削減すると同時にブラウザへ与える負荷の軽減を実現しています。

設計の哲学的なところは、Blogにまとまってました。

小さくて速い、は本当か

RealWorld のベンチマークがあったので、そのデータから。このプロジェクトは、TODOよりも現実的なサンプルアプリが欲しいよね、みたいなモチベーションで作られたものです。Mediumを模したconduitというサイトが集まっています。

ベンチマーク対象のフレームワークは18個。

項目 スコア 順位
パフォーマンス 91 12位
ファイルサイズ 9.7KB 1位
コードの行数 1116行 3位

※ パフォーマンスはLighthouse AuditのMobile/Simulated Fast 3G
A RealWorld Comparison of Front-End Frameworks with Benchmarks (2019 update)

パフォーマンスの項目は、Fast 3G91なのでかなり良い数値ですが、18個中の12位という結果。ファイルサイズはしっかり1位なので、ファイルサイズが小さいからSvelteを選ぶ、というのはあると思います。

どんなコードが出力されるのか

<script>
  let count = 0
</script>
<button on:click={() => count++}>count : {count}</button>

↑のコードが↓になります。
コンポーネントと最小限のヘルパーが一緒に出力されてますね。これで300行ぐらいです。

See the Pen minimal svelte output by nishinoshake (@nishinoshake) on CodePen.

Does this JavaScript spark joy?

konmari
[YouTube] Rich Harris - Rethinking reactivity

書いていて気分が上がったところをいくつか。
見た感じは、ほとんどVueのシングルファイルコンポーネントと同じです。

コードが短い

<script>let count = 0</script>
<button on:click={() => count++}>count : {count}</button>

ボタンのクリック&カウントを、ぐっと縮めたらこうなります。

状態管理がシンプル

stores.js
import { writable, derived } from 'svelte/store';

export const count = writable(0);
export const doubled = derived(count, $count => $count * 2);
export const increment = () => count.update(n => n + 1)
App.svelte
<script>
  import { count, doubled } from './stores.js';
  import Button from './Button.svelte';
</script>

<p>count : {$count}</p>
<p>doubled : {$doubled}</p>
<Button />
Button.svelte
<script>
  import { increment } from './stores.js';
</script>

<button on:click={increment}>+</button>

コンポーネントと切り離して状態を管理する仕組みが備わっています。VuexのGettersにあたる、derivedという関数も用意されているので便利。

トップレベルにHTMLタグを置き放題

<p>A</p>
<p>B</p>
<p>C</p>

Reactでいうところの<React.Fragment>が不要。
これは楽な反面、秩序がなくなると、あちこちで祭りが開催されそう。

HTMLのテンプレートでawait

<script>
  const sleep = () =>
    new Promise(resolve => setTimeout(() => resolve("Loaded!"), 3000))
</script>

{#await sleep()}
  <p>Loading...</p>
{:then message}
  <p>{message}</p>
{:catch error}
  <p>{error.message}</p>
{/await}

すごい。ただの変態。
テンプレート側でPromiseを受け取れるのは面白いけど、 <script> 側で await して状態を変更する方が、コードとしては見やすいと思う。

要素のサイズやスクロール量をバインドできる

<script>
  let w
  let h
  let y
</script>

<svelte:window bind:scrollY={y} />
<div bind:clientWidth={w} bind:clientHeight={h}></div>
<p>w: {w}px h: {h}px y: {y}</p>

要素のサイズ取得は、リサイズ時の再取得とかまで含めると結構面倒なので、これは嬉しい。チュートリアルにも書いてありますが、DOMへのアクセスを伴う重い処理なので控えめに。

さらに取扱注意ですが、 windowbind:scrollY とかもできます。

CSSはデフォルトでスコープあり

scoped-css.png

scopedがデフォルトで、globalがオプション。

クラスの付け外しが楽

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

<style>
  .isActive {
    color: red;
  }
</style>

<p class:isActive>Hello!</p>
<button on:click={() => isActive = !isActive}>toggle</button>

変数名をそのままクラス名として付け外せるショートハンドがある。悩ましいのが、JSの変数名なので、ショートハンドを使うと is-active みたいなケバブな命名ができない。ここはこだわらずにJSの変数名に合わせました。スコープがある前提であれば、そこら辺は割り切りやすい。

アニメーションが豊富

TweenSpringTransitionなどが用意されています。Springまで入っているのはすごい。Vueにもあるけど、flipのアニメーションは感動的。

Sapperとは

ここからは、Svelteの開発環境構築からSSRや静的化まで、必要なことをまとめてやってくれるSapperの話。SvelteのためのNext.js的なやつです。

こちらもSvelteと同じくRich Harrisさんがメインで開発しています。

rollupかwebpack

# for Rollup
npx degit "sveltejs/sapper-template#rollup" my-app

# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app

テンプレートの作成にはrollupかwebpackから選べますが、全体的にrollup推しな雰囲気を感じたので郷に従うことに。degitとは?と思って調べたら、これもRich Harrisさんが作ってた。なんとなく分かると思いますが、sveltejs/sapper-templateがリポジトリ名で#rollupがブランチ名です。

Next.js → Sapperがスムーズ

Next.js is close to this ideal. If you haven't encountered it yet, I strongly recommend going through the tutorials at learnnextjs.com.

Sapper: Towards the ideal web app framework

Next.jsを使ったことがなかったら、とりあえずチュートリアルをやってみて!とブログにも書いてありました。実際、Next.jsかNuxt.jsを触ってからSapperを使ったほうが、すんなり馴染むと思います。

逆に言えば、これらのフレームワークと大きく違うところがないので、ここに書くことがない・・・

SSRして使う場合、ミドルウェアやページ遷移まわりが気になると思うんですが、今回は必要なかったので触ってないです。。

デプロイ

あとは、Sapperの上にSvelteのコンポーネントをゴリゴリ追加して完成です。静的ファイルを生成してS3にデプロイしました。

# 静的ファイルの生成
npm run export

https://github.com/nishinoshake/yokoku/blob/master/.github/workflows/deploy.yml

些細なことだけど、まだ .svelte の拡張子に慣れない。


電影予告集会

↑ 作ったサイト

気になったこと

サイトを作るうえで気になったことをまとめました。だいたいWikiのFAQに答えが書いてます。

https://github.com/sveltejs/svelte/wiki/FAQ

TypeScriptのサポート

今は未対応だけど対応の予定はある。進捗はこちらのissueから。

svelte-preprocess<script lang="typescript"> にしたらある程度は行けるっぽかったですが、公式でサポートされる前にハマりたくなかったので試してないです。

テストはどうする

How do I do testing Svelte apps?
We don't have a good answer to this yet, but it is a priority.

https://github.com/sveltejs/svelte/wiki/FAQ#how-do-i-do-testing-svelte-apps

まだ、これって方法は無さそう。

Storeなどのロジックのテストをメインでやって、大事なところはE2Eでもカバーしつつ、気が向いたらコンポーネントもテストする、ぐらいが今はちょうどいいかも。Sapperのテンプレートには、デフォルトでcypressが入ってました。

いろいろ試していたら長くなったので、別の記事にまとめました。

スタイルガイドが欲しい

Vueのような公式のスタイルガイドや、ベストプラクティス的なものは見当たらなかったので、sveltejs/sapper-templatesveltejs/realworldのコードを参考にしました。

Sassで書きたい

svelte-preprocessのパッケージを追加して設定を変更するとSassが使えるようになります。下記のブログを参考にしました。
Svelte / Sapper with Sass!

変数やmixinを共通で @import したかったんですが、issueを参考にrollupの設定をいじったら行けました。

.browserslistrc
> 0.5% in JP, not dead, not IE 11
rollup.config.js
// src/styles/variables.scss
// src/styles/mixins.scss
// にファイルを置いておく。

// ※ 必要なところのみ抜粋

import sveltePreprocess from 'svelte-preprocess';

const preprocess = sveltePreprocess({
  scss: {
    includePaths: ['src'],
    data: `
      @import 'styles/variables.scss';
      @import 'styles/mixins.scss';
    `
  },
  postcss: {
    plugins: [
      require('autoprefixer')
    ]
  }
})

export default {
  client: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  },
  server: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  }
}

Svelteはスケールするか

<よく聞かれる質問>
大きなランタイムが不要なのはわかったけど、コンポーネントごとにプレーンなJSを生成してたら、コンポーネントを増やすコストが大きそうだから、そのうち従来のフレームワークよりもバンドルサイズが大きくならない?

<回答>
確かにそうなることはあるけど、コストがそこまで大きい訳ではないし、初期ロードのバンドルサイズを抑える方が重要。あとから読み込まれるバンドルが大きくても、Service Workerやpreloadでどうにかなるし。

===== ↑ざっくり訳↑ =====

バンドルサイズや実行時の性能もそうですが、知見が少ない分、スケールするアプリケーションの設計が難しいのがネックになりそう(特に状態管理)。

おわり

作者のRich Harrisさんの講演が面白かったので置いときます。

[YouTube] Rich Harris - Rethinking reactivity

できたもの
https://yokoku.cc

GitHub
https://github.com/nishinoshake/yokoku

KONMARI 〜人生がときめく片付けの魔法〜
https://www.netflix.com/title/80209379

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした