Open6

Svelte 5 マイグレーションメモ

雪猫雪猫

懸念点

聞いた話。

  • Map => SvelteMap
    • SvelteMapset の度に変更が検知される?
      • 最後にまとめて更新ができない
  • $: が無限ループになる?
雪猫雪猫

マイグレーションスクリプト

npx sv migrate svelte-5
npm install

Nostr hours

https://github.com/SnowCait/nostr-hours/pull/18

diff --git a/package.json b/package.json
index a2500c8..080c7c8 100644
--- a/package.json
+++ b/package.json
@@ -18,21 +18,21 @@
 		"@playwright/test": "^1.28.1",
 		"@sveltejs/adapter-auto": "^3.0.0",
 		"@sveltejs/adapter-static": "^3.0.1",
-		"@sveltejs/kit": "^2.0.0",
+		"@sveltejs/kit": "^2.5.27",
 		"@sveltejs/vite-plugin-svelte": "^4.0.0",
 		"@types/eslint": "^9.6.1",
 		"@typescript-eslint/eslint-plugin": "^8.11.0",
 		"@typescript-eslint/parser": "^8.11.0",
 		"eslint": "^9.13.0",
 		"eslint-config-prettier": "^9.1.0",
-		"eslint-plugin-svelte": "^2.36.0-next.4",
+		"eslint-plugin-svelte": "^2.45.1",
 		"prettier": "^3.1.1",
-		"prettier-plugin-svelte": "^3.1.2",
-		"svelte": "^5.0.0-next.1",
+		"prettier-plugin-svelte": "^3.2.6",
+		"svelte": "^5.0.0",
 		"svelte-check": "^4.0.5",
 		"tslib": "^2.4.1",
