仕事でSvelteフレームワークを扱ってみた話

この記事はTech KAYAC Advent Calendar 2022の3日目の記事になります。

こんにちは!技術部フロントエンドエンジニアの古園です。
最近はギアの揃ったファッションをするべくイクラアルバイトに勤しんだり、来年1月公開の映画に備えて蒼穹のファフナー THE BEYONDを見たりしています。

www.kayac.com

今回は最近流行り始めているSvelteを使ってWebサイトの構築を行なった話をしていきたいと思います。

Svelteとは

SvelteはReactやVueと同じくユーザーインタフェースを構築できるJavaScriptフレームワークの1つです。
ReactやVueと違う点としてSvelteは仮想DOMを用いておらず、TypeScriptコンパイラによってコードフレームワークを含まない小さいvanilla jsにコンパイルされます。
そのため、ReactやVueと比べると出力されるファイルが軽いことが特徴です。

案件で扱おうとした経緯

元々Svelteには興味があり、公式ホームページに存在しているチュートリアルを一通り行っていました。

実際にやってみた結果、

  • JavaScriptはscriptタグ内に、HTMLは(Svelteの場合は必ずタグで囲む必要はありませんが)templateタグ内に、 CSSはstyleタグ内に記述するやり方がVueに似ている
  • Vueのmountedに当たるものがSvelteではonMountとなっているなどVueと機能名が似ているものがあるため学習コストが高くないのではないか
  • Viteを使っているのでHMRのコンパイル速度がとても早く魅力的

とシンプルなサイト構築であれば開発コストも下げられそうであったため、挑戦してみることにしました。

案件その1 〜 御成カプセル

採用理由

初めて使用したのは御成カプセルの公式ホームページ制作においてです。(実績記事はこちら)
単一ページのLPでサーバーとのやりとりが必要な構成はないため難しい要素はなく、練習台としてはもってこいでした。

onari-capsule.kayac.com

フレームワーク選定

その上で、制作するサイトは動的に変化する要素がなかったためパフォーマンスを考慮し、静的生成を行えるフレームワークを使いたいと考えました。
初めは当時既にバージョン1.0RCがリリースされ、公式サイトでも推奨されていた「SvelteKit」を採用しようとしていたのですが、調査を行っていくうちにとある問題点が浮上します。
それは、英語公式ドキュメントが整備されているのか不明瞭な部分があったことです。
この記事を執筆している12月現在では解消されていますが、エンジニアにとってドキュメントが整備されているかは死活問題。
初めて扱うということもあり、今回はSveltekitを見送ってSapperを採用しました。

Sapperの構成

今回は僕が普段Nuxtで開発している時と似た構成である、

  • Pug + TypeScript + SassのSSG環境
  • コードフォーマットは ESLint + Prettier + StyleLint + svelte-check

を採用しました。

実際に使ってみて

途中までは順調に進んでいたのですが、ある時からESLintが大量のwarningを吐くようになってしまいました。

ESLintのwarn

実際に問題となった箇所の1つがこちら。

<template lang="pug">
    .about_label-wrap
        +each('Array(4) as _, index')
          .about_label
            Picture(src='about/label{index + 1}' type='jpg')
        +each('Array(4) as _, index')
          .about_label
            Picture(src='about/label{index + 1}' type='jpg')
</template>

公式ドキュメントの通りに記述したのですが、どうやらpugとESLintの相性が一部合わないところがあるようで、Svelte用のPug mixinである +each が解析できていない様子。
そこでissueのコメントを参考に、以下のように変更しました。

<template lang="pug">
    .about_label-wrap
        //- {#each Array(4) as _, index}
        +each('Array(4) as _, index')
          .about_label
            Picture(src='about/label{index + 1}' type='jpg')
        //- {/each}
        //- {#each Array(4) as _, index}
        +each('Array(4) as _, index')
          .about_label
            Picture(src='about/label{index + 1}' type='jpg')
        //- {/each}
</template>

上記のようにpug固有記法の範囲を元の記法で囲って上げることでESLintも正しく理解してくれました!

...これ普通に二度手間で面倒では?

使ってみての感想

良かった点

NuxtやNext.jsと比べると記述がシンプルなのでコーディングにかかる時間が減り、開発効率が上がりました。
子コンポーネントでpropsを受け取るのに

export let hoge: string;

と書くだけでいいのは見やすくて素晴らしいですね。(Nuxtくん、見習って)

また、ビルドも高速でGitHub Actionsで環境立ち上げ→自動ビルド→できたファイルをサーバー送信の一連の流れが、似たような規模のNuxt、Next.js案件では1分40秒程度かかっている所、今回制作したサイト規模では基本1分10秒、長くても1分30秒に収まってくれたのでこれまでよりも早くテストサイトでの確認が行えました!

実装し終わってすぐにデザイナーや制作進行に確認をお願い出来るのはグッド。

苦労した点

