プログラミング/svelte/svelte5手順覚書 の履歴(No.19)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- プログラミング/svelte/svelte5手順覚書 へ行く。
概要†
svelte5 の流儀は svelte4 とずいぶん違うようなので、
https://svelte-5-preview.vercel.app/docs/introduction
を見ながら進めつつ、あとで使えるよういろいろな手順をメモしておく。
- 概要
- メモ
- 困ったとき
- binding_property_non_reactive
- Tagged Template 形式の関数呼び出しがリアクティブにならないバグがある
- style タグの直後で unknown word エラー
- Playwright による integration test で Error: Process from config.webServer was not able to start. Exit code: 1
- satisfies PageServerLoad がエラーになる
- effect_update_depth_exceeded
- typescript で addEventListener
- svelte ファイルをテキストメールのテンプレートに使う
- メンバー関数を変数に取り出すと this が消えてしまう
- get/set で作られたプロパティを変数に入れると混乱する
- reload window で ssh の繋ぎなおし
- VSC で ANSI Color でカラー化されたログを読む
開発環境の整備†
nvm のアップデート†
git でインストールしてある場合には
LANG:console $ cd ~/.nvm $ git fetch $ git for-each-ref --sort=creatordate --format '%(refname) %(creatordate)' refs/tags ... refs/tags/v0.40.0 Tue Jul 30 12:50:18 2024 -0700 $ git checkout v0.40.0
みたいにする。
node†
最新版を入れるなら、
LANG:console $ nvm install --lts Now using node v20.16.0 (npm v10.8.1)
git alias†
~/.gitconfig
[alias] ss = status br = branch co = checkout com = commit comm = commit -m log1 = log -1 log2 = log -2 log3 = log -3 logo = log --oneline --pretty=format:'%C(Yellow)%h%Creset %ad %C(Green)%s%Creset' --date=short logn = log --name-status --oneline --pretty=format:'%C(Yellow)%h%Creset %ad %C(Green)%s%Creset' --date=short tags = for-each-ref --sort=creatordate --format '%(refname) %(creatordate)' refs/tags
peco†
ソフトウェア/peco で紹介したもの。
インストールは https://github.com/peco/peco/releases から peco_windows_amd64.zip を落としてきて、中の peco.exe を ~/bin に置くだけ。
~/.bash_profile に以下を追加して
export PATH=$PATH:~/bin
ログインしなおして、
LANG:console $ ls | peco
でファイル一覧からの選択画面が表示されたら成功。
~/.gitconfig
[alias] # SYNTAX: git d # # 履歴一覧を表示して選択した履歴との diff を取る # 初期状態のまま Enter を押せば単に git diff と打ったのと同様に HEAD と working copy との間の差分をとる # 履歴を1つ選んで Enter を押せば、その履歴と working copy との差分を取る # Ctrl+space で2つ選べば、その間の差分を取る # d = !git diff --minimal --ignore-all-space `git log --oneline --branches | peco | ruby -e 'STDIN.readlines.reverse.each{|s| puts s[0..6]}'` # SYNTAX: git addq # # git add するファイルを peco で選択する addq= "!if [ \"`git status --porcelain | grep -E '^.[^ ]'`\" != \"\" ]; then git add `git status --porcelain | grep -E '^.[^ ]' | peco | sed -e 's/.* //'`; fi" # SYNTAX: git rmq # # git rmするファイルを peco で選択する rmq= "!if [ \"`git status --porcelain | grep -E '^.[^ ]'`\" != \"\" ]; then git rm `git status --porcelain | grep -E '^.[^ ]' | peco | sed -e 's/.* //'`; fi" # SYNTAX: git resetq # # git reset するファイルを peco で選択する resetq = "!if [ \"`git status --porcelain | grep -E '^[^\\? ]'`\" != \"\" ]; then git reset `git status --porcelain | grep -E '^[^\\? ]' | peco | sed -e 's/.* //'`; fi"
VSCode†
- ターミナルから code <file名> とするとエディタ上にファイルを開ける
プロジェクトの作成†
LANG: console $ pnpm create svelte@latest <projectname> $ cd <projectname> $ pnpm install && git init && git add -A && git commit -m "Initial commit" $ pnpm test $ pnpm dev
git add -A と git add . の違いは、git add -Aはレポジトリ内のどこで実行してもレポジトリ全体を処理するが、git add .はカレントディレクトリ以下のみを処理する、とのこと。 ← https://note.nkmk.me/git-add-u-a-period/
コンポーネントのアップデート†
LANG: console $ pnpm update && \ git add -A && git commit -m "pnpm update"
prettier†
.prettierrc
{ - "useTabs": true, + "useTabs": false, "singleQuote": true, - "trailingComma": "none", + "trailingComma": "es5", + "quoteProps": "consistent", "printWidth": 100, "plugins": ["prettier-plugin-svelte"], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] }
LANG: console $ pnpm format && \ git add -A && \ git commit -m "prettier を通した"
prettier の自動実行†
LANG: console $ cat > .git/hooks/pre-commit #!/bin/sh FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') [ -z "$FILES" ] && exit 0 # Prettify all selected files echo "$FILES" | xargs ./node_modules/.bin/prettier --ignore-unknown --write # Add back the modified/prettified files to staging echo "$FILES" | xargs git add exit 0 ^D $ chmod u+x .git/hooks/pre-commit
pnpm†
- run を付ける必要がない
- npx の代わりに pnpm dlx とする
テスト環境†
LANG: console $ pnpm test:integration # playwright による結合テスト $ pnpm test:unit # vitest でユニットテストを行い、自動実行用に待機する $ pnpm test # 上の2つを続けて行い、vitest で待機する $ pnpm playwright test --ui # playwright の gui を立ち上げる
[vitest]
LANG: console $ cat vite.config.ts import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [sveltekit()], test: { include: ['src/**/*.{test,spec}.{js,ts}'] } });
となっているので、src/ の下のすべての .test.ts および .spec.ts がテストの対象になる。
[playwrightインストール]
LANG: console $ pnpm dlx playwright install $ echo /test-results >> .gitignore
$ cat playwright.config.ts import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { webServer: { command: 'pnpm build && pnpm preview', port: 4173 }, testDir: 'tests', testMatch: /(.+\.)?(test|spec)\.[jt]s/ };
tests/ フォルダの *test.ts のようなファイルがテスト対象になる(test の前に . はなくてもいい)。
pnpm dev していない状況では、ファイルを更新した場合にはテスト前に pnpm build し直さなければならないっぽいのでその点には注意が必要だ。
tsx†
tsx を使うと Type Script で書かれたユーティリティスクリプトをトランススクリプトせずコマンドラインから直接実行することができる。
LANG: console $ pnpm i -D tsx
とすると入れられて、プロジェクトの tsconfig.json を食わせて実行すれば大抵のものはそのまま動くみたい。
LANG: consle $ ./node_modules/.bin/tsx --tsconfig tsconfig.json path/to/script.ts
プロジェクト†
css†
DaisyUI を入れる
LANG: console $ pnpm dlx svelte-add@latest tailwindcss ◇ Do you want to use typography plugin? │ Yes $ pnpm add -D daisyui@latest
https://daisyui.com/docs/layout-and-typography/#-1 を参考に、
tailwind.config.ts
LANG: ts + import TailwindcssTypography from '@tailwindcss/typography'; + import DaisyUI from 'daisyui'; export default { //... - plugins: [], + plugins: [ + TailwindcssTypography, + DaisyUI, + ], + + daisyui: { + themes: ['light'] + }, } as Config;
データベース prisma†
インストール
LANG: console $ pnpm add -D prisma && pnpm add @prisma/client $ pnpm dlx prisma init --datasource-provider sqlite && cat .env $ echo "/prisma/*.db" >> .gitignore # db がコミットされないようにする $ echo "/prisma/*.db-journal" >> .gitignore $ git add . && git commit -m "Prisma をインストールした"
サーバーで使うためのコードを追加
src/lib/server/db.ts
import { PrismaClient } from '@prisma/client'; export const db = new PrismaClient();
Prisma チートシート:
- https://www.prisma.io/docs/orm/prisma-client/queries/crud#example-schema
- https://qiita.com/ryskBonn92/items/c45e22ce5f37d82ec8de
prisma/schema.prisma を編集してマイグレートする
$ pnpm prisma migrate dev --name "name for migration"
ユーザー認証 Lucia†
- https://lucia-auth.com/getting-started/sveltekit/
- https://lucia-auth.com/basics/sessions
- User にプロパティを持たせるには getUserAttributes で明示する必要がある
https://lucia-auth.com/basics/users#define-user-attributes
を見ながら
LANG: console $ pnpm add lucia @lucia-auth/adapter-prisma
app.d.ts
LANG: ts declare global { namespace App { ... // Locals を使うことで +page.server.ts 内で load から action へデータを渡せる interface Locals { user: import('lucia').User | null; session: import('lucia').Session | null; } interface PageData { user: import('lucia').User | null; session: import('lucia').Session | null;
src/lib/server/lucia.ts
LANG: ts import { Lucia } from "lucia"; import { dev } from "$app/environment"; import { PrismaAdapter } from "@lucia-auth/adapter-prisma"; import { db } from "$lib/server/db"; import type { RequestEvent } from '@sveltejs/kit'; const adapter = new PrismaAdapter(db.session, db.user); export const lucia = new Lucia(adapter, { sessionCookie: { attributes: { // set to `true` when using HTTPS secure: !dev } } }); // ログインしてるなら cookie から session, user を返す // src/hooks.server.ts で呼び出される export async function findUserAndSession(event: RequestEvent) { const sessionId = event.cookies.get(lucia.sessionCookieName); if (!sessionId) { return { session: null, user: null }; } const { session, user } = await lucia.validateSession(sessionId); if (session && session.fresh) { const sessionCookie = lucia.createSessionCookie(session.id); // sveltekit types deviates from the de-facto standard // you can use 'as any' too event.cookies.set(sessionCookie.name, sessionCookie.value, { path: ".", ...sessionCookie.attributes }); } if (!session) { const sessionCookie = lucia.createBlankSessionCookie(); event.cookies.set(sessionCookie.name, sessionCookie.value, { path: ".", ...sessionCookie.attributes }); } return { session, user }; } declare module "lucia" { interface Register { Lucia: typeof lucia; DatabaseUserAttributes: DatabaseUserAttributes; } } interface DatabaseUserAttributes { name: string, email: string, }
フォームのバリデーション sveltekit-superforms†
LANG: console $ pnpm add -D sveltekit-superforms zod
使い方はこの辺り :
- https://superforms.rocks/get-started
- https://superforms.rocks/examples
- プログラミング/svelte/Prisma と Lucia を使った認証システム#ud9711bc
LANG: ts import { fail, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; export const load = (async (event) => { const form = await superValidate(zod(schema)); return { form }; }) satisfies PageServerLoad; export const actions = { default: async (event) => { // フォームデータのバリデーション const form = await superValidate(event, zod(schema));
src/lib/server/index.ts
// superforms の form コントロールにエラーメッセージを追加する // @example ```addErrorsToSuperValidated(form, 'email', 'そのアドレスは使えません');``` export function addErrorsToSuperValidated< KEYS extends string, FORM extends { valid: boolean; errors: { [key in KEYS]?: string[] }; }, >(form: FORM, item: KEYS, message: string) { form.errors[item] = [...(form.errors[item] || []), message]; form.valid = false; }
フラッシュメッセージ sveltekit-flash-message†
https://github.com/ciscoheat/sveltekit-flash-message
LANG: console $ pnpm add -D sveltekit-flash-message
app.d.ts
LANG: ts declare global { namespace App { ... interface PageData { ... // https://github.com/ciscoheat/sveltekit-flash-message flash?: { type: 'success' | 'error'; message: string };
src\routes\+layout.server.ts
LANG: ts import { loadFlash } from 'sveltekit-flash-message/server'; export const load = loadFlash(async (event) => { // その他の処理 return { someData: 'data' } // $page.data に受け渡すデータ });
src\routes\+layout.svelte
LANG: ts <script lang="ts"> import { getFlash } from 'sveltekit-flash-message'; import { page } from '$app/stores'; const flash = getFlash(page); const { children } = $props(); </script> {#if $flash} {@const bg = $flash.type == 'success' ? '#3D9970' : '#FF4136'} <div style:background-color={bg} class="flash">{$flash.message}</div> {/if} {@render children()}
もう少し凝りたければ、
flash-toast.svelte
LANG: html <script lang="ts"> /** * TODO: 複数のメッセージを同時に表示できるようにしたい */ import { getFlash } from 'sveltekit-flash-message'; import { page } from '$app/stores'; let flashMessage = $state('' as string); let flashType = $state('success' as 'success' | 'error'); let show = $state(false as boolean); // メッセージがセットされれば表示する const flash = getFlash(page); // メッセージがあれば初回表示時に表示する let firstTime = true; $effect(() => { if (firstTime) { firstTime = false; flash.subscribe(($flash) => { if ($flash) { let icon = $flash.type == 'success' ? '✅' : '❌'; flashMessage = icon + ' ' + $flash.message; flashType = $flash.type; show = true; flash.set(undefined); } }); } }); function onanimationend() { show = false; } </script> <div class="flash-toast {flashType}" class:is-show={show} {onanimationend}> <!-- メッセージ部分 --> <div>{@html flashMessage}</div> <!-- 吹き出し部分 --> <div></div> </div> <!-- svelte-ignore css_unused_selector --> <style type="postcss"> .flash-toast { &.success > div { --color-back: #9de8af; } &.error > div { --color-back: #f1aeb5; } /* https://codepen.io/kandai/pen/qBEbgQv を参考にした */ box-sizing: border-box; position: fixed; top: 10px; right: 0px; /* right: -220px; */ transform: translateX(calc(100% + 10px)); display: flex; align-items: start; /* メッセージ部分 */ & > div:first-child { max-width: 25vw; color: #000; padding: 4px; border: 8px solid; border-radius: 8px 0px 8px 8px; background-color: var(--color-back); border-color: var(--color-back); } /* 吹き出しの三角形 */ & > div:nth-child(2) { border: solid transparent; content: ''; height: 0; width: 0; pointer-events: none; position: relative; border-top-width: 0px; border-bottom-width: 10px; border-left-width: 10px; border-right-width: 10px; border-radius: 0; border-left-color: var(--color-back); } &.is-show { animation: anime 4s ease 1 normal; } } @keyframes anime { /* 全部で 4s */ 12.5% { transform: translateX(0); } /* 0.5s で飛び出す */ 87.5% { transform: translateX(0); } /* 0.5s で引っ込む */ } </style>
のようなのを作っておいて、
src\routes\+layout.svelte
LANG: ts <script lang="ts"> import '../../app.css'; import FlashToast from './flash-toast.svelte'; const { children } = $props(); </script> <FlashToast /> <div class="container"> {@render children()} </div>
のようにするとか。
メール送信 nodemailer†
LANG: console $ pnpm add nodemailer && pnpm add -D @types/nodemailer
src/lib/server/transporter.ts
LANG:ts import { createTransport, type SentMessageInfo, type SendMailOptions } from 'nodemailer'; import { env } from '$env/dynamic/private'; import * as fs from 'fs'; export const transporter = createTransport({ host: 'localhost', port: 25, }); // 開発環境やテスト環境なら実際にはメールを送らずに // コンソールへ表示 & test-results/mail-sent.txt へ保存 if (env.DATABASE_URL.match(/\b(dev|test)\.db$/)) { transporter.sendMail = async (mailOptions: SendMailOptions) => { console.log(mailOptions); if (!fs.existsSync('test-results')) { fs.mkdirSync('test-results', { mode: 0o755 }); } fs.writeFileSync('test-results/mail-sent.txt', JSON.stringify(mailOptions)); return null as SentMessageInfo; }; }
node サーバーで動かす場合のデプロイ方法†
https://kit.svelte.jp/docs/adapter-node https://zenn.dev/rabee/articles/sveltekit-nodejs-setup
pnpm build 時に使われるアダプターを adapter-node にすればよい。
まず
LANG:console $ pnpm i -D @sveltejs/adapter-node@next
として adapter-node インストールしてから svelte.config.js の指定を書き換える。
svelte.config.js
LANG:ts // import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-node';
そして、
LANG:console $ pnpm build
これで build/ に実行に必要なファイルができる。
dev セクションに指定したパッケージは production 環境では必要ないので、
LANG:console $ pnpm install --frozen-lockfile --prod
のように --prod を付けて pnpm install することで dev セクションのパッケージを削除しておく。
サーバーの起動は、https://kit.svelte.jp/docs/adapter-node にある環境変数を設定してから node build/index.js すればよい。
LANG:console $ PORT=4174 node build
こうすれば 4174 ポートでサーバーが立ち上がる。
node サーバーのデーモン化†
https://qiita.com/poruruba/items/10df0d94e9127797498f
pm2 を使うといいらしい。 (古い記事では forever が紹介されたりもしているのだけれどこのところメンテナンスされていないので)
LANG:console $ pnpm install pm2
の後に
LANG:console $ pnpm pm2 start build --name <appname>
みたいにすると起動できる。
ただこれだと環境変数を指定できないのかもしれなくて、以降も間違いなく起動できるようにするには pm2.config.cjs を作るとよい。
pm2.config.cjs
LANG:js module.exports = { apps : [ { name: "appname_port3001", script: "./build/index.js", cwd : '/path/to/the/app', env: { "HOST": "127.0.0.1", "PORT": 3001, "NODE_ENV": "production" } } ] }
LANG:console $ pnpm pm2 start pm2.config.cjs
として起動する。
es6 形式でない .js ファイルは .cjs としておかないと、
LANG:console [PM2][ERROR] File pm2.config.js malformated Error [ERR_REQUIRE_ESM]: require() of ES Module pm2.config.js not supported. pm2.config.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules. Instead either rename pm2.config.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in /home/takeuchi/remese-check/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).
と言われてしまうので注意。
動作中のサーバーの一覧を確認するには
LANG:console $ pnpm pm2 ls
0 番目のアプリを止めるには
LANG:console $ pnpm pm2 delete 0
0 番目のアプリの状態を表示するには
LANG:console $pnpm pm2 show 0
ログを表示するには
LANG:console $ pnpm pm2 logs # すべてのプロセスのログ $ pnpm pm2 logs 0 # 0 番目のアプリのログ
pm2 自体を止めるには
LANG:console $ pnpm pm2 kill
システム再起動時に自動実行†
システムの再起動時に pm2 を自動実行するには、
LANG:console $ crontab -e
で crontab 設定を開いて
@reboot /path/to/the/app/node_modules/.bin/pm2 start /path/to/the/app/pm2.config.cjs
を追加すればいい・・・のかな???
開いてるポートを確認†
LANG: console $ sudo lsof -i -P +c15 | grep LISTEN
のようにする。
sudo を付けないと自分が起動したプロセスについてしか調べられないみたい。
メモ†
アクセスログを残す†
https://www.reddit.com/r/sveltejs/comments/xtbkpb/how_are_you_logging_http_requests_in_sveltekit/
を元に少し色付けして、
src/hooks.server.ts
LANG:ts import { sequence } from "@sveltejs/kit/hooks"; import type { Handle } from "@sveltejs/kit"; const logger: Handle = async ({ event, resolve }) => { const requestStartTime = Date.now(); const response = await resolve(event); const time = new Date(requestStartTime).toISOString(); let message = ''; if(response.status < 300) { message += `\x1b[94m`; // 青 } else if(response.status < 400) { message += `\x1b[93m`; // 黄色 } else if(response.status < 500) { message += `\x1b[95m`; // オレンジ } else { message += `\x1b[91m`; // 赤 } message += new Date(requestStartTime).toISOString() + `\x1b[49m `; message += `\x1b[30;106m${event.request.method}\x1b[49m `; // 反転 message += `\x1b[97;49m${event.url.pathname+event.url.search}\x1b[49m`; // 白 console.log( message, `(${Date.now() - requestStartTime}ms)`, response.status ); return response; }; export const handle: Handle = sequence(logger);
開発時のログの取り方†
LANG: console $ pnpm dev
とする代わりに
LANG: console $ pnpm dev | tee -a dev.log
のように起動すればログを dev.log に保存できる。
あるいは、既存のログに追加するなら
LANG: console $ pnpm dev | tee dev.log
とする。
事前に、
LANG: console $ echo /dev.log >> .gitignore
もしておくとよい。
ただ、これをすると画面上でカラー表示がなくなってしまうのが玉に瑕?
https://blog.ayakumo.net/entry/2022/09/10/192732
LANG: console $ sudo apt install expect
しておいて、
LANG: console $ unbuffer pnpm dev 2>&1 | tee dev.log
とすると、カラーのまま表示&保存可能。
カラーコードの残ったログは、VSCode の ANSI Colors プラグインでプレビューすればカラーのまま参照できる。
$effect/$effect.pre の実行順†
"$effect.pre of Outer Component" "$effect.pre of Inner Component" "$effect of Inner Component" "$effect of Outer Component"
の順に呼ばれることが分かった。
サーバー側とクライアント側とで Rune は共有されない†
サーバー側で設定した $state 変数をクライアント側で読み出せるわけではないため、
SSR をうまく効かせるためにはサーバー側でもクライアント側でも同じ $state が設定されるようにしないといけない。
- .server.ts で PageData を設定
- .svelte で data を参照して $state 変数を設定
としてあれば問題ないはず。
zod のスキーマからデータの型を得る†
LANG: ts type Data = typeof schema._type;
$state 変数をモジュールから export する†
$state は .svelte あるいは .svelte.ts/.svelte.js の中でしか使えないので、 $state 変数を export するモジュールの拡張子は .svelte.ts とする。
$state 変数は単体で export できないため、class フィールドとして export するか、 あるいは $state 変数自体ではなくその getter や setter, modifier を export する。
test.svelte.ts
LANG: ts // The `$state` rune is only available inside `.svelte` and `.svelte.js/ts` files at Module.rune_outside_svelte // $state(...)` can only be used as a variable declaration initializer or a class field // Cannot export state from a module if it is reassigned. Either export a function // returning the state value or only mutate the state value's properties // 書き換え可能な $state 変数を直接 export することはできないのでゲッターやセッターを export する let counter = $state(0); export const s = { get count() { return counter; }, increment() { counter += 1 }, }; // クラスフィールドが $state 変数なら直接 export 可能 class T { count = $state(0); increment() { this.count += 1 }; } export const t = new T(); // $state 変数自体を const としてそのフィールドを書き換えるのは OK export const u = $state({ count: 0 });
本当に黒魔法的に感じるけれど、なぜかこれで s.count や t.count、u.count への参照がリアクティブになる。
LANG: html <script lang="ts"> import { s, t, u } from './test.svelte'; </script> {s.count} <br/> <!-- ← ここがリアクティブに変化する --> <br/> <button onclick={()=>s.increment()}>Increment</button><br/> <br/> {t.count} <br/> <!-- ← ここがリアクティブに変化する --> <br/> <button onclick={()=>t.increment()}>Increment</button><br/> <button onclick={()=>t.count -= 1}>Decrement</button><br/> <!-- 直接変更も可能 --> <br/> {u.count} <br/> <!-- ← ここがリアクティブに変化する --> <br/> <button onclick={()=>u.increment()}>Increment</button><br/> <button onclick={()=>u.count -= 1}>Decrement</button><br/> <!-- 直接変更も可能 --> <br/>
まじかすげー。
store が必要なくなるというのも頷ける??
$state な配列の要素読み出しもリアクティブ†
LANG:ts class TObj { array = $state([0]); get arrayTop() { return this.array[0] } // リアクティブになる } let obj = new TObj();
のようにしてあると obj.arrayTop へのアクセスは正しくリアクティブになるため $derived は必要ない。
postcss-color-mod-function を入れた†
https://github.com/csstools/postcss-color-mod-function
css に
background-color: color-mod(#f0ad4e lightness(90%));
みたいに書けるようになる。
$ pnpm i postcss-color-mod-function -D
はいいとして postcss.config.js を
LANG:ts export default { plugins: { autoprefixer: {}, }, };
から
LANG:ts import postcssColorModFunction from 'postcss-color-mod-function'; import autoprefixer from 'autoprefixer'; export default { plugins: [autoprefixer(), postcssColorModFunction()], };
のように変更しなければならなかったのだけれどそういうものなのかしら???
困ったとき†
binding_property_non_reactive†
ブラウザのコンソールにこのエラーが出る。
[svelte] binding_property_non_reactive `bind:this={components[0]}` (.svelte-kit/generated/root.svelte:44:42) is binding to a non-reactive property
bind:this にバインドする変数を $state にしてみても解決しない
https://github.com/sveltejs/svelte/issues/12514
これだと思うのだけれど、待っていれば直るということなのかな?
Tagged Template 形式の関数呼び出しがリアクティブにならないバグがある†
$state 変数により出力の変化する func(s: string) を func(str) として呼び出すならこの呼び出しはリアクティブになるのだけれど、現状では func`some string` の形で呼び出すとりアクティビティが得られない。
これはバグなので将来的には解消されるはず:
https://github.com/sveltejs/svelte/issues/12687
→ 解消した
style タグの直後で unknown word エラー†
間違って // で始まる一行コメントを書いているとこのエラーが出るのだけれど、 一行コメントのある行じゃなくて style タグの直後にエラーが表示されるので 理由が分からず途方に暮れる。
Playwright による integration test で Error: Process from config.webServer was not able to start. Exit code: 1†
TEST RESULT ウィンドウの結果が、
LANG:console Running global setup if any… Error: Process from config.webServer was not able to start. Exit code: 1
となってエラーの詳細が表示されない。
大抵は pnpm build で失敗しているということなので Terminal から
LANG:console $ pnpm build
してエラーメッセージを確認する。
satisfies PageServerLoad がエラーになる†
page.server.ts 内の記述が間違っていないのにここがエラーになるのとすれば、 その上の階層の layout.server.ts において必要な PageData が正しく受け渡せていない可能性があるのでチェックする。
effect_update_depth_exceeded†
リアクションのループが生じているということらしい。
$effect/$effect.pre は参照した rune 変数の変更のたびに呼び出されるので、 「$effect 内で参照している rune 変数を同じ $effect 内で書き換える」を行ってしまうと無限ループになる。
$effect/$effect.pre は複数書いてよく、それぞれの $effect/$effect.pre は依存する rune 変数を別々に管理することに注意しつつ、
さらなるリアクティブ更新を生みたくない部分を
LANG:ts import { untrack } from 'svelte';
で括って隠してやれば避けられる。
$effect(()=>{ depending1; // untrack の外で読んだいずれかの $state 変数が変更されれば depending2; // この $effect が呼び出される untrack(()=>{ // この中で参照する変数が変更されてもこの $effect は呼び出されない }); });
typescript で addEventListener†
普通に (e: Event)=>{ } 形式の関数を関数を渡すとエラーになる?
https://qiita.com/tobita0000/items/7341e11305eb25726dc0
LANG:ts a.addEventListener('click', {handleEvent: (e: Event & { currentTarget: HTMLAnchorElement}) => { const anchor = e.currentTarget; const h = document.querySelector<HTMLHeadingElement>(anchor.href.replace(/^[^#]*/, '')); if (h) animateScroll(h); }});
svelte ファイルをテキストメールのテンプレートに使う†
svelte5 の場合 svelte/server に含まれる render という関数を使うことで文字列化(hydrate)が可能。
LANG: ts import { render } from 'svelte/server'; import Template from './my-template.svelte'; const text = render(Template, { props: { prop1: value1, prop2: value2, }, }).body .replaceAll(/<!--[^>]*-->/g,'') .replaceAll(/^\n*<[^>]+>\n?|<\/[^<]+>\n*$/g, '');
dev モードでは自動生成されるコメントタグがたくさん入るため、後から replaceAll で消す。 pnpm build 後はコメントは入らないので無駄になるけどテストのためにもこのコードは必要。
my-template.svelte
LANG: html <script lang="ts"> let { title, body }: { title: string, body: string } = $props(); </script> <!-- prettier-ignore --> <pre> * {title} {body} </pre>
出力結果を html ではなくプレーンテキストとして用いる場合、 テンプレートは例えば <pre> タグで全体を括って <!-- prettier-ignore --> を付けておく。
そうしないと prettier が勝手なところで改行したり、空白を入れたりしてしまう。
https://github.com/sveltejs/prettier-plugin-svelte/issues/59#issuecomment-683425785
prettier-ignore はタグ単位で効果を発揮するので、上記のように何らかのタグで括る必要がある。
邪魔なタグは後から .replaceAll(/^\n*<[^>]+>\n?|<\/[^<]+>\n*$/g, ''); で消している。
メンバー関数を変数に取り出すと this が消えてしまう†
svelte じゃなく javascript の話だけれど、
LANG: ts class TObj { value = 1 get1 = () => this.value; get2() { return this.value } } const { get1, get2 } = obj; console.log(get1()); // うまく動く console.log(get2()); // this が undefined という TypeError
という結果になる。
さすが ChatGPT 先生
get/set で作られたプロパティを変数に入れると混乱する†
これは svelte の問題ではないのだけれど、
LANG:ts const obj = { #count: 0; set count(c: number) { this.#count = c } get count() { return this.#count } } const { count } = obj;
このとき count 変数は get count() の結果得られたその時点での obj.#count の値を保持するに過ぎないので、obj.#count を変更してもその変化は反映されないし、count へ値を代入しても obj.#count は変化しない。
set/get の定義された属性を安全に使うには常に obj.count のように obj. をつけて呼び出さないとダメだ。
reload window で ssh の繋ぎなおし†
Ctrl+Shift+P からの reload window すると ssh remote での接続をやり直すことができる。
PC がスリープに入ったりで繋がらなくなったら試す。
VSC で ANSI Color でカラー化されたログを読む†
ターミナルに表示した際にカラーになるようなログを読もうとして Esc[1mEsc[36m みたいのがたくさん出て残念な気分になるときは
https://qiita.com/YoshitakeHiroki/items/63db7b6722c031a441ed
にあるように ANSI Colors というプラグインを入れて、 Ctrl + Shift + P から ANSI Text: Open Preview を選ぶとよい。