Claude Media
Claude CodeでSvelteKitを開発する — Runes記法の規約化とsv check

Claude CodeでSvelteKitを開発する — Runes記法の規約化とsv check

Svelte 5のRunesと旧記法の混在をClaude Codeに起こさせない手順。CLAUDE.mdとpath指定ルール、runesモードの強制、sv checkのhook連携までを設定例つきで示します。

Claude CodeにSvelteKitアプリを書かせると、Svelte 5のRunes($state や $props)と、Svelte 3/4の旧記法(export let や on:click)が同じプロジェクトに混ざりがちです。旧記法はSvelte 5でも動くため、動作確認では気づけません。

この混在は3層で止めます。CLAUDE.mdと.claude/rules/で書き方を伝え、コンパイラーのrunesモードで旧記法を書けなくし、sv checkをhookで走らせて結果をClaudeに返します。順に設定していきます。

なぜ旧記法が混ざるのか

Svelte 5は旧記法を廃止していません。Legacy APIsの節には、Svelte 3/4の記法は非推奨だが今も動き、将来削除される、と書かれています。そしてコンポーネントは、runesを使うかコンパイラーオプションrunes: trueを明示した時点でrunesモードになり、その中では旧機能が使えなくなります。

つまり何も指定しない状態では、コンポーネントごとに旧記法モードとrunesモードが決まります。Claudeは学習データ中の旧記法も、新しい記法も知っています。周辺のコードが旧記法なら旧記法に寄せて書きますし、新規ファイルでどちらを選ぶかも指示がなければ揺れます。

旧記法とRunesの対応は、移行ガイドに沿うと次のとおりです。CLAUDE.mdに書く禁止リストの元になります。

