8.2 KiB
Gemini's Svelte 5 Learnings and Best Practices
This document outlines key insights and strategies developed during Svelte 5 (with runes mode) refactoring and bug-fixing tasks. It specifically addresses common pitfalls and effective patterns for an AI agent working with Svelte.
1. Async/Await in Svelte (Runes Mode)
The most frequent source of errors has been related to asynchronous operations and the await keyword.
-
Rule: Any function or block containing
awaitmust be explicitly markedasync.- This applies to callbacks within promise chains (
.then(),.catch(),.finally()), DOM event handlers (onclick,onchange), and Svelte lifecycle functions (onMount). - Common Error:
Cannot use keyword 'await' outside an async function. This happens whenawaitis used in a function/block that is notasync. - Solution: Ensure the surrounding function or the callback itself is declared
async.somePromise.finally(async () => { await someAsyncOperation(); });onclick={async () => { await someAsyncOperation(); }}onMount(async () => { await someAsyncOperation(); });
- This applies to callbacks within promise chains (
-
Asynchronous Navigation (
goto):- The correct pattern for asynchronous navigation using
gotoin Svelte 5 isawait goto(await resolve(path), options);. import { resolve } from '$app/paths';is crucial when usingresolve(). A missing import will lead tono-undeferrors forresolve.resolve(path)is crucial to ensure the path is correctly resolved before navigation, especially in universal applications.- The
awaitbeforegotois necessary if subsequent code depends on the navigation completing or ifinvalidateAllis used.
- The correct pattern for asynchronous navigation using
-
Clearing Stale Caches: When encountering confusing linting or build errors that don't seem to match the current code, especially after significant refactorings or dependency changes, always try running
npm run cleanto clear stale build artifacts and then re-lint/re-build. -
Refactoring Promise Chains:
- When converting
.then().catch().finally()chains toasync/awaitstructure, encapsulate the asynchronous operation within atry...catch...finallyblock. - Incorrect:
someAsyncFunc() .then(() => { await anotherAsyncFunc(); }) // ERROR: .then() callback is not async .catch(() => { /* ... */ }) .finally(() => { await lastAsyncFunc(); }); // ERROR: .finally() callback is not async - Correct:
async () => { try { const result = await someAsyncFunc(); await anotherAsyncFunc(result); } catch (error) { console.error(error); } finally { await lastAsyncFunc(); } }; - Note: If the entire handler (e.g.,
onclick) is alreadyasync, you canawaitthe promise directly within it.
- When converting
2. Reactive Declarations & Scoping
Svelte 5's runes mode introduces new ways of managing reactivity, and understanding variable lifecycles is key.
-
$statefor Reactive Variables:- Variables that are expected to trigger re-renders when their values change, or whose changes need to be observed, should be declared with
$state. - Common Error: Warnings like "This reference only captures the initial value of
data. Did you mean to reference it inside a closure instead?" or "Variablexis updated, but is not declared with$state(...)." - Solution: Declare the variable using
$state(initialValue). - Context for
dataprop: When adataprop (from a SvelteKitloadfunction) is accessed outside of reactive declarations (like$effector event handlers), Svelte might warn that it only captures its initial value. To ensure reactivity or to correctly process it, use it within$effector derive a$statevariable from it.
- Variables that are expected to trigger re-renders when their values change, or whose changes need to be observed, should be declared with
-
Function Scoping and Redeclaration:
- Common Error:
Identifier 'function_name' has already been declared. This occurs when functions with the same name are defined in overlapping scopes. - Solution:
- If a function is only needed within a specific block (e.g., an
onMountcallback), define it inside that block to limit its scope. - If a function is truly global and needs to be accessible from templates and multiple
onMountblocks, define it once outside of anyonMountand ensure it doesn't conflict with other definitions. - Be mindful of helper functions that might be implicitly pulled into global scope by the Svelte compiler if not correctly encapsulated.
- If a function is only needed within a specific block (e.g., an
- Common Error:
3. replace Tool Usage Strategy (Critical for AI)
My efficiency heavily relies on the replace tool, and precision is paramount.
- Exact
old_stringMatching:- The
old_stringparameter must precisely match the target text in the file, including all whitespace, indentation, newlines, and comments. Even a single character difference will cause the tool to fail with "0 occurrences found". - For multi-line replacements, always copy the exact block from the
read_fileoutput.
- The
- Contextual Specificity:
- Avoid generic
old_stringpatterns (e.g.,onclick={() => {) if there are many such occurrences in a file. Instead, expand theold_stringto include enough surrounding unique context (e.g., the entire button element or parentdiv) to ensure it matches only one instance (expected_replacements: 1).
- Avoid generic
- Iterative Refinement:
- For complex refactorings involving multiple changes in a file, perform changes in small, atomic steps.
- Always
read_filebefore eachreplaceoperation. This ensures theold_stringis based on the absolute latest content of the file, preventing mismatches due to previous modifications or unexpected formatting. - After each
replaceoperation, immediately runnpm run build(ornpm run lint) to validate the change and catch new errors early. This is crucial for catching cascading issues introduced by partial refactorings.
4. Error Debugging Workflow
- Prioritize Compiler/Build Errors: These are blocking issues that prevent the application from running. Address them first.
- Analyze Error Messages: Read the full error message carefully, including line numbers, and look for keywords (e.g.,
await,async,declared,undefined). - Consult Svelte Documentation: The Svelte compiler often provides helpful links (
https://svelte.dev/e/js_parse_error) which should be a first point of reference if the error is unfamiliar. - Re-read File Content: If a
replaceoperation fails or produces unexpected results, immediately useread_fileto verify the exact state of the file before attempting another change.
5. Safe Property Binding (Preventing props_invalid_value)
Svelte 5 enforces strict contracts for bound properties (bind:prop). If a component expects a property to have a fallback/default value, you cannot bind undefined to it.
- The Error:
Uncaught Svelte error: props_invalid_value. Cannot do bind:prop={undefined} when prop has a fallback value. - The Cause: Attempting to bind a variable that is currently
undefinedto a component prop that has a default value (e.g.,let { prop = false } = $props()). Svelte cannot determine whether to use the boundundefinedor the component's internal default, so it throws an error. - The Fix: Always initialize bound variables.
- Initialization: Ensure the variable you are binding to is initialized to a valid value (matching the prop's type) before the component mounts.
- Example:
let myVar = $state(false);instead oflet myVar = $state();.
- Example:
- Data Fetching: If the data comes from an asynchronous source (API, DB), ensure the object properties have default values immediately upon assignment, even before the data is fully populated.
- Good:
$slct.obj = { ...apiResult, myBoundProp: apiResult.myBoundProp ?? false }; - Bad:
$slct.obj = apiResult;(ifapiResult.myBoundPropis missing/undefined).
- Good:
- Updates/resets: When resetting or updating the object, explicitly re-initialize the bound properties.
obj = {};->obj = { myBoundProp: false };
- Initialization: Ensure the variable you are binding to is initialized to a valid value (matching the prop's type) before the component mounts.