コンテンツにスキップ
svelte-vitals
日本語
Esc
移動開く⌘Jプレビュー
このページの内容

correctness/orphan-effect · 孤立した $effect

コンポーネント初期化の外で作られた $effect はランタイムで effect_orphan エラーになります。

重大度: critical · カテゴリ: correctness

チェック内容

コンポーネント初期化の外で実行されることが確定している $effect / $effect.pre 呼び出しを検出します。これらはランタイムで Svelte の effect_orphan エラーを投げます。

  • .svelte.ts / .svelte.js の runes モジュール、または .svelte<script module> ブロックのトップレベルの effect。モジュールの import 時に実行され、どのコンポーネントの初期化コンテキストにも属しません。
  • 同一ファイル内で宣言されたクラスのモジュールスコープでの new で、そのクラスの constructor が裸の $effect$effect.root で包まれていないもの)を作るもの。共有状態マネージャのパターンです。検出位置は new の行になります。

検出対象外: 関数内(ファクトリ関数や IIFE を含む)、$effect.root(...) コールバック内、クラスフィールド初期化子や static ブロック内、名前のないクラス式(const Store = class { … })内の effect。コンポーネント内でのみインスタンス化されるクラスと、他ファイルから import されたクラスも対象外です。

検出は関数境界を越えないため、ネストした関数をモジュール評価と取り違えることはありません。その代わりクロスファイルやファクトリ経由のケースは検出できません。なお、実行時に真にならないガードで囲まれた effect は依然として報告されます。ガードは静的に評価できないためです。

トップレベルの if や constructor 引数によるチェック(constructor(persist) { if (persist) $effect(...) })でガードされた effect も、そのガードが実行時に真にならない場合でも検出されます。ガードは静的に評価できないためです。意図的なガードであれば svelte-vitals-disable-next-line correctness/orphan-effect で抑制してください。

なぜ重要か

コンパイラはこれらをすべて警告なしで通し、失敗はランタイムでのみ起こります。サーバー側コンパイラは $effect/$effect.pre 呼び出しを丸ごと削除する(何も出力しない)ため、サーバーサイドレンダリングはエラーなく完了します。サーバーの 500 エラーにはなりません。クラッシュはクライアント側で、モジュールがブラウザで評価されるタイミング(典型的にはハイドレーション時)に起こります。サーバーレンダリングは成功したのに、その直後にクライアント JS が実行された瞬間に壊れる、という形です。開発中は特定のルートでしか import されない場合など気づかないことがあります。

リアクティブな effect は、コンポーネントの初期化中か明示的な $effect.root スコープ内でしか作れません。

修正方法

class QuizStateManager {
  bookmarks = $state<string[]>([]);
  constructor() {
    // ❌ ランタイムで effect_orphan(モジュールスコープにコンポーネントコンテキストはない)
    $effect(() => {
      saveToStorage(this.bookmarks);
    });
  }
}
export const quizState = new QuizStateManager();

$effect.root でスタンドアロンのリアクティブスコープを作ります(effect がアプリ全体と同じ寿命でよいならそのままで構いません。そうでなければ、返り値のクリーンアップ関数を確実に呼んでください)。

constructor() {
  $effect.root(() => {
    $effect(() => {
      saveToStorage(this.bookmarks);
    });
  });
}

または effect のセットアップをコンポーネント初期化時に行うよう構造を変えます。

class QuizStateManager {
  bookmarks = $state<string[]>([]);
  startPersisting() {
    $effect(() => {
      saveToStorage(this.bookmarks);
    });
  }
}
export const quizState = new QuizStateManager();
<script>
  import { quizState } from '$lib/store.svelte.js';
  quizState.startPersisting();
</script>

モードによる違い

ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。

無効化

個別に抑制するには、対象行の直前に <!-- svelte-vitals-disable-next-line correctness/orphan-effect --> を置きます。ルールごと無効化するには、次のように設定します。

export default {
  rules: {
    'correctness/orphan-effect': 'off'
  }
};