# Tooling and Ecosystem Standards for Svelte
This document outlines the recommended tools, libraries, and standards for developing Svelte applications. Adhering to these guidelines will promote consistency, maintainability, performance, and security within a Svelte project. These guidelines are tailored for the latest version of Svelte (currently Svelte 5, using runes where appropriate).
## 1. Project Setup and Configuration
### 1.1. Project Initialization
**Standard:** Use "create-svelte" for initializing new Svelte projects.
**Do This:**
"""bash
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
"""
**Don't Do This:**
* Manually creating project files or using outdated project templates.
**Why:** "create-svelte" sets up a modern Svelte project with recommended tooling, including Vite for fast development, TypeScript support, and ESLint/Prettier for code quality. It also sets up the project for Svelte 5 with runes enabled.
### 1.2. Package Management
**Standard:** Use npm or pnpm for package management. pnpm is preferred for its efficient disk space usage and speed.
**Do This (pnpm):**
"""bash
npm install -g pnpm # If pnpm is not already installed
pnpm install
"""
**Do This (npm):**
"""bash
npm install
"""
**Don't Do This:**
* Mixing package managers within the same project.
**Why:** Consistent package management ensures reproducible builds and manages dependencies effectively. pnpm offers advantages in disk space optimization.
### 1.3. Configuration Files
**Standard:** Keep configuration files (e.g., "svelte.config.js", "vite.config.ts", ".eslintrc.cjs", "prettier.config.cjs") at the root of the project.
**Do This:** Maintain a clean project structure with configuration files at the root level.
**Don't Do This:**
* Scattering configuration files throughout the project.
**Why:** Centralized configuration improves discoverability and maintainability.
### 1.4. TypeScript Configuration
**Standard:** Use TypeScript for all Svelte components. Configure TypeScript properly with strict mode enabled.
**Do This:** Ensure "tsconfig.json" includes:
"""json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true, // Enable strict mode
"skipLibCheck": true,
"jsx": "preserve",
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*"],
"exclude": ["node_modules/*", "dist/*"]
}
"""
**Don't Do This:**
* Disabling strict mode or not using TypeScript at all.
**Why:** TypeScript enhances code quality, prevents runtime errors, and provides better IDE support. Strict mode uncovers potential issues early in development.
## 2. Linting and Formatting
### 2.1. ESLint Configuration
**Standard:** Use ESLint with the recommended Svelte plugin and relevant style guides.
**Do This:** Install necessary packages:
"""bash
npm install -D eslint eslint-plugin-svelte3 @typescript-eslint/parser @typescript-eslint/eslint-plugin
"""
Configure ".eslintrc.cjs":
"""javascript
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: [
'svelte3',
'@typescript-eslint',
],
overrides: [
{
files: ['*.svelte'],
processor: 'svelte3/svelte3',
},
],
rules: {
// Add or override rules as needed
},
settings: {
'svelte3/typescript': true,
},
}
"""
**Don't Do This:**
* Ignoring ESLint warnings or not using a Svelte-specific plugin.
**Why:** ESLint enforces code quality and consistency, preventing potential errors and stylistic inconsistencies.
### 2.2. Prettier Configuration
**Standard:** Use Prettier for code formatting with consistent settings.
**Do This:** Install Prettier:
"""bash
npm install -D prettier prettier-plugin-svelte
"""
Configure ".prettierrc.cjs":
"""javascript
module.exports = {
plugins: ['prettier-plugin-svelte'],
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'always',
svelteSortOrder: 'styles-scripts-markup'
};
"""
**Don't Do This:**
* Ignoring Prettier formatting or using inconsistent formatting settings.
**Why:** Prettier automates code formatting, ensuring consistent style and readability across the project.
### 2.3. Integrating ESLint and Prettier
**Standard:** Integrate ESLint and Prettier to work together seamlessly.
**Do This:** Install "eslint-config-prettier" and "eslint-plugin-prettier":
"""bash
npm install -D eslint-config-prettier eslint-plugin-prettier
"""
Update ".eslintrc.cjs" to extend "prettier":
"""javascript
module.exports = {
// ... other config
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
plugins: [
'svelte3',
'@typescript-eslint',
'prettier',
],
rules: {
'prettier/prettier': 'error',
// ... other rules
},
// ... other config
};
"""
**Why:** Integrating ESLint and Prettier ensures that code is both high-quality and consistently formatted.
## 3. State Management
### 3.1. Component Stores
**Standard:** Use Svelte's built-in runes for component-local state management. For global state management, use Svelte's stores or a dedicated state management library like Valtio or Svelte Query.
**Do This (Component State with Runes):**
"""svelte
Hello, {name}!
{#if isVisible}
<p>This text is visible.</p>
{/if}
Toggle Visibility
<p>Count: {$count}</p>
"""
**Do This (Global State with Stores):**
"""typescript
// stores.ts
import { writable } from 'svelte/store';
export const count = writable(0);
"""
"""svelte
// Component.svelte
Increment
<p>Count: {$count}</p>
"""
**Don't Do This:**
* Overusing props for deeply nested component communication when state management would be more appropriate.
* Mutating store values directly without using the "update" method when applicable (in Svelte 4). With runes, this is handled directly.
**Why:** Runes make local state management more explicit and less magical. Stores centralize state, making it easier to manage and share data across components.
### 3.2. Valtio for Complex State
**Standard:** Consider Valtio for more complex state management scenarios.
**Do This:** Install Valtio:
"""bash
npm install valtio
"""
Example Usage:
"""typescript
// store.ts
import { proxy } from 'valtio';
type Todo = {
id: number;
text: string;
completed: boolean;
};
type State = {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
};
export const state = proxy({
todos: [],
filter: 'all',
});
export const addTodo = (text: string) => {
const newTodo: Todo = {
id: Date.now(),
text,
completed: false,
};
state.todos = [...state.todos, newTodo];
};
"""
"""svelte
Add
{#each snap.todos as todo (todo.id)}
{todo.text}
{/each}
"""
**Why:** Valtio offers a simple and performant way to manage complex state without requiring boilerplate code. "useSnapshot" efficiently connects components to the Valtio proxy.
### 3.3. Svelte Query for Server State
**Standard:** Use Svelte Query for managing server state (fetching, caching, updating data).
**Do This:** Install Svelte Query:
"""bash
npm install @tanstack/svelte-query
"""
Example Usage:
"""svelte
{#if isLoading}
<p>Loading...</p>
{:else if error}
<p>Error: {error.message}</p>
{:else}
{#each $data as todo (todo.id)}
{todo.text}
{/each}
{/if}
"""
**Why:** Svelte Query simplifies data fetching, caching, and updating, improving the performance and user experience of applications that interact with APIs. It handles common concerns like loading states, error handling, and background updates.
## 4. Routing
### 4.1. SvelteKit's Built-in Router
**Standard:** Use SvelteKit's built-in file-based router for handling navigation.
**Do This:** Create pages in the "src/routes" directory:
"""
src/
└── routes/
├── +page.svelte // Home page
├── about/
│ └── +page.svelte // About page
└── blog/
├── +page.svelte // Blog index page
└── [slug]/
└── +page.svelte // Dynamic blog post page
"""
"""svelte
Welcome to the Home Page
About Us
"""
"""svelte
About Us
Home
"""
**Don't Do This:**
* Manually handling routing logic or using outdated routing libraries.
**Why:** SvelteKit's file-based router simplifies navigation, improves performance with prefetching, and provides a consistent and maintainable routing structure.
### 4.2. Navigation with "" and "goto"
**Standard:** Use standard HTML "" tags for simple navigation and the "goto" function to manually perform client-side router navigation in SvelteKit.
**Do This:**
"""svelte
Go to Blog
Go to About
"""
**Why:** Using "" tags provides standard browser navigation behavior and accessibility. "goto" allows for more controlled navigation within components.
## 5. Component Libraries
### 5.1. UI Component Libraries
**Standard:** Use established UI component libraries to accelerate development and maintain consistency. Consider libraries like Skeleton, Flowbite (Svelte), or Melt UI, depending on project needs.
**Do This (Using Skeleton):**
1. Install Skeleton:
"""bash
npm install @skeletonlabs/skeleton-ui @sveltejs/adapter-auto @sveltejs/kit postcss autoprefixer tailwindcss
"""
2. Configure Tailwind CSS (required by Skeleton). Follow the official Skeleton documentation for setting up "tailwind.config.js", "postcss.config.js" and "src/app.postcss"
3. Import and use components:
"""svelte
Click Me
"""
**Don't Do This:**
* Reinventing basic UI components unnecessarily.
**Why:** Component libraries provide pre-built, well-tested components that can be easily customized and integrated into Svelte applications.
### 5.2. Utility Libraries
**Standard:** Utilize utility libraries like Lodash or Ramda only when necessary and strategically. Favor native JavaScript methods and Svelte's reactive statements whenever possible.
**Do This:**
* Use Lodash/Ramda for complex data transformations or utility functions *NOT* readily available in native JavaScript. Avoid pulling in the whole library; import specific functions.
"""javascript
import { cloneDeep } from 'lodash-es'; // or import cloneDeep from 'lodash' but make sure you configure treeshaking
const original = { a: { b: 1 } };
const cloned = cloneDeep(original);
"""
**Don't Do This:**
* Importing entire utility libraries for simple operations.
**Why:** Minimizing dependencies reduces bundle size and improves performance.
## 6. API Communication
### 6.1. Fetch API
**Standard:** Use the native "fetch" API or a lightweight wrapper like "ky" for making HTTP requests. Consider Svelte Query for more advanced data fetching scenarios (see Section 3.3).
**Do This (Fetch API):**
"""typescript
async function fetchData() {
try {
const response = await fetch('/api/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);
return null;
}
}
"""
**Do This (ky):**
"""bash
npm install ky
"""
"""typescript
import ky from 'ky';
async function fetchData() {
try {
const data = await ky.get('/api/data').json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
}
"""
**Don't Do This:**
* Using outdated HTTP request libraries.
**Why:** The "fetch" API is built into modern browsers and provides a straightforward way to make HTTP requests. "ky" offers a cleaner API with features like automatic JSON parsing and error handling, without the overhead of larger libraries like Axios. For complex state management around APIs, Svelte Query is preferred.
### 6.2. Environment Variables
**Standard:** Use environment variables to configure application settings for different environments (development, staging, production).
**Do This:**
1. Create ".env" files for each environment (e.g., ".env.development", ".env.production").
2. Use SvelteKit's "$env/static/private" and "$env/static/public" modules to access environment variables. "$env/static/private" is for server-side only variables, and "$env/static/public" are for variables that can be exposed to the client.
3. Define public environment variables with a "PUBLIC_" prefix.
"""
// .env
API_URL=http://localhost:3000
PUBLIC_APP_NAME=My Svelte App
"""
"""svelte
App Name: {apiUrl}
"""
**Don't Do This:**
* Hardcoding sensitive information in the source code or committing ".env" files to version control.
* Accessing private environment variables client-side.
**Why:** Environment variables allow for flexible configuration and protect sensitive information.
## 7. Component Testing
### 7.1. Testing Libraries
**Standard:** Use Vitest for component testing, Storybook for component previewing and development, and Playwright for end-to-end testing.
**Do This (Vitest):**
1. Set up Vitest: When using "create-svelte", you can choose to install Playwright for component testing from the start. If not, install manually:
"""bash
npm install -D @sveltejs/vite-plugin-svelte @vitest/coverage-v8 @vitest/ui jsdom vitest
"""
2. Create test files alongside components (e.g., "src/components/MyComponent.test.ts"):
"""typescript
// src/components/MyComponent.test.ts
import { render, screen } from '@testing-library/svelte';
import MyComponent from './MyComponent.svelte';
import { describe, it, expect } from 'vitest';
describe('MyComponent', () => {
it('should render correctly', () => {
render(MyComponent, { name: 'Test' });
expect(screen.getByText('Hello, Test!')).toBeInTheDocument();
});
});
"""
**Do This (Storybook):**
1. Install Storybook:
"""bash
npx storybook init
"""
2. Create stories for components:
"""typescript
// src/components/MyComponent.stories.ts
import type { Meta, StoryObj } from '@storybook/svelte';
import MyComponent from './MyComponent.svelte';
const meta: Meta = {
title: 'MyComponent',
component: MyComponent,
argTypes: {
name: { control: 'text' },
},
};
export default meta;
type Story = StoryObj;
export const Primary: Story = {
args: {
name: 'Storybook',
},
};
"""
**Do This (Playwright):**
1. Install Playwright:
"""bash
npm install -D @playwright/test
npx playwright install
"""
2. Create end-to-end tests in the "tests" directory:
"""javascript
//tests/example.spec.ts
import { test, expect } from '@playwright/test';
test('homepage has title and links to intro page', async ({ page }) => {
await page.goto('https://sveltekit.skeleton.dev/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Skeleton/);
// create a locator
const getStarted = page.getByRole('link', { name: 'Get started' });
// Expect an attribute "to be strictly equal" to the value.
await expect(getStarted).toHaveAttribute('href', '/docs/get-started');
// Click the get started link.
await getStarted.click();
// Expect the URL to contain a value.
await expect(page).toHaveURL(/.*get-started/);
});
"""
**Don't Do This:**
* Neglecting component testing or relying solely on manual testing.
* Writing fragile tests that break easily with minor UI changes.
**Why:** Component testing ensures that individual components work as expected, Storybook provides a visual environment for component development and testing, and end-to-end testing verifies the overall application functionality.
## 8. Accessibility
### 8.1. ARIA Attributes and Semantic HTML
**Standard:** Use ARIA attributes and semantic HTML to create accessible components.
**Do This:**
"""svelte
×
Home
About
"""
**Don't Do This:**
* Using generic elements (e.g., "", "") for interactive elements without proper ARIA attributes.
* Neglecting keyboard navigation and focus management.
**Why:** ARIA attributes and semantic HTML provide additional information to assistive technologies, making applications more accessible to users with disabilities.
### 8.2. Accessibility Auditing
**Standard:** Use accessibility auditing tools to identify and fix accessibility issues.
**Do This:**
* Integrate accessibility audits into the development process using tools like Axe DevTools or WAVE.
**Why:** Automated audits help catch common accessibility errors and ensure compliance with accessibility standards.
## 9. Performance Optimization
### 9.1. Code Splitting
**Standard:** Use SvelteKit's automatic code splitting to load only the necessary code for each page.
**Do This:**
1. Structure the application into multiple pages and components.
2. SvelteKit automatically handles code splitting based on the route structure.
**Why:** Code splitting reduces the initial load time and improves overall application performance.
### 9.2. Image Optimization
**Standard:** Optimize images for the web using tools like ImageOptim or TinyPNG. Use responsive images with the "" element or "srcset" attribute.
**Do This:**
"""svelte
"""
**Don't Do This:**
* Using large, unoptimized images.
**Why:** Optimized images reduce bandwidth usage and improve page load times. Responsive images ensure that the appropriate image size is delivered to different devices.
### 9.3. Lazy Loading
**Standard:** Use lazy loading for images and other resources that are not immediately visible on the screen.
**Do This:**
"""svelte
"""
**Why:** Lazy loading improves initial page load time by deferring the loading of non-critical resources.
## 10. Security
### 10.1. Input Validation and Sanitization
**Standard:** Validate and sanitize user inputs to prevent security vulnerabilities like cross-site scripting (XSS) and SQL injection.
**Do This:**
* Use server-side validation and sanitization for all user inputs.
* Escape user-provided data when rendering it in HTML.
**Why:** Input validation and sanitization prevent malicious code from being injected into the application.
### 10.2. Cross-Site Scripting (XSS) Prevention
**Standard:** Prevent XSS attacks by escaping user-provided data, using Content Security Policy (CSP), and avoiding the use of "innerHTML".
**Do This:**
* Escape user-provided data when rendering it in HTML: "{value}" in Svelte automatically escapes.
* Avoid using "innerHTML" to render dynamic content.
**Why:** XSS attacks can compromise the security of the application and user data.
### 10.3. Dependency Management
**Standard:** Keep dependencies up-to-date to patch security vulnerabilities. Use tools like "npm audit" or "pnpm audit" to identify and fix vulnerabilities.
**Do This:**
"""bash
npm audit
# or
pnpm audit
"""
**Why:** Outdated dependencies may contain known security vulnerabilities that can be exploited by attackers.
By adhering to these tooling and ecosystem standards, Svelte developers can create maintainable, performant, secure, and accessible applications.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# 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.
# Core Architecture Standards for Svelte This document outlines the core architectural standards for Svelte projects, focusing on project structure, organization, and fundamental architectural patterns. This guide is intended to promote maintainability, performance, and scalability in Svelte applications. ## 1. Project Structure and Organization A well-defined project structure is crucial for maintainability and collaboration. Svelte projects benefit from a structure that separates concerns and promotes reusability. ### 1.1 Standard Directory Structure **Do This:** Adhere to a standardized directory structure that aligns with SvelteKit's conventions and best practices. **Don't Do This:** Avoid haphazardly placing files and folders without a clear organizational scheme. **Why:** A consistent structure makes it easier for developers to navigate and understand the project. It also facilitates automated tooling and deployment processes. """ ├── src/ │ ├── lib/ # Reusable components, utilities, and stores │ │ ├── components/ # Presentational and container components │ │ ├── utils/ # Utility functions │ │ ├── stores/ # Svelte stores for state management │ │ └── assets/ # Images, SVGs, and other static assets │ ├── routes/ # SvelteKit routes (pages and API endpoints) │ │ ├── +page.svelte # Page component │ │ ├── +page.ts # Page data loading logic │ │ ├── +server.ts # API endpoints │ │ └── components/ # Route-specific components │ ├── app.html # Root HTML template │ ├── app.d.ts # TypeScript definitions │ └── hooks.server.ts # Server-side hooks ├── static/ # Static assets (images, fonts, etc.) ├── svelte.config.js # Svelte configuration ├── vite.config.ts # Vite configuration ├── package.json # Project dependencies and scripts ├── README.md # Project documentation └── tsconfig.json # TypeScript configuration """ * **"src/lib":** Contains reusable components, utilities, and Svelte stores. This directory should be further organized by functionality. * **"src/routes":** Holds SvelteKit routes, where each directory represents a route. "+page.svelte" files are the Svelte components for each page, and "+page.ts" files are used for loading data into the page. "+server.ts" defines API endpoints. * **"static":** Contains static assets like images and fonts. These are served directly. * **"svelte.config.js":** SvelteKit configuration file. Includes adapters, preprocessors, and other Svelte-specific settings. * **"vite.config.ts":** Vite configuration file. Controls the build process, including plugins and optimizations. ### 1.2 Component Organization within "lib" **Do This:** Organize components within the "src/lib/components" directory into logical groupings based on feature or domain. Create subdirectories for related components. **Don't Do This:** Dump all components into a single "components" directory. **Why:** Grouping components makes it easier to locate and understand the purpose of each component. """ src/lib/components/ ├── UserProfile/ │ ├── UserProfileCard.svelte │ ├── UserProfileEditor.svelte │ └── UserAvatar.svelte ├── ProductCard.svelte └── Button.svelte """ **Example:** """svelte <!-- src/lib/components/UserProfile/UserProfileCard.svelte --> <script> import UserAvatar from './UserAvatar.svelte'; export let user; </script> <div class="user-profile-card"> <UserAvatar src={user.avatar} alt={user.name} /> <h2>{user.name}</h2> <p>{user.bio}</p> </div> """ ### 1.3 Route-Specific Components **Do This:** When a component is only used within a specific route, place it in a "components" subdirectory within the route directory. **Don't Do This:** Place route-specific components in the global "src/lib/components" directory. **Why:** This improves encapsulation and reduces the global namespace of components, leading to better maintainability. """ src/routes/ └── blog/ ├── [slug]/ │ ├── +page.svelte │ └── components/ │ └── BlogPostContent.svelte """ ### 1.4 Utility Functions **Do This:** Place reusable utility functions in the "src/lib/utils" directory. Group related functions into separate modules (e.g., "date.ts", "string.ts"). **Don't Do This:** Scatter utility functions throughout the codebase or duplicate them in multiple places. **Why:** Centralized utility functions promote code reuse and reduce redundancy. """typescript // src/lib/utils/date.ts export function formatDate(date: Date): string { return new Intl.DateTimeFormat('en-US').format(date); } """ ### 1.5 Svelte Stores **Do This:** Centralize state management using Svelte stores in the "src/lib/stores" directory. Use separate files for different domains of application state (e.g., "user.ts", "settings.ts"). **Don't Do This:** Manage state in a haphazard way or rely heavily on prop drilling. **Why:** Svelte stores provide a reactive and efficient way to manage application state. """typescript // src/lib/stores/user.ts import { writable } from 'svelte/store'; export const user = writable({ id: null, name: '', email: '' }); export function setUser(userData) { user.set(userData); } export function clearUser() { user.set({ id: null, name: '', email: '' }); } """ ## 2. Architectural Patterns Svelte applications can benefit from well-established architectural patterns to improve organization, testability, and scalability. ### 2.1 Component-Based Architecture **Do This:** Embrace a component-based architecture, breaking down the user interface into reusable components. **Don't Do This:** Create monolithic components that handle too much logic and rendering. **Why:** Component-based architecture promotes code reuse, testability, and maintainability. Svelte's core design encourages this naturally. """svelte <!-- src/lib/components/Button.svelte --> <script> export let label: string; export let onClick: () => void; </script> <button on:click={onClick}>{label}</button> """ ### 2.2 Container/Presentational Component Pattern **Do This:** Separate components into "container" (or "smart") components that handle data fetching and logic, and "presentational" (or "dumb") components that focus on rendering UI. **Don't Do This:** Mix data fetching and complex logic directly within presentational components. **Why:** This separation makes it easier to test and reuse components. Container components can be responsible for connecting to stores or APIs, while presentational components focus on displaying data. """svelte <!-- src/lib/components/ProductList.svelte (Presentational) --> <script> export let products: any[]; </script> <ul> {#each products as product (product.id)} <li>{product.name} - ${product.price}</li> {/each} </ul> <!-- src/routes/+page.svelte (Container) --> <script lang="ts"> import ProductList from '$lib/components/ProductList.svelte'; import { onMount } from 'svelte'; let products = []; onMount(async () => { const response = await fetch('/api/products'); products = await response.json(); }); </script> <ProductList {products} /> """ ### 2.3 Actions for DOM Manipulation **Do This:** Use Svelte actions for DOM manipulation and side effects related to specific elements. **Don't Do This:** Directly manipulate the DOM within component logic. **Why:** Actions provide a clean and declarative way to interact with DOM elements. """svelte <!-- src/lib/actions/tooltip.ts --> export function tooltip(node: HTMLElement, text: string) { let tooltipElement: HTMLDivElement; function createTooltip() { tooltipElement = document.createElement('div'); tooltipElement.textContent = text; tooltipElement.style.position = 'absolute'; tooltipElement.style.background = 'black'; tooltipElement.style.color = 'white'; tooltipElement.style.padding = '5px'; document.body.appendChild(tooltipElement); } function destroyTooltip() { if (tooltipElement) { tooltipElement.remove(); tooltipElement = null; } } function updateTooltipPosition(event: MouseEvent) { if (tooltipElement) { tooltipElement.style.top = "${event.pageY + 10}px"; tooltipElement.style.left = "${event.pageX + 10}px"; } } node.addEventListener('mouseover', createTooltip); node.addEventListener('mousemove', updateTooltipPosition); node.addEventListener('mouseout', destroyTooltip); return { update(newText: string) { text = newText; if (tooltipElement) { tooltipElement.textContent = text; } }, destroy() { node.removeEventListener('mouseover', createTooltip); node.removeEventListener('mousemove', updateTooltipPosition); node.removeEventListener('mouseout', destroyTooltip); destroyTooltip(); } }; } """ """svelte <!-- Usage in a component --> <div use:tooltip="'This is a tooltip'">Hover me</div> """ ### 2.4 Form Handling with Actions **Do This:** Employ Svelte actions for managing form state and validation instead of directly binding to input elements. This promotes cleaner, more reusable, and testable form logic. Libraries like "felte" are an excellent choice. **Don't Do This:** Use inline event handlers and manually manage input values within your components. **Why:** Using actions and form helper libraries encapsulates form logic and improves the overall architecture of forms. """bash npm install @felte/core @felte/validator-zod zod """ """svelte <script lang="ts"> import { createForm } from '@felte/core'; import { validator } from '@felte/validator-zod'; import { z } from 'zod'; const schema = z.object({ firstName: z.string().min(2, { message: "First name must be at least 2 characters." }), lastName: z.string().min(2, { message: "Last name must be at least 2 characters." }), email: z.string().email({ message: "Invalid email address." }), }); const { form, errors, isSubmitting, handleSubmit, data } = createForm({ onSubmit: async (values) => { alert(JSON.stringify(values)); }, validate: validator({ schema }), initialValues: { firstName: '', lastName: '', email: '' } }); </script> <form use:form> <div> <label for="firstName">First Name</label> <input type="text" id="firstName" name="firstName" aria-invalid={$errors.firstName ? 'true' : undefined}> {#if $errors.firstName} <span class="error">{$errors.firstName}</span> {/if} </div> <div> <label for="lastName">Last Name</label> <input type="text" id="lastName" name="lastName" aria-invalid={$errors.lastName ? 'true' : undefined}> {#if $errors.lastName} <span class="error">{$errors.lastName}</span> {/if} </div> <div> <label for="email">Email</label> <input type="email" id="email" name="email" aria-invalid={$errors.email ? 'true' : undefined}> {#if $errors.email} <span class="error">{$errors.email}</span> {/if} </div> <button type="submit" disabled={$isSubmitting}> Submit </button> </form> """ ## 3. State Management Efficient state management is essential for complex Svelte applications. ### 3.1 Choosing the Right State Management Approach **Do This:** Evaluate the scale of state complexity required for each project when choosing a state management solution. Choose Svelte stores for local component state and simple application-wide state. Consider more robust solutions, like Zustand or Redux, for complex scenarios. Pinia is a great choice if using Vue previously as it mimics the API. **Don't Do This:** Over-engineer state management in small projects or under-engineer it in large projects. **Why:** Appropriate state management prevents performance problems, improves maintainability, and enhances code readability. ### 3.2 Global State with Svelte Stores **Do This:** Use Svelte stores for managing global application state. Leverage derived stores for computed values and read-only stores for immutable data. **Don't Do This:** Mutate store values directly without using the "set", "update", or "subscribe" methods. **Why:** Stores provide a reactive and predictable way to manage application state. Using the built-in methods ensures that components are properly updated when the state changes. """typescript // src/lib/stores/cart.ts import { writable, derived } from 'svelte/store'; export const cartItems = writable([]); export const totalItems = derived(cartItems, ($cartItems) => $cartItems.length); export const totalPrice = derived(cartItems, ($cartItems) => { return $cartItems.reduce((total, item) => total + item.price * item.quantity, 0); }); """ """svelte <!-- Usage in a component --> <script> import { cartItems, totalItems, totalPrice } from '$lib/stores/cart'; function addItem(item) { cartItems.update(items => [...items, item]); } </script> <p>Total items: {$totalItems}</p> <p>Total price: {$totalPrice}</p> """ ### 3.3 Context API for Component-Specific State **Do This:** Use the Svelte context API ("setContext", "getContext") to share state and functionality between related components in a localized scope (such as within a specific route or component tree). **Don't Do This:** Overuse the context API for global state management. Svelte stores are generally better for that purpose. **Why:** The context API provides a way to avoid prop drilling and pass data down the component tree without explicitly passing props through each intermediate component. """svelte <!-- src/lib/components/ThemeProvider.svelte --> <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; const theme = writable('light'); setContext('theme', { theme, toggleTheme: () => theme.update(t => t === 'light' ? 'dark' : 'light') }); </script> <slot /> """ """svelte <!-- src/lib/components/ThemedComponent.svelte --> <script> import { getContext } from 'svelte'; const { theme, toggleTheme } = getContext('theme'); </script> <div class="themed-component" class:dark={$theme === 'dark'}> Current theme: {$theme} <button on:click={toggleTheme}>Toggle Theme</button> </div> <style> .themed-component { padding: 10px; border: 1px solid black; } .themed-component.dark { background-color: black; color: white; border-color: white; } </style> """ ## 4. API Communication Consider best practices for fetching data and interacting with APIs. ### 4.1 SvelteKit Load Functions **Do This:** Use SvelteKit's "load" functions ("+page.ts", "+layout.ts") to fetch data for routes and layouts. This allows for server-side rendering and improved SEO. **Don't Do This:** Fetch data directly in components using "onMount" unless it is client-side specific. **Why:** "load" functions execute on the server during SSR and can pre-render the page with data, improving performance and SEO. """typescript // src/routes/+page.ts import type { PageLoad } from './$types'; export const load: PageLoad = async ({ fetch }) => { const response = await fetch('/api/products'); const products = await response.json(); return { products }; }; """ """svelte <!-- src/routes/+page.svelte --> <script> export let data; const products = data.products; </script> <ul> {#each products as product (product.id)} <li>{product.name} - ${product.price}</li> {/each} </ul> """ ### 4.2 API Endpoints with SvelteKit **Do This:** Create API endpoints using SvelteKit's server routes ("+server.ts"). Follow RESTful principles for endpoint design. **Don't Do This:** Implement complex business logic directly within components. **Why:** Server routes allow you to create clean, maintainable APIs that can be used by your Svelte application and other clients. """typescript // src/routes/api/products/+server.ts import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; const products = [ { id: 1, name: 'Product A', price: 20 }, { id: 2, name: 'Product B', price: 30 } ]; export const GET: RequestHandler = async () => { return json(products); }; """ ### 4.3 Error Handling **Do This:** Implement robust error handling for API requests. Use "try...catch" blocks and provide informative error messages to the user. **Don't Do This:** Ignore potential errors or display generic error messages. **Why:** Proper error handling improves the user experience and helps debug issues. """typescript // Example within a SvelteKit load function export const load: PageLoad = async ({ fetch }) => { try { const response = await fetch('/api/products'); if (!response.ok) { throw new Error("Failed to fetch products: ${response.status}"); } const products = await response.json(); return { products }; } catch (error) { console.error(error); return { status: 500, error: new Error('Could not load products') }; } }; """ ## 5. Naming Conventions Consistent naming conventions improve code readability and maintainability. ### 5.1 Components **Do This:** Use PascalCase for component names (e.g., "UserProfileCard.svelte"). **Don't Do This:** Use camelCase or snake_case for component names. **Why:** PascalCase is the standard convention for components in most UI frameworks. ### 5.2 Variables and Functions **Do This:** Use camelCase for variables and functions (e.g., "userName", "formatDate"). **Don't Do This:** Use PascalCase or snake_case for variables and functions. **Why:** camelCase is the standard convention for variables and functions in JavaScript and TypeScript. ### 5.3 Files and Directories **Do This:** Use kebab-case for file and directory names (e.g., "user-profile.svelte", "api-utils"). **Don't Do This:** Use camelCase or snake_case for file and directory names. **Why:** kebab-case is a common convention for file and directory names in web development. ### 5.4 Svelte Stores **Do This:** Use camelCase for store names (e.g., "user", "settings"). Consider adding a "$" prefix when referencing the store's value in the template (e.g. "$user.name"). **Don't Do This:** Use PascalCase or snake_case for store names. **Why:** camelCase aligns with JavaScript naming conventions, and the "$" prefix clearly indicates that you're accessing a reactive value. ## 6. TypeScript Usage Leverage TypeScript to improve code quality and maintainability. ### 6.1 Strict Typing **Do This:** Enable strict mode in your "tsconfig.json" file (""strict": true"). **Don't Do This:** Disable strict mode or use "any" type excessively. **Why:** Strict typing helps catch errors early and improves code reliability. ### 6.2 Explicit Types **Do This:** Provide explicit types for component props, store values, and function parameters. **Don't Do This:** Rely solely on type inference, especially for complex types. **Why:** Explicit types improve code readability and prevent unexpected type errors. """typescript // src/lib/components/Button.svelte <script lang="ts"> export let label: string; export let onClick: () => void; </script> <button on:click={onClick}>{label}</button> """ ### 6.3 Interfaces and Types **Do This:** Define interfaces or types for data structures used in your application. **Don't Do This:** Use inline type definitions or anonymous types. **Why:** Interfaces and types provide a clear and reusable way to define data structures. """typescript // src/lib/types/user.ts export interface User { id: number; name: string; email: string; } """ """typescript // Usage in a store: import { writable } from 'svelte/store'; import type { User } from '$lib/types/user'; export const user = writable<User | null>(null); """ By adhering to these core architectural standards, Svelte developers can create maintainable, performant, and scalable applications. This document is a living guide and should be updated as new best practices and features emerge in the Svelte ecosystem.
# Component Design Standards for Svelte This document outlines component design standards for Svelte applications. These standards aim to promote reusable, maintainable, performant, and secure components, reflecting modern Svelte practices. ## 1. Component Architecture & Structure ### 1.1 Reusability Through Composition **Standard:** Favor component composition over inheritance or complex conditional logic within a single component to achieve reusability. **Why:** Composition promotes modularity, testability, and easier maintenance. Avoid tightly coupled components which are difficult to reuse in different contexts. Inheritance patterns are discouraged in Svelte. **Do This:** """svelte <!-- Button.svelte --> <script> export let label; export let onClick; export let variant = 'primary'; // default value $: buttonClass = "btn btn-${variant}"; </script> <button class={buttonClass} on:click={onClick}> {label} </button> <style> .btn { padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn-primary { background-color: blue; color: white; } .btn-secondary { background-color: gray; color: white; } </style> <!-- ParentComponent.svelte --> <script> import Button from './Button.svelte'; function handleClick() { alert('Clicked!'); } function handleSecondaryClick() { alert('Secondary Clicked!'); } </script> <Button label="Primary Button" onClick={handleClick} /> <Button label="Secondary Button" onClick={handleSecondaryClick} variant="secondary"/> """ **Don't Do This:** """svelte <!-- Button.svelte (Anti-pattern) Using overly complex conditional logic --> <script> export let label; export let onClick; export let isPrimary = true; </script> <button class:primary={isPrimary} class:secondary={!isPrimary} on:click={onClick}> {label} </button> <style> button { padding: 0.5rem 1rem; border: none; cursor: pointer; } .primary { background-color: blue; color: white; } .secondary { background-color: gray; color: white; } </style> """ **Explanation:** The "Do This" example demonstrates creating a reusable "Button" component with customizable "variant" property, promoting cleaner separation of concerns and improved reusability. The "Don't Do This" example tries to adapt the button using conditional logic which makes the code harder to read and maintain. ### 1.2 Single Responsibility Principle (SRP) **Standard:** Each component should have a single, well-defined purpose. **Why:** SRP simplifies component logic, improves readability, and makes components easier to test and maintain. **Do This:** """svelte <!-- UserProfile.svelte - Handles displaying user profile information. --> <script> export let user; </script> <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> <!-- AddressDisplay.svelte - Handles displaying address information. --> <script> export let address; </script> <div> <p>Address: {address.street}, {address.city}, {address.country}</p> </div> <!-- ParentComponent.svelte--> <script> import UserProfile from './UserProfile.svelte'; import AddressDisplay from './AddressDisplay.svelte'; const user = { name: 'John Doe', email: 'john.doe@example.com' }; const address = { street: '123 Main St', city: 'Anytown', country: 'USA' }; </script> <UserProfile {user} /> <AddressDisplay {address} /> """ **Don't Do This:** """svelte <!-- UserProfile.svelte (Anti-pattern) - Mixing multiple responsibilities. --> <script> export let user; export let showAddress = false; </script> <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> {#if showAddress} <p>Address: {user.address.street}, {user.address.city}, {user.address.country}</p> {/if} </div> """ **Explanation:** The SRP is upheld by creating distinct components for user profile and address display. This separation makes each component easier to develop, test, and reuse. Combining these into a single component with conditional rendering as shown in the "Don't Do This" example violates SRP and makes it harder to manage. ### 1.3 Atomic Design Principles **Standard:** Consider atomic design principles (Atoms, Molecules, Organisms) when structuring components. **Why:** It encourages a structured approach to building UIs, promotes reusability, and facilitates consistency. **Example:** * **Atom:** A single input field, a label, or a button. * **Molecule:** A search bar composed of an input field and a button. * **Organism:** A header section composed of a logo, navigation, and search bar. """svelte <!-- Atoms --> <!-- Input.svelte --> <input type={type} bind:value={value} placeholder={placeholder} /> <!-- Button.svelte --> <button on:click>{label}</button> <!-- Molecule --> <!-- SearchBar.svelte --> <script> import Input from './Input.svelte'; import Button from './Button.svelte'; let searchTerm = ''; function handleSearch() { alert("Searching for: ${searchTerm}"); } </script> <div> <Input type="text" placeholder="Search..." bind:value={searchTerm} /> <Button label="Search" on:click={handleSearch} /> </div> <!-- Organism --> <!-- Header.svelte --> <script> import SearchBar from './SearchBar.svelte'; </script> <header> <h1>My Website</h1> <nav> <a href="/">Home</a> <a href="/about">About</a> </nav> <SearchBar /> </header> """ **Explanation:** Demonstrates composition of smaller components into larger ones. ## 2. Component API Design ### 2.1 Props **Standard:** Use descriptive and consistently named props. Leverage typechecking using JSDoc or Typescript when possible. **Why:** Clear prop names improve component usability and reduce errors. Type checking prevents unexpected data types being passed, improving runtime stability. **Do This:** """svelte <!-- ProfileCard.svelte --> <script> /** * @type {{ name: string, email: string, imageUrl: string }} */ export let profile; /** * @type {boolean} */ export let showActions = false; /** * @type {() => void} */ export let onEdit; /** * @type {() => void} */ export let onDelete; </script> <div class="profile-card"> <img src={profile.imageUrl} alt={profile.name}> <h2>{profile.name}</h2> <p>{profile.email}</p> {#if showActions} <button on:click={onEdit}>Edit</button> <button on:click={onDelete}>Delete</button> {/if} </div> <style> /* Styling */ </style> """ **Don't Do This:** """svelte <!-- ProfileCard.svelte (Anti-pattern) --> <script> export let data; // Vague prop name export let actions; // Boolean, but unclear what it controls </script> <div> <h1>{data.name}</h1> <p>{data.email}</p> {#if actions} <button>Edit</button> <button>Delete</button> {/if} </div> """ **Explanation:** The 'Do This' example uses descriptive prop names ("profile", "showActions") and JSDoc type annotations, making the component's API clear. The anti-pattern uses vague prop names like "data" and "actions", making the intent unclear. ### 2.2 Events **Standard:** Use custom events for communication from child to parent components. **Why:** Custom events provide a clean and explicit mechanism for child components to signal changes or actions to their parent components. **Do This:** """svelte <!-- Counter.svelte --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); let count = 0; function increment() { count++; dispatch('update', { count }); } </script> <button on:click={increment}>Increment</button> <!-- Parent.svelte --> <script> import Counter from './Counter.svelte'; let currentCount = 0; function handleUpdate(event) { currentCount = event.detail.count; } </script> <Counter on:update={handleUpdate} /> <p>Count: {currentCount}</p> """ **Don't Do This:** """svelte <!-- Counter.svelte (Anti-pattern) - Directly mutating parent state --> <script> export let count; // Passed down and mutated directly. BAD! function increment() { count++; // Side effect outside of the component } </script> <button on:click={increment}>Increment</button> <!-- Parent.svelte --> <script> import Counter from './Counter.svelte'; let count = 0; </script> <Counter count={count} /> <!-- No way to actually know if the counter updated--> <p>Count: {count}</p> """ **Explanation:** The correct example uses "createEventDispatcher" to emit a custom 'update' event, propagating changes to the parent component. The incorrect example attempts to directly modify a prop passed down from the parent, which is an anti-pattern. This method is not reactive and doesn't properly update the "count" in the Parent component, as well as being potentially buggy. ### 2.3 Slots **Standard:** Utilize slots to allow flexible content injection into components. Explore named slots and scoped slots for more advanced scenarios. **Why:** Slots make components more adaptable to various use cases without requiring extensive prop configuration. **Do This:** """svelte <!-- Card.svelte --> <div class="card"> <header> <slot name="header"> <h2>Default Header</h2> </slot> </header> <main> <slot> <p>Default Content</p> </slot> </main> <footer> <slot name="footer"></slot> </footer> </div> <style> .card { border: 1px solid gray; padding: 1rem; margin: 1rem; } </style> <!-- ParentComponent.svelte --> <script> import Card from './Card.svelte'; </script> <Card> <svelte:fragment slot="header"> <h1>Custom Header</h1> </svelte:fragment> <p>Custom Content</p> <svelte:fragment slot="footer"> <button>Click Me</button> </svelte:fragment> </Card> """ **Don't Do This:** """svelte <!-- Card.svelte (Anti-pattern) - hardcoding specific content --> <div> <h1>Hardcoded Title</h1> <p>Hardcoded Content</p> </div> """ **Explanation:** The 'Do This' example uses named slots for the header and footer, allowing the parent component to inject custom content. It provides a default header and content in case the parent doesn't provide them. ### 2.4 Context API **Standard:** Employ the Svelte context API for sharing states and functions amongst deeply nested components *without* prop drilling. Use sparingly and carefully. Prefer stores when appropriate. **Why:** The context API avoids prop drilling and simplifies state management for related components. However, overuse can lead to implicit dependencies and reduced component isolation. **Do This:** """svelte <!-- ThemeProvider.svelte --> <script> import { setContext } from 'svelte'; const theme = { primaryColor: 'blue', secondaryColor: 'white' }; setContext('theme', theme); </script> <slot /> <!-- Button.svelte --> <script> import { getContext } from 'svelte'; const theme = getContext('theme'); </script> <button style="background-color: {theme.primaryColor}; color: {theme.secondaryColor}"> <slot></slot> </button> <!-- App.svelte --> <script> import ThemeProvider from './ThemeProvider.svelte'; import Button from './Button.svelte'; </script> <ThemeProvider> <Button>My Button</Button> </ThemeProvider> """ **Don't Do This:** """svelte <!-- Button.svelte (Anti-pattern) - Prop Drilling. --> <script> export let primaryColor; export let secondaryColor; </script> <button style="background-color: {primaryColor}; color: {secondaryColor}"> <slot></slot> </button> <!-- App.svelte --> <script> import Button from './Button.svelte'; </script> <Button primaryColor="blue" secondaryColor="white">My Button</Button> """ **Explanation:** The well-implemented version uses the "setContext" and "getContext" functions to share the theme across components without prop drilling. The anti-pattern passes "primaryColor" and "secondaryColor" via props, which can become cumbersome with deeply nested components. This becomes even worse as functionality expands and many more props must be passed to child components. ### 2.5 Stores **Standard:** Use Svelte stores for managing application-wide state, especially data that is shared and mutated across different components. Choose readable/writable, or custom stores appropriately. **Why:** Stores provide a centralized and reactive way to manage application state, simplifying data flow and component updates. **Do This:** """svelte <!-- store.js --> import { writable } from 'svelte/store'; export const user = writable({ name: 'John Doe', email: 'john.doe@example.com' }); <!-- Profile.svelte --> <script> import { user } from './store.js'; import { derived } from 'svelte/store'; const username = derived(user, ($user) => $user.name); </script> <h1>{$username}</h1> <p>{$user.email}</p> <!-- EditProfile.svelte --> <script> import { user } from './store.js'; function updateEmail(newEmail) { user.update(u => ({ ...u, email: newEmail })); } </script> <input type="email" bind:value={$user.email} on:change={() => updateEmail($user.email)}> """ **Don't Do This:** """svelte <!-- Profile.svelte (Anti-pattern) - Managing local state when it should be global --> <script> let user = { name: 'John Doe', email: 'john.doe@example.com' }; </script> <h1>{user.name}</h1> <p>{user.email}</p> <!-- EditProfile.svelte --> <script> import Profile from './Profile.svelte'; let profile = new Profile() let newEmail = profile.user.email //Very hard to get this to work correctly </script> <input type="email" value={newEmail}> """ **Explanation:** The "Do This" example correctly utilizes a writable store to manage the user data across the "Profile" and "EditProfile" components, making updates reactive and centralized. A derived store extracts the user's name. The anti-pattern in which each component independently manages its own copy of the state, leading to inconsistent data and difficulty in synchronization is to be avoided. ## 3. Component Style & Formatting ### 3.1 Scoped Styling **Standard:** Use "<style>" tags within Svelte components for scoped styling. Consider global styles sparingly and only when necessary. **Why:** Scoped styling prevents style conflicts and ensures that component styles are encapsulated, improving maintainability and predictability. **Do This:** """svelte <!-- Button.svelte --> <button class="my-button"> <slot /> </button> <style> .my-button { background-color: blue; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } </style> """ **Don't Do This:** """svelte <!-- styles.css (Anti-pattern) - global styling --> .my-button { background-color: blue; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } """ **Explanation:** The correct implementation encapsulates the button's styles within the component, preventing conflicts with other elements using the same class name. The anti-pattern, global styling, can lead to unexpected styling clashes across the application. While global styles can be useful to import some basic styles across the application, use component styling when possible to avoid collision and make code more organized. ### 3.2 Consistent Formatting **Standard:** Adhere to a consistent code formatting style using tools like Prettier. **Why:** Consistent formatting improves code readability and reduces cognitive load. **Configuration (Example using Prettier):** """json // .prettierrc.json { "semi": true, "singleQuote": true, "trailingComma": "es5", "tabWidth": 2, "useTabs": false } """ **Example:** Use the above config in your IDE or with a command line to format all the Svelte files on save to have consistent semi-colons, single quotes, tab width, etc. ### 3.3 CSS Preprocessors (Optional) **Standard:** Use CSS preprocessors like SCSS or PostCSS for enhanced styling capabilities (nesting, variables, mixins). Ensure that preprocessor integration is configured correctly. **Why:** Preprocessors improve CSS authoring experience, enhance code organization, and make styling more maintainable. **Example:** """svelte <!-- Button.svelte --> <button class="my-button"> <slot /> </button> <style lang="scss"> .my-button { background-color: $primary-color; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; &:hover { background-color: darken($primary-color, 10%); } } </style> """ **Note:** you may need to install "sass" and configure your bundler (e.g. Vite). ## 4. Component Performance ### 4.1 Minimize DOM Updates **Standard:** Leverage Svelte's reactivity to minimize DOM updates. Avoid manually manipulating the DOM whenever possible. **Why:** Svelte's compiler optimizes DOM updates, manual DOM manipulation can interfere with this optimization and degrade performance. **Do This:** """svelte <!-- Counter.svelte --> <script> let count = 0; function increment() { count++; } </script> <button on:click={increment}>Increment</button> <p>Count: {count}</p> """ **Don't Do This:** """svelte <!-- Counter.svelte (Anti-pattern) - Manual DOM manipulation --> <script> let count = 0; function increment() { count++; document.getElementById('count').textContent = count; //Avoid this } </script> <button on:click={increment}>Increment</button> <p id="count">Count: {count}</p> """ **Explanation:** Svelte handles DOM updates automatically when the "count" variable changes. Manually manipulating the DOM is inefficient and can cause inconsistencies. "document.getElementById" is also usually a design smell. ### 4.2 Optimize Event Handlers **Standard:** Use Svelte's event modifiers (e.g., "passive", "preventDefault", "stopPropagation") and consider debouncing or throttling event handlers to avoid excessive updates. Modern approaches are extremely important. **Why:** Optimized event handlers improve responsiveness and prevent performance bottlenecks, especially for frequently triggered events. **Example:** """svelte <!-- SearchInput.svelte --> <script> import { tick } from 'svelte'; // Used for Debouncing let searchTerm = ''; let debouncedSearchTerm = ''; async function handleInput() { await tick(); // Ensure that the DOM is updated before reading the value debouncedSearchTerm = searchTerm; // Debounce: capture the LAST valid value. console.log("Searching for: ${debouncedSearchTerm}"); } </script> <input type="text" bind:value={searchTerm} on:input={handleInput}> """ The tick function pauses execution of the function until the next browser render. If multiple keystrokes occur, tick is invoked multiple times, but the alert is only invoked after the last keystroke. ### 4.3 Lazy Loading **Standard:** Implement lazy loading for images, components, or data that are not immediately visible or required. Dynamically import large components. **Why:** Lazy loading reduces initial load time and improves perceived performance by deferring the loading of non-essential resources. **Example:** """svelte {#await import('./HeavyComponent.svelte')} <!-- loading state --> <p>Loading...</p> {:then HeavyComponent} <!-- loaded state --> <svelte:component this={HeavyComponent.default} /> {:catch error} <!-- error state --> <p>Error: {error.message}</p> {/await} """ ### 4.4 Avoid Unnecessary Reactive Statements **Standard:** Be mindful of reactive statements ("$:") and ensure they are only used when necessary for derived values or side effects that depend on component state. **Why:** Unnecessary reactive statements can lead to performance overhead by triggering unnecessary updates. **Do This:** """svelte <script> export let price; export let quantity; $: total = price * quantity; // Only update when price or quantity changes </script> <p>Total: {total}</p> """ **Don't Do This:** """svelte <script> export let price; export let quantity; let total; $: total = price * quantity; // Assigning to an already declared variable. Less efficient. </script> <p>Total: {total}</p> """ The difference is subtle but declaring the variable and assigning it within the reactive statement is more efficient. ## 5. Component Testing ### 5.1 Unit Tests **Standard:** Write unit tests for individual components to verify their behavior and functionality in isolation. **Why:** Unit tests provide confidence in component correctness, facilitate refactoring, and help prevent regressions. **Recommended Libraries:** Jest, Mocha, Chai, Testing Library. **Example (using Jest and Testing Library):** """javascript // Button.spec.js import { render, fireEvent } from '@testing-library/svelte'; import Button from './Button.svelte'; test('renders with correct label', () => { const { getByText } = render(Button, { label: 'Click Me' }); expect(getByText('Click Me')).toBeInTheDocument(); }); test('calls onClick handler when clicked', async () => { const onClick = jest.fn(); const { getByText } = render(Button, { label: 'Click Me', onClick }); await fireEvent.click(getByText('Click Me')); expect(onClick).toHaveBeenCalled(); }); """ ### 5.2 Integration Tests **Standard:** Write integration tests to verify interactions between components and ensure that they work correctly together. **Why:** Integration tests catch issues that may not be apparent from unit tests, such as incorrect prop passing or event handling. ### 5.3 End-to-End (E2E) Tests **Standard:** Use E2E tests to validate the overall application flow and ensure that components work correctly in a realistic environment. **Why:** E2E tests provide the highest level of confidence by simulating user interactions and verifying that the application behaves as expected! ## 6. Component Security ### 6.1 Input Sanitization **Standard:** Sanitize all user inputs to prevent Cross-Site Scripting (XSS) attacks. **Why:** XSS attacks can compromise the security of the application and expose user data. **Do This:** """svelte <script> import DOMPurify from 'dompurify'; // Need to install this export let content; $: sanitizedContent = DOMPurify.sanitize(content); </script> {@html sanitizedContent} """ **Don't Do This:** """svelte <script> export let content; </script> {@html content} <!-- Vulnerable to XSS --> """ The "Do This" pattern includes the DOMPurify library, which strips potentially dangerous HTML. ### 6.2 Secure Data Handling **Standard:** Handle sensitive data securely (e.g., passwords, API keys) and avoid storing them in client-side code where possible. Use environment variables and secure storage mechanisms. **Why:** Exposing sensitive data in client-side code can lead to security breaches and unauthorized access. ### 6.3 Avoid Third-Party Vulnerabilities **Standard:** Regularly update dependencies and scan for known vulnerabilities using tools like "npm audit" or "yarn audit". **Why:** Dependencies can contain security vulnerabilities that can be exploited by attackers. Keeping dependencies up-to-date minimizes the risk. These component design standards provide a comprehensive guide for developing robust, maintainable, performant, and secure Svelte applications. Adhering to these best practices will contribute to higher quality code and a smoother development experience.
# State Management Standards for Svelte This document outlines state management standards for Svelte applications. It provides guidelines and best practices for managing application state, data flow, and reactivity effectively, promoting maintainability, performance, and scalability. These standards are aligned with the latest Svelte version and modern development patterns. ## 1. Principles of State Management in Svelte Effective state management is crucial for building robust and maintainable Svelte applications. This section outlines the guiding principles. ### 1.1 Local vs. Global Store Selection * **Standard:** Decide whether state belongs in a component (local) or in a store (global). Use stores primarily for data shared across multiple, disparate components, or that represents application-level settings. * **Do This:** Use local state (component variables) when the data is specific to a single component's functionality and doesn't need to be accessed by other parts of the application. Use stores for data shared between components, representing application state, or for data that needs to persist across component lifecycles. * **Don't Do This:** Overuse stores for data that's only relevant to a single component. This creates unnecessary complexity. Also, don't rely solely on prop drilling for data that's needed by many components; consider a store. * **Why:** Clear state boundaries improve component isolation, making it easier to reason about component behavior and reducing the risk of unintended side effects. Using stores judiciously avoids unnecessary complexity. * **Example - Local State:** """svelte <script> let count = 0; function increment() { count += 1; } </script> <button on:click={increment}>Count: {count}</button> """ * **Example - Global Store:** """svelte // store.js import { writable } from 'svelte/store'; export const user = writable(null); // Component A <script> import { user } from './store.js'; </script> <h1>Welcome, {$user?.name}!</h1> // Component B <script> import { user } from './store.js'; function login(userData) { user.set(userData); } </script> <button on:click={() => login({ name: 'John Doe' })}>Login</button> """ ### 1.2 Unidirectional Data Flow * **Standard:** Enforce a top-down (parent-to-child) data flow. Child components should not directly modify the props they receive from their parents. * **Do This:** Pass data down via props. Use stores for shared state that child components can react to rather than modify directly. * **Don't Do This:** Mutate props directly within a child component. This makes it difficult to track the source of state changes and can lead to unexpected bugs. * **Why:** Unidirectional data flow simplifies debugging and makes it easier to understand how changes in one part of the application affect others. It also prevents cascading updates. * **Example:** """svelte // Parent.svelte <script> import Child from './Child.svelte'; let message = 'Hello from parent!'; </script> <Child {message} /> // Child.svelte <script> export let message; function handleClick() { // Don't do this: message = 'New message'; // Mutating the prop is an anti-pattern. } </script> <p>{message}</p> <button on:click={handleClick}>Try to change message</button> """ ### 1.3 Embrace Reactivity * **Standard:** Utilize Svelte's built-in reactivity effectively. Use reactive statements ("$:") to derive state and respond to changes. * **Do This:** Use reactive statements to automatically update the UI based on data changes. Use "$:" blocks to perform side effects (e.g., logging, API calls) triggered by state changes, but do so sparingly and only when component logic dictates. Minimize complex logic inside "$:" blocks. Extract logic into functions. * **Don't Do This:** Manually update the DOM or use imperative approaches when reactivity can handle the task. Overuse reactive statements for complex logic that should be encapsulated in functions. * **Why:** Svelte's reactivity simplifies state management, reducing boilerplate code and improving performance. Properly used reactivity results in clear, concise, and maintainable code. * **Example:** """svelte <script> let price = 10; let quantity = 2; $: total = price * quantity; // Reactive statement function increaseQuantity() { quantity += 1; } </script> <p>Price: {price}</p> <p>Quantity: {quantity}</p> <p>Total: {total}</p> <button on:click={increaseQuantity}>Increase Quantity</button> """ ## 2. Using Svelte Stores Svelte stores are a powerful way to manage global application state. ### 2.1 Store Types: Writable, Readable, and Derived * **Standard:** Choose the appropriate store type based on data mutability and dependency requirements. * **Do This:** * Use "writable" stores for state that can be directly updated. * Use "readable" stores for data that is sourced externally and read-only from the application's perspective. * Use "derived" stores for values that are computed based on other stores. * **Don't Do This:** Use "writable" stores for derived data that should be calculated from other stores. Create unnecessary complexity by manually managing dependencies when "derived" stores can handle it. * **Why:** Using the correct store type ensures clarity and prevents unintended state modifications. It also improves performance by only updating derived stores when their dependencies change. * **Example:** """svelte // store.js import { writable, readable, derived } from 'svelte/store'; export const count = writable(0); export const startTime = readable(new Date()); // Time the app started, never changes internally export const doubleCount = derived(count, ($count) => $count * 2); """ ### 2.2 Subscribing to Stores * **Standard:** Access store values using the "$" prefix within Svelte components. Alternatively, subscribe to stores manually for more control. * **Do This:** Use the "$" prefix for simple component integration. Manually subscribe to stores using "store.subscribe()" when you need to perform side effects or have specific lifecycle management requirements. * **Don't Do This:** Attempt to directly access the value of a store without subscribing or using the "$" prefix. Forget to unsubscribe from stores in components that are frequently created and destroyed, as this can lead to memory leaks. * **Why:** The "$" prefix simplifies store access within Svelte components. Manual subscriptions provide more flexibility for advanced scenarios but require careful cleanup (via "unsubscribe()" function). * **Example:** """svelte <script> import { count } from './store.js'; // Using the $ prefix (auto-subscribe) </script> <p>Count: {$count}</p> <script> import { onMount, onDestroy } from 'svelte'; import { count } from './store.js'; let currentCount; let unsubscribe; onMount(() => { unsubscribe = count.subscribe(value => { currentCount = value; }); }); onDestroy(() => { unsubscribe(); // Clean up the subscription to prevent memory leaks. }); </script> <p>Count: {currentCount}</p> """ ### 2.3 Updating Stores * **Standard:** Use the "set()", "update()", and "subscribe" methods provided by the Svelte store to modify state. * **Do This:** Use "store.set(newValue)" to directly set the store's value. Use "store.update(callback)" to update the store's value based on its previous value, ensuring atomicity. For complex updates, consider a custom store with methods that encapsulate the logic. * **Don't Do This:** Directly modify the store's value without using "set()" or "update()". Avoid complex logic directly within components when updating stores. * **Why:** Using the store's methods ensures that Svelte's reactivity system is properly triggered and that updates are performed safely. * **Example:** """svelte // store.js import { writable } from 'svelte/store'; export const count = writable(0); export const increment = () => { count.update(n => n + 1); } export const decrement = () => { count.update(n => n - 1); } // App.svelte <script> import { count, increment, decrement } from './store.js'; function handleIncrement() { increment(); } function handleDecrement() { decrement(); } </script> <p>Count: {$count}</p> <button on:click={handleIncrement}>+</button> <button on:click={handleDecrement}>-</button> """ ### 2.4 Custom Stores * **Standard:** Create custom stores to encapsulate complex state management logic and provide a clear API for interacting with the store. Use custom stores for asynchronous operations, persistent storage, or advanced state transformations. * **Do This:** Define custom methods for your store that handle specific update scenarios. Consider using a factory function to create multiple instances of a custom store. * **Don't Do This:** Put complex business logic directly in your components. Let the store's custom methods handle the app's business logic. * **Why:** Custom stores improve code organization, reusability, and testability. They allow you to encapsulate complex logic and provide a consistent API for interacting with the store. * **Example:** """svelte // customStore.js import { writable } from 'svelte/store'; function createTodoStore() { const { subscribe, set, update } = writable([]); return { subscribe, add: (text) => { update(todos => [...todos, { id: Date.now(), text, completed: false }]); }, toggle: (id) => { update(todos => todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); }, remove: (id) => { update(todos => todos.filter(todo => todo.id !== id)); } }; } export const todos = createTodoStore(); // App.svelte <script> import { todos } from './customStore.js'; let newTodo = ''; function addTodo() { todos.add(newTodo); newTodo = ''; } </script> <input type="text" bind:value={newTodo} /> <button on:click={addTodo}>Add Todo</button> <ul> {#each $todos as todo (todo.id)} <li> <input type="checkbox" checked={todo.completed} on:change={() => todos.toggle(todo.id)} /> {todo.text} <button on:click={() => todos.remove(todo.id)}>Remove</button> </li> {/each} </ul> """ ## 3. Context API The Context API is useful for providing data to a subtree of components without prop drilling ### 3.1 Providing Context * **Standard:** Use "setContext" to provide context to a component and its descendants. Context values are scoped to the current component. * **Do This:** Call "setContext" within a component's "<script>" tag. Use a unique key (usually a symbol or a string constant) to avoid naming conflicts. * **Don't Do This:** Call "setContext" outside the component's "<script>" tag. Use generic or undescriptive keys. Overuse context for state that's frequently changing; consider stores in such cases. * **Why:** "setContext" provides a straightforward mechanism for making data available to child components. Scoped keys prevent conflicts with other context providers. * **Example:** """svelte // Parent.svelte <script> import { setContext } from 'svelte'; import Child from './Child.svelte'; const theme = { textColor: 'black', backgroundColor: 'white' }; setContext('theme', theme); // Using a string key is acceptable for simple cases const user = { name: "Alice", age: 30 }; const userSymbol = Symbol('user'); // A more robust approach to ensure unique context keys. setContext(userSymbol, user); //Using a unique symbol to set the context </script> <Child /> // Child.svelte <script> import { getContext } from 'svelte'; const theme = getContext('theme'); const userSymbol = Symbol('user'); const user = getContext(userSymbol); //Retrieving context via the unique symbol </script> <div style="color: {theme.textColor}; background-color: {theme.backgroundColor}"> <p>This is a child component using theme from context.</p> <p>User Name: {user.name}</p> <p>User Age: {user.age}</p> </div> """ ### 3.2 Consuming Context * **Standard:** Use "getContext" to retrieve context values within a component. * **Do This:** Call "getContext" within a component's "<script>" tag, using the same key used when setting the context. * **Don't Do This:** Call "getContext" without ensuring that the context has been provided by an ancestor component. Directly modify the context value retrieved from "getContext"; treat it as read-only. * **Why:** "getContext" allows components to access data provided by their ancestors without explicit prop passing. Treating context values as read-only follows the unidirectional data flow principle. ## 4. Form Management ### 4.1 Two-Way Binding * **Standard:** Utilize Svelte's two-way binding ("bind:value") for form inputs. * **Do This:** Use "bind:value" for basic form input elements (text inputs, checkboxes, etc.). For more complex scenarios, such as formatting or validation, use a combination of "bind:value" and custom logic. * **Don't Do This:** Manually handle input changes using event listeners when "bind:value" can achieve the same result more concisely. * **Why:** "bind:value" simplifies form handling and reduces boilerplate code. * **Example:** """svelte <script> let name = ''; let checked = false; </script> <input type="text" bind:value={name} /> <p>Name: {name}</p> <input type="checkbox" bind:checked={checked} /> <p>Checked: {checked}</p> """ ### 4.2 Form Validation * **Standard:** Validate form data on input and submission. Provide clear and helpful error messages to the user. * **Do This:** Use reactive statements or custom functions to validate input values. Provide visual cues to indicate errors (e.g., highlighting invalid fields). * **Don't Do This:** Rely solely on client-side validation. Always validate data on the server as well. * **Why:** Form validation ensures data integrity and improves the user experience. * **Example:** """svelte <script> let email = ''; let emailError = ''; $: { if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { emailError = 'Invalid email address'; } else { emailError = ''; } } function handleSubmit() { if (emailError) { alert('Please fix the errors before submitting.'); return; } alert('Form submitted successfully!'); } </script> <input type="email" bind:value={email} class:error={emailError} /> {#if emailError} <p class="error-message">{emailError}</p> {/if} <style> .error { border: 1px solid red; } </style> """ ## 5. Considerations for Performance ### 5.1 Minimize Store Updates * **Standard:** Update stores only when necessary. Avoid unnecessary store updates that can trigger excessive re-renders. * **Do This:** Use "derived" stores to calculate values only when their dependencies change. Use "immutable" data structures to prevent accidental mutations that can trigger unnecessary updates (see below). * **Don't Do This:** Update stores on every component update, even if the data hasn't changed. Introduce unnecessary overhead. * **Why:** Reducing store updates improves performance by minimizing the number of re-renders. ### 5.2 Immutable Data Structures * **Standard:** Use immutable data structures (or treat your data as immutable) when working with stores, especially for complex objects and arrays. * **Do This:** Use methods like "Object.assign" or the spread operator ("...") to create new objects or arrays instead of modifying them directly. Consider using libraries like Immer or Immutable.js for more advanced scenarios. * **Don't Do This:** Directly modify objects or arrays stored in stores, as this can lead to unexpected behavior and performance issues if reactivity is missed or triggered multiple times. * **Why:** Immutable data structures prevent accidental mutations, making it easier to track state changes and improving performance. * **Example:** """svelte <script> import { writable } from 'svelte/store'; export const items = writable([{ id: 1, name: 'Item 1' }]); function addItem() { items.update(currentItems => { return [...currentItems, { id: Date.now(), name: 'New Item' }]; // Correct: Using the spread operator }); } function updateItemName(id, newName) { items.update(currentItems => { return currentItems.map(item => { if (item.id === id) { return {...item, name: newName}; //Correct } return item; }); }); } </script> <button on:click={addItem}>Add Item</button> """ ### 5.3 Optimize Reactive Statements * **Standard:** Use reactive statements judiciously. Avoid complex or expensive operations within reactive statements that can negatively impact performance. * **Do This:** Minimize side effects within reactive statements. Extract complex logic into functions that can be called from reactive statements. * **Don't Do This:** Perform expensive computations or API calls directly within reactive statements. Rely on "$:" for tasks that would be better handled with "onMount" or other lifecycle methods. * **Why:** Reactive statements are re-evaluated whenever their dependencies change. Optimizing these statements ensures that the UI remains responsive. The goal should be to have "$:" blocks only calculate or transform values, not perform actions that have side effects. ## 6. Testing ### 6.1 Store Testing * **Standard:** Thoroughly test stores to ensure they manage state correctly and provide the expected values. * **Do This:** Write unit tests for custom stores, focusing on their methods and state transitions. Mock dependencies (e.g., API calls) to isolate the store's logic. * **Don't Do This:** Skip testing stores, as this can lead to unexpected behavior and difficult-to-debug issues. Write integration tests before there are good unit tests. * **Why:** Testing stores ensures that the application's state management is reliable and predictable. ## 7. Security ### 7.1 Input Sanitization * **Standard:** Sanitize user inputs to prevent cross-site scripting (XSS) vulnerabilities. * **Do This:** Use Svelte's built-in HTML escaping to prevent XSS attacks. For more complex scenarios, consider using a dedicated sanitization library. * **Don't Do This:** Directly render user-provided data without sanitization. * **Why:** Input sanitization prevents malicious code from being injected into the application. ### 7.2 Secure Store Storage * **Standard:** Protect sensitive data stored in stores (e.g., access tokens, API keys). * **Do This:** Avoid storing sensitive data in client-side stores if possible. If necessary, encrypt the data before storing it and use secure storage mechanisms (e.g., cookies with the "HttpOnly" flag, the browser's "crypto" API). * **Don't Do This:** Store sensitive data in plain text in client-side stores or local storage. * **Why:** Protecting sensitive data prevents unauthorized access and potential security breaches. By adhering to these state management standards, Svelte developers can build applications that are maintainable, performant, secure, scalable.
# Performance Optimization Standards for Svelte This document outlines standards for optimizing Svelte applications, focusing on speed, responsiveness, and minimizing resource usage. These guidelines are designed to improve application performance and maintainability and reflect best practices for the latest version of Svelte. ## 1. Component Architecture and Data Management ### 1.1. Minimize Component Updates **Standard:** Only update components when necessary by carefully managing data dependencies and using fine-grained reactivity. **Why:** Excessive component updates lead to unnecessary re-renders, degrading performance. Svelte's reactivity is powerful, but it's crucial to use it judiciously. **Do This:** * Use derived stores ("derived") to compute values based on other stores, preventing unnecessary updates when the source stores haven't changed. * Employ local state variables ("let") for data that doesn't require reactivity across multiple components. * Use contextual stores sparingly; excessive reliance can lead to performance bottlenecks. * Implement custom equality checks when using mutable data structures inside stores. **Don't Do This:** * Avoid mutating objects or arrays directly inside stores when using "$:" as it triggers updates regardless of changes. Use ".set()" or ".update()" on the store. * Don't update stores with the same value. This is especially true with writable stores. **Example:** """svelte <script> import { derived, writable } from 'svelte/store'; // Writable store for user input const inputText = writable(''); // Derived store for debounced input value - Example const debouncedText = derived(inputText, ($inputText, set) => { const timeout = setTimeout(() => { set($inputText); }, 300); // Debounce time in milliseconds return () => clearTimeout(timeout); // Cleanup function }); // Local variable for internal state let counter = 0; function increment() { counter += 1; // Does NOT trigger rerender of the page. } // Updating the store correctly function updateInput(event) { inputText.set(event.target.value); } </script> <input type="text" on:input={updateInput} /> <p>Debounced Text: {$debouncedText}</p> <p>Counter: {counter}</p> <button on:click={increment}>Increment</button> """ ### 1.2. Use "{#each}" Blocks Efficiently **Standard:** Optimize "{#each}" blocks for performance by providing unique keys and avoiding unnecessary re-renders of list items. **Why:** Svelte's "{#each}" block is efficient, but incorrect usage can lead to performance bottlenecks if items are frequently added, removed, or reordered. **Do This:** * Provide a unique "key" attribute to each item in the list. This helps Svelte identify which items have changed and only update those specific elements. * Use immutable data structures when rendering lists from stores. If data changes, create new copies. If the data isn't immutable, make sure to use the key binding, or Svelte will not know what has changed and re-render everything. * Consider using pagination or virtualization for very long lists to avoid rendering all items at once. **Don't Do This:** * Avoid using the index as a key if the list items can be reordered, as this can cause incorrect updates. This is a frequent source of bugs. * Don't mutate list data directly; instead, create new arrays or objects to trigger updates. **Example:** """svelte <script> import { writable } from 'svelte/store'; const items = writable([ { id: 1, name: 'Item A' }, { id: 2, name: 'Item B' }, { id: 3, name: 'Item C' } ]); function addItem() { items.update(currentItems => [...currentItems, { id: Date.now(), name: 'New Item' }]); } </script> <button on:click={addItem}>Add Item</button> {#each $items as item (item.id)} <!-- Explicit Key Binding --> <div key={item.id}> {item.name} </div> {/each} """ ### 1.3. Optimize Component Size **Standard:** Keep components focused and relatively small to reduce rendering overhead. **Why:** Large, complex components can be challenging to reason about and can lead to slower rendering times. **Do This:** * Break down large components into smaller, reusable components. * Move complex logic into separate functions or modules. * Use slots for flexible content injection. **Don't Do This:** * Don't create "God Components" that handle too much logic and rendering. **Example:** Instead of: """svelte <!-- Bad example of a large component --> <script> let data = [...]; //Lots of data let filter = ''; let sortOrder = 'asc'; function filterData() { ... } function sortData() { ... } function renderItem(item) { ... } </script> <div> <input bind:value={filter}/> <button on:click={sortData}>Sort</button> {#each filterData(data) as item} {renderItem(item)} {/each} </div> """ Do This: Split it into smaller components: """svelte <!-- Good Example --> <script> import FilterInput from './FilterInput.svelte'; import SortButton from './SortButton.svelte'; import ItemList from './ItemList.svelte'; let data = [...]; let filter = ''; let sortOrder = 'asc'; function sortData() { ... } </script> <div> <FilterInput bind:filter={filter} /> <SortButton on:click={sortData} /> <ItemList data={data} filter={filter} /> </div> """ ## 2. Code Splitting and Lazy Loading ### 2.1. Implement Code Splitting **Standard:** Split your application into smaller chunks to reduce the initial load time. **Why:** Code splitting allows browsers to download only critical code initially, improving the perceived performance of your application. Svelte integrates with bundlers like Vite and Rollup, making code splitting relatively straightforward. **Do This:** * Use dynamic imports ("import()") to load components or modules on demand. * Configure your bundler (Vite/Rollup) to automatically split code based on import statements. * Lazy-load routes in your application. **Don't Do This:** * Avoid loading all your application code in a single bundle. **Example:** """svelte <script> let component; async function loadComponent() { component = (await import('./MyComponent.svelte')).default; } </script> {#if component} <svelte:component this={component} /> {:else} <button on:click={loadComponent}>Load Component</button> {/if} """ ### 2.2. Lazy Load Images and Other Assets **Standard:** Defer the loading of non-critical assets like images until they are needed. **Why:** Lazy loading reduces initial page load time and conserves bandwidth by only loading assets when they are visible in the viewport. **Do This:** * Use the "loading="lazy"" attribute for images. * Implement custom lazy loading for components or sections of your application. * Consider using a library like "svelte-lazy" for more advanced lazy-loading scenarios. **Don't Do This:** * Avoid loading all images on page load, especially for long pages. **Example:** """svelte <img src="my-image.jpg" alt="My Image" loading="lazy"> """ ## 3. Binding and Event Handling ### 3.1. Optimize Event Listeners **Standard:** Use passive event listeners for scroll and touch events to improve scrolling performance. Also, use delegated events when appropriate. **Why:** Passive event listeners allow the browser to handle scrolling smoothly without waiting for JavaScript to execute. **Do This:** * Add the "passive: true" option to event listeners for scroll and touch events. * Use delegated events where multiple child element needs to listen to the same event. **Don't Do This:** * Avoid blocking the main thread with long-running event handlers during scrolling. **Example:** """svelte <svelte:window on:scroll={() => { // Your scroll handling logic here }} options={{ passive: true }} /> """ ### 3.2. Minimize Two-Way Binding **Standard:** Use two-way binding ("bind:value") only when necessary, as it can lead to performance issues with frequent updates. **Why:** Two-way binding can trigger frequent component updates, especially with text inputs. **Do This:** * Use one-way binding and event listeners for more control over updates. * Consider debouncing input events to reduce the frequency of updates. **Don't Do This:** * Avoid using two-way binding for large data sets or complex components. **Example:** Instead of: """svelte <!-- Potentially problematic two-way binding --> <input bind:value={myValue} /> """ Do This: """svelte <script> import { writable } from 'svelte/store'; const myValue = writable(""); function updateValue(event) { myValue.set(event.target.value); } </script> <input type="text" on:input={updateValue} /> <p>Value: {$myValue}</p> """ ## 4. Svelte Specific Optimizations ### 4.1. Leveraging Svelte's Reactivity **Standard:** Utilize Svelte's built-in reactivity features effectively to minimize manual DOM manipulation. **Why:** Svelte's compiler optimizes updates efficiently. Avoid directly manipulating the DOM unless absolutely necessary. **Do This:** * Use reactive declarations ("$:") for derived values and side effects. * Rely on Svelte's component updates for rendering changes. * Use transition directives instead of manually handling animations whenever possible. **Don't Do This:** * Avoid using "document.querySelector" or similar methods to directly manipulate the DOM within Svelte components unless critically necessary. **Example:** """svelte <script> let count = 0; $: doubled = count * 2; // Reactive declaration function increment() { count += 1; } </script> <button on:click={increment}>Increment</button> <p>Count: {count}</p> <p>Doubled: {doubled}</p> """ ### 4.2. Efficient Use of Context API **Standard:** Use the Svelte context API thoughtfully, balancing convenience with potential performance implications. **Why:** Excessive use of context, especially with frequent updates, can cause unnecessary re-renders in components that depend on the context. **Do This:** * Provide context at the highest level possible to minimize the number of components affected by context changes. * Consider using stores instead of context for data that needs to be shared and updated frequently. * Memoize values passed via context if they are expensive to compute. **Don't Do This:** * Avoid updating context values unnecessarily. **Example:** """svelte <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; // Create a store to hold context data const myContext = writable({ value: 'Initial Value' }); // Set the context value setContext('myContextKey', myContext); </script> <slot /> """ """svelte <script> import { getContext } from 'svelte'; import { onMount } from 'svelte'; // Get the context value const myContext = getContext('myContextKey'); let value; // Subscribe to the store to update the local variable const unsubscribe = myContext.subscribe(val => { value = val.value; }); onMount(() => { return () => unsubscribe(); }); //Unsubscribing on destory is imporant. </script> <p>Context Value: {value}</p> """ ## 5. External Libraries and Tooling ### 5.1. Use SvelteKit's Load Function Wisely **Standard**: Return only the data you need and avoid unnecessary computations in load functions. **Why**: Load functions run on both the server and the client. Expensive operations can slow down both the initial server render and subsequent client-side navigations. **Do This**: * Fetch only the data necessary for the current page. Avoid fetching entire databases without pagination or filtering. * Move complex data transformations or business logic into separate modules and cache the results if appropriate. * Utilize the "depends" function to invalidate the "load" function only when necessary dependencies change. **Don't Do This**: * Perform database queries directly within components. This breaks separation of concerns and couples your components to the backend. * Include large, unused datasets in the returned "load" function data. **Example:** """typescript // src/routes/blog/[slug]/+page.ts import { getBlogPost } from '$lib/blog'; // Assume this fetches a blog post from a database or CMS export async function load({ params, fetch }) { const post = await getBlogPost(params.slug, fetch); //Assuming getBlogPost now uses fetch api if (!post) { error(404, 'Post not found'); } return { title: post.title, content: post.content, // Only return the necessary parts of the post slug: params.slug }; } """ ### 5.2 Performance Monitoring **Standard**: Use browser developer tools to identify and address performance bottlenecks. **Why**: Profilers and performance monitoring tools are the most reliable way to identify and resolve perfomance related issues **Do This**: * Use the "Performance" tab in Chrome DevTools (or similar in other browsers) to record and analyze the performance of your Svelte applications. * Identify slow-rendering components, long-running JavaScript functions, and excessive DOM manipulations. * Use Svelte Devtools for Component inspection * Use Lighthouse reporting to identify key areas for performance improvement. **Don't Do This**: * Rely solely on intuition. Performance is often counterintuitive. ## 6. Security Considerations While this document is primarily focused on performance, security and performance often intersect: ### 6.1. Sanitize User Input **Standard:** Always sanitize user input to prevent XSS (Cross-Site Scripting) vulnerabilities, which can negatively impact performance by injecting malicious code. **Why:** XSS vulnerabilities allow attackers to inject malicious scripts into your application, potentially leading to data theft, session hijacking, or defacement of your website. Additionally, unsanitized inputs may contain special characters or escape sequences that can lead to unpredictable behavior. **Do This:** * Use Svelte's built-in HTML encoding to prevent malicious code from being injected into your application. * Employ a library like DOMPurify to sanitize HTML input from users. * Avoid using "{@html ...}" directive with user-provided data without proper sanitization. **Don't Do This:** * Never trust user input directly without proper sanitization. **Example:** """svelte <script> import DOMPurify from 'dompurify'; let userInput = ''; let sanitizedHTML = ''; function sanitizeInput() { sanitizedHTML = DOMPurify.sanitize(userInput); } </script> <input bind:value={userInput} on:input={sanitizeInput} /> <div> {@html sanitizedHTML} </div> """ ### 6.2. Secure API Communication **Standard:** Use HTTPS for all API communication to protect data in transit and prevent man-in-the-middle attacks. **Why:** Using HTTPS ensures that data exchanged between your application and backend API is encrypted, preventing eavesdropping and tampering. Unencrypted communication can expose sensitive information and compromise user data. **Do This:** * Ensure all API endpoints use HTTPS (SSL/TLS) encryption. * Use appropriate CORS (Cross-Origin Resource Sharing) policies to restrict access to your API. * Validate and sanitize data received from the API to prevent injection attacks. **Don't Do This:** * Avoid using HTTP for sensitive data transmission or API communication in production environments. ### 6.3. Proper Error Handling **Standard:** Implement robust error handling to prevent information leakage and denial-of-service attacks. **Why:** Proper error handling prevents sensitive information from being exposed in error messages and prevents attackers from exploiting vulnerabilities to crash the application. **Do This:** * Handle exceptions and errors gracefully and log relevant information for debugging. * Avoid revealing sensitive information (e.g., database connection strings, API keys) in error messages. * Implement rate limiting to prevent abuse and denial-of-service attacks. **Don't Do This:** * Avoid displaying detailed error messages directly to users in production environments. This document provides a starting point for defining Svelte coding standards focused on performance optimization. It should evolve as the framework and best practices change and should be tailored to the specific needs of each project.