Closed41

Sapper のプロジェクトを SvelteKit にマイグレーションするか

package.json - type:module

type: module を追加する

 {
+  "type": "module",
 }

package.json - dependencies

express または polkadependencies から取り除く。
また sirvcompression などのミドルウェアも同様。

npm uninstall polka sirc compression
# TypeScript only
npm uninstall @types/polka @types/compression --dev

package.json dev-dependencies

sapper を取り除いて @sveltejs/kit に置き換える。

npm uninstall sapper --dev
npm install @sveltejs/kit --dev

package.json - npm-scripts

npm-scripts を sapper から svelte-kit を使うように置き換える。
Sapper では sapper buildsapper export のようにコマンドでビルドを切り替えていたが SvelteKit では adapter と呼ばれるものを切り替えてビルドする。

   "scripts": {
-    "dev": "sapper dev",
-    "build": "npm run build:tailwind && sapper build --legacy",
-    "export": "npm run build:tailwind && sapper export --legacy",
-    "start": "node __sapper__/build",
+    "dev": "svelte-kit dev",
+    "build": "npm run build:tailwind && svelte-kit build",
+    "start": "node build",
    }

設定ファイル

webpack.config.js または rollup.config.jssvelte.config.js に置き換える

rm rollup.config.js
touch svelte.config.js

以下の通りに記述する

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

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://github.com/sveltejs/svelte-preprocess
	// for more information about preprocessors
	preprocess: preprocess(),

	kit: {
		adapter: adapter(),

		// hydrate the <div id="svelte"> element in src/app.html
		target: '#svelte'
	}
};

export default config;

rollup.config.js には env の設定を記述していたが、 SvelteKit では Vite により .env ファイルが読み込まれるので設定ファイルに記述する必要はなさそう。

How do I use environment variables?

設定ファイル - adapter

https://zenn.dev/link/comments/e7ba4182f189bd で述べたとおり、ビルドを切り替えるために adapter を設定する。

sapper export と同等の adapter@sveltejs/adapter-staticを使用するらしい。

