Closed41

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

azukiazusaazukiazusa

package.json - dependencies

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

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

package.json dev-dependencies

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

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

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",
    }
azukiazusaazukiazusa

設定ファイル

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?

azukiazusaazukiazusa

設定ファイル - 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(),
azukiazusaazukiazusa

src/client.js

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

azukiazusaazukiazusa

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';
azukiazusaazukiazusa

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 に記述する・

azukiazusaazukiazusa

src/node_modules

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

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

rm -rf src/node_modules
azukiazusaazukiazusa

レイアウトファイル

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

  • _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
azukiazusaazukiazusa

$app/navigation

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

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

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

TypeScript

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

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;
}
azukiazusaazukiazusa

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

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()
azukiazusaazukiazusa

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

 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 を使うとだめ?

azukiazusaazukiazusa

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

azukiazusaazukiazusa

また別のエラーが

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')
azukiazusaazukiazusa

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 }))
}
azukiazusaazukiazusa

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
    }
  }
}
azukiazusaazukiazusa

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

storespage が変わっている

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

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

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

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

azukiazusaazukiazusa

公開する環境変数は 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

azukiazusaazukiazusa

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

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

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

このスクラップは2022/01/09にクローズされました