Open52

Svelte 学習帖

hagiwarahagiwara

プロジェクトを静的ファイルとして出力できるように設定する

+layour.js
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

hagiwarahagiwara

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

hagiwarahagiwara
+layout.js
export const trailingSlash = 'always';

trailingSlash
デフォルトでは、SvelteKit は URL から末尾のスラッシュ(trailing slash)を取り除きます。/about/ にアクセスすると、/about へのリダイレクトをレスポンスとして受け取ることになります。この動作は、trailingSlash オプションで変更することができます。指定できる値は 'never' (デフォルト)、'always''ignore' です。

他のページオプションと同様に、+layout.js+layout.server.js からこの値をエクスポートすると、すべての子のページに適用されます。+server.js ファイルからその設定をエクスポートすることもできます。

このオプションは プリレンダリング にも影響します。trailingSlashalways の場合 /about というルート(route)は about/index.html ファイルとなり、それ以外の場合は about.html が作成され、静的な Web サーバの慣習を反映したものになります。

Page options • Docs • SvelteKit https://kit.svelte.jp/docs/page-options#trailingslash

hagiwarahagiwara

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 を設定すると、これがデフォルトで含まれるようになります:

svelte.config.js
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

hagiwarahagiwara

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

hagiwarahagiwara

postcss, autoprefixer も合わせて追加される模様

package.json
	...
	"devDependencies": {
		...
		"postcss": "^8.4.14",
		"postcss-load-config": "^4.0.1",
		"autoprefixer": "^10.4.7",
		"tailwindcss": "^3.1.5"
	},
        ...
hagiwarahagiwara

postcss.config.cjs, tailwind.config.cjs, svelte.config.js, app.postcss, +layout.svelte の追加・更新も

hagiwarahagiwara

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:

hagiwarahagiwara

Preflight - Tailwind CSS https://tailwindcss.com/docs/preflight#extending-preflight

Preflightの上に独自のベーススタイルを追加したい場合は、@layer base ディレクティブを使用してCSSに追加するだけです。

app.postcss
@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;
hagiwarahagiwara

コンポーネント化する

コンポーネントファイル src/lib/Header.svelte を作成して、import して、タグを追加する

+page.svelte
<script>
  import Header from "$lib/Header.svelte"
</script>

<Header />

...
hagiwarahagiwara

$lib
これは src/lib (または config.kit.files.lib に指定されたディレクトリ) へのシンプルなエイリアスです。../../../../ のようなナンセンスなことをせずに、共通のコンポーネントやユーティリティモジュールにアクセスすることができます。

Modules • Docs • SvelteKit https://kit.svelte.jp/docs/modules#$lib

hagiwarahagiwara

コンポーネント内の画像のパスを修正する

src/Header.svelte
<script>
  import { assets } from "$app/paths"
</script>

<header>
  <!-- <img src="logo.png" alt=""> 修正前 -->
  <img src="{assets}/logo.png" alt="">
  ...
hagiwarahagiwara

デフォルトでは /static ディレクトリ
svelte.config.js でカスタマイズできるらしい

svelte.config.js
const config = {
  kit: {
    // ...
    paths: {
      assets: '/static', // ここでカスタマイズ可能
    },
    // ...
  },
};
hagiwarahagiwara

あ、

config.kit.paths.assets にマッチする絶対パス(absolute path)です。

ってそーゆー意味か

hagiwarahagiwara

レイアウト機能
複数のページで共通の要素を1つのファイルにまとめる

  • /routes/+layout.svelte にまとめる
  • 各ページの要素を表示するのは <slot />
/routes/+layout.svelte
<script>
  import Header from "$lib/header/Header.svelte"
  import Footer from "$lib/footer/Footer.svelte"
  import '../app.postcss';
</script>

<Header />

<main>
  <slot /><!-- 各ページの要素はここに入る -->
</main>

<Footer />
hagiwarahagiwara

+layout

多くのアプリでは、トップレベルのナビゲーションやフッターのように 全ての ページで表示されるべき要素があります。全ての +page.svelte にそれらを繰り返し配置する代わりに、レイアウト(layouts) に配置することができます。

+layout.svelte
全てのページに適用するレイアウトを作成するには、src/routes/+layout.svelte というファイルを作成します。

唯一の要求事項は、コンポーネントにページコンテンツのための <slot> を含めることです。