旧記法(Svelte 3/4)Runes(Svelte 5)
トップレベルの let count = 0Runes(Svelte 5)let count = $state(0)
$: double = count * 2Runes(Svelte 5)let double = $derived(count * 2)
$: による副作用Runes(Svelte 5)$effect(() => { ... })
export let nameRunes(Svelte 5)let { name } = $props()
on:click={handler}Runes(Svelte 5)onclick={handler}
createEventDispatcherRunes(Svelte 5)コールバックprop
<slot />Runes(Svelte 5){@render children()} と {#snippet}
$app/storesRunes(Svelte 5)$app/state

最後の行はSvelteKit側の変更です。sv migrateのサブコマンドapp-stateが、.svelteファイル内の$app/storesを$app/stateへ書き換えます。

CLAUDE.mdに書く規約

Claude Codeは、CLAUDE.mdをセッション開始時にコンテキストへ読み込みます。ただしCLAUDE.mdはシステムプロンプトの一部ではなく、その後にユーザーメッセージとして渡される文脈です。厳密な遵守は保証されず、曖昧な指示や矛盾する指示ほど守られにくくなります。

書き方の指針も公式に示されています。1ファイル200行以内、確認できるほど具体的に書く、の2点です。「コードを整えて」ではなく「インデントは2スペース」と書く、という例が挙がっています。Svelteの規約なら次の形です。

# SvelteKit プロジェクト規約
 
- このプロジェクトは Svelte 5 の runes モード。旧記法は書かない
- 状態は `$state`、算出値は `$derived`、props は `$props()` で受ける
- 副作用の `$effect` 内で state を更新しない。まず `$derived` を検討する
- DOM イベントは `onclick` などのイベント属性。`on:click` は使わない
- 子から親への通知はコールバック prop。`createEventDispatcher` は使わない
- 子コンテンツは `{@render children()}`。`<slot />` は使わない
- ページ状態は `$app/state`。`$app/stores` は使わない
- rune を使うモジュールは `.svelte.ts` に置く(通常の `.ts` では使えない)
 
## 検証コマンド
 
- 変更後は `npx sv check --threshold error` を実行し、エラー 0 を確認する

「使わない」だけでなく「代わりに何を使うか」を並べているのがポイントです。禁止だけを書くと、Claudeは何に置き換えるかを自分で選び、そこでまた揺れます。

runesが使える場所の制約も1行入れておきます。Svelteの説明では、runesは.svelteファイルと.svelte.js/.svelte.tsファイルで使うものです。ストアの代わりに共有状態をモジュールへ切り出すとき、Claudeが普通の.tsに$stateを書いてしまう失敗を防げます。

規約が長くなったら.claude/rules/に分ける

Svelte以外の規約も増えてくると、CLAUDE.mdが200行に近づきます。.claude/rules/にトピックごとのmarkdownを置く構成もあります。pathsフロントマターを付けたルールは、Claudeが一致するファイルを読んだときに読み込まれます。pathsのないルールは起動時に無条件で読み込まれます。

Svelteの規約をSvelteファイルだけに効かせる例です。

---
paths:
  - "src/**/*.svelte"
  - "src/**/*.svelte.ts"
---
 
# Svelte 5 規約
 
- runes モード前提。`export let` / `$:` / `on:` / `<slot />` は書かない
- `$derived` には式を渡す。関数が要るときは `$derived.by`
- 大きな API レスポンスは `$state.raw`

$state.rawと$derived.byは、Svelteのベストプラクティスにある指針です。オブジェクトや配列を$stateに入れると深くリアクティブになり、プロキシのオーバーヘッドがかかります。再代入しかしない大きなレスポンスには$state.rawが向く、と説明されています。

CLAUDE.mdの書き方そのものはClaude CodeのCLAUDE.mdを実用に引き上げる10のパターンにまとめています。

コンパイラーで旧記法を書けなくする

文章による指示は、最終的にはClaudeの判断に任されます。Svelte側の仕組みで縛れるなら、そちらが確実です。

1つ目は、プロジェクト全体をrunesモードにするコンパイラーオプションです。svelte.config.jsのcompilerOptionsにrunes: trueを渡します。移行ガイドのcompatibility.componentApiの例と同じく、compilerOptionsの下に置きます。

// svelte.config.js
export default {
  compilerOptions: {
    runes: true
  }
};

runesモードのコンポーネントでは旧機能が使えないため、Claudeがexport letや$:を書けばコンパイルエラーになります。エラーはClaudeが自分で読めるので、書き直しに回ります。

2つ目は、コンポーネント単位の指定です。<svelte:options runes={true} />でそのコンポーネントをrunesモードに固定できます。逆にrunes={false}で旧記法モードに固定もできます。既存プロジェクトの移行中は、書き換え済みのファイルにrunes={true}を付けて、後戻りを防ぐ使い方ができます。

ここで注意が要ります。sv checkの説明に挙がっている検査は、未使用のCSS、アクセシビリティのヒント、JavaScript/TypeScriptのコンパイラーエラーです。旧記法そのものを検出する項目はありません。sv checkが旧記法を落とせるのは、runesモードで旧記法がコンパイルエラーになる、という前提が効いているときです。

sv checkをClaudeの作業ループに組み込む

sv checkは、Svelteプロジェクトのエラーと警告を洗い出すコマンドです。使うにはsvelte-checkパッケージをdevDependenciesに入れておきます。

npm i -D svelte-check
npx sv check --threshold error

--threshold errorを付けると、警告を出さずエラーだけを表示します。警告でも失敗させたいときは--fail-on-warningsを使います。特定の警告だけをエラー扱いにする--compiler-warningsもあります。

npx sv check \
  --compiler-warnings "css_unused_selector:ignore,a11y_missing_attribute:error"

sv checkは変更したファイルだけを対象にできません。FAQによれば、コンポーネントのpropの名前を変えたとき、使用側のエラーは変更していないファイルに出るため、変更ファイルだけの検査では見逃すからです。Claudeが1ファイルを編集するたびにプロジェクト全体が検査される前提で組みます。

PostToolUse hookで自動実行する

CLAUDE.mdに「変更後はsv checkを実行する」と書いても、Claudeが必ず実行するとは限りません。ドキュメントでも、特定のタイミングで必ず走らせたい処理は指示文ではなくhookで書くよう勧められています。

編集直後に走らせるには、PostToolUse hookを使います。Prettierの自動整形例と同じ形で、Edit|Writeでマッチさせ、標準入力のJSONから編集されたファイルのパスを取り出します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/sv-check.sh"
          }
        ]
      }
    ]
  }
}

スクリプト側では、Svelte関連のファイルだけを対象に絞ります。--output machineを指定すると、出力が行ごとの記録になり、エラー行にはERRORが付きます。これを判定に使います。

#!/bin/bash
# .claude/hooks/sv-check.sh
file=$(jq -r '.tool_input.file_path')
case "$file" in
  *.svelte|*.svelte.ts|*.svelte.js|*.ts) ;;
  *) exit 0 ;;
esac
 
out=$(npx sv check --output machine --threshold error 2>&1)
if echo "$out" | grep -q ' ERROR '; then
  echo "$out" | grep ' ERROR ' >&2
  exit 2
fi

終了コード2は、hookが処理をブロックする合図です。PostToolUseではツールがすでに実行済みなので、ブロックはできません。代わりに標準エラー出力の内容がClaudeへ渡されます。エラーの行番号つきのメッセージを受け取ったClaudeは、そのまま修正に進めます。