-		"typescript": "^5.0.0",
-		"vite": "^5.0.3",
+		"typescript": "^5.5.0",
+		"vite": "^5.4.4",
 		"vitest": "^2.1.3"
 	},
 	"type": "module",
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 7ef5dad..0bebbaa 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -1,4 +1,14 @@
-<main><slot /></main>
+<script>
+	/**
+	 * @typedef {Object} Props
+	 * @property {import('svelte').Snippet} [children]
+	 */
+
+	/** @type {Props} */
+	let { children } = $props();
+</script>
+
+<main>{@render children?.()}</main>
 
 <style>
 	main {
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index df09a48..2ab4ec3 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,4 +1,7 @@
 <script lang="ts">
+	import { run, createBubbler, preventDefault } from 'svelte/legacy';
+
+	const bubble = createBubbler();
 	import { onMount } from 'svelte';
 	import { NostrFetcher } from 'nostr-fetch';
 	import { nip19 } from 'nostr-tools';
@@ -6,14 +9,14 @@
 	import { browser } from '$app/environment';
 	import { page } from '$app/stores';
 
-	let npub = '';
-	let metadata: Content.Metadata | undefined;
-	let events: Event[] = [];
+	let npub = $state('');
+	let metadata: Content.Metadata | undefined = $state();
+	let events: Event[] = $state([]);
 
 	const defaultRelays = ['wss://relay.nostr.band/', 'wss://nos.lol/'];
 	const days = 14;
-	let displayEventCount = false;
-	let displayGradation = false;
+	let displayEventCount = $state(false);
+	let displayGradation = $state(false);
 
 	const now = new Date();
 	const dates = Array.from({ length: days }, (_, i) => {
@@ -23,18 +26,6 @@
 	});
 	const hours = Array.from({ length: 24 }, (_, i) => i);
 
-	$: if (npub.startsWith('npub1') && browser) {
-		console.log(npub);
-		try {
-			const { type, data: pubkey } = nip19.decode(npub);
-			if (type === 'npub') {
-				metadata = undefined;
-				events = [];
-				fetch(pubkey);
-				history.replaceState(history.state, '', `${$page.url.pathname}?npub=${npub}`);
-			}
-		} catch (error) {}
-	}
 
 	onMount(() => {
 		npub = $page.url.searchParams.get('npub') ?? '';
@@ -148,33 +139,49 @@
 		return `rgb(${red}, ${green}, ${blue})`;
 	}
 
-	let eventsCountPerHour: number[][] = [];
+	let eventsCountPerHour: number[][] = $state([]);
 
-	$: eventsCountPerHour = dates.map((date) =>
-		hours.map(
-			(hour) =>
-				events.filter((event) => {
-					const createdAt = event.created_at * 1000;
-					return (
-						date.getTime() + hour * 60 * 60 * 1000 <= createdAt &&
-						createdAt < date.getTime() + (hour + 1) * 60 * 60 * 1000
-					);
-				}).length
-		)
-	);
 
 	function totalEventsForDate(index: number) {
 		return eventsCountPerHour[index] ? eventsCountPerHour[index].reduce((a, b) => a + b, 0) : 0;
 	}
+	run(() => {
+		if (npub.startsWith('npub1') && browser) {
+			console.log(npub);
+			try {
+				const { type, data: pubkey } = nip19.decode(npub);
+				if (type === 'npub') {
+					metadata = undefined;
+					events = [];
+					fetch(pubkey);
+					history.replaceState(history.state, '', `${$page.url.pathname}?npub=${npub}`);
+				}
+			} catch (error) {}
+		}
+	});
+	run(() => {
+		eventsCountPerHour = dates.map((date) =>
+			hours.map(
+				(hour) =>
+					events.filter((event) => {
+						const createdAt = event.created_at * 1000;
+						return (
+							date.getTime() + hour * 60 * 60 * 1000 <= createdAt &&
+							createdAt < date.getTime() + (hour + 1) * 60 * 60 * 1000
+						);
+					}).length
+			)
+		);
+	});
 </script>
 
 <h1>Nostr hours</h1>
 <p>How many hours do you spend in Nostr?</p>
 
-<form on:submit|preventDefault>
+<form onsubmit={preventDefault(bubble('submit'))}>
 	<div>
 		<input type="text" bind:value={npub} placeholder="npub1..." />
-		<input type="button" on:click={inputNpub} value="from NIP-07" />
+		<input type="button" onclick={inputNpub} value="from NIP-07" />
 	</div>
 
 	<div>

マイグレーションスクリプトは問題なく実行できたが、npm updatenpm outdated で残っているものを更新しようとしたら一部依存関係を解決できなかった。

> npm install @sveltejs/vite-plugin-svelte@latest vite@latest                     
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: nostr-hours@0.0.1
npm ERR! Found: @sveltejs/vite-plugin-svelte@4.0.2
npm ERR! node_modules/@sveltejs/vite-plugin-svelte
npm ERR!   peer @sveltejs/vite-plugin-svelte@"^3.0.0 || ^4.0.0-next.1 || ^5.0.0" from @sveltejs/kit@2.9.0
npm ERR!   node_modules/@sveltejs/kit
npm ERR!     peer @sveltejs/kit@"^2.0.0" from @sveltejs/adapter-auto@3.3.1
npm ERR!     node_modules/@sveltejs/adapter-auto
npm ERR!       dev @sveltejs/adapter-auto@"^3.0.0" from the root project
npm ERR!     peer @sveltejs/kit@"^2.0.0" from @sveltejs/adapter-static@3.0.6
npm ERR!     node_modules/@sveltejs/adapter-static
npm ERR!       dev @sveltejs/adapter-static@"^3.0.1" from the root project
npm ERR!     1 more (the root project)
npm ERR!   peer @sveltejs/vite-plugin-svelte@"^4.0.0-next.0||^4.0.0" from @sveltejs/vite-plugin-svelte-inspector@3.0.1
npm ERR!   node_modules/@sveltejs/vite-plugin-svelte-inspector
npm ERR!     @sveltejs/vite-plugin-svelte-inspector@"^3.0.0-next.0||^3.0.0" from @sveltejs/vite-plugin-svelte@4.0.2   
npm ERR!   1 more (the root project)
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! dev @sveltejs/vite-plugin-svelte@"5.0.1" from the root project
npm ERR! 
npm ERR! Conflicting peer dependency: vite@6.0.1
npm ERR! node_modules/vite
npm ERR!   peer vite@"^6.0.0" from @sveltejs/vite-plugin-svelte@5.0.1
npm ERR!   node_modules/@sveltejs/vite-plugin-svelte
npm ERR!     dev @sveltejs/vite-plugin-svelte@"5.0.1" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR!
npm ERR! For a full report see:
npm ERR! C:\Users\SnowCait\AppData\Local\npm-cache\_logs\2024-12-01T08_35_06_987Z-eresolve-report.txt

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\SnowCait\AppData\Local\npm-cache\_logs\2024-12-01T08_35_06_987Z-debug-0.log
雪猫雪猫

$state に置き換わることでパフォーマンスが劣化した。
内部的に Proxy が使われているようなのでおそらく更新頻度が高い Array.push に使うと重い。
$state.raw にして代入で更新するようにすると改善された。

https://github.com/SnowCait/nostr-hours/pull/18/commits/1708db1ef7e51861e6df670e9a93d9fdc9229552

diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index abb4053..2304d92 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -11,7 +11,7 @@
 
 	let npub = $state('');
 	let metadata: Content.Metadata | undefined = $state();
-	let events: Event[] = $state([]);
+	let events: Event[] = $state.raw([]);
 
 	const defaultRelays = ['wss://relay.nostr.band/', 'wss://nos.lol/'];
 	const days = 14;
@@ -84,7 +84,7 @@
 		);
 		for await (const event of iterator) {
 			console.log(event);
-			events.push(event);
-			events = events;
+			events = [...events, event];
 		}
 	}
雪猫雪猫

@render 対応で <script /> がなかったコンポーネントに追加されることがあるが言語が JS になっているので TS に変更する必要がある。(TS プロジェクトの場合)

雪猫雪猫

$:svelte/legacyrun に置き換えられるが $derived$effect に修正できるとベター。