# Code Style and Conventions Standards for Svelte
This document outlines the code style and conventions standards for Svelte projects. Adhering to these guidelines promotes code readability, maintainability, and consistency across the project. These standards aim to provide best practices tailored to the latest version of Svelte. Enforcing these standards assists AI coding assistants (like GitHub Copilot, Cursor) in generating code that is compliant and coherent.
## 1. Formatting and Whitespace
Consistent formatting is crucial for code readability.
### 1.1. Indentation
**Do This:**
* Use 2 spaces for indentation. Avoid tabs.
* Configure your editor to automatically convert tabs to spaces.
**Don't Do This:**
* Mix tabs and spaces.
* Use a different number of spaces for indentation.
**Why:** Consistent indentation makes the code structure obvious and reduces visual clutter.
**Example:**
"""svelte
{#if condition}
<p>This is a paragraph.</p>
{/if}
{#if condition}
<p>This is a paragraph.</p>
{/if}
"""
### 1.2. Line Length
**Do This:**
* Limit lines to a maximum of 120 characters.
* Break long lines to improve readability.
**Don't Do This:**
* Have extremely long lines that require horizontal scrolling.
**Why:** Limiting line length improves readability, especially on smaller screens.
**Example:**
"""svelte
"""
### 1.3. Whitespace
**Do This:**
* Use a single blank line between top-level constructs (e.g., between "
Click me
<p>Count: {count}</p>
Click me<p>Count: {count}</p>
"""
## 2. Naming Conventions
Consistent naming conventions are critical for code understanding and maintainability.
### 2.1. Variables and Constants
**Do This:**
* Use camelCase for variable and constant names (e.g., "myVariable", "userCount").
* Use UPPER_SNAKE_CASE for constants (e.g., "API_URL", "DEFAULT_TIMEOUT").
* Use descriptive and meaningful names.
**Don't Do This:**
* Use single-letter variable names (except in "for" loops).
* Use abbreviations that are not widely understood.
**Why:** Clearly named variables and constants make the code self-documenting.
**Example:**
"""svelte
"""
### 2.2. Components
**Do This:**
* Use PascalCase for component names (e.g., "MyComponent", "UserProfile").
* Component files should match the component name (e.g., "MyComponent.svelte").
* Create reusable components for repeating patterns.
**Don't Do This:**
* Use lowercase or snake_case for component names.
* Include logic in root App.svelte file if possible.
* Use cryptic component names.
**Why:** Consistent component naming helps distinguish components from other elements.
**Example:**
"""
src/
├── components/
│ ├── UserProfile.svelte
│ └── ProductCard.svelte
└── App.svelte
"""
Within "UserProfile.svelte":
"""svelte
{user.name}
<p>Email: {user.email}</p>
{data.name}
<p>Email: {data.email}</p>
"""
### 2.3. Functions
**Do This:**
* Use camelCase for function names (e.g., "getData", "calculateTotal").
* Use descriptive names that indicate the function's purpose.
**Don't Do This:**
* Use vague or ambiguous function names.
* Use abbreviations unless they are universally understood.
**Why:** Clear function names improve code readability and maintainability.
**Example:**
"""svelte
"""
### 2.4. Props
**Do This:**
* Use camelCase: For props passed to Svelte components.
* Use Typescript typing: For explicit type declarations.
**Don't Do This:**
* Not documenting props or using PropTypes (since Svelte uses Typescript for props).
* Using snake_case instead of camelCase.
**Why:** Explicit props improve code readability and maintainability. With the introduction of Svelte 5 and newer conventions, it is essential to align with the latest standards.
**Example:**
"""svelte
Hello, {userName}!
{#if isLoggedIn}
<p>Welcome back!</p>
{/if}
Hello, {user_name}!
{#if is_logged_in}
<p>Welcome back!</p>
{/if}
"""
## 3. Svelte-Specific Style
### 3.1. Script Tags
**Do This:**
* Place "
{message}
{message}
"""
### 3.2. Style Tags
**Do This:**
* Use "
"""
### 3.3. Reactive Declarations
**Do This:**
* Use reactive declarations ("$:") for automatically updating values based on dependencies.
* Avoid complex logic inside reactive declarations; delegate to functions.
**Don't Do This:**
* Overuse reactive declarations for non-reactive logic.
* Mutate reactive variables directly without reassignment.
**Why:** Reactive declarations simplify state management and ensure data consistency.
**Example:**
"""svelte
<p>Total: {total}</p>
Update Price
"""
### 3.4. Event Handling
**Do This:**
* Use "on:" directives for event handling (e.g., "on:click", "on:submit").
* Keep event handlers concise and delegate complex logic to functions.
**Don't Do This:**
* Write lengthy event handlers directly in the template.
**Why:** Separating event handling logic improves readability and testability.
**Example:**
"""svelte
Click me
"""
### 3.5. Component Directives and Attributes
**Do This:**
* Use directives like "bind:", "transition:", "animate:", and "use:" for component enhancements.
* Use descriptive attribute names for clarity.
* Consider short-hand syntax where appropriate, e.g. prop
**Don't Do This:**
* Overuse or misuse directives leading to unnecessary complexity.
* Use obscure attribute names.
**Why:** Directives offer powerful ways to integrate behaviors and effects declaratively.
**Example:**
"""svelte
visible = !visible}>Toggle
{#if visible}
<p>This will fade in and out.</p>
{/if}
"""
### 3.6. Stores
**Do This:**
* Use Svelte stores for managing global state.
* Use the "$" prefix to access store values in the template.
* Create custom stores for complex state management logic.
**Don't Do This:**
* Overuse stores for component-local state.
* Mutate store values directly without using "set" or "update".
**Why:** Stores provide a centralized and reactive way to manage application state.
**Example:**
"""svelte
Increment
<p>Count: {$count}</p>
"""
### 3.7. Each Blocks
**Do This:**
* Include a "key" attribute when using "{#each}" blocks to help Svelte efficiently update the DOM. The key should be unique and stable.
* Keep the logic inside "{#each}" blocks simple. If complex, delegate to a component.
**Don't Do This:**
* Omit the "key" attribute when rendering dynamic lists.
* Perform complex operations directly within the "{#each}" block.
**Why:** Keys enable Svelte to efficiently update the DOM when list items change.
**Example:**
"""svelte
{#each items as item (item.id)}
<p>{item.name}</p>
{/each}
"""
## 4. Modern Approaches and Patterns (Svelte 5)
With the advent of Svelte 5, adopting recent conventions is crucial.
### 4.1. Reactive Statements in Svelte 5
**Do This:**
* Use explicit declarations for reactivity like "$derived" and "$effect".
* Understand the nuances between "$derived", which creates a new value reactively, and "$effect", which runs side effects.
**Don't Do This:**
* Rely on automatic reactivity for all variable assignments (which is deprecated).
**Why:** Explicit reactivity enhances clarity and predictability in your code.
**Example:**
"""svelte
<p>a: {a}</p>
<p>b: {b}</p>
<p>Sum: {sum}</p>
Update Values
"""
### 4.2. Enhanced State Management
**Do This:**
* Take advantage of new store features and patterns available in the latest Svelte versions.
* Adopt readable store patterns for complex application states.
**Don't Do This:**
* Ignore the advancements; keep using outdated approaches unnecessarily.
**Why:** Modern state management improves code organization and maintainability.
**Example:**
"""svelte
Increment
<p>Count: {$count}</p>
"""
### 4.3. Optimizing Performance
**Do This:**
* Use "{#each}" blocks with proper "key" attributes.
* Leverage "svelte:window", "svelte:document", and "svelte:body" for optimized event handling.
**Don't Do This:**
* Attach global event listeners directly without using these optimized elements.
**Why:** Optimized event handling helps in improving application performance.
**Example:**
"""svelte
"""
### 4.4 Component Composition and Reusability
**Do This:**
* Favor composition over inheritance. Build complex UIs by combining smaller, reusable components.
* Use slots effectively to create flexible components that accept custom content.
* Write clear and concise prop definitions with TypeScript to ensure type safety and prevent runtime errors.
**Don't Do This:**
* Create monolithic components with excessive logic and markup.
* Rely on deep component hierarchies that are difficult to understand and maintain.
* Neglect to document component props, making it harder for others to use them correctly.
**Why:** Encourages reusability and separation of concerns, making applications easier to maintain and scale.
**Example:**
"""svelte
// Reusable Button Component
{label}
// Usage in Parent Component
"""
### 4.5 Security Considerations
**Do This:**
* Sanitize user inputs to prevent cross-site scripting (XSS) attacks.
* Use Content Security Policy (CSP) to mitigate the risk of injecting malicious code into your application.
* Validate data on the server-side in addition to client-side validation.
**Don't Do This:**
* Directly render unsanitized user inputs in your templates.
* Rely solely on client-side validation for security.
* Store sensitive information in the browser's local storage or cookies without proper encryption.
**Why:** Security vulnerabilities can compromise your application and expose user data.
**Example:**
"""svelte
<p>Sanitized Input: {@html sanitizedValue}</p>
"""
## 5. Tooling and Automation
### 5.1. Linters and Formatters
**Do This:**
* Use ESLint with Svelte-specific plugins for linting.
* Use Prettier for automatic code formatting.
* Integrate these tools into your development workflow (e.g., using Git hooks).
**Don't Do This:**
* Ignore linting and formatting errors.
* Manually format code when automated tools are available.
**Why:** Linters and formatters enforce code style and help catch potential errors.
### 5.2. Testing
**Do This:**
* Write unit tests for components using testing frameworks like Jest or Vitest.
* Use component testing libraries to ensure component behavior.
* Write end-to-end tests for critical user flows.
**Don't Do This:**
* Skip writing tests.
* Rely solely on manual testing.
**Why:** Automated tests ensure code quality and prevent regressions.
### 5.3. Documentation
**Do This:**
* Use JSDoc or TypeScript annotations to document components, functions, and props.
* Maintain a README file with clear instructions on how to set up and run the project.
* Use tools like Storybook to document and showcase components.
**Don't Do This:**
* Neglect writing documentation.
* Write inaccurate or outdated documentation.
**Why:** Documentation makes it easier for others (and your future self) to understand and maintain the code.
## 6. Conclusion
Adhering to these code style and conventions will significantly enhance the quality, readability, and maintainability of Svelte projects. Consistently following these guidelines ensures that the codebase remains clean, understandable, and easy to collaborate on. Furthermore, enforcing these standards allows AI coding assitants to provide compliant and high-quality code suggestions. By adopting these best practices, development teams can build robust and scalable Svelte applications.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Core Architecture Standards for Svelte This document outlines the core architectural standards for Svelte projects, focusing on project structure, organization, and fundamental architectural patterns. This guide is intended to promote maintainability, performance, and scalability in Svelte applications. ## 1. Project Structure and Organization A well-defined project structure is crucial for maintainability and collaboration. Svelte projects benefit from a structure that separates concerns and promotes reusability. ### 1.1 Standard Directory Structure **Do This:** Adhere to a standardized directory structure that aligns with SvelteKit's conventions and best practices. **Don't Do This:** Avoid haphazardly placing files and folders without a clear organizational scheme. **Why:** A consistent structure makes it easier for developers to navigate and understand the project. It also facilitates automated tooling and deployment processes. """ ├── src/ │ ├── lib/ # Reusable components, utilities, and stores │ │ ├── components/ # Presentational and container components │ │ ├── utils/ # Utility functions │ │ ├── stores/ # Svelte stores for state management │ │ └── assets/ # Images, SVGs, and other static assets │ ├── routes/ # SvelteKit routes (pages and API endpoints) │ │ ├── +page.svelte # Page component │ │ ├── +page.ts # Page data loading logic │ │ ├── +server.ts # API endpoints │ │ └── components/ # Route-specific components │ ├── app.html # Root HTML template │ ├── app.d.ts # TypeScript definitions │ └── hooks.server.ts # Server-side hooks ├── static/ # Static assets (images, fonts, etc.) ├── svelte.config.js # Svelte configuration ├── vite.config.ts # Vite configuration ├── package.json # Project dependencies and scripts ├── README.md # Project documentation └── tsconfig.json # TypeScript configuration """ * **"src/lib":** Contains reusable components, utilities, and Svelte stores. This directory should be further organized by functionality. * **"src/routes":** Holds SvelteKit routes, where each directory represents a route. "+page.svelte" files are the Svelte components for each page, and "+page.ts" files are used for loading data into the page. "+server.ts" defines API endpoints. * **"static":** Contains static assets like images and fonts. These are served directly. * **"svelte.config.js":** SvelteKit configuration file. Includes adapters, preprocessors, and other Svelte-specific settings. * **"vite.config.ts":** Vite configuration file. Controls the build process, including plugins and optimizations. ### 1.2 Component Organization within "lib" **Do This:** Organize components within the "src/lib/components" directory into logical groupings based on feature or domain. Create subdirectories for related components. **Don't Do This:** Dump all components into a single "components" directory. **Why:** Grouping components makes it easier to locate and understand the purpose of each component. """ src/lib/components/ ├── UserProfile/ │ ├── UserProfileCard.svelte │ ├── UserProfileEditor.svelte │ └── UserAvatar.svelte ├── ProductCard.svelte └── Button.svelte """ **Example:** """svelte <!-- src/lib/components/UserProfile/UserProfileCard.svelte --> <script> import UserAvatar from './UserAvatar.svelte'; export let user; </script> <div class="user-profile-card"> <UserAvatar src={user.avatar} alt={user.name} /> <h2>{user.name}</h2> <p>{user.bio}</p> </div> """ ### 1.3 Route-Specific Components **Do This:** When a component is only used within a specific route, place it in a "components" subdirectory within the route directory. **Don't Do This:** Place route-specific components in the global "src/lib/components" directory. **Why:** This improves encapsulation and reduces the global namespace of components, leading to better maintainability. """ src/routes/ └── blog/ ├── [slug]/ │ ├── +page.svelte │ └── components/ │ └── BlogPostContent.svelte """ ### 1.4 Utility Functions **Do This:** Place reusable utility functions in the "src/lib/utils" directory. Group related functions into separate modules (e.g., "date.ts", "string.ts"). **Don't Do This:** Scatter utility functions throughout the codebase or duplicate them in multiple places. **Why:** Centralized utility functions promote code reuse and reduce redundancy. """typescript // src/lib/utils/date.ts export function formatDate(date: Date): string { return new Intl.DateTimeFormat('en-US').format(date); } """ ### 1.5 Svelte Stores **Do This:** Centralize state management using Svelte stores in the "src/lib/stores" directory. Use separate files for different domains of application state (e.g., "user.ts", "settings.ts"). **Don't Do This:** Manage state in a haphazard way or rely heavily on prop drilling. **Why:** Svelte stores provide a reactive and efficient way to manage application state. """typescript // src/lib/stores/user.ts import { writable } from 'svelte/store'; export const user = writable({ id: null, name: '', email: '' }); export function setUser(userData) { user.set(userData); } export function clearUser() { user.set({ id: null, name: '', email: '' }); } """ ## 2. Architectural Patterns Svelte applications can benefit from well-established architectural patterns to improve organization, testability, and scalability. ### 2.1 Component-Based Architecture **Do This:** Embrace a component-based architecture, breaking down the user interface into reusable components. **Don't Do This:** Create monolithic components that handle too much logic and rendering. **Why:** Component-based architecture promotes code reuse, testability, and maintainability. Svelte's core design encourages this naturally. """svelte <!-- src/lib/components/Button.svelte --> <script> export let label: string; export let onClick: () => void; </script> <button on:click={onClick}>{label}</button> """ ### 2.2 Container/Presentational Component Pattern **Do This:** Separate components into "container" (or "smart") components that handle data fetching and logic, and "presentational" (or "dumb") components that focus on rendering UI. **Don't Do This:** Mix data fetching and complex logic directly within presentational components. **Why:** This separation makes it easier to test and reuse components. Container components can be responsible for connecting to stores or APIs, while presentational components focus on displaying data. """svelte <!-- src/lib/components/ProductList.svelte (Presentational) --> <script> export let products: any[]; </script> <ul> {#each products as product (product.id)} <li>{product.name} - ${product.price}</li> {/each} </ul> <!-- src/routes/+page.svelte (Container) --> <script lang="ts"> import ProductList from '$lib/components/ProductList.svelte'; import { onMount } from 'svelte'; let products = []; onMount(async () => { const response = await fetch('/api/products'); products = await response.json(); }); </script> <ProductList {products} /> """ ### 2.3 Actions for DOM Manipulation **Do This:** Use Svelte actions for DOM manipulation and side effects related to specific elements. **Don't Do This:** Directly manipulate the DOM within component logic. **Why:** Actions provide a clean and declarative way to interact with DOM elements. """svelte <!-- src/lib/actions/tooltip.ts --> export function tooltip(node: HTMLElement, text: string) { let tooltipElement: HTMLDivElement; function createTooltip() { tooltipElement = document.createElement('div'); tooltipElement.textContent = text; tooltipElement.style.position = 'absolute'; tooltipElement.style.background = 'black'; tooltipElement.style.color = 'white'; tooltipElement.style.padding = '5px'; document.body.appendChild(tooltipElement); } function destroyTooltip() { if (tooltipElement) { tooltipElement.remove(); tooltipElement = null; } } function updateTooltipPosition(event: MouseEvent) { if (tooltipElement) { tooltipElement.style.top = "${event.pageY + 10}px"; tooltipElement.style.left = "${event.pageX + 10}px"; } } node.addEventListener('mouseover', createTooltip); node.addEventListener('mousemove', updateTooltipPosition); node.addEventListener('mouseout', destroyTooltip); return { update(newText: string) { text = newText; if (tooltipElement) { tooltipElement.textContent = text; } }, destroy() { node.removeEventListener('mouseover', createTooltip); node.removeEventListener('mousemove', updateTooltipPosition); node.removeEventListener('mouseout', destroyTooltip); destroyTooltip(); } }; } """ """svelte <!-- Usage in a component --> <div use:tooltip="'This is a tooltip'">Hover me</div> """ ### 2.4 Form Handling with Actions **Do This:** Employ Svelte actions for managing form state and validation instead of directly binding to input elements. This promotes cleaner, more reusable, and testable form logic. Libraries like "felte" are an excellent choice. **Don't Do This:** Use inline event handlers and manually manage input values within your components. **Why:** Using actions and form helper libraries encapsulates form logic and improves the overall architecture of forms. """bash npm install @felte/core @felte/validator-zod zod """ """svelte <script lang="ts"> import { createForm } from '@felte/core'; import { validator } from '@felte/validator-zod'; import { z } from 'zod'; const schema = z.object({ firstName: z.string().min(2, { message: "First name must be at least 2 characters." }), lastName: z.string().min(2, { message: "Last name must be at least 2 characters." }), email: z.string().email({ message: "Invalid email address." }), }); const { form, errors, isSubmitting, handleSubmit, data } = createForm({ onSubmit: async (values) => { alert(JSON.stringify(values)); }, validate: validator({ schema }), initialValues: { firstName: '', lastName: '', email: '' } }); </script> <form use:form> <div> <label for="firstName">First Name</label> <input type="text" id="firstName" name="firstName" aria-invalid={$errors.firstName ? 'true' : undefined}> {#if $errors.firstName} <span class="error">{$errors.firstName}</span> {/if} </div> <div> <label for="lastName">Last Name</label> <input type="text" id="lastName" name="lastName" aria-invalid={$errors.lastName ? 'true' : undefined}> {#if $errors.lastName} <span class="error">{$errors.lastName}</span> {/if} </div> <div> <label for="email">Email</label> <input type="email" id="email" name="email" aria-invalid={$errors.email ? 'true' : undefined}> {#if $errors.email} <span class="error">{$errors.email}</span> {/if} </div> <button type="submit" disabled={$isSubmitting}> Submit </button> </form> """ ## 3. State Management Efficient state management is essential for complex Svelte applications. ### 3.1 Choosing the Right State Management Approach **Do This:** Evaluate the scale of state complexity required for each project when choosing a state management solution. Choose Svelte stores for local component state and simple application-wide state. Consider more robust solutions, like Zustand or Redux, for complex scenarios. Pinia is a great choice if using Vue previously as it mimics the API. **Don't Do This:** Over-engineer state management in small projects or under-engineer it in large projects. **Why:** Appropriate state management prevents performance problems, improves maintainability, and enhances code readability. ### 3.2 Global State with Svelte Stores **Do This:** Use Svelte stores for managing global application state. Leverage derived stores for computed values and read-only stores for immutable data. **Don't Do This:** Mutate store values directly without using the "set", "update", or "subscribe" methods. **Why:** Stores provide a reactive and predictable way to manage application state. Using the built-in methods ensures that components are properly updated when the state changes. """typescript // src/lib/stores/cart.ts import { writable, derived } from 'svelte/store'; export const cartItems = writable([]); export const totalItems = derived(cartItems, ($cartItems) => $cartItems.length); export const totalPrice = derived(cartItems, ($cartItems) => { return $cartItems.reduce((total, item) => total + item.price * item.quantity, 0); }); """ """svelte <!-- Usage in a component --> <script> import { cartItems, totalItems, totalPrice } from '$lib/stores/cart'; function addItem(item) { cartItems.update(items => [...items, item]); } </script> <p>Total items: {$totalItems}</p> <p>Total price: {$totalPrice}</p> """ ### 3.3 Context API for Component-Specific State **Do This:** Use the Svelte context API ("setContext", "getContext") to share state and functionality between related components in a localized scope (such as within a specific route or component tree). **Don't Do This:** Overuse the context API for global state management. Svelte stores are generally better for that purpose. **Why:** The context API provides a way to avoid prop drilling and pass data down the component tree without explicitly passing props through each intermediate component. """svelte <!-- src/lib/components/ThemeProvider.svelte --> <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; const theme = writable('light'); setContext('theme', { theme, toggleTheme: () => theme.update(t => t === 'light' ? 'dark' : 'light') }); </script> <slot /> """ """svelte <!-- src/lib/components/ThemedComponent.svelte --> <script> import { getContext } from 'svelte'; const { theme, toggleTheme } = getContext('theme'); </script> <div class="themed-component" class:dark={$theme === 'dark'}> Current theme: {$theme} <button on:click={toggleTheme}>Toggle Theme</button> </div> <style> .themed-component { padding: 10px; border: 1px solid black; } .themed-component.dark { background-color: black; color: white; border-color: white; } </style> """ ## 4. API Communication Consider best practices for fetching data and interacting with APIs. ### 4.1 SvelteKit Load Functions **Do This:** Use SvelteKit's "load" functions ("+page.ts", "+layout.ts") to fetch data for routes and layouts. This allows for server-side rendering and improved SEO. **Don't Do This:** Fetch data directly in components using "onMount" unless it is client-side specific. **Why:** "load" functions execute on the server during SSR and can pre-render the page with data, improving performance and SEO. """typescript // src/routes/+page.ts import type { PageLoad } from './$types'; export const load: PageLoad = async ({ fetch }) => { const response = await fetch('/api/products'); const products = await response.json(); return { products }; }; """ """svelte <!-- src/routes/+page.svelte --> <script> export let data; const products = data.products; </script> <ul> {#each products as product (product.id)} <li>{product.name} - ${product.price}</li> {/each} </ul> """ ### 4.2 API Endpoints with SvelteKit **Do This:** Create API endpoints using SvelteKit's server routes ("+server.ts"). Follow RESTful principles for endpoint design. **Don't Do This:** Implement complex business logic directly within components. **Why:** Server routes allow you to create clean, maintainable APIs that can be used by your Svelte application and other clients. """typescript // src/routes/api/products/+server.ts import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; const products = [ { id: 1, name: 'Product A', price: 20 }, { id: 2, name: 'Product B', price: 30 } ]; export const GET: RequestHandler = async () => { return json(products); }; """ ### 4.3 Error Handling **Do This:** Implement robust error handling for API requests. Use "try...catch" blocks and provide informative error messages to the user. **Don't Do This:** Ignore potential errors or display generic error messages. **Why:** Proper error handling improves the user experience and helps debug issues. """typescript // Example within a SvelteKit load function export const load: PageLoad = async ({ fetch }) => { try { const response = await fetch('/api/products'); if (!response.ok) { throw new Error("Failed to fetch products: ${response.status}"); } const products = await response.json(); return { products }; } catch (error) { console.error(error); return { status: 500, error: new Error('Could not load products') }; } }; """ ## 5. Naming Conventions Consistent naming conventions improve code readability and maintainability. ### 5.1 Components **Do This:** Use PascalCase for component names (e.g., "UserProfileCard.svelte"). **Don't Do This:** Use camelCase or snake_case for component names. **Why:** PascalCase is the standard convention for components in most UI frameworks. ### 5.2 Variables and Functions **Do This:** Use camelCase for variables and functions (e.g., "userName", "formatDate"). **Don't Do This:** Use PascalCase or snake_case for variables and functions. **Why:** camelCase is the standard convention for variables and functions in JavaScript and TypeScript. ### 5.3 Files and Directories **Do This:** Use kebab-case for file and directory names (e.g., "user-profile.svelte", "api-utils"). **Don't Do This:** Use camelCase or snake_case for file and directory names. **Why:** kebab-case is a common convention for file and directory names in web development. ### 5.4 Svelte Stores **Do This:** Use camelCase for store names (e.g., "user", "settings"). Consider adding a "$" prefix when referencing the store's value in the template (e.g. "$user.name"). **Don't Do This:** Use PascalCase or snake_case for store names. **Why:** camelCase aligns with JavaScript naming conventions, and the "$" prefix clearly indicates that you're accessing a reactive value. ## 6. TypeScript Usage Leverage TypeScript to improve code quality and maintainability. ### 6.1 Strict Typing **Do This:** Enable strict mode in your "tsconfig.json" file (""strict": true"). **Don't Do This:** Disable strict mode or use "any" type excessively. **Why:** Strict typing helps catch errors early and improves code reliability. ### 6.2 Explicit Types **Do This:** Provide explicit types for component props, store values, and function parameters. **Don't Do This:** Rely solely on type inference, especially for complex types. **Why:** Explicit types improve code readability and prevent unexpected type errors. """typescript // src/lib/components/Button.svelte <script lang="ts"> export let label: string; export let onClick: () => void; </script> <button on:click={onClick}>{label}</button> """ ### 6.3 Interfaces and Types **Do This:** Define interfaces or types for data structures used in your application. **Don't Do This:** Use inline type definitions or anonymous types. **Why:** Interfaces and types provide a clear and reusable way to define data structures. """typescript // src/lib/types/user.ts export interface User { id: number; name: string; email: string; } """ """typescript // Usage in a store: import { writable } from 'svelte/store'; import type { User } from '$lib/types/user'; export const user = writable<User | null>(null); """ By adhering to these core architectural standards, Svelte developers can create maintainable, performant, and scalable applications. This document is a living guide and should be updated as new best practices and features emerge in the Svelte ecosystem.
# Component Design Standards for Svelte This document outlines component design standards for Svelte applications. These standards aim to promote reusable, maintainable, performant, and secure components, reflecting modern Svelte practices. ## 1. Component Architecture & Structure ### 1.1 Reusability Through Composition **Standard:** Favor component composition over inheritance or complex conditional logic within a single component to achieve reusability. **Why:** Composition promotes modularity, testability, and easier maintenance. Avoid tightly coupled components which are difficult to reuse in different contexts. Inheritance patterns are discouraged in Svelte. **Do This:** """svelte <!-- Button.svelte --> <script> export let label; export let onClick; export let variant = 'primary'; // default value $: buttonClass = "btn btn-${variant}"; </script> <button class={buttonClass} on:click={onClick}> {label} </button> <style> .btn { padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn-primary { background-color: blue; color: white; } .btn-secondary { background-color: gray; color: white; } </style> <!-- ParentComponent.svelte --> <script> import Button from './Button.svelte'; function handleClick() { alert('Clicked!'); } function handleSecondaryClick() { alert('Secondary Clicked!'); } </script> <Button label="Primary Button" onClick={handleClick} /> <Button label="Secondary Button" onClick={handleSecondaryClick} variant="secondary"/> """ **Don't Do This:** """svelte <!-- Button.svelte (Anti-pattern) Using overly complex conditional logic --> <script> export let label; export let onClick; export let isPrimary = true; </script> <button class:primary={isPrimary} class:secondary={!isPrimary} on:click={onClick}> {label} </button> <style> button { padding: 0.5rem 1rem; border: none; cursor: pointer; } .primary { background-color: blue; color: white; } .secondary { background-color: gray; color: white; } </style> """ **Explanation:** The "Do This" example demonstrates creating a reusable "Button" component with customizable "variant" property, promoting cleaner separation of concerns and improved reusability. The "Don't Do This" example tries to adapt the button using conditional logic which makes the code harder to read and maintain. ### 1.2 Single Responsibility Principle (SRP) **Standard:** Each component should have a single, well-defined purpose. **Why:** SRP simplifies component logic, improves readability, and makes components easier to test and maintain. **Do This:** """svelte <!-- UserProfile.svelte - Handles displaying user profile information. --> <script> export let user; </script> <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> <!-- AddressDisplay.svelte - Handles displaying address information. --> <script> export let address; </script> <div> <p>Address: {address.street}, {address.city}, {address.country}</p> </div> <!-- ParentComponent.svelte--> <script> import UserProfile from './UserProfile.svelte'; import AddressDisplay from './AddressDisplay.svelte'; const user = { name: 'John Doe', email: 'john.doe@example.com' }; const address = { street: '123 Main St', city: 'Anytown', country: 'USA' }; </script> <UserProfile {user} /> <AddressDisplay {address} /> """ **Don't Do This:** """svelte <!-- UserProfile.svelte (Anti-pattern) - Mixing multiple responsibilities. --> <script> export let user; export let showAddress = false; </script> <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> {#if showAddress} <p>Address: {user.address.street}, {user.address.city}, {user.address.country}</p> {/if} </div> """ **Explanation:** The SRP is upheld by creating distinct components for user profile and address display. This separation makes each component easier to develop, test, and reuse. Combining these into a single component with conditional rendering as shown in the "Don't Do This" example violates SRP and makes it harder to manage. ### 1.3 Atomic Design Principles **Standard:** Consider atomic design principles (Atoms, Molecules, Organisms) when structuring components. **Why:** It encourages a structured approach to building UIs, promotes reusability, and facilitates consistency. **Example:** * **Atom:** A single input field, a label, or a button. * **Molecule:** A search bar composed of an input field and a button. * **Organism:** A header section composed of a logo, navigation, and search bar. """svelte <!-- Atoms --> <!-- Input.svelte --> <input type={type} bind:value={value} placeholder={placeholder} /> <!-- Button.svelte --> <button on:click>{label}</button> <!-- Molecule --> <!-- SearchBar.svelte --> <script> import Input from './Input.svelte'; import Button from './Button.svelte'; let searchTerm = ''; function handleSearch() { alert("Searching for: ${searchTerm}"); } </script> <div> <Input type="text" placeholder="Search..." bind:value={searchTerm} /> <Button label="Search" on:click={handleSearch} /> </div> <!-- Organism --> <!-- Header.svelte --> <script> import SearchBar from './SearchBar.svelte'; </script> <header> <h1>My Website</h1> <nav> <a href="/">Home</a> <a href="/about">About</a> </nav> <SearchBar /> </header> """ **Explanation:** Demonstrates composition of smaller components into larger ones. ## 2. Component API Design ### 2.1 Props **Standard:** Use descriptive and consistently named props. Leverage typechecking using JSDoc or Typescript when possible. **Why:** Clear prop names improve component usability and reduce errors. Type checking prevents unexpected data types being passed, improving runtime stability. **Do This:** """svelte <!-- ProfileCard.svelte --> <script> /** * @type {{ name: string, email: string, imageUrl: string }} */ export let profile; /** * @type {boolean} */ export let showActions = false; /** * @type {() => void} */ export let onEdit; /** * @type {() => void} */ export let onDelete; </script> <div class="profile-card"> <img src={profile.imageUrl} alt={profile.name}> <h2>{profile.name}</h2> <p>{profile.email}</p> {#if showActions} <button on:click={onEdit}>Edit</button> <button on:click={onDelete}>Delete</button> {/if} </div> <style> /* Styling */ </style> """ **Don't Do This:** """svelte <!-- ProfileCard.svelte (Anti-pattern) --> <script> export let data; // Vague prop name export let actions; // Boolean, but unclear what it controls </script> <div> <h1>{data.name}</h1> <p>{data.email}</p> {#if actions} <button>Edit</button> <button>Delete</button> {/if} </div> """ **Explanation:** The 'Do This' example uses descriptive prop names ("profile", "showActions") and JSDoc type annotations, making the component's API clear. The anti-pattern uses vague prop names like "data" and "actions", making the intent unclear. ### 2.2 Events **Standard:** Use custom events for communication from child to parent components. **Why:** Custom events provide a clean and explicit mechanism for child components to signal changes or actions to their parent components. **Do This:** """svelte <!-- Counter.svelte --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); let count = 0; function increment() { count++; dispatch('update', { count }); } </script> <button on:click={increment}>Increment</button> <!-- Parent.svelte --> <script> import Counter from './Counter.svelte'; let currentCount = 0; function handleUpdate(event) { currentCount = event.detail.count; } </script> <Counter on:update={handleUpdate} /> <p>Count: {currentCount}</p> """ **Don't Do This:** """svelte <!-- Counter.svelte (Anti-pattern) - Directly mutating parent state --> <script> export let count; // Passed down and mutated directly. BAD! function increment() { count++; // Side effect outside of the component } </script> <button on:click={increment}>Increment</button> <!-- Parent.svelte --> <script> import Counter from './Counter.svelte'; let count = 0; </script> <Counter count={count} /> <!-- No way to actually know if the counter updated--> <p>Count: {count}</p> """ **Explanation:** The correct example uses "createEventDispatcher" to emit a custom 'update' event, propagating changes to the parent component. The incorrect example attempts to directly modify a prop passed down from the parent, which is an anti-pattern. This method is not reactive and doesn't properly update the "count" in the Parent component, as well as being potentially buggy. ### 2.3 Slots **Standard:** Utilize slots to allow flexible content injection into components. Explore named slots and scoped slots for more advanced scenarios. **Why:** Slots make components more adaptable to various use cases without requiring extensive prop configuration. **Do This:** """svelte <!-- Card.svelte --> <div class="card"> <header> <slot name="header"> <h2>Default Header</h2> </slot> </header> <main> <slot> <p>Default Content</p> </slot> </main> <footer> <slot name="footer"></slot> </footer> </div> <style> .card { border: 1px solid gray; padding: 1rem; margin: 1rem; } </style> <!-- ParentComponent.svelte --> <script> import Card from './Card.svelte'; </script> <Card> <svelte:fragment slot="header"> <h1>Custom Header</h1> </svelte:fragment> <p>Custom Content</p> <svelte:fragment slot="footer"> <button>Click Me</button> </svelte:fragment> </Card> """ **Don't Do This:** """svelte <!-- Card.svelte (Anti-pattern) - hardcoding specific content --> <div> <h1>Hardcoded Title</h1> <p>Hardcoded Content</p> </div> """ **Explanation:** The 'Do This' example uses named slots for the header and footer, allowing the parent component to inject custom content. It provides a default header and content in case the parent doesn't provide them. ### 2.4 Context API **Standard:** Employ the Svelte context API for sharing states and functions amongst deeply nested components *without* prop drilling. Use sparingly and carefully. Prefer stores when appropriate. **Why:** The context API avoids prop drilling and simplifies state management for related components. However, overuse can lead to implicit dependencies and reduced component isolation. **Do This:** """svelte <!-- ThemeProvider.svelte --> <script> import { setContext } from 'svelte'; const theme = { primaryColor: 'blue', secondaryColor: 'white' }; setContext('theme', theme); </script> <slot /> <!-- Button.svelte --> <script> import { getContext } from 'svelte'; const theme = getContext('theme'); </script> <button style="background-color: {theme.primaryColor}; color: {theme.secondaryColor}"> <slot></slot> </button> <!-- App.svelte --> <script> import ThemeProvider from './ThemeProvider.svelte'; import Button from './Button.svelte'; </script> <ThemeProvider> <Button>My Button</Button> </ThemeProvider> """ **Don't Do This:** """svelte <!-- Button.svelte (Anti-pattern) - Prop Drilling. --> <script> export let primaryColor; export let secondaryColor; </script> <button style="background-color: {primaryColor}; color: {secondaryColor}"> <slot></slot> </button> <!-- App.svelte --> <script> import Button from './Button.svelte'; </script> <Button primaryColor="blue" secondaryColor="white">My Button</Button> """ **Explanation:** The well-implemented version uses the "setContext" and "getContext" functions to share the theme across components without prop drilling. The anti-pattern passes "primaryColor" and "secondaryColor" via props, which can become cumbersome with deeply nested components. This becomes even worse as functionality expands and many more props must be passed to child components. ### 2.5 Stores **Standard:** Use Svelte stores for managing application-wide state, especially data that is shared and mutated across different components. Choose readable/writable, or custom stores appropriately. **Why:** Stores provide a centralized and reactive way to manage application state, simplifying data flow and component updates. **Do This:** """svelte <!-- store.js --> import { writable } from 'svelte/store'; export const user = writable({ name: 'John Doe', email: 'john.doe@example.com' }); <!-- Profile.svelte --> <script> import { user } from './store.js'; import { derived } from 'svelte/store'; const username = derived(user, ($user) => $user.name); </script> <h1>{$username}</h1> <p>{$user.email}</p> <!-- EditProfile.svelte --> <script> import { user } from './store.js'; function updateEmail(newEmail) { user.update(u => ({ ...u, email: newEmail })); } </script> <input type="email" bind:value={$user.email} on:change={() => updateEmail($user.email)}> """ **Don't Do This:** """svelte <!-- Profile.svelte (Anti-pattern) - Managing local state when it should be global --> <script> let user = { name: 'John Doe', email: 'john.doe@example.com' }; </script> <h1>{user.name}</h1> <p>{user.email}</p> <!-- EditProfile.svelte --> <script> import Profile from './Profile.svelte'; let profile = new Profile() let newEmail = profile.user.email //Very hard to get this to work correctly </script> <input type="email" value={newEmail}> """ **Explanation:** The "Do This" example correctly utilizes a writable store to manage the user data across the "Profile" and "EditProfile" components, making updates reactive and centralized. A derived store extracts the user's name. The anti-pattern in which each component independently manages its own copy of the state, leading to inconsistent data and difficulty in synchronization is to be avoided. ## 3. Component Style & Formatting ### 3.1 Scoped Styling **Standard:** Use "<style>" tags within Svelte components for scoped styling. Consider global styles sparingly and only when necessary. **Why:** Scoped styling prevents style conflicts and ensures that component styles are encapsulated, improving maintainability and predictability. **Do This:** """svelte <!-- Button.svelte --> <button class="my-button"> <slot /> </button> <style> .my-button { background-color: blue; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } </style> """ **Don't Do This:** """svelte <!-- styles.css (Anti-pattern) - global styling --> .my-button { background-color: blue; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } """ **Explanation:** The correct implementation encapsulates the button's styles within the component, preventing conflicts with other elements using the same class name. The anti-pattern, global styling, can lead to unexpected styling clashes across the application. While global styles can be useful to import some basic styles across the application, use component styling when possible to avoid collision and make code more organized. ### 3.2 Consistent Formatting **Standard:** Adhere to a consistent code formatting style using tools like Prettier. **Why:** Consistent formatting improves code readability and reduces cognitive load. **Configuration (Example using Prettier):** """json // .prettierrc.json { "semi": true, "singleQuote": true, "trailingComma": "es5", "tabWidth": 2, "useTabs": false } """ **Example:** Use the above config in your IDE or with a command line to format all the Svelte files on save to have consistent semi-colons, single quotes, tab width, etc. ### 3.3 CSS Preprocessors (Optional) **Standard:** Use CSS preprocessors like SCSS or PostCSS for enhanced styling capabilities (nesting, variables, mixins). Ensure that preprocessor integration is configured correctly. **Why:** Preprocessors improve CSS authoring experience, enhance code organization, and make styling more maintainable. **Example:** """svelte <!-- Button.svelte --> <button class="my-button"> <slot /> </button> <style lang="scss"> .my-button { background-color: $primary-color; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; &:hover { background-color: darken($primary-color, 10%); } } </style> """ **Note:** you may need to install "sass" and configure your bundler (e.g. Vite). ## 4. Component Performance ### 4.1 Minimize DOM Updates **Standard:** Leverage Svelte's reactivity to minimize DOM updates. Avoid manually manipulating the DOM whenever possible. **Why:** Svelte's compiler optimizes DOM updates, manual DOM manipulation can interfere with this optimization and degrade performance. **Do This:** """svelte <!-- Counter.svelte --> <script> let count = 0; function increment() { count++; } </script> <button on:click={increment}>Increment</button> <p>Count: {count}</p> """ **Don't Do This:** """svelte <!-- Counter.svelte (Anti-pattern) - Manual DOM manipulation --> <script> let count = 0; function increment() { count++; document.getElementById('count').textContent = count; //Avoid this } </script> <button on:click={increment}>Increment</button> <p id="count">Count: {count}</p> """ **Explanation:** Svelte handles DOM updates automatically when the "count" variable changes. Manually manipulating the DOM is inefficient and can cause inconsistencies. "document.getElementById" is also usually a design smell. ### 4.2 Optimize Event Handlers **Standard:** Use Svelte's event modifiers (e.g., "passive", "preventDefault", "stopPropagation") and consider debouncing or throttling event handlers to avoid excessive updates. Modern approaches are extremely important. **Why:** Optimized event handlers improve responsiveness and prevent performance bottlenecks, especially for frequently triggered events. **Example:** """svelte <!-- SearchInput.svelte --> <script> import { tick } from 'svelte'; // Used for Debouncing let searchTerm = ''; let debouncedSearchTerm = ''; async function handleInput() { await tick(); // Ensure that the DOM is updated before reading the value debouncedSearchTerm = searchTerm; // Debounce: capture the LAST valid value. console.log("Searching for: ${debouncedSearchTerm}"); } </script> <input type="text" bind:value={searchTerm} on:input={handleInput}> """ The tick function pauses execution of the function until the next browser render. If multiple keystrokes occur, tick is invoked multiple times, but the alert is only invoked after the last keystroke. ### 4.3 Lazy Loading **Standard:** Implement lazy loading for images, components, or data that are not immediately visible or required. Dynamically import large components. **Why:** Lazy loading reduces initial load time and improves perceived performance by deferring the loading of non-essential resources. **Example:** """svelte {#await import('./HeavyComponent.svelte')} <!-- loading state --> <p>Loading...</p> {:then HeavyComponent} <!-- loaded state --> <svelte:component this={HeavyComponent.default} /> {:catch error} <!-- error state --> <p>Error: {error.message}</p> {/await} """ ### 4.4 Avoid Unnecessary Reactive Statements **Standard:** Be mindful of reactive statements ("$:") and ensure they are only used when necessary for derived values or side effects that depend on component state. **Why:** Unnecessary reactive statements can lead to performance overhead by triggering unnecessary updates. **Do This:** """svelte <script> export let price; export let quantity; $: total = price * quantity; // Only update when price or quantity changes </script> <p>Total: {total}</p> """ **Don't Do This:** """svelte <script> export let price; export let quantity; let total; $: total = price * quantity; // Assigning to an already declared variable. Less efficient. </script> <p>Total: {total}</p> """ The difference is subtle but declaring the variable and assigning it within the reactive statement is more efficient. ## 5. Component Testing ### 5.1 Unit Tests **Standard:** Write unit tests for individual components to verify their behavior and functionality in isolation. **Why:** Unit tests provide confidence in component correctness, facilitate refactoring, and help prevent regressions. **Recommended Libraries:** Jest, Mocha, Chai, Testing Library. **Example (using Jest and Testing Library):** """javascript // Button.spec.js import { render, fireEvent } from '@testing-library/svelte'; import Button from './Button.svelte'; test('renders with correct label', () => { const { getByText } = render(Button, { label: 'Click Me' }); expect(getByText('Click Me')).toBeInTheDocument(); }); test('calls onClick handler when clicked', async () => { const onClick = jest.fn(); const { getByText } = render(Button, { label: 'Click Me', onClick }); await fireEvent.click(getByText('Click Me')); expect(onClick).toHaveBeenCalled(); }); """ ### 5.2 Integration Tests **Standard:** Write integration tests to verify interactions between components and ensure that they work correctly together. **Why:** Integration tests catch issues that may not be apparent from unit tests, such as incorrect prop passing or event handling. ### 5.3 End-to-End (E2E) Tests **Standard:** Use E2E tests to validate the overall application flow and ensure that components work correctly in a realistic environment. **Why:** E2E tests provide the highest level of confidence by simulating user interactions and verifying that the application behaves as expected! ## 6. Component Security ### 6.1 Input Sanitization **Standard:** Sanitize all user inputs to prevent Cross-Site Scripting (XSS) attacks. **Why:** XSS attacks can compromise the security of the application and expose user data. **Do This:** """svelte <script> import DOMPurify from 'dompurify'; // Need to install this export let content; $: sanitizedContent = DOMPurify.sanitize(content); </script> {@html sanitizedContent} """ **Don't Do This:** """svelte <script> export let content; </script> {@html content} <!-- Vulnerable to XSS --> """ The "Do This" pattern includes the DOMPurify library, which strips potentially dangerous HTML. ### 6.2 Secure Data Handling **Standard:** Handle sensitive data securely (e.g., passwords, API keys) and avoid storing them in client-side code where possible. Use environment variables and secure storage mechanisms. **Why:** Exposing sensitive data in client-side code can lead to security breaches and unauthorized access. ### 6.3 Avoid Third-Party Vulnerabilities **Standard:** Regularly update dependencies and scan for known vulnerabilities using tools like "npm audit" or "yarn audit". **Why:** Dependencies can contain security vulnerabilities that can be exploited by attackers. Keeping dependencies up-to-date minimizes the risk. These component design standards provide a comprehensive guide for developing robust, maintainable, performant, and secure Svelte applications. Adhering to these best practices will contribute to higher quality code and a smoother development experience.
# State Management Standards for Svelte This document outlines state management standards for Svelte applications. It provides guidelines and best practices for managing application state, data flow, and reactivity effectively, promoting maintainability, performance, and scalability. These standards are aligned with the latest Svelte version and modern development patterns. ## 1. Principles of State Management in Svelte Effective state management is crucial for building robust and maintainable Svelte applications. This section outlines the guiding principles. ### 1.1 Local vs. Global Store Selection * **Standard:** Decide whether state belongs in a component (local) or in a store (global). Use stores primarily for data shared across multiple, disparate components, or that represents application-level settings. * **Do This:** Use local state (component variables) when the data is specific to a single component's functionality and doesn't need to be accessed by other parts of the application. Use stores for data shared between components, representing application state, or for data that needs to persist across component lifecycles. * **Don't Do This:** Overuse stores for data that's only relevant to a single component. This creates unnecessary complexity. Also, don't rely solely on prop drilling for data that's needed by many components; consider a store. * **Why:** Clear state boundaries improve component isolation, making it easier to reason about component behavior and reducing the risk of unintended side effects. Using stores judiciously avoids unnecessary complexity. * **Example - Local State:** """svelte <script> let count = 0; function increment() { count += 1; } </script> <button on:click={increment}>Count: {count}</button> """ * **Example - Global Store:** """svelte // store.js import { writable } from 'svelte/store'; export const user = writable(null); // Component A <script> import { user } from './store.js'; </script> <h1>Welcome, {$user?.name}!</h1> // Component B <script> import { user } from './store.js'; function login(userData) { user.set(userData); } </script> <button on:click={() => login({ name: 'John Doe' })}>Login</button> """ ### 1.2 Unidirectional Data Flow * **Standard:** Enforce a top-down (parent-to-child) data flow. Child components should not directly modify the props they receive from their parents. * **Do This:** Pass data down via props. Use stores for shared state that child components can react to rather than modify directly. * **Don't Do This:** Mutate props directly within a child component. This makes it difficult to track the source of state changes and can lead to unexpected bugs. * **Why:** Unidirectional data flow simplifies debugging and makes it easier to understand how changes in one part of the application affect others. It also prevents cascading updates. * **Example:** """svelte // Parent.svelte <script> import Child from './Child.svelte'; let message = 'Hello from parent!'; </script> <Child {message} /> // Child.svelte <script> export let message; function handleClick() { // Don't do this: message = 'New message'; // Mutating the prop is an anti-pattern. } </script> <p>{message}</p> <button on:click={handleClick}>Try to change message</button> """ ### 1.3 Embrace Reactivity * **Standard:** Utilize Svelte's built-in reactivity effectively. Use reactive statements ("$:") to derive state and respond to changes. * **Do This:** Use reactive statements to automatically update the UI based on data changes. Use "$:" blocks to perform side effects (e.g., logging, API calls) triggered by state changes, but do so sparingly and only when component logic dictates. Minimize complex logic inside "$:" blocks. Extract logic into functions. * **Don't Do This:** Manually update the DOM or use imperative approaches when reactivity can handle the task. Overuse reactive statements for complex logic that should be encapsulated in functions. * **Why:** Svelte's reactivity simplifies state management, reducing boilerplate code and improving performance. Properly used reactivity results in clear, concise, and maintainable code. * **Example:** """svelte <script> let price = 10; let quantity = 2; $: total = price * quantity; // Reactive statement function increaseQuantity() { quantity += 1; } </script> <p>Price: {price}</p> <p>Quantity: {quantity}</p> <p>Total: {total}</p> <button on:click={increaseQuantity}>Increase Quantity</button> """ ## 2. Using Svelte Stores Svelte stores are a powerful way to manage global application state. ### 2.1 Store Types: Writable, Readable, and Derived * **Standard:** Choose the appropriate store type based on data mutability and dependency requirements. * **Do This:** * Use "writable" stores for state that can be directly updated. * Use "readable" stores for data that is sourced externally and read-only from the application's perspective. * Use "derived" stores for values that are computed based on other stores. * **Don't Do This:** Use "writable" stores for derived data that should be calculated from other stores. Create unnecessary complexity by manually managing dependencies when "derived" stores can handle it. * **Why:** Using the correct store type ensures clarity and prevents unintended state modifications. It also improves performance by only updating derived stores when their dependencies change. * **Example:** """svelte // store.js import { writable, readable, derived } from 'svelte/store'; export const count = writable(0); export const startTime = readable(new Date()); // Time the app started, never changes internally export const doubleCount = derived(count, ($count) => $count * 2); """ ### 2.2 Subscribing to Stores * **Standard:** Access store values using the "$" prefix within Svelte components. Alternatively, subscribe to stores manually for more control. * **Do This:** Use the "$" prefix for simple component integration. Manually subscribe to stores using "store.subscribe()" when you need to perform side effects or have specific lifecycle management requirements. * **Don't Do This:** Attempt to directly access the value of a store without subscribing or using the "$" prefix. Forget to unsubscribe from stores in components that are frequently created and destroyed, as this can lead to memory leaks. * **Why:** The "$" prefix simplifies store access within Svelte components. Manual subscriptions provide more flexibility for advanced scenarios but require careful cleanup (via "unsubscribe()" function). * **Example:** """svelte <script> import { count } from './store.js'; // Using the $ prefix (auto-subscribe) </script> <p>Count: {$count}</p> <script> import { onMount, onDestroy } from 'svelte'; import { count } from './store.js'; let currentCount; let unsubscribe; onMount(() => { unsubscribe = count.subscribe(value => { currentCount = value; }); }); onDestroy(() => { unsubscribe(); // Clean up the subscription to prevent memory leaks. }); </script> <p>Count: {currentCount}</p> """ ### 2.3 Updating Stores * **Standard:** Use the "set()", "update()", and "subscribe" methods provided by the Svelte store to modify state. * **Do This:** Use "store.set(newValue)" to directly set the store's value. Use "store.update(callback)" to update the store's value based on its previous value, ensuring atomicity. For complex updates, consider a custom store with methods that encapsulate the logic. * **Don't Do This:** Directly modify the store's value without using "set()" or "update()". Avoid complex logic directly within components when updating stores. * **Why:** Using the store's methods ensures that Svelte's reactivity system is properly triggered and that updates are performed safely. * **Example:** """svelte // store.js import { writable } from 'svelte/store'; export const count = writable(0); export const increment = () => { count.update(n => n + 1); } export const decrement = () => { count.update(n => n - 1); } // App.svelte <script> import { count, increment, decrement } from './store.js'; function handleIncrement() { increment(); } function handleDecrement() { decrement(); } </script> <p>Count: {$count}</p> <button on:click={handleIncrement}>+</button> <button on:click={handleDecrement}>-</button> """ ### 2.4 Custom Stores * **Standard:** Create custom stores to encapsulate complex state management logic and provide a clear API for interacting with the store. Use custom stores for asynchronous operations, persistent storage, or advanced state transformations. * **Do This:** Define custom methods for your store that handle specific update scenarios. Consider using a factory function to create multiple instances of a custom store. * **Don't Do This:** Put complex business logic directly in your components. Let the store's custom methods handle the app's business logic. * **Why:** Custom stores improve code organization, reusability, and testability. They allow you to encapsulate complex logic and provide a consistent API for interacting with the store. * **Example:** """svelte // customStore.js import { writable } from 'svelte/store'; function createTodoStore() { const { subscribe, set, update } = writable([]); return { subscribe, add: (text) => { update(todos => [...todos, { id: Date.now(), text, completed: false }]); }, toggle: (id) => { update(todos => todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); }, remove: (id) => { update(todos => todos.filter(todo => todo.id !== id)); } }; } export const todos = createTodoStore(); // App.svelte <script> import { todos } from './customStore.js'; let newTodo = ''; function addTodo() { todos.add(newTodo); newTodo = ''; } </script> <input type="text" bind:value={newTodo} /> <button on:click={addTodo}>Add Todo</button> <ul> {#each $todos as todo (todo.id)} <li> <input type="checkbox" checked={todo.completed} on:change={() => todos.toggle(todo.id)} /> {todo.text} <button on:click={() => todos.remove(todo.id)}>Remove</button> </li> {/each} </ul> """ ## 3. Context API The Context API is useful for providing data to a subtree of components without prop drilling ### 3.1 Providing Context * **Standard:** Use "setContext" to provide context to a component and its descendants. Context values are scoped to the current component. * **Do This:** Call "setContext" within a component's "<script>" tag. Use a unique key (usually a symbol or a string constant) to avoid naming conflicts. * **Don't Do This:** Call "setContext" outside the component's "<script>" tag. Use generic or undescriptive keys. Overuse context for state that's frequently changing; consider stores in such cases. * **Why:** "setContext" provides a straightforward mechanism for making data available to child components. Scoped keys prevent conflicts with other context providers. * **Example:** """svelte // Parent.svelte <script> import { setContext } from 'svelte'; import Child from './Child.svelte'; const theme = { textColor: 'black', backgroundColor: 'white' }; setContext('theme', theme); // Using a string key is acceptable for simple cases const user = { name: "Alice", age: 30 }; const userSymbol = Symbol('user'); // A more robust approach to ensure unique context keys. setContext(userSymbol, user); //Using a unique symbol to set the context </script> <Child /> // Child.svelte <script> import { getContext } from 'svelte'; const theme = getContext('theme'); const userSymbol = Symbol('user'); const user = getContext(userSymbol); //Retrieving context via the unique symbol </script> <div style="color: {theme.textColor}; background-color: {theme.backgroundColor}"> <p>This is a child component using theme from context.</p> <p>User Name: {user.name}</p> <p>User Age: {user.age}</p> </div> """ ### 3.2 Consuming Context * **Standard:** Use "getContext" to retrieve context values within a component. * **Do This:** Call "getContext" within a component's "<script>" tag, using the same key used when setting the context. * **Don't Do This:** Call "getContext" without ensuring that the context has been provided by an ancestor component. Directly modify the context value retrieved from "getContext"; treat it as read-only. * **Why:** "getContext" allows components to access data provided by their ancestors without explicit prop passing. Treating context values as read-only follows the unidirectional data flow principle. ## 4. Form Management ### 4.1 Two-Way Binding * **Standard:** Utilize Svelte's two-way binding ("bind:value") for form inputs. * **Do This:** Use "bind:value" for basic form input elements (text inputs, checkboxes, etc.). For more complex scenarios, such as formatting or validation, use a combination of "bind:value" and custom logic. * **Don't Do This:** Manually handle input changes using event listeners when "bind:value" can achieve the same result more concisely. * **Why:** "bind:value" simplifies form handling and reduces boilerplate code. * **Example:** """svelte <script> let name = ''; let checked = false; </script> <input type="text" bind:value={name} /> <p>Name: {name}</p> <input type="checkbox" bind:checked={checked} /> <p>Checked: {checked}</p> """ ### 4.2 Form Validation * **Standard:** Validate form data on input and submission. Provide clear and helpful error messages to the user. * **Do This:** Use reactive statements or custom functions to validate input values. Provide visual cues to indicate errors (e.g., highlighting invalid fields). * **Don't Do This:** Rely solely on client-side validation. Always validate data on the server as well. * **Why:** Form validation ensures data integrity and improves the user experience. * **Example:** """svelte <script> let email = ''; let emailError = ''; $: { if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { emailError = 'Invalid email address'; } else { emailError = ''; } } function handleSubmit() { if (emailError) { alert('Please fix the errors before submitting.'); return; } alert('Form submitted successfully!'); } </script> <input type="email" bind:value={email} class:error={emailError} /> {#if emailError} <p class="error-message">{emailError}</p> {/if} <style> .error { border: 1px solid red; } </style> """ ## 5. Considerations for Performance ### 5.1 Minimize Store Updates * **Standard:** Update stores only when necessary. Avoid unnecessary store updates that can trigger excessive re-renders. * **Do This:** Use "derived" stores to calculate values only when their dependencies change. Use "immutable" data structures to prevent accidental mutations that can trigger unnecessary updates (see below). * **Don't Do This:** Update stores on every component update, even if the data hasn't changed. Introduce unnecessary overhead. * **Why:** Reducing store updates improves performance by minimizing the number of re-renders. ### 5.2 Immutable Data Structures * **Standard:** Use immutable data structures (or treat your data as immutable) when working with stores, especially for complex objects and arrays. * **Do This:** Use methods like "Object.assign" or the spread operator ("...") to create new objects or arrays instead of modifying them directly. Consider using libraries like Immer or Immutable.js for more advanced scenarios. * **Don't Do This:** Directly modify objects or arrays stored in stores, as this can lead to unexpected behavior and performance issues if reactivity is missed or triggered multiple times. * **Why:** Immutable data structures prevent accidental mutations, making it easier to track state changes and improving performance. * **Example:** """svelte <script> import { writable } from 'svelte/store'; export const items = writable([{ id: 1, name: 'Item 1' }]); function addItem() { items.update(currentItems => { return [...currentItems, { id: Date.now(), name: 'New Item' }]; // Correct: Using the spread operator }); } function updateItemName(id, newName) { items.update(currentItems => { return currentItems.map(item => { if (item.id === id) { return {...item, name: newName}; //Correct } return item; }); }); } </script> <button on:click={addItem}>Add Item</button> """ ### 5.3 Optimize Reactive Statements * **Standard:** Use reactive statements judiciously. Avoid complex or expensive operations within reactive statements that can negatively impact performance. * **Do This:** Minimize side effects within reactive statements. Extract complex logic into functions that can be called from reactive statements. * **Don't Do This:** Perform expensive computations or API calls directly within reactive statements. Rely on "$:" for tasks that would be better handled with "onMount" or other lifecycle methods. * **Why:** Reactive statements are re-evaluated whenever their dependencies change. Optimizing these statements ensures that the UI remains responsive. The goal should be to have "$:" blocks only calculate or transform values, not perform actions that have side effects. ## 6. Testing ### 6.1 Store Testing * **Standard:** Thoroughly test stores to ensure they manage state correctly and provide the expected values. * **Do This:** Write unit tests for custom stores, focusing on their methods and state transitions. Mock dependencies (e.g., API calls) to isolate the store's logic. * **Don't Do This:** Skip testing stores, as this can lead to unexpected behavior and difficult-to-debug issues. Write integration tests before there are good unit tests. * **Why:** Testing stores ensures that the application's state management is reliable and predictable. ## 7. Security ### 7.1 Input Sanitization * **Standard:** Sanitize user inputs to prevent cross-site scripting (XSS) vulnerabilities. * **Do This:** Use Svelte's built-in HTML escaping to prevent XSS attacks. For more complex scenarios, consider using a dedicated sanitization library. * **Don't Do This:** Directly render user-provided data without sanitization. * **Why:** Input sanitization prevents malicious code from being injected into the application. ### 7.2 Secure Store Storage * **Standard:** Protect sensitive data stored in stores (e.g., access tokens, API keys). * **Do This:** Avoid storing sensitive data in client-side stores if possible. If necessary, encrypt the data before storing it and use secure storage mechanisms (e.g., cookies with the "HttpOnly" flag, the browser's "crypto" API). * **Don't Do This:** Store sensitive data in plain text in client-side stores or local storage. * **Why:** Protecting sensitive data prevents unauthorized access and potential security breaches. By adhering to these state management standards, Svelte developers can build applications that are maintainable, performant, secure, scalable.
# Performance Optimization Standards for Svelte This document outlines standards for optimizing Svelte applications, focusing on speed, responsiveness, and minimizing resource usage. These guidelines are designed to improve application performance and maintainability and reflect best practices for the latest version of Svelte. ## 1. Component Architecture and Data Management ### 1.1. Minimize Component Updates **Standard:** Only update components when necessary by carefully managing data dependencies and using fine-grained reactivity. **Why:** Excessive component updates lead to unnecessary re-renders, degrading performance. Svelte's reactivity is powerful, but it's crucial to use it judiciously. **Do This:** * Use derived stores ("derived") to compute values based on other stores, preventing unnecessary updates when the source stores haven't changed. * Employ local state variables ("let") for data that doesn't require reactivity across multiple components. * Use contextual stores sparingly; excessive reliance can lead to performance bottlenecks. * Implement custom equality checks when using mutable data structures inside stores. **Don't Do This:** * Avoid mutating objects or arrays directly inside stores when using "$:" as it triggers updates regardless of changes. Use ".set()" or ".update()" on the store. * Don't update stores with the same value. This is especially true with writable stores. **Example:** """svelte <script> import { derived, writable } from 'svelte/store'; // Writable store for user input const inputText = writable(''); // Derived store for debounced input value - Example const debouncedText = derived(inputText, ($inputText, set) => { const timeout = setTimeout(() => { set($inputText); }, 300); // Debounce time in milliseconds return () => clearTimeout(timeout); // Cleanup function }); // Local variable for internal state let counter = 0; function increment() { counter += 1; // Does NOT trigger rerender of the page. } // Updating the store correctly function updateInput(event) { inputText.set(event.target.value); } </script> <input type="text" on:input={updateInput} /> <p>Debounced Text: {$debouncedText}</p> <p>Counter: {counter}</p> <button on:click={increment}>Increment</button> """ ### 1.2. Use "{#each}" Blocks Efficiently **Standard:** Optimize "{#each}" blocks for performance by providing unique keys and avoiding unnecessary re-renders of list items. **Why:** Svelte's "{#each}" block is efficient, but incorrect usage can lead to performance bottlenecks if items are frequently added, removed, or reordered. **Do This:** * Provide a unique "key" attribute to each item in the list. This helps Svelte identify which items have changed and only update those specific elements. * Use immutable data structures when rendering lists from stores. If data changes, create new copies. If the data isn't immutable, make sure to use the key binding, or Svelte will not know what has changed and re-render everything. * Consider using pagination or virtualization for very long lists to avoid rendering all items at once. **Don't Do This:** * Avoid using the index as a key if the list items can be reordered, as this can cause incorrect updates. This is a frequent source of bugs. * Don't mutate list data directly; instead, create new arrays or objects to trigger updates. **Example:** """svelte <script> import { writable } from 'svelte/store'; const items = writable([ { id: 1, name: 'Item A' }, { id: 2, name: 'Item B' }, { id: 3, name: 'Item C' } ]); function addItem() { items.update(currentItems => [...currentItems, { id: Date.now(), name: 'New Item' }]); } </script> <button on:click={addItem}>Add Item</button> {#each $items as item (item.id)} <!-- Explicit Key Binding --> <div key={item.id}> {item.name} </div> {/each} """ ### 1.3. Optimize Component Size **Standard:** Keep components focused and relatively small to reduce rendering overhead. **Why:** Large, complex components can be challenging to reason about and can lead to slower rendering times. **Do This:** * Break down large components into smaller, reusable components. * Move complex logic into separate functions or modules. * Use slots for flexible content injection. **Don't Do This:** * Don't create "God Components" that handle too much logic and rendering. **Example:** Instead of: """svelte <!-- Bad example of a large component --> <script> let data = [...]; //Lots of data let filter = ''; let sortOrder = 'asc'; function filterData() { ... } function sortData() { ... } function renderItem(item) { ... } </script> <div> <input bind:value={filter}/> <button on:click={sortData}>Sort</button> {#each filterData(data) as item} {renderItem(item)} {/each} </div> """ Do This: Split it into smaller components: """svelte <!-- Good Example --> <script> import FilterInput from './FilterInput.svelte'; import SortButton from './SortButton.svelte'; import ItemList from './ItemList.svelte'; let data = [...]; let filter = ''; let sortOrder = 'asc'; function sortData() { ... } </script> <div> <FilterInput bind:filter={filter} /> <SortButton on:click={sortData} /> <ItemList data={data} filter={filter} /> </div> """ ## 2. Code Splitting and Lazy Loading ### 2.1. Implement Code Splitting **Standard:** Split your application into smaller chunks to reduce the initial load time. **Why:** Code splitting allows browsers to download only critical code initially, improving the perceived performance of your application. Svelte integrates with bundlers like Vite and Rollup, making code splitting relatively straightforward. **Do This:** * Use dynamic imports ("import()") to load components or modules on demand. * Configure your bundler (Vite/Rollup) to automatically split code based on import statements. * Lazy-load routes in your application. **Don't Do This:** * Avoid loading all your application code in a single bundle. **Example:** """svelte <script> let component; async function loadComponent() { component = (await import('./MyComponent.svelte')).default; } </script> {#if component} <svelte:component this={component} /> {:else} <button on:click={loadComponent}>Load Component</button> {/if} """ ### 2.2. Lazy Load Images and Other Assets **Standard:** Defer the loading of non-critical assets like images until they are needed. **Why:** Lazy loading reduces initial page load time and conserves bandwidth by only loading assets when they are visible in the viewport. **Do This:** * Use the "loading="lazy"" attribute for images. * Implement custom lazy loading for components or sections of your application. * Consider using a library like "svelte-lazy" for more advanced lazy-loading scenarios. **Don't Do This:** * Avoid loading all images on page load, especially for long pages. **Example:** """svelte <img src="my-image.jpg" alt="My Image" loading="lazy"> """ ## 3. Binding and Event Handling ### 3.1. Optimize Event Listeners **Standard:** Use passive event listeners for scroll and touch events to improve scrolling performance. Also, use delegated events when appropriate. **Why:** Passive event listeners allow the browser to handle scrolling smoothly without waiting for JavaScript to execute. **Do This:** * Add the "passive: true" option to event listeners for scroll and touch events. * Use delegated events where multiple child element needs to listen to the same event. **Don't Do This:** * Avoid blocking the main thread with long-running event handlers during scrolling. **Example:** """svelte <svelte:window on:scroll={() => { // Your scroll handling logic here }} options={{ passive: true }} /> """ ### 3.2. Minimize Two-Way Binding **Standard:** Use two-way binding ("bind:value") only when necessary, as it can lead to performance issues with frequent updates. **Why:** Two-way binding can trigger frequent component updates, especially with text inputs. **Do This:** * Use one-way binding and event listeners for more control over updates. * Consider debouncing input events to reduce the frequency of updates. **Don't Do This:** * Avoid using two-way binding for large data sets or complex components. **Example:** Instead of: """svelte <!-- Potentially problematic two-way binding --> <input bind:value={myValue} /> """ Do This: """svelte <script> import { writable } from 'svelte/store'; const myValue = writable(""); function updateValue(event) { myValue.set(event.target.value); } </script> <input type="text" on:input={updateValue} /> <p>Value: {$myValue}</p> """ ## 4. Svelte Specific Optimizations ### 4.1. Leveraging Svelte's Reactivity **Standard:** Utilize Svelte's built-in reactivity features effectively to minimize manual DOM manipulation. **Why:** Svelte's compiler optimizes updates efficiently. Avoid directly manipulating the DOM unless absolutely necessary. **Do This:** * Use reactive declarations ("$:") for derived values and side effects. * Rely on Svelte's component updates for rendering changes. * Use transition directives instead of manually handling animations whenever possible. **Don't Do This:** * Avoid using "document.querySelector" or similar methods to directly manipulate the DOM within Svelte components unless critically necessary. **Example:** """svelte <script> let count = 0; $: doubled = count * 2; // Reactive declaration function increment() { count += 1; } </script> <button on:click={increment}>Increment</button> <p>Count: {count}</p> <p>Doubled: {doubled}</p> """ ### 4.2. Efficient Use of Context API **Standard:** Use the Svelte context API thoughtfully, balancing convenience with potential performance implications. **Why:** Excessive use of context, especially with frequent updates, can cause unnecessary re-renders in components that depend on the context. **Do This:** * Provide context at the highest level possible to minimize the number of components affected by context changes. * Consider using stores instead of context for data that needs to be shared and updated frequently. * Memoize values passed via context if they are expensive to compute. **Don't Do This:** * Avoid updating context values unnecessarily. **Example:** """svelte <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; // Create a store to hold context data const myContext = writable({ value: 'Initial Value' }); // Set the context value setContext('myContextKey', myContext); </script> <slot /> """ """svelte <script> import { getContext } from 'svelte'; import { onMount } from 'svelte'; // Get the context value const myContext = getContext('myContextKey'); let value; // Subscribe to the store to update the local variable const unsubscribe = myContext.subscribe(val => { value = val.value; }); onMount(() => { return () => unsubscribe(); }); //Unsubscribing on destory is imporant. </script> <p>Context Value: {value}</p> """ ## 5. External Libraries and Tooling ### 5.1. Use SvelteKit's Load Function Wisely **Standard**: Return only the data you need and avoid unnecessary computations in load functions. **Why**: Load functions run on both the server and the client. Expensive operations can slow down both the initial server render and subsequent client-side navigations. **Do This**: * Fetch only the data necessary for the current page. Avoid fetching entire databases without pagination or filtering. * Move complex data transformations or business logic into separate modules and cache the results if appropriate. * Utilize the "depends" function to invalidate the "load" function only when necessary dependencies change. **Don't Do This**: * Perform database queries directly within components. This breaks separation of concerns and couples your components to the backend. * Include large, unused datasets in the returned "load" function data. **Example:** """typescript // src/routes/blog/[slug]/+page.ts import { getBlogPost } from '$lib/blog'; // Assume this fetches a blog post from a database or CMS export async function load({ params, fetch }) { const post = await getBlogPost(params.slug, fetch); //Assuming getBlogPost now uses fetch api if (!post) { error(404, 'Post not found'); } return { title: post.title, content: post.content, // Only return the necessary parts of the post slug: params.slug }; } """ ### 5.2 Performance Monitoring **Standard**: Use browser developer tools to identify and address performance bottlenecks. **Why**: Profilers and performance monitoring tools are the most reliable way to identify and resolve perfomance related issues **Do This**: * Use the "Performance" tab in Chrome DevTools (or similar in other browsers) to record and analyze the performance of your Svelte applications. * Identify slow-rendering components, long-running JavaScript functions, and excessive DOM manipulations. * Use Svelte Devtools for Component inspection * Use Lighthouse reporting to identify key areas for performance improvement. **Don't Do This**: * Rely solely on intuition. Performance is often counterintuitive. ## 6. Security Considerations While this document is primarily focused on performance, security and performance often intersect: ### 6.1. Sanitize User Input **Standard:** Always sanitize user input to prevent XSS (Cross-Site Scripting) vulnerabilities, which can negatively impact performance by injecting malicious code. **Why:** XSS vulnerabilities allow attackers to inject malicious scripts into your application, potentially leading to data theft, session hijacking, or defacement of your website. Additionally, unsanitized inputs may contain special characters or escape sequences that can lead to unpredictable behavior. **Do This:** * Use Svelte's built-in HTML encoding to prevent malicious code from being injected into your application. * Employ a library like DOMPurify to sanitize HTML input from users. * Avoid using "{@html ...}" directive with user-provided data without proper sanitization. **Don't Do This:** * Never trust user input directly without proper sanitization. **Example:** """svelte <script> import DOMPurify from 'dompurify'; let userInput = ''; let sanitizedHTML = ''; function sanitizeInput() { sanitizedHTML = DOMPurify.sanitize(userInput); } </script> <input bind:value={userInput} on:input={sanitizeInput} /> <div> {@html sanitizedHTML} </div> """ ### 6.2. Secure API Communication **Standard:** Use HTTPS for all API communication to protect data in transit and prevent man-in-the-middle attacks. **Why:** Using HTTPS ensures that data exchanged between your application and backend API is encrypted, preventing eavesdropping and tampering. Unencrypted communication can expose sensitive information and compromise user data. **Do This:** * Ensure all API endpoints use HTTPS (SSL/TLS) encryption. * Use appropriate CORS (Cross-Origin Resource Sharing) policies to restrict access to your API. * Validate and sanitize data received from the API to prevent injection attacks. **Don't Do This:** * Avoid using HTTP for sensitive data transmission or API communication in production environments. ### 6.3. Proper Error Handling **Standard:** Implement robust error handling to prevent information leakage and denial-of-service attacks. **Why:** Proper error handling prevents sensitive information from being exposed in error messages and prevents attackers from exploiting vulnerabilities to crash the application. **Do This:** * Handle exceptions and errors gracefully and log relevant information for debugging. * Avoid revealing sensitive information (e.g., database connection strings, API keys) in error messages. * Implement rate limiting to prevent abuse and denial-of-service attacks. **Don't Do This:** * Avoid displaying detailed error messages directly to users in production environments. This document provides a starting point for defining Svelte coding standards focused on performance optimization. It should evolve as the framework and best practices change and should be tailored to the specific needs of each project.
# Testing Methodologies Standards for Svelte This document outlines the recommended testing methodologies and best practices for Svelte applications. Effective testing ensures application reliability, maintainability, and performance. It covers unit, integration, and end-to-end testing with Svelte-specific examples and considerations. ## 1. General Testing Principles * **Do This:** Adhere to the testing pyramid: a large base of unit tests, a smaller layer of integration tests, and an even smaller peak of end-to-end tests. * **Don't Do This:** Rely solely on end-to-end tests. They are slower, more brittle, and provide less specific feedback than unit or integration tests. * **Why:** The testing pyramid promotes a balanced testing strategy that maximizes coverage and minimizes testing costs. * **Code Example (Pyramid Visualization - Conceptual):** """ # Unit Tests: Many, Fast, Isolated # Integration Tests: Fewer, Slower, Interacting Units # E2E Tests: Fewest, Slowest, Full Application """ ## 2. Unit Testing ### 2.1. Purpose and Scope * **Do This:** Unit test individual components, functions, and modules in isolation. Focus on validating the logic within each unit. Aim for high code coverage for critical components. * **Don't Do This:** Neglect unit testing in favor of integration or end-to-end tests. This makes isolating bugs significantly harder. * **Why:** Unit tests verify the behavior of individual units of code, making it easier to identify and fix bugs early in the development process. ### 2.2. Tools and Libraries * **Do This:** Use Jest or Vitest as a test runner and assertion library. Consider using "@testing-library/svelte" (or "@testing-library/dom" directly) for rendering and interacting with components in a user-centric way. * **Don't Do This:** Use assertion libraries that are not well-maintained or lack Svelte-specific utilities. * **Why:** Jest and Vitest provide excellent performance, mocking capabilities, and broad ecosystem support. "@testing-library/svelte" promotes testing components as a user would interact with them. ### 2.3. Writing Effective Unit Tests * **Do This:** Write tests that are readable, maintainable, and focused on a single concern. Use descriptive test names that clearly communicate the expected behavior. Utilize "describe" blocks to organize tests. * **Don't Do This:** Write overly complex or brittle tests that are difficult to understand or maintain. Avoid testing implementation details that are likely to change. Don't assert vague or overly broad outcomes. * **Why:** Readable and maintainable tests improve the overall quality of the codebase and make it easier to refactor and add new features. Testing behavior (what the user sees/experiences) rather than implementation leads to more robust and less fragile tests. ### 2.4. Unit Testing Examples #### 2.4.1. Testing a Simple Component """svelte <!-- Counter.svelte --> <script> let count = 0; function increment() { count += 1; } </script> <button on:click={increment}> Count: {count} </button> """ """javascript // Counter.spec.js (using Vitest and @testing-library/svelte) import { render, fireEvent } from '@testing-library/svelte'; import Counter from './Counter.svelte'; import { describe, it, expect } from 'vitest'; describe('Counter Component', () => { it('should render the initial count', () => { const { getByText } = render(Counter); expect(getByText('Count: 0')).toBeInTheDocument(); }); it('should increment the count when the button is clicked', async () => { const { getByText } = render(Counter); const button = getByText('Count: 0'); await fireEvent.click(button); expect(getByText('Count: 1')).toBeInTheDocument(); }); }); """ #### 2.4.2. Testing Component Properties """svelte <!-- Greeting.svelte --> <script> export let name; </script> <h1>Hello, {name}!</h1> """ """javascript // Greeting.spec.js import { render } from '@testing-library/svelte'; import Greeting from './Greeting.svelte'; import { describe, it, expect } from 'vitest'; describe('Greeting Component', () => { it('should render the greeting with the provided name', () => { const { getByText } = render(Greeting, { props: { name: 'World' } }); expect(getByText('Hello, World!')).toBeInTheDocument(); }); it('should render the greeting with a different name', () => { const { getByText } = render(Greeting, { props: { name: 'Svelte' } }); expect(getByText('Hello, Svelte!')).toBeInTheDocument(); }); }); """ #### 2.4.3. Testing Events """svelte <!-- Button.svelte --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function handleClick() { dispatch('click', { text: 'Button Clicked!' }); } </script> <button on:click={handleClick}>Click Me</button> """ """javascript // Button.spec.js import { render, fireEvent } from '@testing-library/svelte'; import Button from './Button.svelte'; import { describe, it, expect, vi } from 'vitest'; describe('Button Component', () => { it('should dispatch a click event when the button is clicked', async () => { const { getByText, component } = render(Button); const button = getByText('Click Me'); const mockFn = vi.fn(); component.$on('click', mockFn); // Use component.$on for listening await fireEvent.click(button); expect(mockFn).toHaveBeenCalledTimes(1); expect(mockFn).toHaveBeenCalledWith(expect.objectContaining({detail: { text: 'Button Clicked!'}})); }); }); """ ### 2.5. Mocking Dependencies * **Do This:** Use mocking libraries like Jest's "jest.mock()" or Vitest's "vi.mock()" to isolate the component being tested from its dependencies. Mock external APIs, stores, and other components. * **Don't Do This:** Mock indiscriminately. Only mock dependencies that are not relevant to the unit being tested. Avoid mocking implementation details. * **Why:** Mocking allows you to control the behavior of dependencies, ensuring that the unit being tested is isolated and that tests are deterministic. * **Code Example:** """javascript // api.js (Example API Module) export async function fetchData() { const response = await fetch('/api/data'); return response.json(); } """ """svelte <!-- MyComponent.svelte --> <script> import { fetchData } from './api.js'; let data = null; async function loadData() { data = await fetchData(); } loadData(); </script> {#if data} <p>Data: {data.value}</p> {:else} <p>Loading...</p> {/if} """ """javascript // MyComponent.spec.js import { render, waitFor, screen } from '@testing-library/svelte'; import MyComponent from './MyComponent.svelte'; import * as api from './api.js'; // Import the module import { describe, it, expect, vi } from 'vitest'; describe('MyComponent', () => { it('should render data fetched from the API', async () => { const mockFetchData = vi.spyOn(api, 'fetchData'); mockFetchData.mockResolvedValue({ value: 'Mocked Data' }); render(MyComponent); await waitFor(() => { expect(screen.getByText('Data: Mocked Data')).toBeInTheDocument(); }); mockFetchData.mockRestore(); // Restore the original function. Important for clean tests }); }); """ ### 2.6. Testing Stores * **Do This:** Test the behavior of stores independently from components that use them. Use "get()" from "svelte/store" to synchronously access the store's value during tests. Ensure async updates to stores are handled correctly in tests (using "await tick()" where necessary within components, but primarily using "await" with the store updates directly within the test). * **Don't Do This:** Test stores implicitly through component tests. * **Why:** Testing stores directly ensures their core logic is correct before being used within components. * **Code Example:** """javascript // myStore.js import { writable } from 'svelte/store'; export const count = writable(0); export function increment() { count.update(n => n + 1); } export function decrement() { count.update(n => n - 1); } """ """javascript // myStore.spec.js import { get } from 'svelte/store'; import { count, increment, decrement } from './myStore'; import { describe, it, expect } from 'vitest'; describe('myStore', () => { it('should initialize with a count of 0', () => { expect(get(count)).toBe(0); }); it('should increment the count', () => { increment(); expect(get(count)).toBe(1); }); it('should decrement the count', () => { decrement(); expect(get(count)).toBe(0); }); }); """ ## 3. Integration Testing ### 3.1. Purpose and Scope * **Do This:** Integration test how multiple components and units of code work together. Focus on verifying interactions between different parts of the application. * **Don't Do This:** Treat integration tests as replacements for unit tests. Ensure to test units individually before testing them in conjunction. * **Why:** Integration tests expose issues that arise when different parts of the application interact, such as incorrect data flow or unexpected side effects. ### 3.2. Testing Strategies * **Do This:** Use a combination of top-down and bottom-up integration testing approaches. Focus on major user flows and critical application features. * **Don't Do This:** Avoid testing all possible combinations of interactions. Prioritize testing the most common and important scenarios. * **Why:** A balanced approach to integration testing provides comprehensive coverage without becoming overwhelming. ### 3.3. Integration Testing Examples #### 3.3.1. Testing Component Communication """svelte <!-- Parent.svelte --> <script> import Child from './Child.svelte'; let messageFromChild = ''; function handleMessage(event) { messageFromChild = event.detail.message; } </script> <Child on:message={handleMessage} /> <p>Message from Child: {messageFromChild}</p> """ """svelte <!-- Child.svelte --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function sendMessage() { dispatch('message', { message: 'Hello from Child!' }); } </script> <button on:click={sendMessage}>Send Message</button> """ """javascript // Parent.spec.js import { render, fireEvent, screen } from '@testing-library/svelte'; import Parent from './Parent.svelte'; import { describe, it, expect } from 'vitest'; describe('Parent Component Integration', () => { it('should receive and display a message from the child component', async () => { render(Parent); const button = screen.getByText('Send Message'); await fireEvent.click(button); expect(screen.getByText('Message from Child: Hello from Child!')).toBeInTheDocument(); }); }); """ #### 3.3.2. Testing Store Interactions """svelte <!-- CounterComponent.svelte --> <script> import { count, increment, decrement } from './myStore'; import { get } from 'svelte/store'; </script> <button on:click={increment}>Increment</button> <button on:click={decrement}>Decrement</button> <p>Count: {$count}</p> """ """javascript // CounterComponent.spec.js import { render, fireEvent, screen } from '@testing-library/svelte'; import CounterComponent from './CounterComponent.svelte'; import { increment, decrement, count } from './myStore'; import { describe, it, expect} from 'vitest'; import { get } from 'svelte/store'; // Ensure store is reset before each test. Vitest runs tests in parallel, and stores are global! import { beforeEach } from 'vitest'; beforeEach(() => { count.set(0); }); describe('CounterComponent with Store Integration', () => { it('should increment and decrement the count using the store', async () => { render(CounterComponent); const incrementButton = screen.getByText('Increment'); const decrementButton = screen.getByText('Decrement'); await fireEvent.click(incrementButton); expect(screen.getByText('Count: 1')).toBeInTheDocument(); await fireEvent.click(decrementButton); expect(screen.getByText('Count: 0')).toBeInTheDocument(); }); it('should update the store directly and reflect in the component', async () => { render(CounterComponent); increment(); expect(screen.getByText('Count: 1')).toBeInTheDocument(); }); }); """ ## 4. End-to-End (E2E) Testing ### 4.1. Purpose and Scope * **Do This:** Use E2E tests to verify the entire application flow, from user interaction to data persistence, across different browsers and environments. Focus on critical user journeys. * **Don't Do This:** Rely on E2E tests as the primary testing method. They are slow, expensive, and provide less specific feedback than unit or integration tests. * **Why:** E2E tests provide confidence that the application works as expected from the user's perspective, including interactions with external systems. ### 4.2. Tools and Libraries * **Do This:** Use Playwright or Cypress for writing and running E2E tests. These tools provide excellent browser automation capabilities, automatic waiting, and debugging features. Consider using Docker for consistent test environments. * **Don't Do This:** Use outdated or unmaintained E2E testing tools. * **Why:** Playwright and Cypress offer modern features that simplify E2E testing and improve reliability. ### 4.3. Writing Effective E2E Tests * **Do This:** Write tests that are resilient to UI changes. Use data attributes or role-based selectors to locate elements. Avoid relying on CSS classes or text content that may change. Use "getByRole" and "findByRole" from Testing Library within Playwright/Cypress where possible. * **Don't Do This:** Write brittle tests that are easily broken by minor UI changes. This leads to test fatigue and reduces the value of E2E testing. Don't include sleeps or hardcoded waits if possible; leverage the automatic waiting features of Playwright/Cypress. * **Why:** Resilient tests reduce maintenance costs and provide more reliable feedback. ### 4.4. E2E Testing Example (Playwright) """javascript // playwright.config.js import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, reporter: 'html', use: { baseURL: 'http://localhost:3000', // Replace with your app's URL trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, // Add other browsers as needed ], webServer: { command: 'npm run dev', // Replace with your dev server command url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); """ """javascript // tests/example.spec.js import { test, expect } from '@playwright/test'; test('has title', async ({ page }) => { await page.goto('/'); await expect(page).toHaveTitle("My Svelte App"); // Replace with your app's title }); test('counter increments correctly', async ({ page }) => { await page.goto('/'); const incrementButton = await page.getByRole('button', {name: 'Increment'}); const countText = page.locator('text=Count:'); // Use a CSS or text locator if roles aren't applicable await expect(countText).toContainText('Count: 0'); await incrementButton.click(); await expect(countText).toContainText('Count: 1'); }); """ ### 4.5. E2E Testing Considerations for Svelte * **Testing Transitions/Animations:** Playwright and Cypress can handle waiting for animations and transitions to complete before making assertions. Use "waitFor" or "waitForSelector" with appropriate timeouts to ensure elements are fully rendered. * **Shadow DOM:** If using web components extensively, understand how to query elements within the shadow DOM using Playwright or Cypress APIs. * **SvelteKit Specifics:** When testing SvelteKit apps, ensure the development server is running before running tests and that the correct base URL is configured. ## 5. Accessibility Testing * **Do This:** Integrate accessibility testing into your workflow. Use tools like axe DevTools, Lighthouse, or pa11y to identify accessibility issues. Write tests that verify that components are accessible to users with disabilities. Use semantic HTML. Employ ARIA attributes where necessary, testing them for correctness. * **Don't Do This:** Ignore accessibility testing. This can exclude users with disabilities and violate accessibility guidelines. * **Why:** Accessibility testing ensures that the application is usable by everyone, regardless of their abilities. * **Code Example (using axe DevTools):** """javascript // Accessibility.spec.js (Playwright) import { test, expect } from '@playwright/test'; import { AxeBuilder } from '@axe-core/playwright'; test.describe('Accessibility Tests', () => { test('homepage should not have any automatically detectable accessibility issues', async ({ page }) => { await page.goto('/'); const axeBuilder = new AxeBuilder({ page }); const accessibilityScanResults = await axeBuilder.analyze(); expect(accessibilityScanResults.violations).toEqual([]); }); }); """ ## 6. Performance Testing * **Do This:** Use performance testing tools like Lighthouse or WebPageTest to measure the performance of the application. Write tests that verify that components render quickly and efficiently. Profile components with Svelte's "$debug" and browser devtools to identify performance bottlenecks. * **Don't Do This:** Neglect performance testing. This can lead to slow and unresponsive applications that provide a poor user experience. * **Why:** Performance testing ensures that the application meets performance requirements and provides a smooth user experience. ## 7. Continuous Integration (CI) * **Do This:** Integrate all testing types into a CI/CD pipeline. Run unit tests on every commit. Run integration and accessibility tests regularly. Schedule E2E tests to run nightly or weekly. * **Don't Do This:** Manually run tests. CI/CD automates testing and reduces the risk of human error. * **Why:** CI/CD automates the testing process, ensuring that code changes are thoroughly tested before being deployed. ## 8. Code Coverage * **Do This:** Aim for high code coverage, particularly for core components and critical functionality. Use code coverage tools to identify untestable or untested code. * **Don't Do This:** Use code coverage as the *sole* measure of test quality. High coverage does not guarantee that tests are effective or that the application is bug-free. * **Why:** Code coverage provides a metric for assessing the extent to which the codebase is tested. ## 9. Svelte 5 Testing Considerations * **Reactivity Changes:** Be mindful of the reactivity changes in Svelte 5 (using runes). Ensure tests accurately reflect the new reactivity system, particularly when testing component updates and store interactions. Pay extra attention to testing derived state and how it updates based on reactive dependencies. * **.svelte.js/.ts Files:** Recognize that tests might now involve importing and testing ".svelte.js" or ".svelte.ts" files directly, which contain logic previously confined to the "<script>" tag within ".svelte" components. By following these testing methodologies and best practices, you can build robust, maintainable, and performant Svelte applications. This leads to higher quality software and a better experience for both developers and users.