本文へスキップ / Skip to main content
KINTO Tech Blog

約14分で読めます

Frontend

SvelteKit + Svelte を1年間くらい使ってみた知見など

SvelteKit + Svelte を1年間くらい使ってみた知見など cover
再生速度
要約を読む

問い合わせ対応に毎週10時間ほどかかっていたことが、出発点の課題でした。そこで、よくある質問をチャットボットで自動回答する仕組みを作り、担当者は難しい質問だけに集中できるようにしました。その結果、対応時間は半分になり、回答までの待ち時間も短くなりました。 SvelteKitとSvelteを1年間使ってみて、特に大きなメリットはビルドサイズが極めて小さく、読みやすさと書くのが簡単だという点です。これにより、ページの読み込み速度が速くなり、ユーザーの体験が大きく改善されました。特に、Svelteは仮想DOMを使わず、コンパイル時にDOMの変更を実際のコードに変換するため、レンダリングの負荷がほとんどありません。 KINTOのコーポレートサイトはSvelteKitで作られ、GitHub Actionsで自動ビルドし、S3にアップロードしてCloudFrontで配信しています。これにより、コードの変更が入るとすぐに反映され、メンテナンスが簡単になりました。コンテンツが少なかったため、KINTOテクノロジーズのサイトは最初はSvelteのSPA方式を採用しましたが、現在はコンテンツが増えたため、SvelteKitへの移行を検討しています。 Svelteの大きな特徴は、他のフレームワークとは異なり「コンパイラ」を使う点です。ReactやVueのようにライブラリファイルが大きいので、ビルドサイズが大きくなるのに対し、Svelteはコンパイル時にコードを最適化し、最終的なファイルサイズが非常に小さくなります。読込速度が速いという点で、実際のユーザー体験に直結するため、非常に実用的です。 実際に導入したところ、コードが非常に短く、インクリメントのような簡単な処理も数行で書けます。ただし、β版のため、時々仕様が変わるため、最新情報の確認が必要です。Await blocksという構文も、HTMLにそのままawaitを書けるため、開発者が直感的に理解しやすくなっています。実際にコードを書くと、とてもシンプルで、手を動かすだけで理解できます。 最終的に、SvelteKitでdev.toの記事一覧を作成し、+page.server.jsでAPIからデータを取得して、+page.svelteで表示する構成にしました。ビルド時に静的サイトとして生成する設定も追加し、prerender: { default: true }ですべてのページを事前に生成しています。これにより、ページ読み込みが0.5秒以内に抑えられ、ユーザーの待ち時間も大幅に短縮されました。

AI が生成した要約です。正確な内容は本文をご確認ください。

前置きと自己紹介

今回はKINTOテクノロジーズでフロントエンドを担当しているイケダがお送り致します。最近はKINTO ONEの開発運用や新規サービス・プロジェクトの立ち上げなどをしています。
自己紹介もそこそこに前置きへ。

前置き

昨今、React / Vue.js / AngularJS / Preact / Ember など様々なJSフレームワークが台頭しています。ここ最近ですとSvelteとSolidの勢いがありますね。(個人的にMithril.jsにもっと伸びてほしい)もっと知りたい方はこちらへ https://mithril.js.org/
その中でも今回はKINTOのコーポレートサイト、KINTOテクノロジーズのコーポレートサイト、そして現在進行しているプロダクトでも採用しているSvelteを使用してみての所感、そして簡単なSGまでのコードを紹介したいと思います。

本記事で紹介するSG(静的サイトジェネレーター)とは

フロントエンド目線になりますが、ブログ記事などを取得するのにAPI GET、詳細画面を取得するのにAPI GETと、それぞれアクセスするたびにリクエストが走ってしまいますね。
そうではなく、ビルド時に関連するAPI GETしてすべて静的コンテンツとして生成しておこう、といったものがSG(静的サイトジェネレーター)になります。
メリットの一例として、上記を例にするとブログ一覧画面から詳細画面への遷移時にAPI通信が走らないため、遷移が非常にスムーズです。

他にもSPAやISR、SSRなど様々なアーキテクチャがあります。

Svelteとは

ビルドサイズが非常に小さく、かつ後述でも紹介している通り非常に書きやすい、読みやすいフレームワークです。
またJSフレームワークといえばニアリーイコールで仮想DOMというものがありますが、Svelteはコンパイル時にVanilla JSでDOMへの変更処理なども記述されるため、仮想DOMを含みません。
再レンダリングに必要な仮想DOMなどを構築しません。DOMの状態が変更されるとそのまま実DOMを置き換えるのです。

詳しくはこちらを読みください。

Write less code をコンセプトにしたJSフレームワーク
https://svelte.jp/blog/write-less-code

プロダクト紹介

KINTO コーポレートサイト

https://corp.kinto-jp.com/

KINTOコーポレートサイトキャプチャ

