mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
9462d56be7
* better instructions for claude * remove file * better rules * better claude action * add api routes prefixes * typo * typo * fix * fix * add typegen explanations * remove npm run format
229 lines
10 KiB
Plaintext
229 lines
10 KiB
Plaintext
---
|
|
description:
|
|
globs: frontend/src/**/*.svelte
|
|
alwaysApply: false
|
|
---
|
|
# Svelte 5 Best Practices
|
|
|
|
This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. These rules MUST NOT be applied on svelte 4 files unless explicitly asked to do so.
|
|
|
|
## Reactivity with Runes
|
|
|
|
Svelte 5 introduces Runes for more explicit and flexible reactivity.
|
|
|
|
1. **Embrace Runes for State Management**:
|
|
* Use `$state` for reactive local component state.
|
|
```svelte
|
|
<script>
|
|
let count = $state(0);
|
|
|
|
function increment() {
|
|
count += 1;
|
|
}
|
|
</script>
|
|
|
|
<button onclick={increment}>
|
|
Clicked {count} {count === 1 ? 'time' : 'times'}
|
|
</button>
|
|
```
|
|
* Use `$derived` for computed values based on other reactive state.
|
|
```svelte
|
|
<script>
|
|
let count = $state(0);
|
|
const doubled = $derived(count * 2);
|
|
</script>
|
|
|
|
<p>{count} * 2 = {doubled}</p>
|
|
```
|
|
* Use `$effect` for side effects that need to run when reactive values change (e.g., logging, manual DOM manipulation, data fetching). Remember `$effect` does not run on the server.
|
|
```svelte
|
|
<script>
|
|
let count = $state(0);
|
|
|
|
$effect(() => {
|
|
console.log('The count is now', count);
|
|
if (count > 5) {
|
|
alert('Count is too high!');
|
|
}
|
|
});
|
|
</script>
|
|
```
|
|
|
|
2. **Props with `$props`**:
|
|
* Declare component props using `$props()`. This offers better clarity and flexibility compared to `export let`.
|
|
```svelte
|
|
<script>
|
|
// ChildComponent.svelte
|
|
let { name, age = $state(30) } = $props();
|
|
</script>
|
|
|
|
<p>Name: {name}</p>
|
|
<p>Age: {age}</p>
|
|
```
|
|
* For bindable props, use `$bindable`.
|
|
```svelte
|
|
<script>
|
|
// MyInput.svelte
|
|
let { value = $bindable() } = $props();
|
|
</script>
|
|
|
|
<input bind:value />
|
|
```
|
|
|
|
## Event Handling
|
|
|
|
* **Use direct event attributes**: Svelte 5 moves away from `on:` directives for DOM events.
|
|
* **Do**: `<button onclick={handleClick}>...</button>`
|
|
* **Don't**: `<button on:click={handleClick}>...</button>`
|
|
* **For component events, prefer callback props**: Instead of `createEventDispatcher`, pass functions as props.
|
|
```svelte
|
|
<!-- Parent.svelte -->
|
|
<script>
|
|
import Child from './Child.svelte';
|
|
let message = $state('');
|
|
function handleChildEvent(detail) {
|
|
message = detail;
|
|
}
|
|
</script>
|
|
<Child onCustomEvent={handleChildEvent} />
|
|
<p>Message from child: {message}</p>
|
|
|
|
<!-- Child.svelte -->
|
|
<script>
|
|
let { onCustomEvent } = $props();
|
|
function emitEvent() {
|
|
onCustomEvent('Hello from child!');
|
|
}
|
|
</script>
|
|
<button onclick={emitEvent}>Send Event</button>
|
|
```
|
|
|
|
## Snippets for Content Projection
|
|
|
|
* **Use `{#snippet ...}` and `{@render ...}` instead of slots**: Snippets are more powerful and flexible.
|
|
```svelte
|
|
<!-- Parent.svelte -->
|
|
<script>
|
|
import Card from './Card.svelte';
|
|
</script>
|
|
|
|
<Card>
|
|
{#snippet title()}
|
|
My Awesome Title
|
|
{/snippet}
|
|
{#snippet content()}
|
|
<p>Some interesting content here.</p>
|
|
{/snippet}
|
|
</Card>
|
|
|
|
<!-- Card.svelte -->
|
|
<script>
|
|
let { title, content } = $props();
|
|
</script>
|
|
|
|
<article>
|
|
<header>{@render title()}</header>
|
|
<div>{@render content()}</div>
|
|
</article>
|
|
```
|
|
* Default content is passed via the `children` prop (which is a snippet).
|
|
```svelte
|
|
<!-- Wrapper.svelte -->
|
|
<script>
|
|
let { children } = $props();
|
|
</script>
|
|
<div>
|
|
{@render children?.()}
|
|
</div>
|
|
```
|
|
|
|
## Component Design
|
|
|
|
1. **Create Small, Reusable Components**: Break down complex UIs into smaller, focused components. Each component should have a single responsibility. This also aids performance by limiting the scope of reactivity updates.
|
|
2. **Descriptive Naming**: Use clear and descriptive names for variables, functions, and components.
|
|
3. **Minimize Logic in Components**: Move complex business logic to utility functions or services. Keep components focused on presentation and interaction.
|
|
|
|
## State Management (Stores)
|
|
|
|
1. **Segment Stores**: Avoid a single global store. Create multiple stores, each responsible for a specific piece of global state (e.g., `userStore.js`, `themeStore.js`). This can help limit reactivity updates to only the parts of the UI that depend on specific state segments.
|
|
2. **Use Custom Stores for Complex Logic**: For stores with related methods, create custom stores.
|
|
```javascript
|
|
// counterStore.js
|
|
import { writable } from 'svelte/store';
|
|
|
|
function createCounter() {
|
|
const { subscribe, set, update } = writable(0);
|
|
|
|
return {
|
|
subscribe,
|
|
increment: () => update(n => n + 1),
|
|
decrement: () => update(n => n - 1),
|
|
reset: () => set(0)
|
|
};
|
|
}
|
|
export const counter = createCounter();
|
|
```
|
|
3. **Use Context API for Localized State**: For state shared within a component subtree, consider Svelte's context API (`setContext`, `getContext`) instead of global stores when the state doesn't need to be truly global.
|
|
|
|
## Performance Optimizations (Svelte 5)
|
|
|
|
When generating Svelte 5 code, prioritize frontend performance by applying the following principles:
|
|
|
|
### General Svelte 5 Principles
|
|
|
|
- **Leverage the Compiler:** Trust Svelte's compiler to generate optimized JavaScript. Avoid manual DOM manipulation (`document.querySelector`, etc.) unless absolutely necessary for integrating third-party libraries that lack Svelte adapters.
|
|
- **Keep Components Small and Focused:** Reinforcing from Component Design, smaller components lead to less complex reactivity graphs and more targeted, efficient updates.
|
|
|
|
### Reactivity & State Management
|
|
|
|
- **Optimize Computations with `$derived`:** Always use `$derived` for computed values that depend on other state. This ensures the computation only runs when its specific dependencies change, avoiding unnecessary work compared to recomputing derived values in `$effect` or less efficient methods.
|
|
- **Minimize `$effect` Usage:** Use `$effect` sparingly and only for true side effects that interact with the outside world or non-Svelte state. Avoid putting complex logic or state updates *within* an `$effect` unless those updates are explicitly intended as a reaction to external changes or non-Svelte state. Excessive or complex effects can impact rendering performance.
|
|
- **Structure State for Fine-Grained Updates:** Design your `$state` objects or variables such that updates affect only the necessary parts of the UI. Avoid putting too much unrelated state into a single large object that gets frequently updated, as this can potentially trigger broader updates than necessary. Consider normalizing complex, nested state.
|
|
|
|
### List Rendering (`{#each}`)
|
|
|
|
- **Mandate `key` Attribute:** Always use a `key` attribute (`{#each items as item (item.id)}`) that refers to a unique, stable identifier for each item in a list. This is critical for allowing Svelte to efficiently update, reorder, add, or remove list items without destroying and re-creating unnecessary DOM elements and component instances.
|
|
|
|
### Component Loading & Bundling
|
|
|
|
- **Implement Lazy Loading/Code Splitting:** For routes, components, or modules that are not immediately needed on page load, use dynamic imports (`import(...)`) to split the code bundle. SvelteKit handles this automatically for routes, but it can be applied manually to components using helper patterns if needed.
|
|
- **Be Mindful of Third-Party Libraries:** When incorporating external libraries, import only the necessary functions or components to minimize the final bundle size. Prefer libraries designed to be tree-shakeable.
|
|
|
|
### Rendering & DOM
|
|
|
|
- **Use CSS for Animations/Transitions:** Prefer CSS animations or transitions where possible for performance. Svelte's built-in `transition:` directive is also highly optimized and should be used for complex state-driven transitions, but simple cases can often use plain CSS.
|
|
- **Optimize Image Loading:** Implement best practices for images: use optimized formats (WebP, AVIF), lazy loading (`loading="lazy"`), and responsive images (`<picture>`, `srcset`) to avoid loading unnecessarily large images.
|
|
|
|
### Server-Side Rendering (SSR) & Hydration
|
|
|
|
- **Ensure SSR Compatibility:** Write components that can be rendered on the server for faster initial page loads. Avoid relying on browser-specific APIs (like `window` or `document`) in the main `<script>` context. If necessary, use `$effect` or check `if (browser)` inside effects to run browser-specific code only on the client.
|
|
- **Minimize Work During Hydration:** Structure components and data fetching such that minimal complex setup or computation is required when the client-side Svelte code takes over from the server-rendered HTML. Heavy synchronous work during hydration can block the main thread.
|
|
|
|
## General Clean Code Practices
|
|
|
|
1. **Organized File Structure**: Group related files together. A common structure:
|
|
```
|
|
/src
|
|
|-- /routes // Page components (if using a router like SvelteKit)
|
|
|-- /lib // Utility functions, services, constants (SvelteKit often uses this)
|
|
| |-- /stores
|
|
| |-- /utils
|
|
| |-- /services
|
|
| |-- /components // Reusable UI components
|
|
|-- App.svelte
|
|
|-- main.js (or main.ts)
|
|
```
|
|
2. **Scoped Styles**: Keep CSS scoped to components to avoid unintended side effects and improve maintainability. Avoid `:global` where possible.
|
|
3. **Immutability**: With Svelte 5 and `$state`, direct assignments to properties of `$state` objects (`obj.prop = value;`) are generally fine as Svelte's reactivity system handles updates. However, for non-rune state or when interacting with other systems, understanding and sometimes preferring immutable updates (creating new objects/arrays) can still be relevant.
|
|
4. **Use `class:` and `style:` directives**: For dynamic classes and styles, use Svelte's built-in directives for cleaner templates and potentially optimized updates.
|
|
```svelte
|
|
<script>
|
|
let isActive = $state(true);
|
|
let color = $state('blue');
|
|
</script>
|
|
|
|
<div class:active={isActive} style:color={color}>
|
|
Hello
|
|
</div>
|
|
```
|
|
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes. |