ただ、先述したようにPugの利用には難がありました。

  • Pugを採用することで得られるメリットがSvelte単体で得られるにもかかわらず、かえって手間が増えてしまうこと
  • ESLintとの親和性にも問題があったこと

手間を減らすつもりで選んだのに結局の所は手間が増えてしまうばかりで実装の効率化は得られませんでした。

悔しいですが、結果的にはSapperのポテンシャルを引き出しきれなかった形です。

案件その2 〜 じゃがりこコミック

採用理由

こちらはじゃがりこのプロモーションサイトです。(実績記事はまだないので公開され次第URLを貼らさせていただきます)

www.calbee.co.jp

御成カプセルとはページ数が違いますが、シンプルで記事を見やすいデザインになっているため、Sapperで得た知識を元に満を持してSveltekitに挑戦する良い機会でした。

Sveltekitの構成

前回の反省を活かしてPugを使うのはやめて

  • HTML + TypeScript + SassのSSG環境
  • ESLint + Prettier + StyleLint + svelte-checkでコードフォーマット

にしました。

使ってみての感想

使ってみて意外だったのはNuxtやNext.jsはコマンドでSSR、SSGの切り替えを行うところをSveltekitの場合はadapterで行うことです。
デフォルトはSSR用のadapterが設定されているため、 公式サイトに書いてあるとおり @sveltejs/adapter-static を使用するようにします。

yarn add -D @sveltejs/adapter-static

そして svelte.config.js にそれを設定してあげればOK。

import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: preprocess(),
  ...
};

export default config;

これでビルドを行えばOK...かと思いきやそうではなく、プリレンダリングの設定を変更する必要がありました。

プリレンダリングとはブラウザ上でコンポーネントを再度レンダリングし、状態をインタラクティブなものにする機能でこれでファイルを静的生成した場合もルーティングの設定を残したままにすることができます。
デフォルトのままだとトップページにしかその設定がなされておらず、そのままビルドするとルーティングが解決できないエラーで動作が止まってしまいます。
その対応としてこれも公式が書いてある通り +layout.ts

export const prerender = true;

を設定し、SSG環境の作成ができました。

良かった点

Sapper同様HMRの速度が速く、変更が即反映されるのは実装しやすくて良かったです。
また、ネックとなっていたPugの使用をやめたことで前回よりも効率を上げることができました!

苦労した点

SveltekitはSapperの後継とはいえ、設定や仕組みが違う部分もあります。
特にプリレンダリングの範囲指定は理解するまで時間がかかりました。
この案件では実行環境がPHPで、 ビルド後に指定されたPHPのコードを埋め込む必要があったのですが、どれだけやってもプリレンダリング時にその変更が無かったことにされてしまいました。

原因

Sveltekitはデフォルトで以下の様にbodyに data-sveltekit-prefetch が付いていてそれを基準にプリレンダリングを行います。
(最新のバージョンでは data-sveltekit-preload-data="hover" が付いていました。今後も変わる可能性がありそうです)

<body data-sveltekit-prefetch>
  <div>%sveltekit.body%</div>
</body>

変更が無かったことにされた原因はまさにその箇所で、初めのコードから%sveltekit.body%の親要素のdivはいらないだろうと短慮に消してしまった上で、data-sveltekit-prefetchの特性を理解しきれなかったことが重なり、かなりの時間を費やすこととなりました。
最終的には先輩にも助けて頂いて事なきを得たのですが、これが上手くいかなければNuxtで作り直すしかないと絶望していたので持つべきものは知識・経験豊富な先輩です。

実装

// 僕が書いて動かなかったコード

<body data-sveltekit-prefetch>
  <?php hoge ?>
  %sveltekit.body%
</body>

この状態から下記のように%sveltekit.body%の親要素を復活させ、そこにdata-sveltekit-prefetchを移動します。
こうすることでタグはプリレンダリングの範囲から外れるので追加読み込みされた部分が消えなくなります。

// 想定通りに動いたコード

<body>
  <?php hoge ?>
  <div data-sveltekit-prefetch>%sveltekit.body%</div>
</body>

総括

個人的にSSGで作成するそこまで凝ってないWebサイトならSveltekitで作っていきたいと思っています!
公式ドキュメントも遂に整備されましたし、HMRも今までより高速。次世代のフレームワークという感じがします。
ただ、まだ出たばかりだからこそバグが取り切れてない点、記事が少ない点が挙げられるため、より複雑な実装が要求されるWebアプリのような大規模開発には早いと感じました。
ちょうど最近Next.jsの13が発表されて気になっているのもありますし、しばらくはWebサイトをSveltekitで、WebアプリをNext.jsで実装してみるのも良さそうです。

おまけ

Svelteの勉強会でじゃがりこが取り上げられていたらしい(8ページ目)
speakerdeck.com

最後に

カヤックではSveltekitのような最新のフレームワークを試して働きたい方を募集しています!
www.kayac.com
www.kayac.com