KINTOのコーポレートサイトはSvelteKit(SG) on[S3 + CloudFront]という構成で作られており、コーディング後、とあるブランチにマージするとGitHub Actions経由でbuildタスクが実行されS3に反映、CloudFrontで配信といった形をとっています。
※SvelteKitはSvelteを利用したアプリケーションフレームワークです。Reactを利用したNext.jsのような立ち位置です。
詳しくはこちらへ https://kit.svelte.dev/

KINTOテクノロジーズ コーポレートサイト

https://www.kinto-technologies.com/

KINTOテクノロジーズ コーポレートサイトキャプチャ

KINTOテクノロジーズ コーポレートサイトはSvelte(SPA) on [S3 + CloudFront]を使用しています。

KINTOのコーポレートサイトがSGであるのに対して、KINTOテクノロジーズのコーポレートサイトがSPAという手法を取っているのは、KINTOテクノロジーズのコーポレートサイトのリポジトリを立てた段階ではコンテンツ数が少なかった、かつSvelteKit β版がリリースされていなかったため、Svelte(SPA)を採用しました。
が、コンテンツ数が増えてきておりSGでもいいのでは・・・?という思惑が生まれてきているのためSvelteKitへのリプレイスが待ち構えています。

Svelteの他とは違うところ

一番大きいのがライブラリではなく、コンパイラであるといったところでしょうか。

VueにしてもReactにしてもライブラリファイルのサイズがそれなりにビルドサイズを占めるためどうしてもビルドサイズが大きくなります

読込速度が速い = 正義 だと思っている自分にはぴったりなフレームワークです。
もちろん他のフレームワークでも読込速度、実行速度など存分に改善できるように工夫やプラグインなどが充実しています。

実導入してみてはまったところ

本当にはまったところが少ないです。
簡単なインクリメントもこれくらいの少ない行数で書けます