ルーティング • Docs • SvelteKit https://kit.svelte.jp/docs/routing#layout

hagiwarahagiwara

レイアウトは ネスト させることができます。例えば、単一の /settings ページだけでなく、/settings/profile/settings/notifications のような共有のサブメニューを持つネストしたページがあるとします

/settings 配下のページにのみ適用されるレイアウトを作成することができます (トップレベルの nav を持つ最上位のレイアウト(root layout)を継承しています):

src/routes/settings/+layout.svelte
...

デフォルトでは、各レイアウトはその上にあるレイアウトを継承します。そうしたくない場合は、advanced layouts が役に立つでしょう。

hagiwarahagiwara

各ページで個別のタイトルを設定する

<svelte:head>
  <!-- ページごとの <head> の内容 -->
  <title>トップページ</title>
</svelte:head>
hagiwarahagiwara

<svelte:head>...</svelte:head>
この要素を使うと、 document.head に要素を挿入することができます。

この要素はコンポーネントのトップレベルにのみ置くことができ、ブロックや要素の中に置くことはできません。

Docs • Svelte https://svelte.jp/docs#template-syntax-svelte-head

hagiwarahagiwara

Route announcements

旧来のサーバーレンダリングアプリケーションでは、全てのナビゲーション (例えば、<a> タグをクリックするなど) で、ページのフルリロードを引き起こします。これが起こると、スクリーンリーダーやその他の支援技術が新しいページのタイトルを読み上げ、それによってユーザーはページが変更されたことを理解します。

SvelteKit では、ページ間のナビゲーションではページのリロードが発生しないため (クライアントサイドルーティングとして知られる)、SvelteKit はナビゲーションごとに新しいページ名が読み上げられるようにライブリージョンをページに注入します。これは、<title> 要素を検査することで、アナウンスするページ名を決定します。

この動作のために、アプリの全ページにユニークで説明的なタイトルを付けるべきです。SvelteKit では、各ページに <svelte:head> 要素を配置することでこれを行うことができます:

これにより、スクリーンリーダーやその他の支援技術が、ナビゲーション後に新しいページを識別することができるようになります。説明的なタイトルを提供することは、SEO にとっても重要なことです。

Accessibility • Docs • SvelteKit https://kit.svelte.jp/docs/accessibility#route-announcements

hagiwarahagiwara

<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

hagiwarahagiwara

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

hagiwarahagiwara

表示されているページの URL 情報を取得する

$app/store モジュールの $page ストアを使用する

Header.svelte
<script>
  import { page } from "$app/stores"

  console.log($page)
</script>

