# 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
Count: {count}
"""
* **Example - Global Store:**
"""svelte
// store.js
import { writable } from 'svelte/store';
export const user = writable(null);
// Component A
Welcome, {$user?.name}!
// Component B
login({ name: 'John Doe' })}>Login
"""
### 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
// Child.svelte
<p>{message}</p>
Try to change message
"""
### 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
<p>Price: {price}</p>
<p>Quantity: {quantity}</p>
<p>Total: {total}</p>
Increase Quantity
"""
## 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
<p>Count: {$count}</p>
<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
<p>Count: {$count}</p>
+
-
"""
### 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
Add Todo
{#each $todos as todo (todo.id)}
todos.toggle(todo.id)} />
{todo.text}
todos.remove(todo.id)}>Remove
{/each}
"""
## 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 "
// Child.svelte
<p>This is a child component using theme from context.</p>
<p>User Name: {user.name}</p>
<p>User Age: {user.age}</p>
"""
### 3.2 Consuming Context
* **Standard:** Use "getContext" to retrieve context values within a component.
* **Do This:** Call "getContext" within a component's "
<p>Name: {name}</p>
<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
{#if emailError}
<p>{emailError}</p>
{/if}
"""
## 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
Add Item
"""
### 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.
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'
# 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.
# Deployment and DevOps Standards for Svelte This document outlines the coding standards for deployment and DevOps practices specific to Svelte applications. Adhering to these standards will help ensure consistent, maintainable, performant, and secure deployments. These standards are based on the latest version of Svelte and its ecosystem. ## 1. Build Processes and CI/CD ### 1.1. Standardizing Build Processes **Do This:** * Use a consistent build tool and configuration across all environments (development, staging, production). SvelteKit projects should use the "svelte-package" and Vite configuration. * Define build scripts in "package.json" using standard commands like "build", "preview", and "check". * Utilize environment variables properly for configuration management. **Don't Do This:** * Don't rely on manual build steps or environment-specific build scripts. * Don't hardcode sensitive information (API keys, secrets) directly in the code or build scripts. **Why:** Consistent build processes ensure reproducibility and prevent environment-specific issues. Using environment variables allows for flexible configuration without modifying the code. **Example:** """json // package.json { "scripts": { "dev": "vite dev", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.5.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte3": "^4.0.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.8.1", "svelte": "^3.54.0", "svelte-check": "^3.0.1", "tslib": "^2.4.1", "typescript": "^5.0.0", "vite": "^4.0.0" } } """ ### 1.2. CI/CD Pipelines **Do This:** * Implement a CI/CD pipeline using tools like GitHub Actions, GitLab CI, or Jenkins. * Automate build, test, and deployment processes. * Include linting, code formatting, and static code analysis in the pipeline. * Use automated testing (unit, integration, end-to-end) to ensure code quality. * Cache dependencies to speed up build times. * Implement version control tagging for releases. * Use SvelteKit's adapters for different deployment targets (Node, Vercel, Netlify, static). **Don't Do This:** * Don't deploy directly from developer machines. * Don't skip automated testing in the pipeline. * Don't expose sensitive data in CI/CD logs. **Why:** CI/CD pipelines automate the deployment process, reduce errors, and improve code quality. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' # Or the version your project targets cache: 'npm' - name: Install dependencies run: npm install - name: Lint and Format run: npm run lint && npm run format - name: Run Tests run: npm run test:unit # or your test command - name: Build run: npm run build - name: Deploy to Vercel # Or other deployment target like Netlify run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} """ ### 1.3 Adapters **Do This:** * Choose an appropriate adapter for your deployment environment in "svelte.config.js". SvelteKit provides official adapters for Node.js, Vercel, Netlify, Cloudflare Pages, amongst many others. Community adapters are also available. * Configure the adapter correctly based on your environment needs (e.g., static adapter settings for SPA deployment). * Use environment variables to configure the adapter at build time. **Don't Do This:** * Ignore the adapter configuration. Incorrect adapter or options can make your application not function as expected. * Commit adapter configuration variables (like API keys) to your repository. **Why:** Adapters adapt SvelteKit applications to different server environments. Configuring the adapter is crucial for optimizing the appliclation for that server environment (or edge runtime, or static hoster). **Example ("svelte.config.js" with Vercel adapter):** """javascript // svelte.config.js import adapter from '@sveltejs/adapter-vercel'; import { vitePreprocess } from 'svelte-preprocess'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter({ // Options for the adapter can be added here. }), }, preprocess: [vitePreprocess()] }; export default config; """ ## 2. Production Considerations ### 2.1. Environment Variables **Do This:** * Store all configuration parameters as environment variables. * Use ".env" files (with tooling like "dotenv") for local development, but NEVER commit ".env" files to version control. * Set environment variables in the deployment environment (e.g., Vercel, Netlify). **Don't Do This:** * Don't hardcode configuration parameters in the source code. * Don't commit API keys or secrets to version control. * Avoid using "process.env" directly in components. Create separate configuration modules for improved testability. **Why:** Using environment variables separates configuration from code, making it easier to manage deployments across different environments and improving security. **Example:** """javascript // src/lib/config.js export const API_URL = process.env.API_URL || 'http://localhost:3000/api'; // Default for development export const APP_NAME = process.env.APP_NAME || 'My Svelte App'; """ ### 2.2. Security **Do This:** * Use HTTPS for all production traffic. Enforce HTTPS redirects at your hosting provider. * Sanitize all user inputs to prevent XSS vulnerabilities. Use Svelte's templating engine for automatic escaping. * Implement proper authentication and authorization mechanisms. Use established libraries or services for authentication. * Implement rate limiting to protect against brute-force attacks. * Regularly update dependencies to patch security vulnerabilities. Use tools like "npm audit" to identify vulnerable packages. **Don't Do This:** * Don't store sensitive data (passwords, API keys) in local storage or cookies. * Don't disable CSP (Content Security Policy) without a strong understanding of the implications. * Don't rely on client-side validation alone for security. Always validate data on the server. **Why:** Security vulnerabilities can compromise user data and application integrity. Addressing these issues early in development is essential. Svelte's reactivity and component model can help with consistent sanitization if used correctly. **Example (Server-side validation using SvelteKit endpoints):** """typescript // src/routes/api/submit/+server.ts import { json, type RequestHandler } from '@sveltejs/kit'; export const POST: RequestHandler = async ({ request }) => { const data = await request.json(); // Sanitize and validate input const email = data.email?.trim(); if (!email || !email.includes('@')) { return json({ error: 'Invalid email' }, { status: 400 }); } // Process the data if valid // ... return json({ success: true }); }; """ ### 2.3. Performance **Do This:** * Optimize images using tools like ImageOptim or "imagemin". Use responsive images with "<picture>" or "srcset" attributes. Consider using Svelte's "<img>" tag enhancements. * Use code splitting to reduce initial load time by splitting the app into smaller chunks, which you can configure in Vite. * Lazy-load non-critical components and images. * Enable browser caching for static assets. Configure cache headers appropriately. * Monitor application performance using tools like Google PageSpeed Insights, web.dev, or browser developer tools. * Use Svelte's built-in reactivity efficiently. Avoid unnecessary computations in reactive blocks. * Leverage server-side rendering (SSR) or static site generation (SSG) where appropriate for improved initial load time and SEO. **Don't Do This:** * Don't load large, unoptimized images. * Don't block the main thread with long-running JavaScript tasks. * Don't make excessive network requests. * Don't over-rely on javascript when SSR options for Svelte or SvelteKit will do. **Why:** Performance is critical for user experience. Optimizing assets and code can significantly improve load times and reduce resource consumption. **Example (Lazy loading a component):** """svelte {#if showComponent} <MyComponent /> {:else} <button on:click={() => showComponent = true}>Load Component</button> {/if} <script> let showComponent = false; import MyComponent from './MyComponent.svelte'; </script> """ ### 2.4. Logging and Monitoring **Do This:** * Implement centralized logging using tools like Winston, Bunyan, or a dedicated logging service (e.g., Datadog, Loggly). * Log important events, errors, and warnings. Include contextual information in log messages (e.g., user ID, request ID). * Use monitoring tools (e.g., Prometheus, Grafana, New Relic) to track application health, performance metrics, and error rates. * Set up alerts for critical errors and performance degradation. **Don't Do This:** * Don't log sensitive data (passwords, API keys). * Don't rely solely on console logs for production debugging. * Don't ignore error messages or warnings. Address them promptly. **Why:** Logging and monitoring provide visibility into application behavior, making it easier to identify and resolve issues. **Example (Logging using Winston - server-side):** """javascript // src/hooks.server.js or a dedicated logging module import winston from 'winston'; const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ], }); export const handle = async ({ event, resolve }) => { try { const response = await resolve(event); return response; } catch (error) { logger.error('Unhandled exception', { url: event.url.pathname, error: error.message, stack: error.stack, }); throw error; // Re-throw to allow SvelteKit error handling } }; """ ### 2.5 SvelteKit Specific Enhancements **Do This:** * Take full advantage of SvelteKit's "handle" hook for request lifecycle management, including authentication, authorization, and data enrichment. * Consider using SvelteKit's serverless functions for API endpoints. * Learn SvelteKit's data loading best practices. Don't create side effects in your component initialization. * Use the "invalidate" function to trigger re-fetches when data changes **Don't Do This:** * Over complicate using SvelteKit "+page.server.js" to fetch data * Forget to use environment variables when creating a "baseURL" **Why:** These features improve developer productivity, performance, and maintainability. **Example:** """typescript // src/routes/+layout.server.ts import { API_URL } from '$lib/config'; export const load = async ({ fetch, cookies, url }) => { const session = cookies.get('sessionid'); try { const response = await fetch("${API_URL}/todos", { headers: { accept: 'application/json', cookie: "sessionid=${session ?? ''}" } }); const todos = await response.json(); return {todos}; } catch(e) { console.log(e) return { todos: [] }; } }; """ ## 3. DevOps Best Practices ### 3.1. Infrastructure as Code (IaC) **Do This:** * Use IaC tools like Terraform, AWS CloudFormation, or Azure Resource Manager to define and manage infrastructure. * Store IaC configurations in version control. * Automate infrastructure deployments using CI/CD pipelines. **Don't Do This:** * Don't manually provision infrastructure. * Don't store infrastructure configurations locally. **Why:** IaC enables repeatable and predictable infrastructure deployments. ### 3.2. Containerization **Do This:** * Package Svelte applications into Docker containers. * Use Docker Compose for local development and testing. * Orchestrate containers using Kubernetes or Docker Swarm for production deployments. **Don't Do This:** * Don't deploy applications directly to bare metal servers. * Don't expose unnecessary ports or volumes in Docker containers. **Why:** Containerization provides consistent and isolated environments for applications. ### 3.3. Monitoring and Alerting **Do This:** * Implement comprehensive monitoring of infrastructure and application resources. * Set up alerts for critical events (e.g., high CPU usage, disk space exhaustion, application errors). * Use a central monitoring dashboard to visualize application health and performance. **Don't Do This:** * Don't ignore monitoring alerts. * Don't rely solely on manual monitoring. **Why:** Monitoring and alerting enable proactive identification and resolution of issues. By consistently applying these deployment and DevOps strategies, development teams can deliver Svelte applications that are robust, scalable, and secure. This will improve deployment confidence and reduce operational costs.
# 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.
# 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 <!-- Correct --> <div> {#if condition} <p>This is a paragraph.</p> {/if} </div> <!-- Incorrect --> <div> {#if condition} <p>This is a paragraph.</p> {/if} </div> """ ### 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 <!-- Correct --> <script> import { someVeryLongFunctionName } from './utils'; let someVeryLongVariableName = someVeryLongFunctionName( 'argument1', 'argument2', 'argument3' ); </script> <!-- Incorrect --> <script>import { someVeryLongFunctionName } from './utils'; let someVeryLongVariableName = someVeryLongFunctionName('argument1', 'argument2', 'argument3');</script> """ ### 1.3. Whitespace **Do This:** * Use a single blank line between top-level constructs (e.g., between "<script>", "<style>", and HTML). * Use spaces around operators and after commas. * Ensure consistent spacing within HTML attributes. * Use a newline at the end of each file. **Don't Do This:** * Omit whitespace around operators. * Use multiple blank lines unnecessarily. * Forget the trailing newline. **Why:** Proper whitespace enhances code clarity significantly. **Example:** """svelte <!-- Correct --> <script> let count = 0; function increment() { count += 1; } </script> <style> button { padding: 10px; } </style> <div> <button on:click={increment}>Click me</button> <p>Count: {count}</p> </div> <!-- Incorrect --> <script> let count=0; function increment(){ count+=1; } </script> <style>button{padding:10px;}</style> <div><button on:click={increment}>Click me</button><p>Count: {count}</p></div> """ ## 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 <script> // Correct let userAge = 30; const API_ENDPOINT = '/api/users'; // Incorrect let a = 30; const url = '/api/users'; </script> """ ### 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 <!-- Correct --> <script> export let user; </script> <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> <!-- Incorrect --> <script> export let data; </script> <div> <h1>{data.name}</h1> <p>Email: {data.email}</p> </div> """ ### 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 <script> // Correct function calculateTotal(items) { return items.reduce((sum, item) => sum + item.price, 0); } // Incorrect function calc(items) { return items.reduce((sum, item) => sum + item.price, 0); } </script> """ ### 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 <!-- Correct --> <script lang="ts"> export let userName: string; export let isLoggedIn: boolean = false; </script> <h1>Hello, {userName}!</h1> {#if isLoggedIn} <p>Welcome back!</p> {/if} <!-- Incorrect --> <script> export let user_name; export let is_logged_in = false; </script> <h1>Hello, {user_name}!</h1> {#if is_logged_in} <p>Welcome back!</p> {/if} """ ## 3. Svelte-Specific Style ### 3.1. Script Tags **Do This:** * Place "<script>" tags either at the top or bottom of the component file. * Use "<script lang="ts">" for TypeScript support. **Don't Do This:** * Mix "<script>" tags throughout the HTML unless absolutely necessary. **Why:** Clear separation of logic and markup improves readability. **Example:** """svelte <!-- Correct --> <script lang="ts"> export let message: string; </script> <h1>{message}</h1> <!-- Incorrect --> <h1>{message}</h1> <script> export let message; </script> """ ### 3.2. Style Tags **Do This:** * Use "<style>" tags to encapsulate component-specific styles. * Use ":global(...)" selector sparingly, only when necessary to override global styles. **Don't Do This:** * Define global styles directly in component style tags. * Use inline styles unless for very specific, dynamic styling. **Why:** Scoped styles prevent style conflicts and improve maintainability. **Example:** """svelte <!-- Correct --> <style> h1 { color: blue; } </style> <!-- Incorrect --> <style> :global(body) { font-family: Arial; } </style> """ ### 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 <script> let price = 10; let quantity = 2; $: total = price * quantity; // Correct function updateTotal() { price = 20; // Correct, triggers reactivity } </script> <p>Total: {total}</p> <button on:click={updateTotal}>Update Price</button> """ ### 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 <script> function handleClick() { alert('Button clicked!'); } </script> <button on:click={handleClick}>Click me</button> """ ### 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 <script> import { fade } from 'svelte/transition'; let visible = false; let inputValue = ''; </script> <input bind:value={inputValue} /> <button on:click={() => visible = !visible}>Toggle</button> {#if visible} <p transition:fade>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 <script> import { count } from './stores'; function increment() { $count += 1; } </script> <button on:click={increment}>Increment</button> <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 <script> let items = [ { id: 1, name: 'Apple' }, { id: 2, name: 'Banana' }, { id: 3, name: 'Orange' } ]; </script> {#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 <script> let a = 1; let b = 2; $: sum = a + b; // Shorthand syntax for $derived $: { // Shorthand syntax for $effect console.log('The sum is', sum); } function updateValues() { a += 1; b += 1; } </script> <p>a: {a}</p> <p>b: {b}</p> <p>Sum: {sum}</p> <button on:click={updateValues}>Update Values</button> """ ### 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 <script> import { writable } from 'svelte/store'; const count = writable(0); function increment() { count.update(n => n + 1); } </script> <button on:click={increment}>Increment</button> <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 <svelte:window on:keydown={handleKeyDown} /> <script> function handleKeyDown(event) { console.log('Key pressed:', event.key); } </script> """ ### 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 <script lang="ts"> export let label: string; export let onClick: () => void; </script> <button on:click={onClick}>{label}</button> // Usage in Parent Component <script> import Button from './Button.svelte'; function handleClick() { alert('Button Clicked!'); } </script> <Button label="Click Me" onClick={handleClick} /> """ ### 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 <script> import { onDestroy } from 'svelte'; let inputValue = ''; // Function to sanitize input value function sanitizeInput(value) { // Implement your sanitization logic here return value.replace(/</g, '<').replace(/>/g, '>'); } // Reactive statement to update sanitized value $: sanitizedValue = sanitizeInput(inputValue); </script> <input type="text" bind:value={inputValue} /> <!-- Display the sanitized value --> <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.
# API Integration Standards for Svelte This document outlines the recommended coding standards for integrating APIs within Svelte applications. Following these standards will promote maintainability, performance, security, and overall code quality. It assumes familiarity with Svelte's core concepts and common web development practices. ## 1. Architectural Patterns for API Integration Choosing the right architectural pattern is crucial for a scalable and maintainable Svelte application. This section covers common patterns for handling API requests. ### 1.1. Services Pattern The Services Pattern encapsulates API interaction logic (data fetching, error handling, request formatting) within reusable service modules. **Standard:** Isolate API interaction logic into dedicated service modules. **Do This:** Create separate ".js" or ".ts" files for each API endpoint or group of related endpoints. **Don't Do This:** Embed API calls directly within Svelte components. **Why:** * **Improved Code Organization:** Keeps components focused on presentation and user interaction. * **Reusability:** Services can be reused across multiple components and even different applications. * **Testability:** Services are easier to test in isolation. * **Reduced Component Complexity:** Keeps components lean and readable. **Example:** """javascript // src/services/userService.js const API_BASE_URL = 'https://api.example.com'; async function getUser(id) { try { const response = await fetch("${API_BASE_URL}/users/${id}"); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } catch (error) { console.error('Error fetching user:', error); throw error; // Re-throw to be handled by the component } } async function createUser(userData) { try { const response = await fetch("${API_BASE_URL}/users", { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData), }); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } catch (error) { console.error("Error creating user:", error); throw error; } } export { getUser, createUser }; // Export multiple functions as an object """ """svelte <!-- src/components/UserProfile.svelte --> <script> import { onMount } from 'svelte'; import { getUser } from '../services/userService'; // Import the service let user = null; export let userId; onMount(async () => { try { user = await getUser(userId); } catch (error) { // Handle errors gracefully (e.g., display an error message) console.error('Failed to load user:', error); alert('Failed to load user.'); } }); </script> {#if user} <h1>{user.name}</h1> <p>Email: {user.email}</p> {:else} <p>Loading user...</p> {/if} <!-- Another component, e.g., UserForm.svelte --> <script> import { createUser } from '../services/userService'; async function handleSubmit(event) { event.preventDefault(); const formData = new FormData(event.target); const userData = Object.fromEntries(formData.entries()); try { const newUser = await createUser(userData); console.log('User created:', newUser); // Redirect or update the UI } catch (error) { console.error('Error creating user:', error); alert("Error creating user!"); } } </script> <form on:submit={handleSubmit}> <label for="name">Name:</label> <input type="text" id="name" name="name" required> <label for="email">Email:</label> <input type="email" id="email" name="email" required> <button type="submit">Create User</button> </form> """ ### 1.2. Store-Based Data Management Svelte stores provide a reactive data container, ideal for managing data fetched from APIs. **Standard:** Use Svelte stores to manage the application's state related to API data. **Do This:** Create writable, readable, or derived stores to hold and manipulate API responses. **Don't Do This:** Directly mutate component-level variables with API data, bypassing reactivity. **Why:** * **Reactivity:** Changes to the store automatically trigger updates in subscribed components. * **Centralized State:** Provides a single source of truth for application data. * **Simplified Data Sharing:** Easier to share data between components without prop drilling. * **Simplified Testing:** Stores are easier to test outside of components. * **Fine-grained updates:** Only components using parts of the store that changed will re-render. **Example:** """javascript // src/stores/userStore.js import { writable } from 'svelte/store'; import { getUser } from '../services/userService'; const userStore = writable({ data: null, loading: false, error: null, }); async function loadUser(id) { userStore.update(state => ({ ...state, loading: true, error: null })); try { const userData = await getUser(id); userStore.set({ data: userData, loading: false, error: null }); } catch (error) { userStore.set({ data: null, loading: false, error }); } } export { userStore, loadUser }; """ """svelte <!-- src/components/UserProfile.svelte --> <script> import { onMount } from 'svelte'; import { userStore, loadUser } from '../stores/userStore'; export let userId; onMount(() => { loadUser(userId); }); </script> {#if $userStore.loading} <p>Loading user...</p> {:else if $userStore.error} <p>Error: {$userStore.error.message}</p> {:else if $userStore.data} <h1>{$userStore.data.name}</h1> <p>Email: {$userStore.data.email}</p> {:else} <p>User not found</p> {/if} """ ### 1.3. Server-Side Rendering (SSR) and API Integration For performance and SEO benefits, consider Server-Side Rendering (SSR) with SvelteKit. **Standard:** Fetch initial data on the server for faster initial load times. **Do This:** Utilize SvelteKit's "load" function within "+page.server.js" for server-side API calls. (Note: filename might change to "+server.js" depending on SvelteKit version.) **Don't Do This:** Perform all API calls on the client, especially for critical content. **Why:** * **Improved SEO:** Search engines can easily crawl and index content rendered server-side. * **Faster Initial Load:** Users see content faster, improving user experience. * **Reduced Client-Side Processing:** Offloads work from the client's browser, especially on low-powered devices. **Example:** """javascript // src/routes/users/[userId]/+page.server.js import { getUser } from '../../../services/userService'; export async function load({ params }) { const { userId } = params; try { const user = await getUser(userId); return { user, }; } catch (error) { return { status: 500, error: 'Failed to load user', }; } } """ """svelte <!-- src/routes/users/[userId]/+page.svelte --> <script> export let data; // data passed from the load function $: ({ user } = data); </script> {#if user} <h1>{user.name}</h1> <p>Email: {user.email}</p> {:else} <p>Failed to load user.</p> {/if} """ ## 2. Data Fetching Best Practices Efficient data fetching is paramount for a responsive Svelte application. ### 2.1. Async/Await **Standard:** Use "async/await" syntax for cleaner and more readable asynchronous code. **Do This:** Wrap API calls in "async" functions and use "await" to handle promises. **Don't Do This:** Rely on ".then()" callbacks, which can lead to complex and hard-to-read code. **Why:** * **Improved Readability:** "async/await" makes asynchronous code look and behave more like synchronous code. * **Easier Error Handling:** Simplifies error handling with "try/catch" blocks. **Example:** """javascript async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); throw error; // Re-throw for component-level handling } } """ ### 2.2. Error Handling **Standard:** Implement robust error handling to gracefully manage API failures. **Do This:** Wrap API calls in "try/catch" blocks and provide user-friendly error messages. Log errors to the console or a logging service. Consider using a dedicated error reporting service (e.g., Sentry). **Don't Do This:** Ignore errors or display generic and unhelpful error messages. **Why:** * **Improved User Experience:** Prevents the application from crashing or displaying cryptic errors. * **Easier Debugging:** Provides valuable information for diagnosing and resolving issues. **Example:** """javascript async function fetchData() { try { const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); alert('An error occurred while fetching data. Please try again later.'); // User-friendly message throw error; // Re-throw if component needs to handle it } } """ ### 2.3. Loading Indicators **Standard:** Provide visual feedback to users during API requests. **Do This:** Display loading indicators (e.g., spinners, progress bars) while data is being fetched. Hide the indicator once the data is loaded or an error occurs. **Don't Do This:** Leave the user in the dark without any indication of activity. **Why:** **Improved User Experience:** Keeps users informed and prevents them from thinking the application is unresponsive. **Example:** """svelte <script> import { onMount } from 'svelte'; let data = null; let loading = true; let error = null; onMount(async () => { try { const response = await fetch('https://api.example.com/data'); data = await response.json(); } catch (e) { error = e; console.error("Error during fetch:", e); } finally { loading = false; } }); </script> {#if loading} <p>Loading...</p> {:else if error} <p>Error: {error.message}</p> {:else if data} <pre>{JSON.stringify(data, null, 2)}</pre> {:else} <p>No data available.</p> {/if} """ ### 2.4. Data Transformations **Standard:** Transform API responses into a format suitable for the application's needs. **Do This:** Create functions to map, filter, or reshape data from the API before storing it in stores or displaying it in components. Ensure that you handle possible null/undefined values. **Don't Do This:** Directly use API responses without any processing, which can lead to inconsistencies and inflexibility. **Why:** * **Decoupling:** Isolates the application from changes in the API's data structure. * **Improved Performance:** Optimizes data for specific use cases, such as filtering large data sets. * **Enhanced Readability:** Transforms data into a more human-readable format. **Example:** """javascript function transformUserData(apiData) { if (!apiData) return null; // Handle null or undefined cases return { id: apiData.id, fullName: "${apiData.firstName} ${apiData.lastName}", emailAddress: apiData.email || 'N/A', // Handle missing values }; } // usage in service async function getUser(id) { try { const response = await fetch("${API_BASE_URL}/users/${id}"); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const apiResponse = await response.json(); return transformUserData(apiResponse); //Applying transform } catch (error) { console.error('Error fetching user:', error); throw error; // Re-throw to be handled by the component } } """ ## 3. API Client Libraries Leverage existing libraries to simplify API interaction. ### 3.1. Fetch API **Standard:** Utilize the built-in "fetch" API for making HTTP requests. **Do This:** Use "fetch" for simple API calls. Implement custom headers, request methods, and error handling as needed. **Don't Do This:** Rely on older libraries like "XMLHttpRequest" unless absolutely necessary. **Why:** * **Native Integration:** "fetch" is a standard web API supported by all modern browsers and Node.js. * **Promise-Based:** Provides a clean and modern API for asynchronous operations. **Example:** """javascript async function fetchData() { const response = await fetch('https://api.example.com/data', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer <token>', }, }); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } """ ### 3.2. Axios **Standard:** Consider using Axios for advanced API interaction and features not available in the native "fetch" API. **Do This:** Use Axios for features like request cancellation, automatic JSON transformation, and interceptors. **Don't Do This:** Use Axios for simple API calls that can be easily handled by "fetch". **Why:** * **Feature-Rich:** Provides a wide range of features for advanced API interaction. * **Interceptors:** Allows you to intercept and modify requests and responses globally. * **Request Cancellation:** Enables you to cancel pending requests, which is useful for improving performance and user experience. **Example:** """javascript import axios from 'axios'; async function fetchData() { try { const response = await axios.get('https://api.example.com/data', { headers: { Authorization: 'Bearer <token>', }, }); return response.data; } catch (error) { console.error('Error fetching data:', error); throw error; } } // Example of request Interceptor axios.interceptors.request.use( config => { // Do something before request is sent config.headers['X-Request-Id'] = generateRequestId(); return config; }, error => { // Do something with request error return Promise.reject(error); } ); // Example of response Interceptor axios.interceptors.response.use( response => { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data return response; }, error => { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error if (error.response && error.response.status === 401) { // Handle unauthorized error (e.g., redirect to login) window.location.href = '/login'; } return Promise.reject(error); } ); """ ## 4. Security Considerations Protecting sensitive data and preventing vulnerabilities is critical. ### 4.1. Environment Variables **Standard:** Store API keys and other sensitive information in environment variables. **Do This:** Use ".env" files (in development) and environment variables on the server to store sensitive data. In SvelteKit, use "$env/static/private" for private environment variables. Access public environment variables via "$env/static/public". **Don't Do This:** Hardcode API keys or commit them to the repository. **Why:** * **Security:** Prevents sensitive data from being exposed in the codebase. * **Configuration:** Allows you to easily configure the application for different environments. **Example:** """ // .env API_KEY=your_api_key API_URL=https://api.example.com """ """javascript // svelte.config.js import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { loadEnv } from 'vite'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), kit: { adapter: adapter() } }; export default config; // access from server files import { env } from '$env/static/private'; const apiKey = env.API_KEY; //access from public files (browser) import { env } from '$env/static/public'; const publicApiUrl = env.PUBLIC_API_URL """ ### 4.2. Input Validation **Standard:** Validate all user inputs before sending them to the API. **Do This:** Implement client-side and server-side validation to prevent malicious data from being sent to the API. **Don't Do This:** Trust user input without validation, which can lead to vulnerabilities like SQL injection or cross-site scripting (XSS). **Why:** * **Security:** Prevents malicious data from compromising the application or the API. * **Data Integrity:** Ensures that the data being sent to the API is valid and consistent. ### 4.3. CORS **Standard:** Configure Cross-Origin Resource Sharing (CORS) properly on the server. **Do This:** Set appropriate CORS headers on the API server to allow requests from the application's origin. **Don't Do This:** Use a overly permissive CORS configuration, opening up the API to potential security risks. **Why:** * **Security:** Prevents unauthorized access to the API from other domains. * **Functionality:** Ensures that the application can make requests to the API from different origins. ## 5. Performance Optimization Optimize API integration for speed and efficiency. ### 5.1. Caching **Standard:** Implement caching to reduce the number of API requests. **Do This:** Use browser caching, server-side caching (e.g., Redis), or a service worker to cache API responses. Use "stale-while-revalidate" strategy for fast initial loads and background updates. **Don't Do This:** Cache data aggressively without considering data freshness or potential inconsistencies. **Why:** * **Improved Performance:** Reduces network latency and server load. * **Offline Support:** Allows the application to function even when the user is offline. ### 5.2. Pagination **Standard:** Implement pagination for APIs that return large datasets. **Do This:** Use pagination parameters (e.g., "page", "limit") to retrieve data in smaller chunks. Display pagination controls in the UI to allow users to navigate through the data. **Don't Do This:** Load entire datasets at once, which can lead to performance issues and slow load times. **Why:** * **Improved Performance:** Reduces the amount of data that needs to be transferred and processed. * **Better User Experience:** Allows users to browse large datasets more efficiently. ### 5.3. Debouncing and Throttling **Standard**: Use debouncing and throttling for API calls triggered by user input. **Do This**: Debounce API calls for search inputs, so the API is only called once the user stops typing. Throttle API calls for scroll events to prevent excessive calls. **Don't Do This**: Make API calls on every keystroke for search, or every pixel scrolled down which can overwhelm the API. **Why:** * **Reduced Network Traffic:** Limits calls to the API, reducing server load. * **Improved Performance:** Avoids unnecessary processing on the server. * **Better User Experience:** Prevents UI lag from excessive API calls. ### 5.4. GraphQL **Standard**: Consider adopting GraphQL for complex data fetching needs. **Do This**: Use GraphQL when you need to fetch data from multiple resources, request only the data needed, and avoid over-fetching. **Don't Do This**: Use GraphQL for simple APIs, as it adds complexity to your infrastructure. **Why:** * **Efficient Data Fetching**: Only fetch the required data. * **Avoid Over-Fetching**: Prevent transferring unnecessary data. * **Schema Definition**: Provides type safety and improves documentation. ## 6. Testing Ensuring API integration works as expected. ### 6.1. Unit Tests **Standard**: Write unit tests for service functions and data transformations. **Do This**: Mock API calls and verify data transformations. **Don't Do This**: Skip unit testing API integration logic. ### 6.2. Integration Tests **Standard**: Test integration between components and the backend. **Do This**: Use a testing framework to simulate user interactions. Verify API calls are made correctly. **Don't Do This**: Deploy without testing API integration with actual components. ### 6.3. End-to-End Tests **Standard**: Write end-to-end tests to ensure the entire application works correctly. **Do This**: Simulate user workflows, from login to API calls, to ensure a smooth user experience. **Don't Do This**: Assume the application works without testing the whole flow. ## 7. Specific Svelte Considerations ### 7.1. Context API **Standard**: Use context API for passing data down deeply nested components. **Do This**: Create a context to hold API-related functions and state. **Don't Do This**: Overuse context; prop drilling may be simpler for a small number of components. """svelte <script> import { setContext } from 'svelte'; import { yourApiService } from './services'; // Create API-related context setContext('api', { fetchData: yourApiService.getData, // Add more functions or data that child components might need }); </script> <slot /> <!-- Render child components --> """ ### 7.2 Lifecycle Methods **Standard**: Use "onMount" for data fetching in components. **Do This**: Initiate API calls within the "onMount" lifecycle method to ensure component is mounted. **Don't Do This**: Make API calls outside lifecycle methods, which can lead to unexpected behavior. ### 7.3 Reactive Statements **Standard**: Use reactive statements for data manipulation and updates. **Do This**: Update the component when API responses come in. Use dependency arrays to react to specific store updates. **Don't Do This**: Directly manipulate DOM elements or skip reactivity, leading to inconsistent UI updates. ## 8. Documentation **Standard**: Document All API integrations clearly. **Do This:** Use JSDoc or Typescript annotations to detail request/response structures. Document error handling strategies. Add clear comments explaining the purpose of integration code. **Don't Do This:** Omit documentation leaving API integrations opaque and hard to maintain. ## CONCLUSION Adhering to these coding standards will result in more maintainable, performant, secure, and testable Svelte applications. By following these guidelines, development teams can establish consistency and reduce the likelihood of errors. Remember to adapt these guidelines as Svelte evolves, and as specific project needs arise. Regular code reviews are highly recommended to enforce these standards and ensure continuous improvement.