hookの全イベントと設定はClaude Code Hooks完全ガイドに、Lintを編集ごとに自動修正する型はPostToolUse hookのLint自動修正にあります。

終了時にまとめて検査する

プロジェクトが大きく、編集のたびに全体検査が重いなら、Stop hookに移す手もあります。Claudeが応答を終えるタイミングで1回だけ走らせる形です。ただしStop hookは終了そのものを止めて作業を続けさせます。8回連続でブロックしても進展がないとClaude Codeが上書きするため、stop_hook_activeを見て2回目以降は素通りさせる処理が要ります。使い分けはClaude Code hooksでテストを自動実行するが詳しいです。

既存プロジェクトをRunesへ移す

旧記法のコードが大量にあるなら、先に移行します。Svelteは移行スクリプトを用意しています。

npx sv migrate svelte-5

このスクリプトは、依存関係の更新、letから$stateへの書き換え、on:clickからonclickへの書き換え、<slot />から{@render}への書き換えなどを自動で行います。自動で移せない箇所は残り、@migrationというコメントで印が付きます。この印はコードベースを検索すれば拾えます。

Claudeに手作業の残りを頼むときは、この印を入口にします。

@migration コメントが残っている箇所を全部洗い出して。
1 ファイルずつ runes 記法に直し、直すたびに npx sv check --threshold error を
実行してエラー 0 を確認してから次へ進んで。

一括で全ファイルを頼むより、1ファイル単位で検査を挟むほうが、崩れた箇所を特定しやすくなります。

Svelte公式のAIツールを足す

Svelteは、AIエージェント向けの公式ツールを別に配布しています。npx sv add ai-toolsで導入でき、MCPサーバー、Svelte 5を正しく書かせるためのskills、Svelteファイル編集用のサブエージェントが入ります。Claude Codeでは、コミットした.claude/settings.json経由でプラグインが有効になります。プロジェクトを開くとワークスペースを信頼するか聞かれ、承認するとインストールされます。

手動で入れるなら、Claude Codeのプラグインマーケットプレイスとして追加します。

/plugin marketplace add sveltejs/ai-tools
/plugin install svelte

MCPサーバーにはSvelteコードを解析して問題を返すツール(svelte-autofixer)があり、Svelteの案内では、コードを書いたら指摘がなくなるまでこのツールを呼ばせる使い方が示されています。案内には、CLAUDE.mdやAGENTS.mdに貼るプロンプトの例も載っています。

CLAUDE.mdの規約、runesモード、sv checkのhookは、ここまでに設定した3層です。MCPツールはその上に足す4層目という位置づけです。AGENTS.mdだけを置いているプロジェクトでは注意点があります。Claude Codeは、CLAUDE.mdがどこにも無いときにだけAGENTS.mdを読みます。両方を効かせたいなら、CLAUDE.mdからAGENTS.mdを@で取り込むか、/configのProject instructionsをclaude-md-and-agents-mdにします。

よくあるつまずき

  • runesを普通の.tsに書いてしまう: $stateは.svelte.tsか.svelte.jsでしか使えません。CLAUDE.mdに拡張子の制約を書いておくと防げます。
  • $effectで値を更新して無限ループになる: ベストプラクティスは、effectを最後の手段と位置づけ、state更新を避けるよう求めています。算出は$derivedを使います。
  • $effect内にブラウザ判定を書く: effectはサーバーで実行されないため、if (browser)で囲む必要はありません。
  • 旧記法モードで配列のpushが反映されない: 旧記法では代入がリアクティブの起点なので、pushのあとに再代入が要りました。runesモードの$stateなら変更がそのまま追跡されます。
  • hookが重くて編集が遅くなる: sv checkは全体を見る仕様です。重いときは--ignoreでビルド成果物を除くか、Stop hookに移します。--ignoreは--no-tsconfigと併用したときだけ診断対象に効き、--tsconfigを使う場合はtsconfig.jsonの指定が対象を決めます。

まとめ

SvelteKitでClaude Codeを使うときの旧記法の混在は、指示だけでは止まりません。CLAUDE.mdには禁止と代替をセットで書き、runes: trueで旧記法をコンパイルエラーにし、sv checkをPostToolUse hookでClaudeの作業ループへ入れます。エラーが自動で返ってくる仕組みがあれば、規約は文章でなく、検証を通るかどうかで守られます。

Go言語のプロジェクトで同じ考え方を適用した例はClaude CodeでGoアプリを開発する手順にあります。

この記事を共有:XはてブLinkedIn