KINTO Tech Blog
Development

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

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

前置きと自己紹介

今回は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.svelteexport 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

関連記事 | Related Posts

We are hiring!

【フロントエンドエンジニア(コンテンツ開発)】新車サブスク開発G/東京

新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。​業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、クルマのサブスクリプションサービス『KINTO ONE』のWebサイトコンテンツの開発・運用業務を担っていただきます。

【フロントエンドエンジニア】新車サブスク開発G/東京

新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。​業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、TOYOTAのクルマのサブスクリプションサービス『KINTO ONE』のWebサイトの開発、運用を行っていただきます。