npm install @sveltejs/adapter-static --dev
svelte.config.js
+ import adapter from '@sveltejs/adapter-static';

 /** @type {import('@sveltejs/kit').Config} */
 const config = {
   // options passed to svelte.compile (https://svelte.dev/docs#svelte_compile)
   compilerOptions: null,
 
   // an array of file extensions that should be treated as Svelte components
   extensions: ['.svelte'],
 
   kit: {
-    adapter: null,
+    adapter: adapter(),

src/client.js

sapper.start(...) の前に記述しているロジックがある場合には __layout.svelteonMount コールバックの中に処理を移す必要がある。

src/server.js

このファイルに記述したロジックは hooks module に処理を移す。

src/service-worker.js

sapper/service-worker から import していたものを $service-worker に置き換える。

以下の変更点が存在する

  • shellbuild
  • routes → 削除
- import { timestamp, files, shell } from '@sapper/service-worker';
+ import { timestamp, files, build } from '@sapper/service-worker';

src/template.html

src/template.htmlsrc/app.html にリネームする。

mv src/template.html src/app.html

以下の記述をは削除する。

  • %sapper.base%
  • %sapper.scripts%
  • %sapper.styles%

以下の記述は置き換える

  • %sapper.head%%svelte.head%
  • %sapper.html%%svelte.body%

<div id="sapper"> は不要になった。特定の要素にマウントしたい場合には svelte.config.js ファイルの target に記述する・

src/node_modules

Sapper では内部ライブラリは src/node_modules に配置していたがこれは Vite では機能しないので src/lib に置き換える。

特に使用 src/node-modules にファイルを配置していないのであればそのまま削除して良さそう。

rm -rf src/node_modules

レイアウトファイル

以下のファイルをそれぞれリネームする。

  • _layout.svelte__layout.svelte
  • _error.svelte_error.svelte
mv src/routes/_layout.svelte src/routes/__layout.svelte
mv src/routes/_error.svelte src/routes/__error.svelte

$app/navigation

@sapper/app からインポートしている goto,prefetch,prefetchRoutes$app/navigation からインポートするように置き換える。

<script lang="ts">
-   import { goto } from '@sapper/app'
+  import { goto } from '$app/navigation'

Preload

preloadSapper において Next.jsgetInitialPropsNuxt.jsasyncData と同等の機能。

まずはじめに preloadload にリネームする。

src/routes/blog/[slug].svelte
 <script context="module" lang="ts">
-   export async function preload({ params }) {
+  export async function load({ params }) {
     const res = await this.fetch(`blog/${params.slug}.json`)
     const data = await res.json()
     if (res.status === 200) {
       const { post, contents } = data
       return { post, contents }
     } else {
       const { message } = data
       this.error(res.status, message)
     }
   }
 </script>

TypeScript

<script context="module" lang="ts">
import { load } from '@sveltejs/kit'
   export const load = async ({ params }) {
}

Pleload - APIの変更

preloadpagesession の 2つの引数を受け取っていたが、これらは1つの引数に統合されることになった。

また、this.fetchfetch を引数から受け取るように変更になった。

src/routes/blog/[slug].svelte
<script context="module" lang="ts">
-  export async function load({ params }) {
+ export async function load({ params, fetch }) {
    const page = Number(params.page)
-    const res = await this.fetch(`tags/${params.slug}/page/${page}.json`)
+   const res = await fetch(`tags/${params.slug}/page/${page}.json`) 
    const data = await res.json()
    if (res.status === 200) {
      const { tag } = data
      return { tag }
    } else {
      const { message } = data
      this.error(res.status, message)
    }
  }
</script>

また、preload ではオブジェクトをそのまま返却していたが load では props のプロパティとして返却するようになった。

src/routes/blog/[slug].svelte
<script context="module" lang="ts">
  export async function load({ params, fetch }) {
    const page = Number(params.page)
    const res = await fetch(`tags/${params.slug}/page/${page}.json`)
    const data = await res.json()
    if (res.status === 200) {
      const { tag } = data
-      return { tag }
+     return { props: tag }
    } else {
      const { message } = data
      this.error(res.status, message)
    }
  }
</script>

なお load が何も return しない場合には 404 が返される。

インターフェイス
// Declaration types for Loading
// * declarations that are not exported are for internal use

export interface LoadInput<
	PageParams extends Record<string, string> = Record<string, string>,
	Stuff extends Record<string, any> = Record<string, any>,
	Session = any
> {
	url: URL;
	params: PageParams;
	fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
	session: Session;
	stuff: Stuff;
}

export interface LoadOutput<
	Props extends Record<string, any> = Record<string, any>,
	Stuff extends Record<string, any> = Record<string, any>
> {
	status?: number;
	error?: string | Error;
	redirect?: string;
	props?: Props;
	stuff?: Stuff;
	maxage?: number;
}

queryurl.searchParams になってた

Preload - this の廃止

load 内では this は使われなくなった。

this.error,this.redirect は代わりに return するオブジェクトのプロパティとして指定するようになった。

src/routes/blog/[slug].svelte
<script context="module" lang="ts">
  export async function load({ params, fetch }) {
    const page = Number(params.page)
    const res = await fetch(`tags/${params.slug}/page/${page}.json`)
    const data = await res.json()
    if (res.status === 200) {
      const { tag } = data
      return { props: tag }
    } else {
      const { message } = data
-      this.error(res.status, message)
+     return { 
+       status: res.status,
+       error: new Error(message) 
    }
  }
</script>
src/routes/index.svelte
<script context="module">
  export function load() {
-    return this.redirect(302, 'blog')
+    return {
+      status: 302,
+      redirect: 'blog',
+    }
  }
</script>

Stores

Sapper では page,sessionなどのは @sapper/app から stores をインポートしてそれを関数呼び出しして取り出していた。

import { stores } from '@sapper/app';
const { preloading, page, session } = stores();

SvelteKit では $app/stores から直接インポートするようになった。また preloadingnavigating に置き換えられた。

import { getStores, navigating, page, session } from '$app/stores';
const stores = getStores()

Routing

正規表現ベースのルーティングは廃止になった。代わりにFallthrough routesを使う

<a> タグ

  • rel="prefetch" or sapper:prefetchsveltekit:prefetch
  • sapper:noscrollsveltekit:noscroll

とりあえずここまでで動かしてみる

 npm run dev

> TODO@0.0.1 dev
> svelte-kit dev

Malformed svelte.config.js
Error [ERR_REQUIRE_ESM]: require() of ES Module /sapper-blog-app/svelte.config.js from /sapper-blog-app/@root not supported.
Instead change the require of svelte.config.js in /sapper-blog-app/@root to a dynamic import() which is available in all CommonJS modules.

require を使うとだめ?

postcss.config.jstailwind.config.js のような Commonjs として扱うファイルは拡張子を .cjs に変換する必要がある

sapper-google-analytics が動かないので一旦消す

.gitignore に以下を追加する必要があった

/.svelte-kit

また別のエラーが

Cannot read properties of undefined (reading 'end')
TypeError: Cannot read properties of undefined (reading 'end')
    at get (/sapper-blog-app/src/routes/blog/index.json.ts:8:10)
    at async render_endpoint (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:94:19)
    at async resolve (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1792:10)
    at async respond (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1769:10)
    at async fetch (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:980:23)
    at async load (index.svelte:3:22)
    at async load_node (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1114:12)
    at async respond$1 (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1321:15)
    at async render_page (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1507:19)
    at async resolve (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1793:10)
Unexpected token T in JSON at position 0
SyntaxError: Unexpected token T in JSON at position 0
    at JSON.parse (<anonymous>)
    at Proxy.<anonymous> (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1077:22)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async load (index.svelte:4:28)
    at async load_node (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1114:12)
    at async respond$1 (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1321:15)
    at async render_page (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1507:19)
    at async resolve (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1793:10)
    at async respond (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:1769:10)
    at async file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/chunks/index.js:4374:24
Cannot read properties of undefined (reading 'message')
TypeError: Cannot read properties of undefined (reading 'message')
    at __error.svelte:37:17
    at Object.$$render (/sapper-blog-app/node_modules/svelte/internal/index.js:1702:22)
    at Object.default (root.svelte:43:47)
    at eval (/src/routes/__layout.svelte:71:80)
    at Object.$$render (/sapper-blog-app/node_modules/svelte/internal/index.js:1702:22)
    at root.svelte:37:45
    at $$render (/sapper-blog-app/node_modules/svelte/internal/index.js:1702:22)
    at Object.render (/sapper-blog-app/node_modules/svelte/internal/index.js:1710:26)
    at render_response (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:605:28)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
Cannot read properties of undefined (reading 'message')
TypeError: Cannot read properties of undefined (reading 'message')
    at __error.svelte:37:17
    at Object.$$render (/sapper-blog-app/node_modules/svelte/internal/index.js:1702:22)
    at Object.default (root.svelte:43:47)
    at eval (/src/routes/__layout.svelte:71:80)
    at Object.$$render (/sapper-blog-app/node_modules/svelte/internal/index.js:1702:22)
    at root.svelte:37:45
    at $$render (/sapper-blog-app/node_modules/svelte/internal/index.js:1702:22)
    at Object.render (/sapper-blog-app/node_modules/svelte/internal/index.js:1710:26)
    at render_response (file:///sapper-blog-app/node_modules/@sveltejs/kit/dist/ssr.js:605:28)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
Cannot read properties of undefined (reading 'end')

server routes も修正する必要がありそう。

import type { ServerResponse } from 'http'
import type { Request } from 'polka'
import RepositoryFactory, { POST } from '../../repositories/RepositoryFactory'
const PostRepository = RepositoryFactory[POST]

export async function get(req: Request, res: ServerResponse) {
  const posts = await PostRepository.get({})
  res.end(JSON.stringify({ posts }))
}

endpoints に書き換えたらこんな感じ

import type { RequestHandler } from '@sveltejs/kit'
import RepositoryFactory, { POST } from '../../repositories/RepositoryFactory'
const PostRepository = RepositoryFactory[POST]

export const get: RequestHandler = async (req) => {
  const posts = await PostRepository.get({})
  return {
    body: {
      posts
    }
  }
}

ページ表示できた!

$page.path has been replaced by $page.url.pathname

storespage が変わっている

- $: url = `${protocol}://${$page.host}${$page.path}`
+ $: url = `${protocol}://${$page.url.host}${$page.url.pathname}`

fetch のパスの指定方法が変わってる

  export async function load({ params, fetch }) {
-    const res = await fetch(`blog/${params.slug}.json`)
+   const res = await fetch(`/blog/${params.slug}.json`)

process.env の値が取得できていない

公開する環境変数は VITE をプレフィックスにする必要がある。
その後 import.meta.env から値を取得する。

<style> タグ内で環境変数を利用するとビルド時に問題があるようなので lib/variables.ts にまとめて定義する方法が紹介されている

src/lib/variabels.ts
export const variables = {
  basePath: import.meta.env.VITE_PUBLIC_BASE_PATH
};
<script lang="ts">
  import { variables } from '$lib/variables';
</script>

<div>basePath: {variables.basePath}</div>

参考↓

https://timdeschryver.dev/blog/environment-variables-with-sveltekit

global.d.ts ファイルを作成して以下のように sveltekit の型を効かせる

/// <reference types="@sveltejs/kit" />

__layout.sveltesegmentundefined を返す

$app/stores から page をインポートして page.url.pathname が同等

Vercel のビルドコマンドも npm run export から npm run build に修正した

デプロイ完了🎉

このスクラップは2022/01/09にクローズされました
ログインするとコメントできます