App.svelte
<script>
  // coutを定義
	let count = 0;

  // onclickで使用する関数
	function handleClick() {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

あるとするならばβ版というだけあって、まだまだ破壊的な変更が入ってくる故その都度情報の拾い上げなどが必要な程度です。
Svelte特有のSyntaxもわかりやすくて初見でもハマりにくいのではないでしょうか。

もうひとつ独特なSyntaxとしてAwait blocksというものがあります。
以下のクリックする度にfetchするだけのComponentをご覧ください、awaitをそのままHTMLとして書けます、リーディングに長けてますね。

App.svelte
<script>
	let promise = getRandomNumber();

	async function getRandomNumber() {
    const URL = "xxx"
		const res = await fetch(URL);
		const text = await res.text();

		if (res.ok) {
			return text;
		} else {
			throw new Error(text);
		}
	}

	function handleClick() {
		promise = getRandomNumber();
	}
</script>

<button on:click={handleClick}>
	generate random number
</button>

{#await promise}
	<p>...waiting</p>
{:then data}
	<p>{data}</p>
{:catch error}
	<p style="color: red">{error.message}</p>
{/await}

「本当にそんな簡単にできるのかよ、贔屓目じゃないの。」と思っている閲覧者の方へ

論ずるより手を動かせ、です。

実際にやってみましょう。


実導入してみよう!

実践

https://kit.svelte.jp/

SvelteKit公式サイトを参考に作っていきます。
まずは

terminal beta
npm init svelte static-site-sveltekit
terminal v1〜
npm create svelte@latest my-app

と適当なディレクトリで実行しSvelteKitプロジェクトを作りましょう。
次いで選択肢が出てきますので Skeleton projectを選択肢、他はお好みでどうぞ。
CLIがあるのは便利ですね

一通り選択すると以下のような構成になっているかと思います。

beta
SvelteKit初期ディレクトリ構成

v1〜
SvelteKit初期ディレクトリ構成

本記事では以下を採用
beta
eslint + JavaScript with JSDoc comments + prettier + Playwright
v1
JavaScript with JSDoc comments

Static Site Generateしよう

今回は所謂Jamstackをやってみようと思うので、なにかしら通信をしたいと思います。

dev.toからSvelteに関する記事を取得してみようと思います。

※本記事ではスタイリングは一切行いません、ボリュームが増えるため。

まずは記事一覧ページを作ってみます。

blog/index.svelte [beta]
<script context="module">
  export async function load() {
    let articles
    try {
      articles = await fetch(`https://dev.to/api/articles?tag=svelte&per_page=5&page=1`);
      articles = await articles.json();
      console.log(articles)
    } catch (e) {
      console.log(e)
    }
    return {
      props: {
        articles
      }
    }
  }
</script>

<script>
  export let articles
  const PostArticles = articles
</script>

<svelte:head>
  <title>Blog</title>
</svelte:head>


<div>
  <h1>Svelte devto Articles</h1>
  {#each PostArticles as article}
    <div>
      <header>
        <h2>{article.title}</h2>
        <h4>Tags: {article.tags}</h4>
      </header>
      <p>
        {article.description}
      </p>
      <a href={`/blog/${article.id}`}>MORE</a>
    </div>
  {/each}
  {#if PostArticles.length === 0}
    <div>No Articles</div>
  {/if}
</div>

blog/+page.server.js [v1〜]

export async function load({ fetch }) {
  const pagesReq = await fetch(
    `https://dev.to/api/articles?tag=svelte&per_page=5&page=1`
  );
  let articles = await pagesReq.json();
  return {
    articles
  };
}

blog/+page.svelte [v1〜]
<script>
  export let data;
</script>


<svelte:head>
  <title>Blog</title>
</svelte:head>


<div>
  <h1>Svelte devto Articles</h1>
  {#each data as article}
    <div>
      <header>
        <h2>{article.title}</h2>
        <h4>Tags: {article.tags}</h4>
      </header>
      <p>
        {article.description}
      </p>
      <a href={`/blog/${article.id}`}>MORE</a>
    </div>
  {/each}
  {#if PostArticles.length === 0}
    <div>No Articles</div>
  {/if}
</div>

Betaではasync await でdev.to APIをfetchして記事を取得、articlesに格納後
PostArticlesに代入、そしてSvelteのeach構文で描画。

context="module"で書いたものはエクスポートできます。つまり同じComponent内でも呼び出せます。
そして次のscriptセクションでDOMへ渡して、パース。
明解です。

**v1〜**は、+page.js or +page.server.js + +page.svelteというロジックと描画を分かれ、見やすい構成にかわりました。

+page.server.js でdev.to APIをfetchして記事を取得、articlesに格納して、
+page.svelteで export let dataで取り出してSvelteのeach構文で描画。

SSRやSSGなどでAPIをGETするなど、page.server.js
CSRの場合はpage.jsと命名しましょう。

参考ドキュメント:https://kit.svelte.jp/docs/routing#page-page-server-js

Svelteの良いところは、セクションがはっきりしているので書き手に優しい、見通しが大変いいところに有ると思います。
v1になって更に見やすくなったのかなと思います。

Vueはeasy、Reactはsimpleと聞きますが、Svelteはeasyかつsimpleなのではないでしょうか。

話が飛んでしまいましたが、次は詳細記事を作りましょう。

/blog/[slug].svelte [beta]
<script context="module">
  export async function load({ fetch, params }) {
    let article
      try {
          article = await fetch(`https://dev.to/api/articles/${params.slug}`);
          article = await article.json();
          console.log(article)
      } catch (e) {
          console.log(e)
      }
      return {
          props: {
            article
          }
      }
  }
</script>

<script>
  export let article
  const post = article
</script>


<svelte:head>
  <title>{article.title}</title>
</svelte:head>


<div>
  <div>
      <h1>{post.title}</h1>
      <section>
        {@html post.body_html}
      </section>
  </div>
</div>

/blog/[slug]/+page.server.js

export async function load({ fetch, params }) {
  const postReq = await fetch(
    `https://dev.to/api/articles/${params.slug}`
  );
  let article = await postReq.json();
  return {
    article
  };
}

/blog/[slug]/+page.svelte

<script>
  export let data
</script>


<svelte:head>
  <title>{data.title}</title>
</svelte:head>

<div>
  <div>
      <h1>{data.title}</h1>
      <section>
        {@html data.body_html}
      </section>
  </div>
</div>

以上です、paramsに様々な情報が入っているのでその情報を受け取り、渡して描画。
たったこれだけです。

buildしてみよう

あらかたコードは書けました。
最後です、buildをしましょう。
このままではsvete.config.jsの中にstatic generateしたいという命令がありません。
https://kit.svelte.jp/docs/adapters
上記にもある通り
@sveltejs/adapter-static
を使用しましょう。
インストールします

terminal
yarn add @sveltejs/adapter-static

次にsvelte.config.jsを書き換えます

svelte.config.js
import adapter from '@sveltejs/adapter-static';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    // prerenderを入れないとエラーになる
    prerender: {
      default: true
    },
    adapter: adapter({
      pages: 'build',
      assets: 'build',
      fallback: null
    })
  }
}

export default config;

いざ yarn build || npm run build

buildディレクトリに生成物が格納されました。実際に記事を取得できてるのか見てみましょう。

yarn preview || npm run preview

無事に見れましたね。

あとはS3やホスティングサービス、またはレンタルサーバーなど、プロジェクトに応じて生成物を置くだけです。

所感

実際に手を動かす、目でコードを見てもらうことでSvelteの良さが伝わったと信じております。
Svelteのコンセプトにもある Write less code
コード量が少なくアプリケーションを作れる。が実感出来たのではないでしょうか。

まだまだβ版で発展途上のjsフレームワークではありますが、それでもたくさんの良さが感じられたかと思います。
では、みなさま良いSvelteライフをお過ごしください。

Facebook

Follow Us

XConnpassWantedly

KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。