Svelte 学習帖
Svelte学習帖: SvelteKitとFirebaseを用いた静的Webサイト構築手法編
- Svelte • サイバネティクスで強化されたWebアプリ
https://svelte.jp/ - Docs • Svelte
https://svelte.jp/docs
- SvelteKit • 効率的で無駄のない、研ぎ澄まされた Web 開発
https://kit.svelte.jp/ - イントロダクション • Docs • SvelteKit
https://kit.svelte.jp/docs/introduction
Replit で blank repl から Sveltekit をセットアップする
- How can i setup sveletekit 1.0.0 in replit.please? - Replit Help - Replit Ask
https://ask.replit.com/t/how-can-i-setup-sveletekit-1-0-0-in-replit-please/4874
プロジェクトを静的ファイルとして出力できるように設定する
export const prerender = false;
prerender
あなたのアプリの、少なくともいくつかのルートは、ビルド時に生成されるシンプルな HTML ファイルとして表現されることが多いでしょう。これらのルート(routes)を プリレンダリング することができます。
代わりに、
export const prerender = true
を最上位(root)の+layout.js
または+layout.server.js
に設定し、明示的にプリレンダリングしないものとしてマークされたページを除き、全てをプリレンダリングできます:
Page options • Docs • SvelteKit https://kit.svelte.jp/docs/page-options#prerender
SvelteKit を static site generator (SSG) として使用するには、
adapter-static
を使用します。この adapter はサイト全体を静的なファイルのコレクションとしてプリレンダリングします。もし、一部のページのみをプリレンダリングして他のページは動的にサーバーでレンダリングしたい場合、別の adapter と
prerender
オプション を組み合わせて使用する必要があります。
npm i -D @sveltejs/adapter-static
を実行してインストールし、svelte.config.js
にこの adapter を追加します:〜
そして
prerender
オプションを最上位のレイアウト(root layout)に追加します:〜
Static site generation • Docs • SvelteKit https://kit.svelte.jp/docs/adapter-static
export const trailingSlash = 'always';
trailingSlash
デフォルトでは、SvelteKit は URL から末尾のスラッシュ(trailing slash)を取り除きます。/about/
にアクセスすると、/about
へのリダイレクトをレスポンスとして受け取ることになります。この動作は、trailingSlash
オプションで変更することができます。指定できる値は'never'
(デフォルト)、'always'
、'ignore'
です。他のページオプションと同様に、
+layout.js
や+layout.server.js
からこの値をエクスポートすると、すべての子のページに適用されます。+server.js
ファイルからその設定をエクスポートすることもできます。
このオプションは プリレンダリング にも影響します。
trailingSlash
がalways
の場合/about
というルート(route)はabout/index.html
ファイルとなり、それ以外の場合はabout.html
が作成され、静的な Web サーバの慣習を反映したものになります。
Page options • Docs • SvelteKit https://kit.svelte.jp/docs/page-options#trailingslash
Sass(SCSS)を使えるようにする
Preprocessors
プリプロセッサ(Preprocessors)は、.svelte
ファイルをコンパイラに渡す前に変換します。例えば、.svelte
ファイルに TypeScript と PostCSS が使用されている場合、それを最初に JavaScript と CSS に変換し、Svelte コンパイラが処理できるようにしなければなりません。
vitePreprocess
vite-plugin-svelte
にはvitePreprocess
という機能があり、Vite をプリプロセスに用いることができます。これによって Vite が扱える言語フレーバー (TypeScript、PostCSS、SCSS、Less、Stylus、SugarSS) の処理が可能になります。便宜上、これは@sveltejs/kit/vite
パッケージから再エクスポートされています。プロジェクトに TypeScript を設定すると、これがデフォルトで含まれるようになります:
import { vitePreprocess } from '@sveltejs/kit/vite';
export default {
preprocess: [vitePreprocess()]
};
svelte-preprocess
svelte-preprocess
は、Pug、Babel、global styles のサポートなど、vitePreprocess
には無い機能があります。しかし、vitePreprocess
はより速く、設定が少ないため、デフォルトではvitePreprocess
が使用されます。
Integrations • Docs • SvelteKit https://kit.svelte.jp/docs/integrations#preprocessors
Tailwind CSS を追加する
Svelte Adders は、Tailwind、PostCSS、Storybook、Firebase、GraphQL、mdsvexなど、様々な複雑なインテグレーションを1つのコマンドでセットアップできるようにしてくれます。Svelte と SvelteKitで利用可能なテンプレート、コンポーネント、ツールの全ての一覧については、 sveltesociety.dev をご覧ください。
Integrations • Docs • SvelteKit https://kit.svelte.jp/docs/integrations#adders
Template - Svelte Society https://sveltesociety.dev/templates#adders
This adder's codename is
tailwindcss
, and can be used like so:
npx svelte-add@latest tailwindcss
svelte-add/tailwindcss: Add Tailwind CSS to your Svelte project https://github.com/svelte-add/tailwindcss
postcss, autoprefixer も合わせて追加される模様
...
"devDependencies": {
...
"postcss": "^8.4.14",
"postcss-load-config": "^4.0.1",
"autoprefixer": "^10.4.7",
"tailwindcss": "^3.1.5"
},
...
postcss.config.cjs
, tailwind.config.cjs
, svelte.config.js
, app.postcss
, +layout.svelte
の追加・更新も
↑ の方が楽そう
書籍の参考は ↓
Install Tailwind CSS with SvelteKit - Tailwind CSS https://tailwindcss.com/docs/guides/sveltekit
CSS リセット的なのも含まれてるのね
Preflight - Tailwind CSS https://tailwindcss.com/docs/preflight
Preflight
An opinionated set of base styles for Tailwind projects.Overview
Built on top of modern-normalize, Preflight is a set of base styles for Tailwind projects that are designed to smooth over cross-browser inconsistencies and make it easier for you to work within the constraints of your design system.Tailwind automatically injects these styles when you include
@tailwind base
in your CSS:
Preflight - Tailwind CSS https://tailwindcss.com/docs/preflight#extending-preflight
Preflightの上に独自のベーススタイルを追加したい場合は、
@layer base
ディレクティブを使用してCSSに追加するだけです。
@tailwind base;
@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
h3 {
@apply text-lg;
}
a {
@apply text-blue-600 underline;
}
}
@tailwind components;
@tailwind utilities;
コンポーネント化する
コンポーネントファイル src/lib/Header.svelte
を作成して、import
して、タグを追加する
<script>
import Header from "$lib/Header.svelte"
</script>
<Header />
...
$lib
これはsrc/lib
(またはconfig.kit.files.lib
に指定されたディレクトリ) へのシンプルなエイリアスです。../../../../
のようなナンセンスなことをせずに、共通のコンポーネントやユーティリティモジュールにアクセスすることができます。
Modules • Docs • SvelteKit https://kit.svelte.jp/docs/modules#$lib
コンポーネント内の画像のパスを修正する
<script>
import { assets } from "$app/paths"
</script>
<header>
<!-- <img src="logo.png" alt=""> 修正前 -->
<img src="{assets}/logo.png" alt="">
...
$app/paths
assets
config.kit.paths.assets
にマッチする絶対パス(absolute path)です。
Modules • Docs • SvelteKit https://kit.svelte.jp/docs/modules#$app-paths
デフォルトでは /static
ディレクトリ
svelte.config.js
でカスタマイズできるらしい
const config = {
kit: {
// ...
paths: {
assets: '/static', // ここでカスタマイズ可能
},
// ...
},
};
あ、
config.kit.paths.assets
にマッチする絶対パス(absolute path)です。
ってそーゆー意味か
レイアウト機能
複数のページで共通の要素を1つのファイルにまとめる
-
/routes/+layout.svelte
にまとめる - 各ページの要素を表示するのは
<slot />
<script>
import Header from "$lib/header/Header.svelte"
import Footer from "$lib/footer/Footer.svelte"
import '../app.postcss';
</script>
<Header />
<main>
<slot /><!-- 各ページの要素はここに入る -->
</main>
<Footer />
+layout
多くのアプリでは、トップレベルのナビゲーションやフッターのように 全ての ページで表示されるべき要素があります。全ての
+page.svelte
にそれらを繰り返し配置する代わりに、レイアウト(layouts) に配置することができます。
+layout.svelte
全てのページに適用するレイアウトを作成するには、src/routes/+layout.svelte
というファイルを作成します。
唯一の要求事項は、コンポーネントにページコンテンツのための
<slot>
を含めることです。
ルーティング • Docs • SvelteKit https://kit.svelte.jp/docs/routing#layout
レイアウトは ネスト させることができます。例えば、単一の
/settings
ページだけでなく、/settings/profile
や/settings/notifications
のような共有のサブメニューを持つネストしたページがあるとします
/settings
配下のページにのみ適用されるレイアウトを作成することができます (トップレベルの nav を持つ最上位のレイアウト(root layout)を継承しています):
src/routes/settings/+layout.svelte
...
デフォルトでは、各レイアウトはその上にあるレイアウトを継承します。そうしたくない場合は、advanced layouts が役に立つでしょう。
各ページで個別のタイトルを設定する
<svelte:head>
<!-- ページごとの <head> の内容 -->
<title>トップページ</title>
</svelte:head>
<svelte:head>...</svelte:head>
この要素を使うと、document.head
に要素を挿入することができます。
この要素はコンポーネントのトップレベルにのみ置くことができ、ブロックや要素の中に置くことはできません。
Docs • Svelte https://svelte.jp/docs#template-syntax-svelte-head
Route announcements
旧来のサーバーレンダリングアプリケーションでは、全てのナビゲーション (例えば、
<a>
タグをクリックするなど) で、ページのフルリロードを引き起こします。これが起こると、スクリーンリーダーやその他の支援技術が新しいページのタイトルを読み上げ、それによってユーザーはページが変更されたことを理解します。SvelteKit では、ページ間のナビゲーションではページのリロードが発生しないため (クライアントサイドルーティングとして知られる)、SvelteKit はナビゲーションごとに新しいページ名が読み上げられるようにライブリージョンをページに注入します。これは、
<title>
要素を検査することで、アナウンスするページ名を決定します。この動作のために、アプリの全ページにユニークで説明的なタイトルを付けるべきです。SvelteKit では、各ページに
<svelte:head>
要素を配置することでこれを行うことができます:
これにより、スクリーンリーダーやその他の支援技術が、ナビゲーション後に新しいページを識別することができるようになります。説明的なタイトルを提供することは、SEO にとっても重要なことです。
Accessibility • Docs • SvelteKit https://kit.svelte.jp/docs/accessibility#route-announcements
<title> と <meta>
全てのページで、よく練られたユニークな
<title>
と<meta name="description">
を<svelte:head>
の内側に置くべきです。
説明的な title と description の書き方に関するガイダンスと、検索エンジンにとってわかりやすいコンテンツを作るためのその他の方法については、Google の Lighthouse SEO audits のドキュメントで見つけることができます。
SEO • Docs • SvelteKit https://kit.svelte.jp/docs/seo#manual-setup-title-and-meta
SvelteKit は、アプリにアクセシブルなプラットフォームをデフォルトで提供するよう努めています。Svelte の コンパイル時のアクセシビリティチェック(compile-time accessibility checks) は、あなたがビルドする SvelteKit アプリケーションにも適用されます。
ここでは、SvelteKit の組み込みのアクセシビリティ(accessibility)機能がどのように動作するか、そしてこれらの機能が可能な限りうまく動作するようにするために必要なことについて説明します。
Accessibility • Docs • SvelteKit https://kit.svelte.jp/docs/accessibility
アクセシビリティ(a11y と略されます)を正しく理解することは容易ではありませんが、Svelte は、アクセシブルではないマークアップを書くとコンパイル時に警告してくれます。しかし、多くのアクセシビリティの問題は、他の自動化されたツールを使用したり、手動でアプリケーションをテストするなど、実行時に特定できることを忘れないでください。
Docs • Svelte https://svelte.jp/docs#accessibility-warnings
SEO で最も重要なのは、高品質なコンテンツを作ること、そしてそれが web 上で広くリンクされることです。しかし、ランクが高いサイトを構築するためにいくつか技術的に考慮すべきこともあります。
SEO • Docs • SvelteKit https://kit.svelte.jp/docs/seo
条件分岐
{#if 式}...{/if}
{#if 式}...{:else if 式}...{/if}
{#if 式}...{:else}...{/if}
Docs • Svelte https://svelte.jp/docs#template-syntax-if
表示されているページの URL 情報を取得する
$app/store
モジュールの $page
ストアを使用する
<script>
import { page } from "$app/stores"
console.log($page)
</script>
...
{#if $page.url.pathname === '/'}
<!-- トップページの場合 -->
{:else}
<!-- その他の場合 -->
{/if}
page
ページのデータの値を含む読み取り可能なストア(readable store)です。
Modules • Docs • SvelteKit https://kit.svelte.jp/docs/modules#$app-stores-page
Types • Docs • SvelteKit https://kit.svelte.jp/docs/types#public-types-page
- openBD | 書誌情報・書影を自由に https://openbd.jp/
- OpenBD 書誌APIデータ仕様 (v1) https://openbd.jp/spec/
Web API からデータを取得して表示する
<script>
const API = 'https://api.example.com/get?isbn='
const ISBN = '4621066080'
const getBooks = async () => {
let res = await fetch(API + ISBN)
return res.json()
}
</script>
<div>
{#await getBooks()}
<!-- pending(保留中) -->
<p>データを取得しています…</p>
{:then books}
<!-- fulfilled(成功) -->
{#each books as book}
<h3>{book.summary.title}</h3>
<img src={book.summary.cover} alt="">
{/each}
{:catch error}
<!-- rejected(失敗) -->
<p>データの取得に失敗しました</p>
{/await}
</div>
{#await ...}
await ブロックを使用すると、Promise が取りうる 3 つの状態(pending(保留中)、fulfilled(成功)、rejected(失敗))に分岐できます。
{#await promise}
<!-- promise is pending -->
<p>waiting for the promise to resolve...</p>
{:then value}
<!-- promise was fulfilled -->
<p>The value is {value}</p>
{:catch error}
<!-- promise was rejected -->
<p>Something went wrong: {error.message}</p>
{/await}
Docs • Svelte https://svelte.jp/docs#template-syntax-await
コンポーネント間でデータを受け渡す
// コンポーネント側
<script>
// 受け渡される変数を export する
export let isbn = ''
...
</script>
...
// コンポーネントを使用する側
<script>
import Booklist from './Booklist.svelte'
const ISBN = '4621066080'
</script>
...
// コンポーネントの変数に値を渡す
<Booklist isbn={ISBN} />
- export creates a component prop
Svelte では、 変数の宣言を プロパティ(prop)としてマークするために
export
キーワードを使います。これによってそのコンポーネントを使用する際にその変数にアクセスできるようになります(より詳しい情報は 属性とプロパティを見てください)。
<script>
export let foo;
// プロパティとして渡された変数は、
// 即座に使用可能になります
console.log({ foo });
</script>
const
やclass
、function
をエクスポートすると、コンポーネントの外からは読み取り専用になります。
Docs • Svelte https://svelte.jp/docs#component-format-script-1-export-creates-a-component-prop
Docs • Svelte https://svelte.jp/docs#template-syntax-attributes-and-props
Svelte の store 機能を使う
import { readable } from "svelte/store"
export const favoriteBooks = readable('4621066080')
<script>
import Booklist from "./Booklist.svelte"
import { favoriteBooks } from "$lib/store.js"
const ISBN = $favoriteBooks
</script>
svelte/store
モジュールは、readable、writable、 derived ストアを作成するための関数をエクスポートします。
Docs • Svelte https://svelte.jp/docs#run-time-svelte-store
slug パラメータを使ったルーティング
-
[slug]/+page.js
のload()
がslug
の値を使って何らかの処理をしてreturn
する -
[slug]/+page.svelte
が[slug]/+page.js
からの戻り値をdata
プロパティとして受け取って何らかの処理をする
<script>
// +page.js load() の戻り値
export let data
import Booklist from "../Booklist.svelte"
</script>
<div>
<h2>オススメ書籍の詳細情報ページ</h2>
<Booklist isbn={data.isbn} />
</div>
import { error } from "@sveltejs/kit"
import { favoriteBooks } from "$lib/store.js"
// { params } に URL の [slug] の値が渡される
export function load({ params }) {
let isbn = ''
// favoriteBooksストアが更新されたら、その値を取得してコールバック関数を実行する
favoriteBooks.subscribe(val => {
isbn = val
})
if (isbn.includes(params.slug)) {
// load() の戻り値は +page.svelte で data プロパティとして使用できる
return { isbn: params.slug }
}
// [slug] に該当するデータがない場合、404 エラーを返す
throw error(404, 'Not found')
}
SvelteKit の中心は、 ファイルシステムベースのルーター です。アプリのルート(routes) — 例えばユーザーがアクセスできる URL パス — は、コードベースのディレクトリによって定義されます:
src/routes/blog/[slug]
は パラメータslug
を使ったルート(route)を作成します。パラメータは、ユーザーからのリクエストが/blog/hello-world
のようなページに行われた場合に、動的にデータを読み込むために使用することができます
ルート(route)のディレクトリはそれぞれ1つ以上の ルートファイル(route files) を格納します。ルートファイル(route files)には
+
という接頭辞が付いているので、それで見分けることができます。
ルーティング • Docs • SvelteKit https://kit.svelte.jp/docs/routing
+page.svelte
+page.svelte
コンポーネントはアプリのページを定義します。デフォルトでは、ページは最初のリクエストではサーバー (SSR) でレンダリングされ、その後のナビゲーションではブラウザ (CSR) でレンダリングされます。
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<h1>{data.title}</h1>
<div>{@html data.content}</div>
ルーティング • Docs • SvelteKit https://kit.svelte.jp/docs/routing#page-page-svelte
+page.js
ページではたびたび、レンダリングの前になんらかのデータを読み込む必要があります。これに対応するため、
load
関数をエクスポートする+page.js
モジュールを追加しています:
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
throw error(404, 'Not found');
}
この関数は
+page.svelte
とともに実行されます。サーバーサイドレンダリング中はサーバーで実行され、クライアントサイドナビゲーション中はブラウザで実行されます。API の詳細は load をご参照ください。
+page.js
では、load
だけでなくページの動作(behaviour)を設定するための値をエクスポートすることができます:
export const prerender = true
またはfalse
または'auto'
export const ssr = true
またはfalse
export const csr = true
またはfalse
これらに関するより詳しい情報は page options をご覧ください。
ルーティング • Docs • SvelteKit https://kit.svelte.jp/docs/routing#page-page-js
Loading data • Docs • SvelteKit https://kit.svelte.jp/docs/load
+page.svelte
コンポーネント (と+layout.svelte
コンポーネント) をレンダリングする前に、データを取得する必要があるケースが多いでしょう。load
関数を定義することでこれができるようになります。
+page.svelte
ファイルは、load
関数をエクスポートする+page.js
という兄弟ファイルを持つことができ、load
関数の戻り値はpage
でdata
プロパティを介して使用することができます。
+page.js
ファイルのload
関数はサーバーでもブラウザでも実行されます。load
関数を常にサーバーで実行させたければ (例えばプライベートな環境変数を使用していたり、データベースにアクセスする場合など)、代わりに+page.server.js
に load 関数を置くとよいでしょう。
$page.data
+page.svelte
コンポーネントとその上の各+layout.svelte
コンポーネントは、自身のデータとその親の全てのデータにアクセスすることができます。場合によっては、その逆も必要かもしれません — 親レイアウトからページのデータや子レイアウトのデータにアクセスする必要があるかもしれません。例えば、最上位のレイアウト(root layout)から、
+page.js
や+page.server.js
のload
関数から返されたtitle
プロパティにアクセスしたい場合があるでしょう。これは$page.data
で行うことができます:
<script>
import { page } from '$app/stores';
</script>
<svelte:head>
<title>{$page.data.title}</title>
</svelte:head>
Errors
load
中にエラーがスローされた場合、最も近くにある+error.svelte
がレンダリングされます。想定されるエラーには、@sveltejs/kit
からインポートできるerror
ヘルパーを使用して HTTP ステータスコードとオプションのメッセージを指定できます:
Page options • Docs • SvelteKit https://kit.svelte.jp/docs/page-options
デフォルトでは、SvelteKit はどのコンポーネントも最初はサーバーでレンダリング (または プリレンダリング) し、それを HTML としてクライアントに送信します。その後、ブラウザ上でコンポーネントを再度レンダリングし、ハイドレーション(hydration)と呼ばれるプロセスでそれをインタラクティブなものにします。
それぞれオプションを
+page.js
や+page.server.js
からエクスポートすることでページごとに、または共有の+layout.js
や+layout.server.js
を使用してページグループごとに制御することが可能です。
〜
例えば、プリレンダリングをアプリ全体で有効にし、それから動的にレンダリングする必要があるページではそれを無効にすることができます。
アプリの様々な領域でこれらのオプションをうまく組み合わせることができます。例えば、マーケティングページは高速化を最大限にするためにプリレンダリングし、動的なページは SEO とアクセシビリティのためにサーバーでレンダリングし、管理者用のセクションはクライアントのみでレンダリングするようにして SPA にすることができます。このように、SvelteKit はとても万能で多くの用途にお使いいただけます。
テキスト中の \n
を <br>
に変換して改行して表示する
...
{@html `${content.Text}`.replace(/\n/g, '<br>')}
...
{@html ...}
テキスト式(
{式}
の構文)では、<
や>
のような文字はエスケープされますが、HTML 式ではエスケープされません。
式は単独で正しい HTML になっている必要があります。
{@html "<div>"}content{@html "</div>"}
は</div>
の部分が正しい HTML ではないため、動作しません。また、Svelteコードをコンパイルすることもできません。
Svelte は HTML を挿入する前に式をサニタイズしません。データが信頼できないソースからのものである場合は自分でサニタイズする必要があります。そうしないと、ユーザーを XSS の脆弱性にさらしてしまいます。
Docs • Svelte https://svelte.jp/docs#template-syntax-html
transition:
エフェクトを使って、要素が変化する際(この場合、ページの表示・非表示時)に画像の大きさをアニメーションで変化させる
<img src={book.summary.cover} alt="" transitoin:scale>
<img src={book.summary.cover} alt="" in:scale>
<img src={book.summary.cover} alt="" out:scale>
svelte/transition
モジュールは7つの関数をエクスポートします。fade
、blur
、fly
、slide
、scale
、draw
、crossfade
の7つの関数をエクスポートします。これらは Sveltetransitions
で使用します。
scale
要素の opacity と scale をアニメーション化します。
Docs • Svelte https://svelte.jp/docs#run-time-svelte-transition
Docs • Svelte https://svelte.jp/docs#template-syntax-element-directives-transition-fn
transition:
に似ていますが、in:
は DOM に入る要素だけに、out:
は出る要素だけに適用されます。
Docs • Svelte https://svelte.jp/docs#template-syntax-element-directives-in-fn-out-fn
要素をクリック時に何らかの処理を行う
<script>
const hoge = () => {
...
}
</script>
<button on:click="{() => hoge()}">何らか</button>
Element directives
要素には、属性と同じようにディレクティブを持たせることができます。これは何らかの方法で要素の動作を制御します。
DOM イベントをリッスンするには
on:
ディレクティブを使用します。
ハンドラはパフォーマンスを低下させることなくインラインで宣言できます。 属性と同様、ディレクティブの値はシンタックスハイライトのために引用符で囲むことができます。
Docs • Svelte https://svelte.jp/docs#template-syntax-element-directives
ひとまず done
style ほぼ付いてないのでそのうち