...

  {#if $page.url.pathname === '/'}
    <!-- トップページの場合 -->
  {:else}
    <!-- その他の場合 -->
  {/if}

hagiwarahagiwara

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>
hagiwarahagiwara

{#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

hagiwarahagiwara

コンポーネント間でデータを受け渡す

Booklist.svelte
// コンポーネント側

<script>
  // 受け渡される変数を export する
  export let isbn = ''

   ...
</script>

...
+page.svelte
// コンポーネントを使用する側
<script>
  import Booklist from './Booklist.svelte'

  const ISBN = '4621066080'
</script>

...

// コンポーネントの変数に値を渡す
<Booklist isbn={ISBN} />

hagiwarahagiwara
  1. export creates a component prop

Svelte では、 変数の宣言を プロパティ(prop)としてマークするために export キーワードを使います。これによってそのコンポーネントを使用する際にその変数にアクセスできるようになります(より詳しい情報は 属性とプロパティを見てください)。

<script>
	export let foo;

	// プロパティとして渡された変数は、
	// 即座に使用可能になります
	console.log({ foo });
</script>

constclassfunction をエクスポートすると、コンポーネントの外からは読み取り専用になります。

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

hagiwarahagiwara

Svelte の store 機能を使う

src/lib/store.js
import { readable } from "svelte/store"

export const favoriteBooks = readable('4621066080')
+page.svelte
<script>
  import Booklist from "./Booklist.svelte"
  import { favoriteBooks } from "$lib/store.js"

  const ISBN = $favoriteBooks
</script>
hagiwarahagiwara

slug パラメータを使ったルーティング

  1. [slug]/+page.jsload()slug の値を使って何らかの処理をして return する
  2. [slug]/+page.svelte[slug]/+page.js からの戻り値を data プロパティとして受け取って何らかの処理をする
[slug]/+page.svelte
<script>
  // +page.js load() の戻り値
  export let data

  import Booklist from "../Booklist.svelte"
</script>

<div>
  <h2>オススメ書籍の詳細情報ページ</h2>
  <Booklist isbn={data.isbn} />
</div>
[slug]/+page.js
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')
}
hagiwarahagiwara

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

hagiwarahagiwara

+page.svelte

+page.svelte コンポーネントはアプリのページを定義します。デフォルトでは、ページは最初のリクエストではサーバー (SSR) でレンダリングされ、その後のナビゲーションではブラウザ (CSR) でレンダリングされます。

src/routes/blog/[slug]/+page.svelte
<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

hagiwarahagiwara

+page.js

ページではたびたび、レンダリングの前になんらかのデータを読み込む必要があります。これに対応するため、load 関数をエクスポートする +page.js モジュールを追加しています:

src/routes/blog/[slug]/+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

hagiwarahagiwara

Loading data • Docs • SvelteKit https://kit.svelte.jp/docs/load

+page.svelte コンポーネント (と +layout.svelte コンポーネント) をレンダリングする前に、データを取得する必要があるケースが多いでしょう。load 関数を定義することでこれができるようになります。

+page.svelte ファイルは、load 関数をエクスポートする +page.js という兄弟ファイルを持つことができ、load 関数の戻り値は pagedata プロパティを介して使用することができます。

+page.js ファイルの load 関数はサーバーでもブラウザでも実行されます。load 関数を常にサーバーで実行させたければ (例えばプライベートな環境変数を使用していたり、データベースにアクセスする場合など)、代わりに +page.server.js に load 関数を置くとよいでしょう。


$page.data

+page.svelte コンポーネントとその上の各 +layout.svelte コンポーネントは、自身のデータとその親の全てのデータにアクセスすることができます。

場合によっては、その逆も必要かもしれません — 親レイアウトからページのデータや子レイアウトのデータにアクセスする必要があるかもしれません。例えば、最上位のレイアウト(root layout)から、+page.js+page.server.jsload 関数から返された title プロパティにアクセスしたい場合があるでしょう。これは $page.data で行うことができます:

src/routes/+layout.svelte
<script>
  import { page } from '$app/stores';
</script>

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

Errors

load 中にエラーがスローされた場合、最も近くにある +error.svelte がレンダリングされます。想定されるエラーには、@sveltejs/kit からインポートできる error ヘルパーを使用して HTTP ステータスコードとオプションのメッセージを指定できます:

hagiwarahagiwara

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 はとても万能で多くの用途にお使いいただけます。

hagiwarahagiwara

テキスト中の \n<br> に変換して改行して表示する

...
  {@html `${content.Text}`.replace(/\n/g, '<br>')}
...
hagiwarahagiwara

{@html ...}

テキスト式({式} の構文)では、 <> のような文字はエスケープされますが、HTML 式ではエスケープされません。

式は単独で正しい HTML になっている必要があります。{@html "<div>"}content{@html "</div>"}</div> の部分が正しい HTML ではないため、動作しません。また、Svelteコードをコンパイルすることもできません。

Svelte は HTML を挿入する前に式をサニタイズしません。データが信頼できないソースからのものである場合は自分でサニタイズする必要があります。そうしないと、ユーザーを XSS の脆弱性にさらしてしまいます。

Docs • Svelte https://svelte.jp/docs#template-syntax-html

hagiwarahagiwara

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>
hagiwarahagiwara

svelte/transition モジュールは7つの関数をエクスポートします。fadeblurfly slidescaledrawcrossfade の7つの関数をエクスポートします。これらは Svelte transitions で使用します。

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

hagiwarahagiwara

要素をクリック時に何らかの処理を行う

<script>
  const hoge = () => {
    ...
  }
</script>

<button on:click="{() => hoge()}">何らか</button>
hagiwarahagiwara

Element directives

要素には、属性と同じようにディレクティブを持たせることができます。これは何らかの方法で要素の動作を制御します。

DOM イベントをリッスンするには on: ディレクティブを使用します。

ハンドラはパフォーマンスを低下させることなくインラインで宣言できます。 属性と同様、ディレクティブの値はシンタックスハイライトのために引用符で囲むことができます。

Docs • Svelte https://svelte.jp/docs#template-syntax-element-directives