# 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'
# Testing Methodologies Standards for Svelte This document outlines the recommended testing methodologies and best practices for Svelte applications. Effective testing ensures application reliability, maintainability, and performance. It covers unit, integration, and end-to-end testing with Svelte-specific examples and considerations. ## 1. General Testing Principles * **Do This:** Adhere to the testing pyramid: a large base of unit tests, a smaller layer of integration tests, and an even smaller peak of end-to-end tests. * **Don't Do This:** Rely solely on end-to-end tests. They are slower, more brittle, and provide less specific feedback than unit or integration tests. * **Why:** The testing pyramid promotes a balanced testing strategy that maximizes coverage and minimizes testing costs. * **Code Example (Pyramid Visualization - Conceptual):** """ # Unit Tests: Many, Fast, Isolated # Integration Tests: Fewer, Slower, Interacting Units # E2E Tests: Fewest, Slowest, Full Application """ ## 2. Unit Testing ### 2.1. Purpose and Scope * **Do This:** Unit test individual components, functions, and modules in isolation. Focus on validating the logic within each unit. Aim for high code coverage for critical components. * **Don't Do This:** Neglect unit testing in favor of integration or end-to-end tests. This makes isolating bugs significantly harder. * **Why:** Unit tests verify the behavior of individual units of code, making it easier to identify and fix bugs early in the development process. ### 2.2. Tools and Libraries * **Do This:** Use Jest or Vitest as a test runner and assertion library. Consider using "@testing-library/svelte" (or "@testing-library/dom" directly) for rendering and interacting with components in a user-centric way. * **Don't Do This:** Use assertion libraries that are not well-maintained or lack Svelte-specific utilities. * **Why:** Jest and Vitest provide excellent performance, mocking capabilities, and broad ecosystem support. "@testing-library/svelte" promotes testing components as a user would interact with them. ### 2.3. Writing Effective Unit Tests * **Do This:** Write tests that are readable, maintainable, and focused on a single concern. Use descriptive test names that clearly communicate the expected behavior. Utilize "describe" blocks to organize tests. * **Don't Do This:** Write overly complex or brittle tests that are difficult to understand or maintain. Avoid testing implementation details that are likely to change. Don't assert vague or overly broad outcomes. * **Why:** Readable and maintainable tests improve the overall quality of the codebase and make it easier to refactor and add new features. Testing behavior (what the user sees/experiences) rather than implementation leads to more robust and less fragile tests. ### 2.4. Unit Testing Examples #### 2.4.1. Testing a Simple Component """svelte <!-- Counter.svelte --> <script> let count = 0; function increment() { count += 1; } </script> <button on:click={increment}> Count: {count} </button> """ """javascript // Counter.spec.js (using Vitest and @testing-library/svelte) import { render, fireEvent } from '@testing-library/svelte'; import Counter from './Counter.svelte'; import { describe, it, expect } from 'vitest'; describe('Counter Component', () => { it('should render the initial count', () => { const { getByText } = render(Counter); expect(getByText('Count: 0')).toBeInTheDocument(); }); it('should increment the count when the button is clicked', async () => { const { getByText } = render(Counter); const button = getByText('Count: 0'); await fireEvent.click(button); expect(getByText('Count: 1')).toBeInTheDocument(); }); }); """ #### 2.4.2. Testing Component Properties """svelte <!-- Greeting.svelte --> <script> export let name; </script> <h1>Hello, {name}!</h1> """ """javascript // Greeting.spec.js import { render } from '@testing-library/svelte'; import Greeting from './Greeting.svelte'; import { describe, it, expect } from 'vitest'; describe('Greeting Component', () => { it('should render the greeting with the provided name', () => { const { getByText } = render(Greeting, { props: { name: 'World' } }); expect(getByText('Hello, World!')).toBeInTheDocument(); }); it('should render the greeting with a different name', () => { const { getByText } = render(Greeting, { props: { name: 'Svelte' } }); expect(getByText('Hello, Svelte!')).toBeInTheDocument(); }); }); """ #### 2.4.3. Testing Events """svelte <!-- Button.svelte --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function handleClick() { dispatch('click', { text: 'Button Clicked!' }); } </script> <button on:click={handleClick}>Click Me</button> """ """javascript // Button.spec.js import { render, fireEvent } from '@testing-library/svelte'; import Button from './Button.svelte'; import { describe, it, expect, vi } from 'vitest'; describe('Button Component', () => { it('should dispatch a click event when the button is clicked', async () => { const { getByText, component } = render(Button); const button = getByText('Click Me'); const mockFn = vi.fn(); component.$on('click', mockFn); // Use component.$on for listening await fireEvent.click(button); expect(mockFn).toHaveBeenCalledTimes(1); expect(mockFn).toHaveBeenCalledWith(expect.objectContaining({detail: { text: 'Button Clicked!'}})); }); }); """ ### 2.5. Mocking Dependencies * **Do This:** Use mocking libraries like Jest's "jest.mock()" or Vitest's "vi.mock()" to isolate the component being tested from its dependencies. Mock external APIs, stores, and other components. * **Don't Do This:** Mock indiscriminately. Only mock dependencies that are not relevant to the unit being tested. Avoid mocking implementation details. * **Why:** Mocking allows you to control the behavior of dependencies, ensuring that the unit being tested is isolated and that tests are deterministic. * **Code Example:** """javascript // api.js (Example API Module) export async function fetchData() { const response = await fetch('/api/data'); return response.json(); } """ """svelte <!-- MyComponent.svelte --> <script> import { fetchData } from './api.js'; let data = null; async function loadData() { data = await fetchData(); } loadData(); </script> {#if data} <p>Data: {data.value}</p> {:else} <p>Loading...</p> {/if} """ """javascript // MyComponent.spec.js import { render, waitFor, screen } from '@testing-library/svelte'; import MyComponent from './MyComponent.svelte'; import * as api from './api.js'; // Import the module import { describe, it, expect, vi } from 'vitest'; describe('MyComponent', () => { it('should render data fetched from the API', async () => { const mockFetchData = vi.spyOn(api, 'fetchData'); mockFetchData.mockResolvedValue({ value: 'Mocked Data' }); render(MyComponent); await waitFor(() => { expect(screen.getByText('Data: Mocked Data')).toBeInTheDocument(); }); mockFetchData.mockRestore(); // Restore the original function. Important for clean tests }); }); """ ### 2.6. Testing Stores * **Do This:** Test the behavior of stores independently from components that use them. Use "get()" from "svelte/store" to synchronously access the store's value during tests. Ensure async updates to stores are handled correctly in tests (using "await tick()" where necessary within components, but primarily using "await" with the store updates directly within the test). * **Don't Do This:** Test stores implicitly through component tests. * **Why:** Testing stores directly ensures their core logic is correct before being used within components. * **Code Example:** """javascript // myStore.js import { writable } from 'svelte/store'; export const count = writable(0); export function increment() { count.update(n => n + 1); } export function decrement() { count.update(n => n - 1); } """ """javascript // myStore.spec.js import { get } from 'svelte/store'; import { count, increment, decrement } from './myStore'; import { describe, it, expect } from 'vitest'; describe('myStore', () => { it('should initialize with a count of 0', () => { expect(get(count)).toBe(0); }); it('should increment the count', () => { increment(); expect(get(count)).toBe(1); }); it('should decrement the count', () => { decrement(); expect(get(count)).toBe(0); }); }); """ ## 3. Integration Testing ### 3.1. Purpose and Scope * **Do This:** Integration test how multiple components and units of code work together. Focus on verifying interactions between different parts of the application. * **Don't Do This:** Treat integration tests as replacements for unit tests. Ensure to test units individually before testing them in conjunction. * **Why:** Integration tests expose issues that arise when different parts of the application interact, such as incorrect data flow or unexpected side effects. ### 3.2. Testing Strategies * **Do This:** Use a combination of top-down and bottom-up integration testing approaches. Focus on major user flows and critical application features. * **Don't Do This:** Avoid testing all possible combinations of interactions. Prioritize testing the most common and important scenarios. * **Why:** A balanced approach to integration testing provides comprehensive coverage without becoming overwhelming. ### 3.3. Integration Testing Examples #### 3.3.1. Testing Component Communication """svelte <!-- Parent.svelte --> <script> import Child from './Child.svelte'; let messageFromChild = ''; function handleMessage(event) { messageFromChild = event.detail.message; } </script> <Child on:message={handleMessage} /> <p>Message from Child: {messageFromChild}</p> """ """svelte <!-- Child.svelte --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function sendMessage() { dispatch('message', { message: 'Hello from Child!' }); } </script> <button on:click={sendMessage}>Send Message</button> """ """javascript // Parent.spec.js import { render, fireEvent, screen } from '@testing-library/svelte'; import Parent from './Parent.svelte'; import { describe, it, expect } from 'vitest'; describe('Parent Component Integration', () => { it('should receive and display a message from the child component', async () => { render(Parent); const button = screen.getByText('Send Message'); await fireEvent.click(button); expect(screen.getByText('Message from Child: Hello from Child!')).toBeInTheDocument(); }); }); """ #### 3.3.2. Testing Store Interactions """svelte <!-- CounterComponent.svelte --> <script> import { count, increment, decrement } from './myStore'; import { get } from 'svelte/store'; </script> <button on:click={increment}>Increment</button> <button on:click={decrement}>Decrement</button> <p>Count: {$count}</p> """ """javascript // CounterComponent.spec.js import { render, fireEvent, screen } from '@testing-library/svelte'; import CounterComponent from './CounterComponent.svelte'; import { increment, decrement, count } from './myStore'; import { describe, it, expect} from 'vitest'; import { get } from 'svelte/store'; // Ensure store is reset before each test. Vitest runs tests in parallel, and stores are global! import { beforeEach } from 'vitest'; beforeEach(() => { count.set(0); }); describe('CounterComponent with Store Integration', () => { it('should increment and decrement the count using the store', async () => { render(CounterComponent); const incrementButton = screen.getByText('Increment'); const decrementButton = screen.getByText('Decrement'); await fireEvent.click(incrementButton); expect(screen.getByText('Count: 1')).toBeInTheDocument(); await fireEvent.click(decrementButton); expect(screen.getByText('Count: 0')).toBeInTheDocument(); }); it('should update the store directly and reflect in the component', async () => { render(CounterComponent); increment(); expect(screen.getByText('Count: 1')).toBeInTheDocument(); }); }); """ ## 4. End-to-End (E2E) Testing ### 4.1. Purpose and Scope * **Do This:** Use E2E tests to verify the entire application flow, from user interaction to data persistence, across different browsers and environments. Focus on critical user journeys. * **Don't Do This:** Rely on E2E tests as the primary testing method. They are slow, expensive, and provide less specific feedback than unit or integration tests. * **Why:** E2E tests provide confidence that the application works as expected from the user's perspective, including interactions with external systems. ### 4.2. Tools and Libraries * **Do This:** Use Playwright or Cypress for writing and running E2E tests. These tools provide excellent browser automation capabilities, automatic waiting, and debugging features. Consider using Docker for consistent test environments. * **Don't Do This:** Use outdated or unmaintained E2E testing tools. * **Why:** Playwright and Cypress offer modern features that simplify E2E testing and improve reliability. ### 4.3. Writing Effective E2E Tests * **Do This:** Write tests that are resilient to UI changes. Use data attributes or role-based selectors to locate elements. Avoid relying on CSS classes or text content that may change. Use "getByRole" and "findByRole" from Testing Library within Playwright/Cypress where possible. * **Don't Do This:** Write brittle tests that are easily broken by minor UI changes. This leads to test fatigue and reduces the value of E2E testing. Don't include sleeps or hardcoded waits if possible; leverage the automatic waiting features of Playwright/Cypress. * **Why:** Resilient tests reduce maintenance costs and provide more reliable feedback. ### 4.4. E2E Testing Example (Playwright) """javascript // playwright.config.js import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, reporter: 'html', use: { baseURL: 'http://localhost:3000', // Replace with your app's URL trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, // Add other browsers as needed ], webServer: { command: 'npm run dev', // Replace with your dev server command url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); """ """javascript // tests/example.spec.js import { test, expect } from '@playwright/test'; test('has title', async ({ page }) => { await page.goto('/'); await expect(page).toHaveTitle("My Svelte App"); // Replace with your app's title }); test('counter increments correctly', async ({ page }) => { await page.goto('/'); const incrementButton = await page.getByRole('button', {name: 'Increment'}); const countText = page.locator('text=Count:'); // Use a CSS or text locator if roles aren't applicable await expect(countText).toContainText('Count: 0'); await incrementButton.click(); await expect(countText).toContainText('Count: 1'); }); """ ### 4.5. E2E Testing Considerations for Svelte * **Testing Transitions/Animations:** Playwright and Cypress can handle waiting for animations and transitions to complete before making assertions. Use "waitFor" or "waitForSelector" with appropriate timeouts to ensure elements are fully rendered. * **Shadow DOM:** If using web components extensively, understand how to query elements within the shadow DOM using Playwright or Cypress APIs. * **SvelteKit Specifics:** When testing SvelteKit apps, ensure the development server is running before running tests and that the correct base URL is configured. ## 5. Accessibility Testing * **Do This:** Integrate accessibility testing into your workflow. Use tools like axe DevTools, Lighthouse, or pa11y to identify accessibility issues. Write tests that verify that components are accessible to users with disabilities. Use semantic HTML. Employ ARIA attributes where necessary, testing them for correctness. * **Don't Do This:** Ignore accessibility testing. This can exclude users with disabilities and violate accessibility guidelines. * **Why:** Accessibility testing ensures that the application is usable by everyone, regardless of their abilities. * **Code Example (using axe DevTools):** """javascript // Accessibility.spec.js (Playwright) import { test, expect } from '@playwright/test'; import { AxeBuilder } from '@axe-core/playwright'; test.describe('Accessibility Tests', () => { test('homepage should not have any automatically detectable accessibility issues', async ({ page }) => { await page.goto('/'); const axeBuilder = new AxeBuilder({ page }); const accessibilityScanResults = await axeBuilder.analyze(); expect(accessibilityScanResults.violations).toEqual([]); }); }); """ ## 6. Performance Testing * **Do This:** Use performance testing tools like Lighthouse or WebPageTest to measure the performance of the application. Write tests that verify that components render quickly and efficiently. Profile components with Svelte's "$debug" and browser devtools to identify performance bottlenecks. * **Don't Do This:** Neglect performance testing. This can lead to slow and unresponsive applications that provide a poor user experience. * **Why:** Performance testing ensures that the application meets performance requirements and provides a smooth user experience. ## 7. Continuous Integration (CI) * **Do This:** Integrate all testing types into a CI/CD pipeline. Run unit tests on every commit. Run integration and accessibility tests regularly. Schedule E2E tests to run nightly or weekly. * **Don't Do This:** Manually run tests. CI/CD automates testing and reduces the risk of human error. * **Why:** CI/CD automates the testing process, ensuring that code changes are thoroughly tested before being deployed. ## 8. Code Coverage * **Do This:** Aim for high code coverage, particularly for core components and critical functionality. Use code coverage tools to identify untestable or untested code. * **Don't Do This:** Use code coverage as the *sole* measure of test quality. High coverage does not guarantee that tests are effective or that the application is bug-free. * **Why:** Code coverage provides a metric for assessing the extent to which the codebase is tested. ## 9. Svelte 5 Testing Considerations * **Reactivity Changes:** Be mindful of the reactivity changes in Svelte 5 (using runes). Ensure tests accurately reflect the new reactivity system, particularly when testing component updates and store interactions. Pay extra attention to testing derived state and how it updates based on reactive dependencies. * **.svelte.js/.ts Files:** Recognize that tests might now involve importing and testing ".svelte.js" or ".svelte.ts" files directly, which contain logic previously confined to the "<script>" tag within ".svelte" components. By following these testing methodologies and best practices, you can build robust, maintainable, and performant Svelte applications. This leads to higher quality software and a better experience for both developers and users.
# Deployment and DevOps Standards for Svelte This document outlines the coding standards for deployment and DevOps practices specific to Svelte applications. Adhering to these standards will help ensure consistent, maintainable, performant, and secure deployments. These standards are based on the latest version of Svelte and its ecosystem. ## 1. Build Processes and CI/CD ### 1.1. Standardizing Build Processes **Do This:** * Use a consistent build tool and configuration across all environments (development, staging, production). SvelteKit projects should use the "svelte-package" and Vite configuration. * Define build scripts in "package.json" using standard commands like "build", "preview", and "check". * Utilize environment variables properly for configuration management. **Don't Do This:** * Don't rely on manual build steps or environment-specific build scripts. * Don't hardcode sensitive information (API keys, secrets) directly in the code or build scripts. **Why:** Consistent build processes ensure reproducibility and prevent environment-specific issues. Using environment variables allows for flexible configuration without modifying the code. **Example:** """json // package.json { "scripts": { "dev": "vite dev", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.5.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte3": "^4.0.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.8.1", "svelte": "^3.54.0", "svelte-check": "^3.0.1", "tslib": "^2.4.1", "typescript": "^5.0.0", "vite": "^4.0.0" } } """ ### 1.2. CI/CD Pipelines **Do This:** * Implement a CI/CD pipeline using tools like GitHub Actions, GitLab CI, or Jenkins. * Automate build, test, and deployment processes. * Include linting, code formatting, and static code analysis in the pipeline. * Use automated testing (unit, integration, end-to-end) to ensure code quality. * Cache dependencies to speed up build times. * Implement version control tagging for releases. * Use SvelteKit's adapters for different deployment targets (Node, Vercel, Netlify, static). **Don't Do This:** * Don't deploy directly from developer machines. * Don't skip automated testing in the pipeline. * Don't expose sensitive data in CI/CD logs. **Why:** CI/CD pipelines automate the deployment process, reduce errors, and improve code quality. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' # Or the version your project targets cache: 'npm' - name: Install dependencies run: npm install - name: Lint and Format run: npm run lint && npm run format - name: Run Tests run: npm run test:unit # or your test command - name: Build run: npm run build - name: Deploy to Vercel # Or other deployment target like Netlify run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} """ ### 1.3 Adapters **Do This:** * Choose an appropriate adapter for your deployment environment in "svelte.config.js". SvelteKit provides official adapters for Node.js, Vercel, Netlify, Cloudflare Pages, amongst many others. Community adapters are also available. * Configure the adapter correctly based on your environment needs (e.g., static adapter settings for SPA deployment). * Use environment variables to configure the adapter at build time. **Don't Do This:** * Ignore the adapter configuration. Incorrect adapter or options can make your application not function as expected. * Commit adapter configuration variables (like API keys) to your repository. **Why:** Adapters adapt SvelteKit applications to different server environments. Configuring the adapter is crucial for optimizing the appliclation for that server environment (or edge runtime, or static hoster). **Example ("svelte.config.js" with Vercel adapter):** """javascript // svelte.config.js import adapter from '@sveltejs/adapter-vercel'; import { vitePreprocess } from 'svelte-preprocess'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter({ // Options for the adapter can be added here. }), }, preprocess: [vitePreprocess()] }; export default config; """ ## 2. Production Considerations ### 2.1. Environment Variables **Do This:** * Store all configuration parameters as environment variables. * Use ".env" files (with tooling like "dotenv") for local development, but NEVER commit ".env" files to version control. * Set environment variables in the deployment environment (e.g., Vercel, Netlify). **Don't Do This:** * Don't hardcode configuration parameters in the source code. * Don't commit API keys or secrets to version control. * Avoid using "process.env" directly in components. Create separate configuration modules for improved testability. **Why:** Using environment variables separates configuration from code, making it easier to manage deployments across different environments and improving security. **Example:** """javascript // src/lib/config.js export const API_URL = process.env.API_URL || 'http://localhost:3000/api'; // Default for development export const APP_NAME = process.env.APP_NAME || 'My Svelte App'; """ ### 2.2. Security **Do This:** * Use HTTPS for all production traffic. Enforce HTTPS redirects at your hosting provider. * Sanitize all user inputs to prevent XSS vulnerabilities. Use Svelte's templating engine for automatic escaping. * Implement proper authentication and authorization mechanisms. Use established libraries or services for authentication. * Implement rate limiting to protect against brute-force attacks. * Regularly update dependencies to patch security vulnerabilities. Use tools like "npm audit" to identify vulnerable packages. **Don't Do This:** * Don't store sensitive data (passwords, API keys) in local storage or cookies. * Don't disable CSP (Content Security Policy) without a strong understanding of the implications. * Don't rely on client-side validation alone for security. Always validate data on the server. **Why:** Security vulnerabilities can compromise user data and application integrity. Addressing these issues early in development is essential. Svelte's reactivity and component model can help with consistent sanitization if used correctly. **Example (Server-side validation using SvelteKit endpoints):** """typescript // src/routes/api/submit/+server.ts import { json, type RequestHandler } from '@sveltejs/kit'; export const POST: RequestHandler = async ({ request }) => { const data = await request.json(); // Sanitize and validate input const email = data.email?.trim(); if (!email || !email.includes('@')) { return json({ error: 'Invalid email' }, { status: 400 }); } // Process the data if valid // ... return json({ success: true }); }; """ ### 2.3. Performance **Do This:** * Optimize images using tools like ImageOptim or "imagemin". Use responsive images with "<picture>" or "srcset" attributes. Consider using Svelte's "<img>" tag enhancements. * Use code splitting to reduce initial load time by splitting the app into smaller chunks, which you can configure in Vite. * Lazy-load non-critical components and images. * Enable browser caching for static assets. Configure cache headers appropriately. * Monitor application performance using tools like Google PageSpeed Insights, web.dev, or browser developer tools. * Use Svelte's built-in reactivity efficiently. Avoid unnecessary computations in reactive blocks. * Leverage server-side rendering (SSR) or static site generation (SSG) where appropriate for improved initial load time and SEO. **Don't Do This:** * Don't load large, unoptimized images. * Don't block the main thread with long-running JavaScript tasks. * Don't make excessive network requests. * Don't over-rely on javascript when SSR options for Svelte or SvelteKit will do. **Why:** Performance is critical for user experience. Optimizing assets and code can significantly improve load times and reduce resource consumption. **Example (Lazy loading a component):** """svelte {#if showComponent} <MyComponent /> {:else} <button on:click={() => showComponent = true}>Load Component</button> {/if} <script> let showComponent = false; import MyComponent from './MyComponent.svelte'; </script> """ ### 2.4. Logging and Monitoring **Do This:** * Implement centralized logging using tools like Winston, Bunyan, or a dedicated logging service (e.g., Datadog, Loggly). * Log important events, errors, and warnings. Include contextual information in log messages (e.g., user ID, request ID). * Use monitoring tools (e.g., Prometheus, Grafana, New Relic) to track application health, performance metrics, and error rates. * Set up alerts for critical errors and performance degradation. **Don't Do This:** * Don't log sensitive data (passwords, API keys). * Don't rely solely on console logs for production debugging. * Don't ignore error messages or warnings. Address them promptly. **Why:** Logging and monitoring provide visibility into application behavior, making it easier to identify and resolve issues. **Example (Logging using Winston - server-side):** """javascript // src/hooks.server.js or a dedicated logging module import winston from 'winston'; const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ], }); export const handle = async ({ event, resolve }) => { try { const response = await resolve(event); return response; } catch (error) { logger.error('Unhandled exception', { url: event.url.pathname, error: error.message, stack: error.stack, }); throw error; // Re-throw to allow SvelteKit error handling } }; """ ### 2.5 SvelteKit Specific Enhancements **Do This:** * Take full advantage of SvelteKit's "handle" hook for request lifecycle management, including authentication, authorization, and data enrichment. * Consider using SvelteKit's serverless functions for API endpoints. * Learn SvelteKit's data loading best practices. Don't create side effects in your component initialization. * Use the "invalidate" function to trigger re-fetches when data changes **Don't Do This:** * Over complicate using SvelteKit "+page.server.js" to fetch data * Forget to use environment variables when creating a "baseURL" **Why:** These features improve developer productivity, performance, and maintainability. **Example:** """typescript // src/routes/+layout.server.ts import { API_URL } from '$lib/config'; export const load = async ({ fetch, cookies, url }) => { const session = cookies.get('sessionid'); try { const response = await fetch("${API_URL}/todos", { headers: { accept: 'application/json', cookie: "sessionid=${session ?? ''}" } }); const todos = await response.json(); return {todos}; } catch(e) { console.log(e) return { todos: [] }; } }; """ ## 3. DevOps Best Practices ### 3.1. Infrastructure as Code (IaC) **Do This:** * Use IaC tools like Terraform, AWS CloudFormation, or Azure Resource Manager to define and manage infrastructure. * Store IaC configurations in version control. * Automate infrastructure deployments using CI/CD pipelines. **Don't Do This:** * Don't manually provision infrastructure. * Don't store infrastructure configurations locally. **Why:** IaC enables repeatable and predictable infrastructure deployments. ### 3.2. Containerization **Do This:** * Package Svelte applications into Docker containers. * Use Docker Compose for local development and testing. * Orchestrate containers using Kubernetes or Docker Swarm for production deployments. **Don't Do This:** * Don't deploy applications directly to bare metal servers. * Don't expose unnecessary ports or volumes in Docker containers. **Why:** Containerization provides consistent and isolated environments for applications. ### 3.3. Monitoring and Alerting **Do This:** * Implement comprehensive monitoring of infrastructure and application resources. * Set up alerts for critical events (e.g., high CPU usage, disk space exhaustion, application errors). * Use a central monitoring dashboard to visualize application health and performance. **Don't Do This:** * Don't ignore monitoring alerts. * Don't rely solely on manual monitoring. **Why:** Monitoring and alerting enable proactive identification and resolution of issues. By consistently applying these deployment and DevOps strategies, development teams can deliver Svelte applications that are robust, scalable, and secure. This will improve deployment confidence and reduce operational costs.
# Performance Optimization Standards for Svelte This document outlines standards for optimizing Svelte applications, focusing on speed, responsiveness, and minimizing resource usage. These guidelines are designed to improve application performance and maintainability and reflect best practices for the latest version of Svelte. ## 1. Component Architecture and Data Management ### 1.1. Minimize Component Updates **Standard:** Only update components when necessary by carefully managing data dependencies and using fine-grained reactivity. **Why:** Excessive component updates lead to unnecessary re-renders, degrading performance. Svelte's reactivity is powerful, but it's crucial to use it judiciously. **Do This:** * Use derived stores ("derived") to compute values based on other stores, preventing unnecessary updates when the source stores haven't changed. * Employ local state variables ("let") for data that doesn't require reactivity across multiple components. * Use contextual stores sparingly; excessive reliance can lead to performance bottlenecks. * Implement custom equality checks when using mutable data structures inside stores. **Don't Do This:** * Avoid mutating objects or arrays directly inside stores when using "$:" as it triggers updates regardless of changes. Use ".set()" or ".update()" on the store. * Don't update stores with the same value. This is especially true with writable stores. **Example:** """svelte <script> import { derived, writable } from 'svelte/store'; // Writable store for user input const inputText = writable(''); // Derived store for debounced input value - Example const debouncedText = derived(inputText, ($inputText, set) => { const timeout = setTimeout(() => { set($inputText); }, 300); // Debounce time in milliseconds return () => clearTimeout(timeout); // Cleanup function }); // Local variable for internal state let counter = 0; function increment() { counter += 1; // Does NOT trigger rerender of the page. } // Updating the store correctly function updateInput(event) { inputText.set(event.target.value); } </script> <input type="text" on:input={updateInput} /> <p>Debounced Text: {$debouncedText}</p> <p>Counter: {counter}</p> <button on:click={increment}>Increment</button> """ ### 1.2. Use "{#each}" Blocks Efficiently **Standard:** Optimize "{#each}" blocks for performance by providing unique keys and avoiding unnecessary re-renders of list items. **Why:** Svelte's "{#each}" block is efficient, but incorrect usage can lead to performance bottlenecks if items are frequently added, removed, or reordered. **Do This:** * Provide a unique "key" attribute to each item in the list. This helps Svelte identify which items have changed and only update those specific elements. * Use immutable data structures when rendering lists from stores. If data changes, create new copies. If the data isn't immutable, make sure to use the key binding, or Svelte will not know what has changed and re-render everything. * Consider using pagination or virtualization for very long lists to avoid rendering all items at once. **Don't Do This:** * Avoid using the index as a key if the list items can be reordered, as this can cause incorrect updates. This is a frequent source of bugs. * Don't mutate list data directly; instead, create new arrays or objects to trigger updates. **Example:** """svelte <script> import { writable } from 'svelte/store'; const items = writable([ { id: 1, name: 'Item A' }, { id: 2, name: 'Item B' }, { id: 3, name: 'Item C' } ]); function addItem() { items.update(currentItems => [...currentItems, { id: Date.now(), name: 'New Item' }]); } </script> <button on:click={addItem}>Add Item</button> {#each $items as item (item.id)} <!-- Explicit Key Binding --> <div key={item.id}> {item.name} </div> {/each} """ ### 1.3. Optimize Component Size **Standard:** Keep components focused and relatively small to reduce rendering overhead. **Why:** Large, complex components can be challenging to reason about and can lead to slower rendering times. **Do This:** * Break down large components into smaller, reusable components. * Move complex logic into separate functions or modules. * Use slots for flexible content injection. **Don't Do This:** * Don't create "God Components" that handle too much logic and rendering. **Example:** Instead of: """svelte <!-- Bad example of a large component --> <script> let data = [...]; //Lots of data let filter = ''; let sortOrder = 'asc'; function filterData() { ... } function sortData() { ... } function renderItem(item) { ... } </script> <div> <input bind:value={filter}/> <button on:click={sortData}>Sort</button> {#each filterData(data) as item} {renderItem(item)} {/each} </div> """ Do This: Split it into smaller components: """svelte <!-- Good Example --> <script> import FilterInput from './FilterInput.svelte'; import SortButton from './SortButton.svelte'; import ItemList from './ItemList.svelte'; let data = [...]; let filter = ''; let sortOrder = 'asc'; function sortData() { ... } </script> <div> <FilterInput bind:filter={filter} /> <SortButton on:click={sortData} /> <ItemList data={data} filter={filter} /> </div> """ ## 2. Code Splitting and Lazy Loading ### 2.1. Implement Code Splitting **Standard:** Split your application into smaller chunks to reduce the initial load time. **Why:** Code splitting allows browsers to download only critical code initially, improving the perceived performance of your application. Svelte integrates with bundlers like Vite and Rollup, making code splitting relatively straightforward. **Do This:** * Use dynamic imports ("import()") to load components or modules on demand. * Configure your bundler (Vite/Rollup) to automatically split code based on import statements. * Lazy-load routes in your application. **Don't Do This:** * Avoid loading all your application code in a single bundle. **Example:** """svelte <script> let component; async function loadComponent() { component = (await import('./MyComponent.svelte')).default; } </script> {#if component} <svelte:component this={component} /> {:else} <button on:click={loadComponent}>Load Component</button> {/if} """ ### 2.2. Lazy Load Images and Other Assets **Standard:** Defer the loading of non-critical assets like images until they are needed. **Why:** Lazy loading reduces initial page load time and conserves bandwidth by only loading assets when they are visible in the viewport. **Do This:** * Use the "loading="lazy"" attribute for images. * Implement custom lazy loading for components or sections of your application. * Consider using a library like "svelte-lazy" for more advanced lazy-loading scenarios. **Don't Do This:** * Avoid loading all images on page load, especially for long pages. **Example:** """svelte <img src="my-image.jpg" alt="My Image" loading="lazy"> """ ## 3. Binding and Event Handling ### 3.1. Optimize Event Listeners **Standard:** Use passive event listeners for scroll and touch events to improve scrolling performance. Also, use delegated events when appropriate. **Why:** Passive event listeners allow the browser to handle scrolling smoothly without waiting for JavaScript to execute. **Do This:** * Add the "passive: true" option to event listeners for scroll and touch events. * Use delegated events where multiple child element needs to listen to the same event. **Don't Do This:** * Avoid blocking the main thread with long-running event handlers during scrolling. **Example:** """svelte <svelte:window on:scroll={() => { // Your scroll handling logic here }} options={{ passive: true }} /> """ ### 3.2. Minimize Two-Way Binding **Standard:** Use two-way binding ("bind:value") only when necessary, as it can lead to performance issues with frequent updates. **Why:** Two-way binding can trigger frequent component updates, especially with text inputs. **Do This:** * Use one-way binding and event listeners for more control over updates. * Consider debouncing input events to reduce the frequency of updates. **Don't Do This:** * Avoid using two-way binding for large data sets or complex components. **Example:** Instead of: """svelte <!-- Potentially problematic two-way binding --> <input bind:value={myValue} /> """ Do This: """svelte <script> import { writable } from 'svelte/store'; const myValue = writable(""); function updateValue(event) { myValue.set(event.target.value); } </script> <input type="text" on:input={updateValue} /> <p>Value: {$myValue}</p> """ ## 4. Svelte Specific Optimizations ### 4.1. Leveraging Svelte's Reactivity **Standard:** Utilize Svelte's built-in reactivity features effectively to minimize manual DOM manipulation. **Why:** Svelte's compiler optimizes updates efficiently. Avoid directly manipulating the DOM unless absolutely necessary. **Do This:** * Use reactive declarations ("$:") for derived values and side effects. * Rely on Svelte's component updates for rendering changes. * Use transition directives instead of manually handling animations whenever possible. **Don't Do This:** * Avoid using "document.querySelector" or similar methods to directly manipulate the DOM within Svelte components unless critically necessary. **Example:** """svelte <script> let count = 0; $: doubled = count * 2; // Reactive declaration function increment() { count += 1; } </script> <button on:click={increment}>Increment</button> <p>Count: {count}</p> <p>Doubled: {doubled}</p> """ ### 4.2. Efficient Use of Context API **Standard:** Use the Svelte context API thoughtfully, balancing convenience with potential performance implications. **Why:** Excessive use of context, especially with frequent updates, can cause unnecessary re-renders in components that depend on the context. **Do This:** * Provide context at the highest level possible to minimize the number of components affected by context changes. * Consider using stores instead of context for data that needs to be shared and updated frequently. * Memoize values passed via context if they are expensive to compute. **Don't Do This:** * Avoid updating context values unnecessarily. **Example:** """svelte <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; // Create a store to hold context data const myContext = writable({ value: 'Initial Value' }); // Set the context value setContext('myContextKey', myContext); </script> <slot /> """ """svelte <script> import { getContext } from 'svelte'; import { onMount } from 'svelte'; // Get the context value const myContext = getContext('myContextKey'); let value; // Subscribe to the store to update the local variable const unsubscribe = myContext.subscribe(val => { value = val.value; }); onMount(() => { return () => unsubscribe(); }); //Unsubscribing on destory is imporant. </script> <p>Context Value: {value}</p> """ ## 5. External Libraries and Tooling ### 5.1. Use SvelteKit's Load Function Wisely **Standard**: Return only the data you need and avoid unnecessary computations in load functions. **Why**: Load functions run on both the server and the client. Expensive operations can slow down both the initial server render and subsequent client-side navigations. **Do This**: * Fetch only the data necessary for the current page. Avoid fetching entire databases without pagination or filtering. * Move complex data transformations or business logic into separate modules and cache the results if appropriate. * Utilize the "depends" function to invalidate the "load" function only when necessary dependencies change. **Don't Do This**: * Perform database queries directly within components. This breaks separation of concerns and couples your components to the backend. * Include large, unused datasets in the returned "load" function data. **Example:** """typescript // src/routes/blog/[slug]/+page.ts import { getBlogPost } from '$lib/blog'; // Assume this fetches a blog post from a database or CMS export async function load({ params, fetch }) { const post = await getBlogPost(params.slug, fetch); //Assuming getBlogPost now uses fetch api if (!post) { error(404, 'Post not found'); } return { title: post.title, content: post.content, // Only return the necessary parts of the post slug: params.slug }; } """ ### 5.2 Performance Monitoring **Standard**: Use browser developer tools to identify and address performance bottlenecks. **Why**: Profilers and performance monitoring tools are the most reliable way to identify and resolve perfomance related issues **Do This**: * Use the "Performance" tab in Chrome DevTools (or similar in other browsers) to record and analyze the performance of your Svelte applications. * Identify slow-rendering components, long-running JavaScript functions, and excessive DOM manipulations. * Use Svelte Devtools for Component inspection * Use Lighthouse reporting to identify key areas for performance improvement. **Don't Do This**: * Rely solely on intuition. Performance is often counterintuitive. ## 6. Security Considerations While this document is primarily focused on performance, security and performance often intersect: ### 6.1. Sanitize User Input **Standard:** Always sanitize user input to prevent XSS (Cross-Site Scripting) vulnerabilities, which can negatively impact performance by injecting malicious code. **Why:** XSS vulnerabilities allow attackers to inject malicious scripts into your application, potentially leading to data theft, session hijacking, or defacement of your website. Additionally, unsanitized inputs may contain special characters or escape sequences that can lead to unpredictable behavior. **Do This:** * Use Svelte's built-in HTML encoding to prevent malicious code from being injected into your application. * Employ a library like DOMPurify to sanitize HTML input from users. * Avoid using "{@html ...}" directive with user-provided data without proper sanitization. **Don't Do This:** * Never trust user input directly without proper sanitization. **Example:** """svelte <script> import DOMPurify from 'dompurify'; let userInput = ''; let sanitizedHTML = ''; function sanitizeInput() { sanitizedHTML = DOMPurify.sanitize(userInput); } </script> <input bind:value={userInput} on:input={sanitizeInput} /> <div> {@html sanitizedHTML} </div> """ ### 6.2. Secure API Communication **Standard:** Use HTTPS for all API communication to protect data in transit and prevent man-in-the-middle attacks. **Why:** Using HTTPS ensures that data exchanged between your application and backend API is encrypted, preventing eavesdropping and tampering. Unencrypted communication can expose sensitive information and compromise user data. **Do This:** * Ensure all API endpoints use HTTPS (SSL/TLS) encryption. * Use appropriate CORS (Cross-Origin Resource Sharing) policies to restrict access to your API. * Validate and sanitize data received from the API to prevent injection attacks. **Don't Do This:** * Avoid using HTTP for sensitive data transmission or API communication in production environments. ### 6.3. Proper Error Handling **Standard:** Implement robust error handling to prevent information leakage and denial-of-service attacks. **Why:** Proper error handling prevents sensitive information from being exposed in error messages and prevents attackers from exploiting vulnerabilities to crash the application. **Do This:** * Handle exceptions and errors gracefully and log relevant information for debugging. * Avoid revealing sensitive information (e.g., database connection strings, API keys) in error messages. * Implement rate limiting to prevent abuse and denial-of-service attacks. **Don't Do This:** * Avoid displaying detailed error messages directly to users in production environments. This document provides a starting point for defining Svelte coding standards focused on performance optimization. It should evolve as the framework and best practices change and should be tailored to the specific needs of each project.
# Code Style and Conventions Standards for Svelte This document outlines the code style and conventions standards for Svelte projects. Adhering to these guidelines promotes code readability, maintainability, and consistency across the project. These standards aim to provide best practices tailored to the latest version of Svelte. Enforcing these standards assists AI coding assistants (like GitHub Copilot, Cursor) in generating code that is compliant and coherent. ## 1. Formatting and Whitespace Consistent formatting is crucial for code readability. ### 1.1. Indentation **Do This:** * Use 2 spaces for indentation. Avoid tabs. * Configure your editor to automatically convert tabs to spaces. **Don't Do This:** * Mix tabs and spaces. * Use a different number of spaces for indentation. **Why:** Consistent indentation makes the code structure obvious and reduces visual clutter. **Example:** """svelte <!-- Correct --> <div> {#if condition} <p>This is a paragraph.</p> {/if} </div> <!-- Incorrect --> <div> {#if condition} <p>This is a paragraph.</p> {/if} </div> """ ### 1.2. Line Length **Do This:** * Limit lines to a maximum of 120 characters. * Break long lines to improve readability. **Don't Do This:** * Have extremely long lines that require horizontal scrolling. **Why:** Limiting line length improves readability, especially on smaller screens. **Example:** """svelte <!-- Correct --> <script> import { someVeryLongFunctionName } from './utils'; let someVeryLongVariableName = someVeryLongFunctionName( 'argument1', 'argument2', 'argument3' ); </script> <!-- Incorrect --> <script>import { someVeryLongFunctionName } from './utils'; let someVeryLongVariableName = someVeryLongFunctionName('argument1', 'argument2', 'argument3');</script> """ ### 1.3. Whitespace **Do This:** * Use a single blank line between top-level constructs (e.g., between "<script>", "<style>", and HTML). * Use spaces around operators and after commas. * Ensure consistent spacing within HTML attributes. * Use a newline at the end of each file. **Don't Do This:** * Omit whitespace around operators. * Use multiple blank lines unnecessarily. * Forget the trailing newline. **Why:** Proper whitespace enhances code clarity significantly. **Example:** """svelte <!-- Correct --> <script> let count = 0; function increment() { count += 1; } </script> <style> button { padding: 10px; } </style> <div> <button on:click={increment}>Click me</button> <p>Count: {count}</p> </div> <!-- Incorrect --> <script> let count=0; function increment(){ count+=1; } </script> <style>button{padding:10px;}</style> <div><button on:click={increment}>Click me</button><p>Count: {count}</p></div> """ ## 2. Naming Conventions Consistent naming conventions are critical for code understanding and maintainability. ### 2.1. Variables and Constants **Do This:** * Use camelCase for variable and constant names (e.g., "myVariable", "userCount"). * Use UPPER_SNAKE_CASE for constants (e.g., "API_URL", "DEFAULT_TIMEOUT"). * Use descriptive and meaningful names. **Don't Do This:** * Use single-letter variable names (except in "for" loops). * Use abbreviations that are not widely understood. **Why:** Clearly named variables and constants make the code self-documenting. **Example:** """svelte <script> // Correct let userAge = 30; const API_ENDPOINT = '/api/users'; // Incorrect let a = 30; const url = '/api/users'; </script> """ ### 2.2. Components **Do This:** * Use PascalCase for component names (e.g., "MyComponent", "UserProfile"). * Component files should match the component name (e.g., "MyComponent.svelte"). * Create reusable components for repeating patterns. **Don't Do This:** * Use lowercase or snake_case for component names. * Include logic in root App.svelte file if possible. * Use cryptic component names. **Why:** Consistent component naming helps distinguish components from other elements. **Example:** """ src/ ├── components/ │ ├── UserProfile.svelte │ └── ProductCard.svelte └── App.svelte """ Within "UserProfile.svelte": """svelte <!-- Correct --> <script> export let user; </script> <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> <!-- Incorrect --> <script> export let data; </script> <div> <h1>{data.name}</h1> <p>Email: {data.email}</p> </div> """ ### 2.3. Functions **Do This:** * Use camelCase for function names (e.g., "getData", "calculateTotal"). * Use descriptive names that indicate the function's purpose. **Don't Do This:** * Use vague or ambiguous function names. * Use abbreviations unless they are universally understood. **Why:** Clear function names improve code readability and maintainability. **Example:** """svelte <script> // Correct function calculateTotal(items) { return items.reduce((sum, item) => sum + item.price, 0); } // Incorrect function calc(items) { return items.reduce((sum, item) => sum + item.price, 0); } </script> """ ### 2.4. Props **Do This:** * Use camelCase: For props passed to Svelte components. * Use Typescript typing: For explicit type declarations. **Don't Do This:** * Not documenting props or using PropTypes (since Svelte uses Typescript for props). * Using snake_case instead of camelCase. **Why:** Explicit props improve code readability and maintainability. With the introduction of Svelte 5 and newer conventions, it is essential to align with the latest standards. **Example:** """svelte <!-- Correct --> <script lang="ts"> export let userName: string; export let isLoggedIn: boolean = false; </script> <h1>Hello, {userName}!</h1> {#if isLoggedIn} <p>Welcome back!</p> {/if} <!-- Incorrect --> <script> export let user_name; export let is_logged_in = false; </script> <h1>Hello, {user_name}!</h1> {#if is_logged_in} <p>Welcome back!</p> {/if} """ ## 3. Svelte-Specific Style ### 3.1. Script Tags **Do This:** * Place "<script>" tags either at the top or bottom of the component file. * Use "<script lang="ts">" for TypeScript support. **Don't Do This:** * Mix "<script>" tags throughout the HTML unless absolutely necessary. **Why:** Clear separation of logic and markup improves readability. **Example:** """svelte <!-- Correct --> <script lang="ts"> export let message: string; </script> <h1>{message}</h1> <!-- Incorrect --> <h1>{message}</h1> <script> export let message; </script> """ ### 3.2. Style Tags **Do This:** * Use "<style>" tags to encapsulate component-specific styles. * Use ":global(...)" selector sparingly, only when necessary to override global styles. **Don't Do This:** * Define global styles directly in component style tags. * Use inline styles unless for very specific, dynamic styling. **Why:** Scoped styles prevent style conflicts and improve maintainability. **Example:** """svelte <!-- Correct --> <style> h1 { color: blue; } </style> <!-- Incorrect --> <style> :global(body) { font-family: Arial; } </style> """ ### 3.3. Reactive Declarations **Do This:** * Use reactive declarations ("$:") for automatically updating values based on dependencies. * Avoid complex logic inside reactive declarations; delegate to functions. **Don't Do This:** * Overuse reactive declarations for non-reactive logic. * Mutate reactive variables directly without reassignment. **Why:** Reactive declarations simplify state management and ensure data consistency. **Example:** """svelte <script> let price = 10; let quantity = 2; $: total = price * quantity; // Correct function updateTotal() { price = 20; // Correct, triggers reactivity } </script> <p>Total: {total}</p> <button on:click={updateTotal}>Update Price</button> """ ### 3.4. Event Handling **Do This:** * Use "on:" directives for event handling (e.g., "on:click", "on:submit"). * Keep event handlers concise and delegate complex logic to functions. **Don't Do This:** * Write lengthy event handlers directly in the template. **Why:** Separating event handling logic improves readability and testability. **Example:** """svelte <script> function handleClick() { alert('Button clicked!'); } </script> <button on:click={handleClick}>Click me</button> """ ### 3.5. Component Directives and Attributes **Do This:** * Use directives like "bind:", "transition:", "animate:", and "use:" for component enhancements. * Use descriptive attribute names for clarity. * Consider short-hand syntax where appropriate, e.g. prop **Don't Do This:** * Overuse or misuse directives leading to unnecessary complexity. * Use obscure attribute names. **Why:** Directives offer powerful ways to integrate behaviors and effects declaratively. **Example:** """svelte <script> import { fade } from 'svelte/transition'; let visible = false; let inputValue = ''; </script> <input bind:value={inputValue} /> <button on:click={() => visible = !visible}>Toggle</button> {#if visible} <p transition:fade>This will fade in and out.</p> {/if} """ ### 3.6. Stores **Do This:** * Use Svelte stores for managing global state. * Use the "$" prefix to access store values in the template. * Create custom stores for complex state management logic. **Don't Do This:** * Overuse stores for component-local state. * Mutate store values directly without using "set" or "update". **Why:** Stores provide a centralized and reactive way to manage application state. **Example:** """svelte <script> import { count } from './stores'; function increment() { $count += 1; } </script> <button on:click={increment}>Increment</button> <p>Count: {$count}</p> """ ### 3.7. Each Blocks **Do This:** * Include a "key" attribute when using "{#each}" blocks to help Svelte efficiently update the DOM. The key should be unique and stable. * Keep the logic inside "{#each}" blocks simple. If complex, delegate to a component. **Don't Do This:** * Omit the "key" attribute when rendering dynamic lists. * Perform complex operations directly within the "{#each}" block. **Why:** Keys enable Svelte to efficiently update the DOM when list items change. **Example:** """svelte <script> let items = [ { id: 1, name: 'Apple' }, { id: 2, name: 'Banana' }, { id: 3, name: 'Orange' } ]; </script> {#each items as item (item.id)} <p>{item.name}</p> {/each} """ ## 4. Modern Approaches and Patterns (Svelte 5) With the advent of Svelte 5, adopting recent conventions is crucial. ### 4.1. Reactive Statements in Svelte 5 **Do This:** * Use explicit declarations for reactivity like "$derived" and "$effect". * Understand the nuances between "$derived", which creates a new value reactively, and "$effect", which runs side effects. **Don't Do This:** * Rely on automatic reactivity for all variable assignments (which is deprecated). **Why:** Explicit reactivity enhances clarity and predictability in your code. **Example:** """svelte <script> let a = 1; let b = 2; $: sum = a + b; // Shorthand syntax for $derived $: { // Shorthand syntax for $effect console.log('The sum is', sum); } function updateValues() { a += 1; b += 1; } </script> <p>a: {a}</p> <p>b: {b}</p> <p>Sum: {sum}</p> <button on:click={updateValues}>Update Values</button> """ ### 4.2. Enhanced State Management **Do This:** * Take advantage of new store features and patterns available in the latest Svelte versions. * Adopt readable store patterns for complex application states. **Don't Do This:** * Ignore the advancements; keep using outdated approaches unnecessarily. **Why:** Modern state management improves code organization and maintainability. **Example:** """svelte <script> import { writable } from 'svelte/store'; const count = writable(0); function increment() { count.update(n => n + 1); } </script> <button on:click={increment}>Increment</button> <p>Count: {$count}</p> """ ### 4.3. Optimizing Performance **Do This:** * Use "{#each}" blocks with proper "key" attributes. * Leverage "svelte:window", "svelte:document", and "svelte:body" for optimized event handling. **Don't Do This:** * Attach global event listeners directly without using these optimized elements. **Why:** Optimized event handling helps in improving application performance. **Example:** """svelte <svelte:window on:keydown={handleKeyDown} /> <script> function handleKeyDown(event) { console.log('Key pressed:', event.key); } </script> """ ### 4.4 Component Composition and Reusability **Do This:** * Favor composition over inheritance. Build complex UIs by combining smaller, reusable components. * Use slots effectively to create flexible components that accept custom content. * Write clear and concise prop definitions with TypeScript to ensure type safety and prevent runtime errors. **Don't Do This:** * Create monolithic components with excessive logic and markup. * Rely on deep component hierarchies that are difficult to understand and maintain. * Neglect to document component props, making it harder for others to use them correctly. **Why:** Encourages reusability and separation of concerns, making applications easier to maintain and scale. **Example:** """svelte // Reusable Button Component <script lang="ts"> export let label: string; export let onClick: () => void; </script> <button on:click={onClick}>{label}</button> // Usage in Parent Component <script> import Button from './Button.svelte'; function handleClick() { alert('Button Clicked!'); } </script> <Button label="Click Me" onClick={handleClick} /> """ ### 4.5 Security Considerations **Do This:** * Sanitize user inputs to prevent cross-site scripting (XSS) attacks. * Use Content Security Policy (CSP) to mitigate the risk of injecting malicious code into your application. * Validate data on the server-side in addition to client-side validation. **Don't Do This:** * Directly render unsanitized user inputs in your templates. * Rely solely on client-side validation for security. * Store sensitive information in the browser's local storage or cookies without proper encryption. **Why:** Security vulnerabilities can compromise your application and expose user data. **Example:** """svelte <script> import { onDestroy } from 'svelte'; let inputValue = ''; // Function to sanitize input value function sanitizeInput(value) { // Implement your sanitization logic here return value.replace(/</g, '<').replace(/>/g, '>'); } // Reactive statement to update sanitized value $: sanitizedValue = sanitizeInput(inputValue); </script> <input type="text" bind:value={inputValue} /> <!-- Display the sanitized value --> <p>Sanitized Input: {@html sanitizedValue}</p> """ ## 5. Tooling and Automation ### 5.1. Linters and Formatters **Do This:** * Use ESLint with Svelte-specific plugins for linting. * Use Prettier for automatic code formatting. * Integrate these tools into your development workflow (e.g., using Git hooks). **Don't Do This:** * Ignore linting and formatting errors. * Manually format code when automated tools are available. **Why:** Linters and formatters enforce code style and help catch potential errors. ### 5.2. Testing **Do This:** * Write unit tests for components using testing frameworks like Jest or Vitest. * Use component testing libraries to ensure component behavior. * Write end-to-end tests for critical user flows. **Don't Do This:** * Skip writing tests. * Rely solely on manual testing. **Why:** Automated tests ensure code quality and prevent regressions. ### 5.3. Documentation **Do This:** * Use JSDoc or TypeScript annotations to document components, functions, and props. * Maintain a README file with clear instructions on how to set up and run the project. * Use tools like Storybook to document and showcase components. **Don't Do This:** * Neglect writing documentation. * Write inaccurate or outdated documentation. **Why:** Documentation makes it easier for others (and your future self) to understand and maintain the code. ## 6. Conclusion Adhering to these code style and conventions will significantly enhance the quality, readability, and maintainability of Svelte projects. Consistently following these guidelines ensures that the codebase remains clean, understandable, and easy to collaborate on. Furthermore, enforcing these standards allows AI coding assitants to provide compliant and high-quality code suggestions. By adopting these best practices, development teams can build robust and scalable Svelte applications.
# API Integration Standards for Svelte This document outlines the recommended coding standards for integrating APIs within Svelte applications. Following these standards will promote maintainability, performance, security, and overall code quality. It assumes familiarity with Svelte's core concepts and common web development practices. ## 1. Architectural Patterns for API Integration Choosing the right architectural pattern is crucial for a scalable and maintainable Svelte application. This section covers common patterns for handling API requests. ### 1.1. Services Pattern The Services Pattern encapsulates API interaction logic (data fetching, error handling, request formatting) within reusable service modules. **Standard:** Isolate API interaction logic into dedicated service modules. **Do This:** Create separate ".js" or ".ts" files for each API endpoint or group of related endpoints. **Don't Do This:** Embed API calls directly within Svelte components. **Why:** * **Improved Code Organization:** Keeps components focused on presentation and user interaction. * **Reusability:** Services can be reused across multiple components and even different applications. * **Testability:** Services are easier to test in isolation. * **Reduced Component Complexity:** Keeps components lean and readable. **Example:** """javascript // src/services/userService.js const API_BASE_URL = 'https://api.example.com'; async function getUser(id) { try { const response = await fetch("${API_BASE_URL}/users/${id}"); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } catch (error) { console.error('Error fetching user:', error); throw error; // Re-throw to be handled by the component } } async function createUser(userData) { try { const response = await fetch("${API_BASE_URL}/users", { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData), }); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } catch (error) { console.error("Error creating user:", error); throw error; } } export { getUser, createUser }; // Export multiple functions as an object """ """svelte <!-- src/components/UserProfile.svelte --> <script> import { onMount } from 'svelte'; import { getUser } from '../services/userService'; // Import the service let user = null; export let userId; onMount(async () => { try { user = await getUser(userId); } catch (error) { // Handle errors gracefully (e.g., display an error message) console.error('Failed to load user:', error); alert('Failed to load user.'); } }); </script> {#if user} <h1>{user.name}</h1> <p>Email: {user.email}</p> {:else} <p>Loading user...</p> {/if} <!-- Another component, e.g., UserForm.svelte --> <script> import { createUser } from '../services/userService'; async function handleSubmit(event) { event.preventDefault(); const formData = new FormData(event.target); const userData = Object.fromEntries(formData.entries()); try { const newUser = await createUser(userData); console.log('User created:', newUser); // Redirect or update the UI } catch (error) { console.error('Error creating user:', error); alert("Error creating user!"); } } </script> <form on:submit={handleSubmit}> <label for="name">Name:</label> <input type="text" id="name" name="name" required> <label for="email">Email:</label> <input type="email" id="email" name="email" required> <button type="submit">Create User</button> </form> """ ### 1.2. Store-Based Data Management Svelte stores provide a reactive data container, ideal for managing data fetched from APIs. **Standard:** Use Svelte stores to manage the application's state related to API data. **Do This:** Create writable, readable, or derived stores to hold and manipulate API responses. **Don't Do This:** Directly mutate component-level variables with API data, bypassing reactivity. **Why:** * **Reactivity:** Changes to the store automatically trigger updates in subscribed components. * **Centralized State:** Provides a single source of truth for application data. * **Simplified Data Sharing:** Easier to share data between components without prop drilling. * **Simplified Testing:** Stores are easier to test outside of components. * **Fine-grained updates:** Only components using parts of the store that changed will re-render. **Example:** """javascript // src/stores/userStore.js import { writable } from 'svelte/store'; import { getUser } from '../services/userService'; const userStore = writable({ data: null, loading: false, error: null, }); async function loadUser(id) { userStore.update(state => ({ ...state, loading: true, error: null })); try { const userData = await getUser(id); userStore.set({ data: userData, loading: false, error: null }); } catch (error) { userStore.set({ data: null, loading: false, error }); } } export { userStore, loadUser }; """ """svelte <!-- src/components/UserProfile.svelte --> <script> import { onMount } from 'svelte'; import { userStore, loadUser } from '../stores/userStore'; export let userId; onMount(() => { loadUser(userId); }); </script> {#if $userStore.loading} <p>Loading user...</p> {:else if $userStore.error} <p>Error: {$userStore.error.message}</p> {:else if $userStore.data} <h1>{$userStore.data.name}</h1> <p>Email: {$userStore.data.email}</p> {:else} <p>User not found</p> {/if} """ ### 1.3. Server-Side Rendering (SSR) and API Integration For performance and SEO benefits, consider Server-Side Rendering (SSR) with SvelteKit. **Standard:** Fetch initial data on the server for faster initial load times. **Do This:** Utilize SvelteKit's "load" function within "+page.server.js" for server-side API calls. (Note: filename might change to "+server.js" depending on SvelteKit version.) **Don't Do This:** Perform all API calls on the client, especially for critical content. **Why:** * **Improved SEO:** Search engines can easily crawl and index content rendered server-side. * **Faster Initial Load:** Users see content faster, improving user experience. * **Reduced Client-Side Processing:** Offloads work from the client's browser, especially on low-powered devices. **Example:** """javascript // src/routes/users/[userId]/+page.server.js import { getUser } from '../../../services/userService'; export async function load({ params }) { const { userId } = params; try { const user = await getUser(userId); return { user, }; } catch (error) { return { status: 500, error: 'Failed to load user', }; } } """ """svelte <!-- src/routes/users/[userId]/+page.svelte --> <script> export let data; // data passed from the load function $: ({ user } = data); </script> {#if user} <h1>{user.name}</h1> <p>Email: {user.email}</p> {:else} <p>Failed to load user.</p> {/if} """ ## 2. Data Fetching Best Practices Efficient data fetching is paramount for a responsive Svelte application. ### 2.1. Async/Await **Standard:** Use "async/await" syntax for cleaner and more readable asynchronous code. **Do This:** Wrap API calls in "async" functions and use "await" to handle promises. **Don't Do This:** Rely on ".then()" callbacks, which can lead to complex and hard-to-read code. **Why:** * **Improved Readability:** "async/await" makes asynchronous code look and behave more like synchronous code. * **Easier Error Handling:** Simplifies error handling with "try/catch" blocks. **Example:** """javascript async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); throw error; // Re-throw for component-level handling } } """ ### 2.2. Error Handling **Standard:** Implement robust error handling to gracefully manage API failures. **Do This:** Wrap API calls in "try/catch" blocks and provide user-friendly error messages. Log errors to the console or a logging service. Consider using a dedicated error reporting service (e.g., Sentry). **Don't Do This:** Ignore errors or display generic and unhelpful error messages. **Why:** * **Improved User Experience:** Prevents the application from crashing or displaying cryptic errors. * **Easier Debugging:** Provides valuable information for diagnosing and resolving issues. **Example:** """javascript async function fetchData() { try { const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); alert('An error occurred while fetching data. Please try again later.'); // User-friendly message throw error; // Re-throw if component needs to handle it } } """ ### 2.3. Loading Indicators **Standard:** Provide visual feedback to users during API requests. **Do This:** Display loading indicators (e.g., spinners, progress bars) while data is being fetched. Hide the indicator once the data is loaded or an error occurs. **Don't Do This:** Leave the user in the dark without any indication of activity. **Why:** **Improved User Experience:** Keeps users informed and prevents them from thinking the application is unresponsive. **Example:** """svelte <script> import { onMount } from 'svelte'; let data = null; let loading = true; let error = null; onMount(async () => { try { const response = await fetch('https://api.example.com/data'); data = await response.json(); } catch (e) { error = e; console.error("Error during fetch:", e); } finally { loading = false; } }); </script> {#if loading} <p>Loading...</p> {:else if error} <p>Error: {error.message}</p> {:else if data} <pre>{JSON.stringify(data, null, 2)}</pre> {:else} <p>No data available.</p> {/if} """ ### 2.4. Data Transformations **Standard:** Transform API responses into a format suitable for the application's needs. **Do This:** Create functions to map, filter, or reshape data from the API before storing it in stores or displaying it in components. Ensure that you handle possible null/undefined values. **Don't Do This:** Directly use API responses without any processing, which can lead to inconsistencies and inflexibility. **Why:** * **Decoupling:** Isolates the application from changes in the API's data structure. * **Improved Performance:** Optimizes data for specific use cases, such as filtering large data sets. * **Enhanced Readability:** Transforms data into a more human-readable format. **Example:** """javascript function transformUserData(apiData) { if (!apiData) return null; // Handle null or undefined cases return { id: apiData.id, fullName: "${apiData.firstName} ${apiData.lastName}", emailAddress: apiData.email || 'N/A', // Handle missing values }; } // usage in service async function getUser(id) { try { const response = await fetch("${API_BASE_URL}/users/${id}"); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const apiResponse = await response.json(); return transformUserData(apiResponse); //Applying transform } catch (error) { console.error('Error fetching user:', error); throw error; // Re-throw to be handled by the component } } """ ## 3. API Client Libraries Leverage existing libraries to simplify API interaction. ### 3.1. Fetch API **Standard:** Utilize the built-in "fetch" API for making HTTP requests. **Do This:** Use "fetch" for simple API calls. Implement custom headers, request methods, and error handling as needed. **Don't Do This:** Rely on older libraries like "XMLHttpRequest" unless absolutely necessary. **Why:** * **Native Integration:** "fetch" is a standard web API supported by all modern browsers and Node.js. * **Promise-Based:** Provides a clean and modern API for asynchronous operations. **Example:** """javascript async function fetchData() { const response = await fetch('https://api.example.com/data', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer <token>', }, }); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const data = await response.json(); return data; } """ ### 3.2. Axios **Standard:** Consider using Axios for advanced API interaction and features not available in the native "fetch" API. **Do This:** Use Axios for features like request cancellation, automatic JSON transformation, and interceptors. **Don't Do This:** Use Axios for simple API calls that can be easily handled by "fetch". **Why:** * **Feature-Rich:** Provides a wide range of features for advanced API interaction. * **Interceptors:** Allows you to intercept and modify requests and responses globally. * **Request Cancellation:** Enables you to cancel pending requests, which is useful for improving performance and user experience. **Example:** """javascript import axios from 'axios'; async function fetchData() { try { const response = await axios.get('https://api.example.com/data', { headers: { Authorization: 'Bearer <token>', }, }); return response.data; } catch (error) { console.error('Error fetching data:', error); throw error; } } // Example of request Interceptor axios.interceptors.request.use( config => { // Do something before request is sent config.headers['X-Request-Id'] = generateRequestId(); return config; }, error => { // Do something with request error return Promise.reject(error); } ); // Example of response Interceptor axios.interceptors.response.use( response => { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data return response; }, error => { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error if (error.response && error.response.status === 401) { // Handle unauthorized error (e.g., redirect to login) window.location.href = '/login'; } return Promise.reject(error); } ); """ ## 4. Security Considerations Protecting sensitive data and preventing vulnerabilities is critical. ### 4.1. Environment Variables **Standard:** Store API keys and other sensitive information in environment variables. **Do This:** Use ".env" files (in development) and environment variables on the server to store sensitive data. In SvelteKit, use "$env/static/private" for private environment variables. Access public environment variables via "$env/static/public". **Don't Do This:** Hardcode API keys or commit them to the repository. **Why:** * **Security:** Prevents sensitive data from being exposed in the codebase. * **Configuration:** Allows you to easily configure the application for different environments. **Example:** """ // .env API_KEY=your_api_key API_URL=https://api.example.com """ """javascript // svelte.config.js import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { loadEnv } from 'vite'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), kit: { adapter: adapter() } }; export default config; // access from server files import { env } from '$env/static/private'; const apiKey = env.API_KEY; //access from public files (browser) import { env } from '$env/static/public'; const publicApiUrl = env.PUBLIC_API_URL """ ### 4.2. Input Validation **Standard:** Validate all user inputs before sending them to the API. **Do This:** Implement client-side and server-side validation to prevent malicious data from being sent to the API. **Don't Do This:** Trust user input without validation, which can lead to vulnerabilities like SQL injection or cross-site scripting (XSS). **Why:** * **Security:** Prevents malicious data from compromising the application or the API. * **Data Integrity:** Ensures that the data being sent to the API is valid and consistent. ### 4.3. CORS **Standard:** Configure Cross-Origin Resource Sharing (CORS) properly on the server. **Do This:** Set appropriate CORS headers on the API server to allow requests from the application's origin. **Don't Do This:** Use a overly permissive CORS configuration, opening up the API to potential security risks. **Why:** * **Security:** Prevents unauthorized access to the API from other domains. * **Functionality:** Ensures that the application can make requests to the API from different origins. ## 5. Performance Optimization Optimize API integration for speed and efficiency. ### 5.1. Caching **Standard:** Implement caching to reduce the number of API requests. **Do This:** Use browser caching, server-side caching (e.g., Redis), or a service worker to cache API responses. Use "stale-while-revalidate" strategy for fast initial loads and background updates. **Don't Do This:** Cache data aggressively without considering data freshness or potential inconsistencies. **Why:** * **Improved Performance:** Reduces network latency and server load. * **Offline Support:** Allows the application to function even when the user is offline. ### 5.2. Pagination **Standard:** Implement pagination for APIs that return large datasets. **Do This:** Use pagination parameters (e.g., "page", "limit") to retrieve data in smaller chunks. Display pagination controls in the UI to allow users to navigate through the data. **Don't Do This:** Load entire datasets at once, which can lead to performance issues and slow load times. **Why:** * **Improved Performance:** Reduces the amount of data that needs to be transferred and processed. * **Better User Experience:** Allows users to browse large datasets more efficiently. ### 5.3. Debouncing and Throttling **Standard**: Use debouncing and throttling for API calls triggered by user input. **Do This**: Debounce API calls for search inputs, so the API is only called once the user stops typing. Throttle API calls for scroll events to prevent excessive calls. **Don't Do This**: Make API calls on every keystroke for search, or every pixel scrolled down which can overwhelm the API. **Why:** * **Reduced Network Traffic:** Limits calls to the API, reducing server load. * **Improved Performance:** Avoids unnecessary processing on the server. * **Better User Experience:** Prevents UI lag from excessive API calls. ### 5.4. GraphQL **Standard**: Consider adopting GraphQL for complex data fetching needs. **Do This**: Use GraphQL when you need to fetch data from multiple resources, request only the data needed, and avoid over-fetching. **Don't Do This**: Use GraphQL for simple APIs, as it adds complexity to your infrastructure. **Why:** * **Efficient Data Fetching**: Only fetch the required data. * **Avoid Over-Fetching**: Prevent transferring unnecessary data. * **Schema Definition**: Provides type safety and improves documentation. ## 6. Testing Ensuring API integration works as expected. ### 6.1. Unit Tests **Standard**: Write unit tests for service functions and data transformations. **Do This**: Mock API calls and verify data transformations. **Don't Do This**: Skip unit testing API integration logic. ### 6.2. Integration Tests **Standard**: Test integration between components and the backend. **Do This**: Use a testing framework to simulate user interactions. Verify API calls are made correctly. **Don't Do This**: Deploy without testing API integration with actual components. ### 6.3. End-to-End Tests **Standard**: Write end-to-end tests to ensure the entire application works correctly. **Do This**: Simulate user workflows, from login to API calls, to ensure a smooth user experience. **Don't Do This**: Assume the application works without testing the whole flow. ## 7. Specific Svelte Considerations ### 7.1. Context API **Standard**: Use context API for passing data down deeply nested components. **Do This**: Create a context to hold API-related functions and state. **Don't Do This**: Overuse context; prop drilling may be simpler for a small number of components. """svelte <script> import { setContext } from 'svelte'; import { yourApiService } from './services'; // Create API-related context setContext('api', { fetchData: yourApiService.getData, // Add more functions or data that child components might need }); </script> <slot /> <!-- Render child components --> """ ### 7.2 Lifecycle Methods **Standard**: Use "onMount" for data fetching in components. **Do This**: Initiate API calls within the "onMount" lifecycle method to ensure component is mounted. **Don't Do This**: Make API calls outside lifecycle methods, which can lead to unexpected behavior. ### 7.3 Reactive Statements **Standard**: Use reactive statements for data manipulation and updates. **Do This**: Update the component when API responses come in. Use dependency arrays to react to specific store updates. **Don't Do This**: Directly manipulate DOM elements or skip reactivity, leading to inconsistent UI updates. ## 8. Documentation **Standard**: Document All API integrations clearly. **Do This:** Use JSDoc or Typescript annotations to detail request/response structures. Document error handling strategies. Add clear comments explaining the purpose of integration code. **Don't Do This:** Omit documentation leaving API integrations opaque and hard to maintain. ## CONCLUSION Adhering to these coding standards will result in more maintainable, performant, secure, and testable Svelte applications. By following these guidelines, development teams can establish consistency and reduce the likelihood of errors. Remember to adapt these guidelines as Svelte evolves, and as specific project needs arise. Regular code reviews are highly recommended to enforce these standards and ensure continuous improvement.