Files
simplify-code/references/SVELTE.md
T
2026-06-23 01:17:03 +02:00

5.7 KiB

Svelte / SvelteKit support

Svelte is compiler-based and .svelte files mix three languages — an HTML-like template, one or more <script> / <script lang="ts"> blocks, and <style>. That means generic structural tooling needs special handling. This skill supports Svelte in two layers; use whichever the environment allows.

Detection strategy (in priority order)

1. Project's own Svelte tooling — PRIMARY, zero extra setup

Any SvelteKit project already ships the best Svelte analyzers. Use them as enrichment when present (run via the project's package manager; never install them yourself):

Tool Catches Invoke
svelte-check type errors, component validation, machine-readable diagnostics npx svelte-check --output machine
eslint-plugin-svelte 100+ rules: store misuse, reactive-statement bugs, SvelteKit violations, a11y project's eslint run on **/*.svelte
svelte/compiler warnings reactivity (non_reactive_update, state_referenced_locally), a11y (a11y_*), deprecations, unused CSS, perf (perf_*) compile()/parse() (needs Node)

Fold their output into the ledger as findings (detector: "svelte-check" etc.), classified track: llm unless the tool offers a safe autofix (eslint --fix), in which case track: ast-grep-equivalent (tool-applied) is fine.

These run only if already in the project (Node + the dev deps). Their absence never blocks a run — fall back to layer 2 / model-driven refactors.

Recipe — svelte-check → ledger (works today, no grammar):

# machine output is one diagnostic per line: TYPE filepath line col code message
npx --no-install svelte-check --output machine 2>/dev/null

For each ERROR/WARNING line, add a finding: detector: "svelte-check", file/start_line from the diagnostic, apply_track: llm, difficulty: semantic (or mechanical if the code is eslint --fix-able), status: pending. Same idea for eslint -f json **/*.svelte (use messages[].ruleId/line, fix present ⇒ mechanical).

2. ast-grep structural rules — OPT-IN (one-command grammar build)

ast-grep has no built-in Svelte grammar, so this is opt-in (a compiled grammar can't be bundled cross-platform). Setup is one command + a separate config — validated working:

  1. Build the grammar into grammars/svelte.<ext> (~1s; needs only a C compiler — no tree-sitter CLI, because the grammar ships a prebuilt parser.c):
    git clone --depth 1 https://github.com/tree-sitter-grammars/tree-sitter-svelte /tmp/tss
    cc -shared -fPIC -O2 -I /tmp/tss/src /tmp/tss/src/parser.c /tmp/tss/src/scanner.c \
       -o grammars/svelte.so          # .dylib on macOS, .dll on Windows
    
  2. Scan Svelte files with the Svelte config (no edits to the default config needed):
    ast-grep scan -c sgconfig.svelte.yml --json=compact <paths>
    
  3. Result (proven on fixtures/svelte/Messy.svelte): the typescript/javascript rule packs apply to the code inside <script> blocks via injection (e.g. indexOfincludes, ternary→Boolean), and rules-svelte/ adds template rules (on: event directives, <slot>).

grammars/ is gitignored (like the engine binary), so this stays a one-command local setup. Most users can rely on layers 1 + 3; turn this on for structural script-block cleanups at scale.

3. Model-driven semantic refactors — always available

For Svelte-specific refactors that no rule expresses, the model edits the relevant span with the guardrails below.

Svelte simplification catalog

Svelte 4 → 5 runes migration (mostly mechanical, but verify):

  • export let prop;let { prop } = $props();
  • let x = v; that is reactive → let x = $state(v);
  • $: doubled = x * 2; (pure computed) → let doubled = $derived(x * 2);
  • $: { sideEffect(); } (side effect) → $effect(() => { sideEffect(); });
  • createEventDispatcher() + dispatch('e') → callback props
  • <slot />{@render children?.()}
  • on:click={h}onclick={h} (template; see rules-svelte/template.yml)

Reactivity correctness (semantic — judgment required):

  • $effect used to compute a value → should be $derived (perf + correctness).
  • Object/array mutated without reassignment in legacy mode → won't react.
  • Redundant writable store used purely inside one component → local $state.
  • derived(store, ...) replaceable by $derived(...).

Mechanical vs semantic:

  • Mechanical: on: → property, export let$props(), <slot/>{@render}.
  • Semantic: $derived vs $effect choice, store→state, hoisting load functions.

SvelteKit guardrails — DO NOT BREAK

Refactor the insides freely; never violate these conventions:

  • +page.svelte / +layout.svelte: UI only. load/actions must NOT live here. Keep the children snippet/render in layouts.
  • +page.ts / +page.server.ts / +layout(.server).ts: load lives here; form actions only in +page.server.ts. Don't move load into a component. Don't change universal-vs-server (.server) semantics. A page load's data is page-scoped; only hoist to a layout load if siblings actually need it.
  • +server.ts: HTTP handlers (GET/POST/…) — preserve method semantics; no GET side effects.
  • +error.svelte must keep accepting error; +layout.svelte must keep accepting data from its load.
  • Named vs default form actions can't coexist on one page; named actions need action="?/name". Don't rename one without the other.
  • Never touch generated/$app/*/$env/* wiring or .svelte-kit/.

After any Svelte change, the verify gate should run the project's svelte-check and build/test from the ledger config, not just a generic build.