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 コーポレートサイト
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テクノロジーズ コーポレートサイトはSvelte(SPA) on [S3 + CloudFront]を使用しています。
KINTOのコーポレートサイトがSGであるのに対して、KINTOテクノロジーズのコーポレートサイトがSPAという手法を取っているのは、KINTOテクノロジーズのコーポレートサイトのリポジトリを立てた段階ではコンテンツ数が少なかった、かつSvelteKit β版がリリースされていなかったため、Svelte(SPA)を採用しました。
が、コンテンツ数が増えてきておりSGでもいいのでは・・・?という思惑が生まれてきているのためSvelteKitへのリプレイスが待ち構えています。
Svelteの他とは違うところ
一番大きいのがライブラリではなく、コンパイラであるといったところでしょうか。
VueにしてもReactにしてもライブラリファイルのサイズがそれなりにビルドサイズを占めるためどうしてもビルドサイズが大きくなります
読込速度が速い = 正義 だと思っている自分にはぴったりなフレームワークです。
もちろん他のフレームワークでも読込速度、実行速度など存分に改善できるように工夫やプラグインなどが充実しています。
実導入してみてはまったところ
本当にはまったところが少ないです。
簡単なインクリメントもこれくらいの少ない行数で書けます
<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として書けます、リーディングに長けてますね。
<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}
「本当にそんな簡単にできるのかよ、贔屓目じゃないの。」と思っている閲覧者の方へ
論ずるより手を動かせ、です。
実際にやってみましょう。
実導入してみよう!
実践
SvelteKit公式サイトを参考に作っていきます。
まずは
npm init svelte static-site-sveltekit
npm create svelte@latest my-app
と適当なディレクトリで実行しSvelteKitプロジェクトを作りましょう。
次いで選択肢が出てきますので Skeleton projectを選択肢、他はお好みでどうぞ。
CLIがあるのは便利ですね
一通り選択すると以下のような構成になっているかと思います。
beta
v1〜
本記事では以下を採用
beta
eslint + JavaScript with JSDoc comments + prettier + Playwright
v1
JavaScript with JSDoc comments
Static Site Generateしよう
今回は所謂Jamstackをやってみようと思うので、なにかしら通信をしたいと思います。
dev.toからSvelteに関する記事を取得してみようと思います。
※本記事ではスタイリングは一切行いません、ボリュームが増えるため。
まずは記事一覧ページを作ってみます。
<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>
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
};
}
<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なのではないでしょうか。
話が飛んでしまいましたが、次は詳細記事を作りましょう。
<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>
export async function load({ fetch, params }) {
const postReq = await fetch(
`https://dev.to/api/articles/${params.slug}`
);
let article = await postReq.json();
return {
article
};
}
<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
を使用しましょう。
インストールします
yarn add @sveltejs/adapter-static
次に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ライフをお過ごしください。
関連記事 | Related Posts
We are hiring!
【フロントエンドエンジニア(コンテンツ開発)】新車サブスク開発G/東京
新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、クルマのサブスクリプションサービス『KINTO ONE』のWebサイトコンテンツの開発・運用業務を担っていただきます。
【フロントエンドエンジニア】新車サブスク開発G/東京
新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、TOYOTAのクルマのサブスクリプションサービス『KINTO ONE』のWebサイトの開発、運用を行っていただきます。