# Tooling and Ecosystem Standards for Solid.js
This document outlines the recommended tooling and ecosystem standards for Solid.js development. Adhering to these standards will improve code quality, maintainability, performance, and collaboration within your team.
## 1. Recommended Libraries and Tools
Here's a curated list of recommended libraries and tools that complement Solid.js, along with guidelines for their proper usage:
### 1.1. State Management
* **zustand:** Simple, fast, and unopinionated bearbones state-management solution using stores.
* **Do This:** Use "zustand" for simple to medium complexity global state management needs.
* **Don't Do This:** Overuse "zustand" for component-local state; use Solid's built-in signals. Rely on "zustand" for highly complex state scenarios with many dependencies.
* **Why:** "zustand" is lightweight and easy to integrate with Solid's reactivity. For highly complex scenarios, consider more robust solutions like effector with solid-effector.
"""jsx
import { create } from 'zustand';
import { createStore, createEffect } from 'solid-js';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
Count: {count}
Increment
Decrement
);
}
"""
* **effector (with solid-effector):** A powerful and type-safe state management library with reactive primitives and unidirectional data flow.
* **Do This:** Utilize effector for complex application state, side-effect management, and handling asynchronous logic.
* **Don't Do This:** Reinvent the wheel for common state management patterns. Ignore the type safety and traceability benefits effector provides.
* **Why:** Effector provides excellent tools for side-effect management and scales well with application complexity.
"""jsx
import { createStore, createEvent } from 'effector';
import { useStore } from 'solid-effector';
import { render } from 'solid-js/web';
const increment = createEvent();
const decrement = createEvent();
const $count = createStore(0)
.on(increment, state => state + 1)
.on(decrement, state => state - 1);
function Counter() {
const count = useStore($count);
return (
<>
Count: {count()}
increment()}>Increment
decrement()}>Decrement
);
}
"""
### 1.2. Routing
* **solid-app-router:** The officially recommended router for Solid.js applications.
* **Do This:** Use "solid-app-router" for all routing needs in single-page applications. Utilize its "" and "" components.
* **Don't Do This:** Attempt to create your own routing solution; use the official router. Bind directly to "window.location" (use the router's APIs instead).
* **Why:** "solid-app-router" is specifically designed for Solid.js, offering excellent performance and integration with Solid's reactivity system.
"""jsx
import { Route, Link, Routes } from "solid-app-router";
import { Component } from 'solid-js';
const Home:Component = () => Home;
const About:Component = () => About;
function App() {
return (
Home | About
);
}
import { render } from 'solid-js/web';
render(() => , document.getElementById('app'));
"""
### 1.3. Form Handling
* **@felte/solid:** A form library that does not use uncontrolled components.
* **Do This:** Integrate with "@felte/solid" to handle form states and validations.
* **Don't Do This:** Manually manage form state (onChange handlers). Use "defaultValue" instead of "value" as it leverages Solid's reactivity.
* **Why:** Felte simplifies form handling by abstracting the complexities of state management and reactivity.
"""jsx
import { createForm } from '@felte/solid';
function MyForm() {
const { form, data, errors } = createForm({
onSubmit: (values) => {
alert(JSON.stringify(values));
}
});
return (
First Name:
Last Name:
Submit
);
}
"""
### 1.4. UI Libraries
* **Headless UI + Solid.js:** Combine the accessibility and flexibility of Headless UI with Solid's performance.
* **Do This:** Use controlled components with signals. Implement custom styling for your components.
* **Don't Do This:** Directly manipulate the DOM of Headless UI components.
* **Why:** Headless UI provides the accessible logic and ARIA attributes, while Solid.js handles the reactivity and performance.
"""jsx
import { createSignal } from 'solid-js';
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/solid';
function MyDropdown() {
const [isOpen, setIsOpen] = createSignal(false);
return (
setIsOpen(!isOpen())}>
Options
Edit
Delete
);
}
"""
* **Hope UI + Solid.js":** Use a component UI library that is built with Solid.js.
* **Do This:** Integrate component libraries such as Hope UI to build faster.
* **Don't Do This:** Create the wheel using a framework component library that is not built specifically with and for Solid.js.
### 1.5. Utility Libraries
* **clsx:** A tiny (239B) utility for constructing "className" strings conditionally.
* **Do This:** Use "clsx" to manage CSS classes based on component state.
* **Don't Do This:** Manually construct class names using string concatenation (prone to errors).
* **Why:** "clsx" simplifies conditional class name management, improving readability and reducing errors.
"""jsx
import clsx from 'clsx';
import { createSignal } from 'solid-js';
function MyComponent() {
const [isActive, setIsActive] = createSignal(false);
const className = clsx('base-class', {
'active-class': isActive(),
'disabled-class': false,
});
return (
My Component
setIsActive(!isActive())}>Toggle
);
}
"""
### 1.6. Testing
* **Vitest:** A Vite-native test framework with lightning-fast speed.
* **Do This:** Use Vitest for unit and integration testing. Configure your tests to run in a browser environment.
* **Don't Do This:** Use outdated or slow test runners that don't integrate well with Vite.
* **Why:** Vitest provides excellent performance and a smooth developer experience when testing Solid.js components.
* **Example "vitest.config.ts":**
"""typescript
import { defineConfig } from 'vitest/config';
import solid from 'vite-plugin-solid';
export default defineConfig({
plugins: [solid()],
test: {
environment: 'jsdom', // Or 'happy-dom'
transformMode: {
web: [/.[jt]sx?$/],
},
},
});
"""
### 1.7. Formatting and Linting
* **Prettier:** An opinionated code formatter.
* **ESLint:** A tool for identifying and reporting on patterns found in ECMAScript/JavaScript code.
* **typescript-eslint:** Allows ESLint to lint TypeScript code.
* **Do This:** Configure ESLint and Prettier to work together. Use a shareable ESLint configuration (e.g., "eslint-config-prettier", "@typescript-eslint/recommended").
* **Don't Do This:** Skip code formatting and linting. Ignore warnings and errors from your linter.
* **Why:** Consistent formatting and linting improve code readability and help catch potential errors early.
* **Example ".eslintrc.cjs":**
"""javascript
module.exports = {
root: true,
env: { browser: true, es2020: true, node: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:solid/recommended',
'prettier',
],
plugins: ['@typescript-eslint', 'solid', 'prettier'],
rules: {
'prettier/prettier': ['error'],
},
};
"""
### 1.8. Build Tooling
* **Vite:** Extremely fast and lightweight build tool.
* **Do This:** Use Vite for development and production builds. Take advantage of its hot module replacement (HMR) for faster development feedback.
* **Don't Do This:** Use older build tools that are significantly slower.
* **Why:** Vite provides a superior development experience due to its speed and ease of configuration. The Solid.js template is based on Vite.
### 1.9. Solid CLI
* **degit:** Scaffolding tool to create new Solid.js projects based on templates.
* **Do This:** Use "degit" to scaffold new solid projects.
* **Don't Do This:** Manually create directory structure and install dependencies.
* **Why:** Saves time and ensures consistency across projects.
"""bash
npx degit solidjs/templates/js my-solid-project
cd my-solid-project
npm install
npm run dev
"""
## 2. Tool Configuration and Best Practices
### 2.1. Vite Configuration
* **Standard:** Use the "vite-plugin-solid" plugin for seamless integration with Solid.js.
* **Do This:** Include "vite-plugin-solid" in your "vite.config.ts" file. Configure any necessary options for JSX transformation.
* **Don't Do This:** Forget to install or configure the plugin.
* **Why:** "vite-plugin-solid" handles JSX transformation and provides other optimizations for Solid.js.
* **Example "vite.config.ts":**
"""typescript
import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';
export default defineConfig({
plugins: [solid()],
});
"""
### 2.2. TypeScript Configuration
* **Standard:** Use strict TypeScript settings for enhanced type safety.
* **Do This:** Enable "strict: true" in your "tsconfig.json" file along with ""jsx": "preserve"""jsxImportSource": "solid-js"".
* **Don't Do This:** Disable strict mode; it helps catch potential type errors.
* **Why:** Strict mode provides better type checking and helps prevent runtime errors.
* **Example "tsconfig.json":**
"""json
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
},
"include": ["src"],
}
"""
### 2.3. Editor Configuration
* **Standard:** Configure your editor with extensions for Solid.js syntax highlighting, code completion, and linting.
* **Do This:** Install the official Solid.js extension for VS Code or similar editor.
* **Don't Do This:** Use a plain text editor without language support; it hinders productivity and can lead to errors.
* **Why:** Editor extensions enhance the developer experience by providing code completion, syntax highlighting, linting, and other useful features.
### 2.4. Environment Variables
* **Standard:** Manage environment-specific configuration with ".env" files and Vite's "import.meta.env".
* **Do This:** Prefix environment variables with "VITE_" to expose them to the client.
* **Don't Do This:** Hardcode sensitive information directly in your source code.
* **Why:** Centralizes configuration and prevents secrets from being committed to source control.
### 2.5. Server Side Rendering
* **Standard:** Use "solid-start" meta-framework to build scalable and performant web apps.
* **Do This:** Integrate "solid-start" with your project to leverage SSR with Solid.
* **Don't Do This:** Roll out a custom solution for basic functionalities offered by the framework.
* **Why:** "solid-start" integrates best with Solid.js.
## 3. Code Organization and Modularity
### 3.1. Component Structure
* **Standard:** Organize components into logical directories based on feature or domain.
* **Do This:** Create separate directories for each component, including its related styles, tests, and documentation.
* **Don't Do This:** Dump all components into a single directory; it becomes difficult to manage as the application grows.
* **Why:** Organizes the structure of the code. Easier to reason about.
### 3.2. Module Bundling
* **Standard:** Leverage ES modules for code splitting and lazy loading.
* **Do This:** Use dynamic imports ("import()") to load modules on demand. Configure Vite to optimize your bundle for production.
* **Don't Do This:** Load all modules upfront, as it can increase initial load time.
* **Why:** Improves the initial load time.
### 3.3. Dependency Injection
* **Standard:** Utilize Solid's Context API or custom signals for dependency injection.
* **Do This:** Create a context provider to share dependencies across components.
* **Don't Do This:** Pass dependencies through multiple layers of components (prop drilling).
* **Why:** Simplifies the dependency management.
## 4. Performance Optimization
### 4.1. Fine-Grained Updates
* **Standard:** Leverage Solid's fine-grained reactivity system for efficient UI updates.
* **Do This:** Use signals, memos, and effects to manage state and side effects. Avoid unnecessary component re-renders.
* **Don't Do This:** Force updates via global state without considering fine-grained reactivity.
* **Why:** Optimized efficient, reactive state management.
### 4.2. Lazy Loading and Code Splitting
* **Standard:** Implement lazy loading for components and modules that are not immediately needed.
* **Do This:** Use the "lazy" and "Suspense" components.
* **Don't Do This:** Load all components at once. Increase size of initial load.
* **Why:** Lazy components improve the user experience by providing faster initial load times.
### 4.3. Virtualization
* **Standard:** Use virtualization techniques for rendering large lists of data.
* **Do This:** Employ libraries like "solid-virtual" to render only the visible items.
* **Don't Do This:** Render entire lists at once if working with datasets of significant size, or with large render complexity in the list items.
"""jsx
import { createSignal, onMount } from 'solid-js';
import { Virtualizer } from 'solid-virtual';
interface Item {
id: number;
name: string;
}
function MyList() {
const [items, setItems] = createSignal([]);
onMount(() => {
// Simulate fetching data
setTimeout(() => {
const fakeData = Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: "Item ${i}",
}));
setItems(fakeData);
}, 500);
});
return (
items={items()}
size={50}
overscan={10}
>
{(item) => (
{item.name}
)}
);
}
export default MyList;
"""
### 4.4. Memoization
* **Standard:** Use memoization to avoid re-computing values that have not changed.
* **Do This:** Leverage Solid's "createMemo" function to cache computed values based on their dependencies.
* **Don't Do This:** Create memos for values that are not computationally intensive.
* **Why:** memoization reduces the amount of work the CPU has to do.
## 5. Security Best Practices
### 5.1. Input Validation
* **Standard:** Validate all user inputs to prevent malicious data from entering your application.
* **Do This:** Use a validation library to enforce input constraints; sanitize data using libraries like DOMPurify.
* **Don't Do This:** Trust user input without validation; it can lead to security vulnerabilities like XSS and SQL injection.
* **Why:** Prevents against hackers and other malicious agents.
### 5.2. Cross-Site Scripting (XSS) Prevention
* **Standard:** Sanitize user-generated content to prevent XSS attacks which can be injected through user controlled inputs.
* **Do This:** Use template literals with escaping or libraries like DOMPurify to sanitize HTML content.
* **Don't Do This:** Directly render user-provided HTML without sanitization.
* **Why:** Prevents malicious code injection.
### 5.3. Secure API Communication
* **Standard:** Implement secure API communication using HTTPS and proper authentication/authorization mechanisms.
* **Do This:** Always use HTTPS for API requests. Use JSON Web Tokens (JWT) for authentication and authorization.
* **Don't Do This:** Transmit sensitive data over HTTP; it is vulnerable to interception. Store private API keys directly in the code.
* **Why:** Secures data transfers.
### 5.4. Dependency Management
* **Standard:** Regularly audit and update your dependencies to patch known security vulnerabilities.
* **Do This:** Use "npm audit" or "yarn audit" to identify vulnerabilities in your dependencies. Keep your dependencies up-to-date.
* **Don't Do This:** Ignore security warnings from your package manager; they can indicate serious vulnerabilities.
* **Why:** Reduce vulnerability exposure.
## 6. Observability and Debugging
### 6.1. Logging
* **Standard:** Implement comprehensive logging to track application behavior and diagnose issues.
* **Do This:** Use console.log, console.warn, and console.error for debugging purposes.
* **Don't Do This:** Neglect to log key events and errors; it makes debugging difficult. Put sensitive information in logs.
### 6.2. Error Handling
* **Standard:** Implement robust error handling to prevent application crashes.
* **Do This:** Use centralized error handling with "ErrorBoundary"
* **Don't Do This:** Allow errors to propagate unhandled; it can lead to a poor user experience.
* **Why:** Provides a robust and reliable user experience.
### 6.3. Browser DevTools
* **Standard:** Use the browser's developer tools for debugging and performance analysis.
* **Do This:** Use the browser's JavaScript debugger to step through code and inspect variables. Use the performance profiler to identify bottlenecks. Use the network panel to analyze network requests.
* **Don't Do This:** Fail to leverage the dev tools; they are essential for debugging and optimization.
## 7. Collaboration and Documentation
### 7.1. Code Reviews
* **Standard:** Conduct thorough code reviews to ensure code quality and prevent errors.
* **Do This:** Review code changes before they are merged into the main branch. Provide constructive feedback.
* **Don't Do This:** Skip code reviews; they are essential for catching potential issues.
### 7.2. Documentation
* **Standard:** Maintain comprehensive documentation for your components, modules, and APIs.
* **Do This:** Use JSDoc or TypeScriptDoc to document your code.
* **Don't Do This:** Neglect documentation.
* **Why:** Easily read and understood codebase.
### 7.3. Version Control
* **Standard:** Use Git for version control.
* **Do This:** Use a branching strategy (e.g. Gitflow). Make small, focused commits with descriptive messages. Use pull requests for code review.
* **Don't Do This:** Commit directly to the main branch.
* **Why:** Easily track and trace changes.
By adhering to these tooling and ecosystem standards, you can ensure the maintainability, performance, and security of your Solid.js 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'
# State Management Standards for Solid.js This document outlines the recommended coding standards and best practices for state management in Solid.js applications. Adhering to these standards will lead to more maintainable, performant, and robust applications. ## 1. Core Principles of State Management in Solid.js Solid.js provides fine-grained reactivity out of the box. This makes state management both powerful and potentially complex. Understanding the core principles is crucial. * **Explicit State:** Be explicit about which variables constitute your application's state. Hidden state can lead to unpredictable behavior and difficulty in debugging. * **Reactivity Boundaries:** Clearly define the boundaries of reactive updates. Avoid unnecessarily large reactive graphs to prevent needless re-renders and performance bottlenecks. * **Immutability (Conceptual):** While Solid.js doesn't enforce immutability like React, treating state updates as if they are immutable can greatly improve predictability and simplify reasoning about your application. * **Single Source of Truth:** Each piece of data should have a single, definitive source. Avoid duplicating data across multiple stores or components, as this leads to inconsistencies and synchronization issues. * **Declarative Updates:** Prefer declarative approaches to state updates. Rely on Solid's reactive primitives such as signals to propagate change. Avoid directly manipulating the DOM. ## 2. Choosing a State Management Approach Solid.js offers flexibility in state management. Choose the approach that best suits the complexity and scale of your application. ### 2.1. Signals: The Foundation Signals are the fundamental reactive primitive in Solid.js. * **Standard:** Use signals for local component state and for simple global state management. * **Why:** Signals provide fine-grained reactivity and are performant for frequent updates. * **Do This:** """javascript import { createSignal } from 'solid-js'; function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount(count() + 1); return ( <div> <p>Count: {count()}</p> <button onClick={increment}>Increment</button> </div> ); } """ * **Don't Do This:** Mutating the signal's value directly without using the setter function. """javascript import { createSignal } from 'solid-js'; function IncorrectCounter() { const [count, setCount] = createSignal(0); const increment = () => { // ANTI-PATTERN: Mutating the signal directly // This will not trigger reactivity! count = count() + 1; setCount(count); // Doing this after doesn't fix the problem. }; return ( <div> <p>Count: {count()}</p> <button onClick={increment}>Increment</button> </div> ); } """ * **Anti-Pattern:** Creating signals inside components unnecessarily. If a signal is only read in a component but written to elsewhere, create it outside. This avoids unnecessary re-creation on component re-renders. This is very important for frequently rendered components. """javascript // Outer scope reduces recreation and improves performance. const [externalCount, setExternalCount] = createSignal(0); function MyComponent() { return ( <div> <p>External Count: {externalCount()}</p> </div> ); } """ ### 2.2. Stores: Structured Data Management Stores provide a structured way to manage complex data in Solid.js. They offer shallow reactivity, making them ideal for managing objects and arrays. * **Standard:** Use stores for managing complex data structures and collections. Prefer "createMutable" from "solid-js/store" for mutable state, and immutable updates. For truly immutable data structures, follow patterns in section 2.4. * **Why:** Stores simplify state updates and provide built-in mechanisms for batching updates. * **Do This (Mutable Store):** """javascript import { createStore } from 'solid-js/store'; function TaskList() { const [tasks, setTasks] = createStore([ { id: 1, text: 'Learn Solid.js', completed: false }, { id: 2, text: 'Build a project', completed: false }, ]); const toggleCompleted = (id: number) => { setTasks( (task) => task.id === id, 'completed', (completed) => !completed ); }; return ( <ul> {tasks.map((task) => ( <li key={task.id}> <input type="checkbox" checked={task.completed} onChange={() => toggleCompleted(task.id)} /> <span>{task.text}</span> </li> ))} </ul> ); } """ * **Do This (Nested Updates):** Use array indices and object-property accessors within "setTasks" to perform targeted updates. Avoid unnecessarily re-creating the whole task object. """javascript import { createStore } from "solid-js/store"; function MyComponent() { const [state, setState] = createStore({ nested: { count: 0, items: ["a", "b"], }, }); const increment = () => { setState("nested", "count", (c) => c + 1); // Correct: Functional updater setState("nested", "items", 0, "new value"); // Update the first item in the nested array }; return ( <div> <p>Count: {state.nested.count}</p> <ul>{state.nested.items.map((item, i) => <li key={i}>{item}</li>)}</ul> <button onClick={increment}>Increment</button> </div> ); } """ * **Don't Do This:** Directly mutating the store object. This will not trigger reactivity. """javascript import { createStore } from "solid-js/store"; function IncorrectStore() { const [state, setState] = createStore({ count: 0 }); const increment = () => { // ANTI-PATTERN: Direct mutation state.count++; // Won't trigger reactivity! setState("count", state.count); // Does not fix the situation }; return ( <div> <p>Count: {state.count}</p> <button onClick={increment}>Increment</button> </div> ); } """ * **Anti-Pattern:** Using stores as drop-in replacements for signals when fine-grained reactivity is needed. Stores use property access to detect changes. A component that reads a complex property and only needs updates when, say, the 3rd element in an array changes would benefit from signals. ### 2.3. Context API: Implicit State Sharing The Context API provides a way to share state implicitly down the component tree. * **Standard:** Use the Context API for providing configuration values, themes, or other global settings. Do not use as a primary means of state management for frequently changing data. Prefer signals or stores in the context. * **Why:** Context provides a clean way to avoid prop drilling, but it can make data flow less explicit. * **Do This:** """javascript import { createContext, useContext, createSignal, ParentComponent } from 'solid-js'; interface ThemeContextType { theme: string; setTheme: (theme: string) => void; } const ThemeContext = createContext<ThemeContextType>({ theme: 'light', setTheme: () => {}, // Provide a default no-op function }); function ThemeProvider(props: { children: JSX.Element }) { const [theme, setTheme] = createSignal('light'); const value: ThemeContextType = { theme: theme(), setTheme: setTheme, }; return ( <ThemeContext.Provider value={value}> {props.children} </ThemeContext.Provider> ); } function ThemedComponent() { const { theme } = useContext(ThemeContext); return ( <div style={{ background: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}> Themed Content </div> ); } function App() { return ( <ThemeProvider> <ThemedComponent /> </ThemeProvider> ); } """ * **Don't Do This:** Overusing context for frequently changing application state. This can lead to performance issues due to unnecessary re-renders of context consumers. Instead, provide signals or stores through context. * **Anti-Pattern:** Relying solely on context for very specific state that only a few components need. Prop drilling might be more explicit and easier to maintain in such cases. ### 2.4. External Libraries: Managing Global State For larger applications, consider using dedicated state management libraries. * **Standard:** Evaluate libraries like Zustand or Effector for complex global state management involving asynchronous actions or derived state. * **Why:** These libraries provide a structured approach to managing state and side effects, improving maintainability and testability. Solid-specific solutions should be preferred. * **Effector:** Effector is a batteries include solution with stores, derived state, and asynchronous actions. It is a solid choice for complex applications where the full feature set is useful. * **Do This (Effector):** Define stores, events, and effects separately and compose them to manage state. """javascript import { createStore, createEvent, createEffect, useStore } from 'effector'; // Define a store const $count = createStore(0); // Define an event const increment = createEvent(); // Define an effect (asynchronous operation) const incrementAsync = createEffect(async () => { await new Promise(resolve => setTimeout(resolve, 500)); // Simulate async return 1; }); // Connect the event to update the store $count.on(increment, state => state + 1); $count.on(incrementAsync.doneData, (state, payload) => state + payload); // Trigger the effect on event incrementAsync.use(async () => { await new Promise(resolve => setTimeout(resolve, 500)); // Simulate async return 1; }); function Counter() { const count = useStore($count); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={incrementAsync}>Increment Async</button> </div> ); } """ * **Zustand:** Zustand is a less opinionated state management solution ideal for simpler applications. It offers a very small API with excellent performance characteristics. Zustand directly modifies the state, which can be preferable in many situations due to its simplicity. Zustand does not work well with Solid’s store. """javascript import { create } from 'zustand'; interface BearState { bears: number increase: (by: number) => void } const useBearStore = create<BearState>()((set) => ({ bears: 0, increase: (by: number) => set((state) => ({ bears: state.bears + by })), })) function MyComponent() { const bears = useBearStore(state => state.bears); const increase = useBearStore(state => state.increase); return ( <div> Bears: {bears} <button onClick={() => increase(1)}>Increase</button> </div> ) } """ * **Signals and Stores are Sufficient:** For many cases with smaller amounts of application state, signals and stores are simpler to implement and debug. Unless external libraries truly simplify the management of complex state, it is best to keep state management using the framework's primitives only for faster performance and easier debugging. ## 3. Derived State and Computation Solid.js excels at efficiently deriving state from existing signals or stores. ### 3.1. "createMemo": Caching Computed Values * **Standard:** Use "createMemo" to cache computationally expensive derived values. A memo only recalculates when its dependencies change. * **Why:** "createMemo" avoids unnecessary recalculations, improving performance. * **Do This:** """javascript import { createSignal, createMemo } from 'solid-js'; function FullName() { const [firstName, setFirstName] = createSignal('John'); const [lastName, setLastName] = createSignal('Doe'); const fullName = createMemo(() => "${firstName()} ${lastName()}"); return ( <div> <p>Full Name: {fullName()}</p> <input value={firstName()} onInput={e => setFirstName(e.currentTarget.value)} /> <input value={lastName()} onInput={e => setLastName(e.currentTarget.value)} /> </div> ); } """ * **Don't Do This:** Performing expensive calculations directly in the component render function without memoization. This can lead to performance issues when the component re-renders frequently. * **Anti-Pattern:** Overusing "createMemo" when the computation is trivial. The overhead of memoization can outweigh the benefits for simple calculations. ### 3.2. "createEffect": Reacting to State Changes * **Standard:** Use "createEffect" to perform side effects in response to state changes. * **Why:** "createEffect" automatically tracks dependencies and re-runs the effect only when those dependencies change. * **Do This:** """javascript import { createSignal, createEffect } from 'solid-js'; function Logger() { const [count, setCount] = createSignal(0); createEffect(() => { console.log('Count changed:', count()); }); const increment = () => setCount(count() + 1); return ( <div> <p>Count: {count()}</p> <button onClick={increment}>Increment</button> </div> ); } """ * **Don't Do This:** Directly manipulating the DOM outside of a "createEffect". This can lead to inconsistencies and break Solid's reactivity. * **Anti-Pattern:** Overusing "createEffect" for synchronous logic that can be handled with derived signals using "createMemo". ### 3.3. Avoiding Unnecessary Updates * **Standard:** Use "batch" to make multiple state updates in a single transaction. * **Why:** Updates will be processed at once to avoid unnecessary re-renders. * **Do This:** """javascript import { createSignal, batch } from 'solid-js'; function MultiUpdate() { const [firstName, setFirstName] = createSignal('John'); const [lastName, setLastName] = createSignal('Doe'); const updateName = (first: string, last: string) => { batch(() => { setFirstName(first); setLastName(last); }); }; return ( <div> <p>Name: {firstName()} {lastName()}</p> <button onClick={() => updateName('Jane', 'Smith')}>Update Name</button> </div> ); } """ * **Standard:** Ensure that signal values are only updated when the new value differs from the current value. Although signals can prevent updates during recomputation, they can still run when the references are the same. * **Why:** Signals that update with the same value will potentially trigger unnecessary re-renders. * **Do This:** """javascript import { createSignal } from 'solid-js'; function Example() { const [text, setText] = createSignal(''); const handleChange = (e: Event) => { const target = e.target as HTMLInputElement; if (target.value !== text()) { setText(target.value); } }; return ( <input type="text" value={text()} onChange={handleChange} /> ); } """ ## 4. Asynchronous State Updates Managing asynchronous operations and their impact on state requires careful consideration. ### 4.1. Async Functions and State * **Standard:** Use "async/await" syntax for asynchronous operations. Update state within the "async" function using signals or stores. * **Why:** "async/await" simplifies asynchronous code and makes it easier to reason about data flow. * **Do This:** """javascript import { createSignal } from 'solid-js'; function DataFetcher() { const [data, setData] = createSignal(null); const [loading, setLoading] = createSignal(false); const [error, setError] = createSignal(null); const fetchData = async () => { setLoading(true); setError(null); try { const response = await fetch('https://api.example.com/data'); const result = await response.json(); setData(result); } catch (e: any) { setError(e); } finally { setLoading(false); } }; return ( <div> <button onClick={fetchData} disabled={loading()}> Fetch Data </button> {loading() && <p>Loading...</p>} {error() && <p>Error: {error().message}</p>} {data() && <pre>{JSON.stringify(data(), null, 2)}</pre>} </div> ); } """ * **Don't Do This:** Directly mutating state outside of the component's scope in an asynchronous callback. Use the signal or store's setter function. * **Anti-Pattern:** Ignoring potential errors in asynchronous operations. Always handle errors gracefully and update the state accordingly. ### 4.2. Suspense for Data Fetching * **Standard:** Use Solid.js's "<Suspense>" component to handle asynchronous data fetching and display fallback content while data is loading. * **Why:** Suspense simplifies the handling of loading states and improves the user experience. * **Do This:** *(Note: Solid Start is needed to use resource fetching)* """javascript import { createResource, Suspense } from 'solid-js'; async function fetchData(url: string) { const res = await fetch(url); if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); } function DataComponent(props: { url: string }) { const [data] = createResource(() => props.url, fetchData); return ( <div> {data() ? ( <pre>{JSON.stringify(data(), null, 2)}</pre> ) : ( <p>Loading data...</p> )} </div> ); } function App() { return ( <Suspense fallback={<p>Loading...</p>}> <DataComponent url="https://api.example.com/data" /> </Suspense> ); } """ * **Don't Do This:** Manually managing loading states when Suspense can handle it automatically. ## 5. Testing State Management Thoroughly test your state management logic to ensure correctness and prevent regressions. ### 5.1. Unit Testing Signals and Stores * **Standard:** Write unit tests for individual signals and stores to verify their behavior. * **Why:** Unit tests isolate state management logic and make it easier to identify and fix bugs. * **Do This (Example with Jest):** """javascript import { createSignal } from 'solid-js'; import { createRoot } from 'solid-js/web'; describe('Counter Signal', () => { it('should increment the count', () => { createRoot(() => { const [count, setCount] = createSignal(0); setCount(1); // Increment the count expect(count()).toBe(1); }); }); }); """ ### 5.2. Integration Testing Components with State * **Standard:** Write integration tests for components that interact with state to ensure that the UI updates correctly in response to state changes. Use testing libraries like "@testing-library/solid" or Cypress. * **Why:** Integration tests verify that the state management logic is correctly connected to the UI. ## 6. Accessibility Considerations * **Standard:** Ensure that state changes are reflected in an accessible manner for users with disabilities. Use ARIA attributes to provide context and status updates to assistive technologies. * **Why:** Accessibility is a core principle of inclusive design. * **Do This:** Use ARIA live regions to announce state updates to screen readers. """javascript import { createSignal } from 'solid-js'; function StatusMessage() { const [message, setMessage] = createSignal(''); const updateMessage = (newMessage: string) => { setMessage(newMessage); }; return ( <div aria-live="polite" aria-atomic="true"> {message()} <button onClick={() => updateMessage('Data loaded successfully')}>Load Data</button> </div> ); } """ ## 7. Performance Optimization * **Standard:** Limit re-renders by only updating the parts of the UI that need to change. * **Why:** Updating only what is necessary improves performance and reduces wasted resources. ### 7.1. Granular Updates Solid's fine-grained reactivity system makes it easier to only update the parts of the UI that need to change. Avoid unnecessary re-renders by ensuring that components only subscribe to the specific state they need. ### 7.2. Memoization Use "createMemo" to cache the results of expensive computations. This can prevent the need to re-compute the same value multiple times. Always keep in mind the overhead of "createMemo". ### 7.3. Virtualized Lists Use a virtualized list component for displaying large lists of data. A virtualized list only renders the items that are currently visible on the screen. ## 8. Security Considerations * **Standard:** Sanitize user input before storing it in state to prevent XSS attacks. * **Why:** Security is paramount. Failing to sanitize user input can lead to vulnerabilities that compromise the application. This comprehensive guide provides a foundation for building robust and maintainable Solid.js applications with effective state management strategies. By adhering to these standards, developers can ensure code quality, performance, and security.
# Deployment and DevOps Standards for Solid.js This document outlines the standards and best practices for deploying and managing Solid.js applications. It covers build processes, CI/CD pipelines, production environment considerations, and monitoring, emphasizing Solid.js-specific aspects and modern DevOps approaches. ## 1. Build Processes and Optimization ### 1.1. Use a Modern Build Toolchain **Do This:** Utilize a modern JavaScript build toolchain like Vite, Rollup or Parcel for bundling and optimizing your Solid.js application. **Don't Do This:** Rely on outdated build processes or hand-rolled bundling solutions. **Why:** Modern build tools offer tree-shaking, code splitting, minification, and automatic handling of dependencies, leading to reduced bundle sizes and improved initial load times, crucial for Solid.js's performance. **Example (Vite):** """javascript // vite.config.js import { defineConfig } from 'vite'; import solid from 'vite-plugin-solid'; export default defineConfig({ plugins: [solid()], build: { target: 'esnext', // Optimize for modern browsers minify: 'esbuild', // Use esbuild for faster and more efficient minification rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { return 'vendor'; // Separate vendor dependencies } } } } } }); """ **Explanation:** * "vite-plugin-solid" integrates seamlessly with Solid.js projects. * "build.target: 'esnext'" targets modern browsers for optimal performance. * "build.minify: 'esbuild'" utilizes esbuild for fast minification. * "rollupOptions.output.manualChunks" enables code splitting, separating vendor dependencies into a "vendor" chunk for better caching. This allows browser to cache 3rd party libraries independently. ### 1.2. Configure Environment Variables **Do This:** Use environment variables for configuration settings that vary between environments (development, staging, production). **Don't Do This:** Hardcode sensitive information or environment-specific configurations directly in your source code. **Why:** Environment variables improve security, configurability, and portability, allowing you to easily adapt your application to different deployment environments. **Example:** """javascript // Accessing environment variables in Solid.js component import { createSignal, onMount } from 'solid-js'; function MyComponent() { const [apiUrl, setApiUrl] = createSignal(''); onMount(() => { // Access the API_URL environment variable const envApiUrl = import.meta.env.VITE_API_URL; setApiUrl(envApiUrl || 'default_api_url'); // Fallback if not defined }); return ( <div> API URL: {apiUrl()} </div> ); } export default MyComponent; // .env file VITE_API_URL=https://api.example.com """ **Explanation:** * Using "import.meta.env" (Vite's way of exposing env variables) to access environment variables at runtime. This works for other build tools like Rollup too, albeit with different env variable importing syntax. * Providing a default value as a fallback if the environment variable is not set. * Prefixing env variables with "VITE_" is required by vite. ### 1.3. Optimize Assets **Do This:** Optimize images, fonts, and other static assets before deployment. **Don't Do This:** Deploy large, unoptimized assets, which can significantly impact loading times. **Why:** Optimized assets reduce bandwidth consumption, improve loading speeds, and enhance the user experience. **Example:** * **Image Optimization:** Use tools like "imagemin" or online services like TinyPNG to compress images without significant loss of quality. * **Font Optimization:** Utilize font subsetting and modern font formats like WOFF2 to reduce font file sizes. * **Serving Compressed Assets:** Configure your server to serve assets with gzip or Brotli compression. ### 1.4. Enable Code Splitting **Do This:** Configure your build tool to perform code splitting, breaking your application into smaller chunks that can be loaded on demand. **Don't Do This:** Bundle your entire application into a single, monolithic JavaScript file. **Why:** Code splitting reduces initial load times by deferring the loading of non-critical code, improving the perceived performance of your Solid.js application. **Example (Vite Configuration - continued from above):** See section 1.1. for vite.config.js example. The "rollupOptions.output.manualChunks" configuration in that section enables code splitting. ### 1.5. Prerendering and SSR (Server-Side Rendering) **Do This:** Consider using Prerendering or Server-Side Rendering for parts of your application that need SEO or faster initial load times. SolidJS works well with tools like SolidStart for SSR. **Don't Do This:** Neglect optimizing for SEO if it's a requirement, or leave critical content rendered client-side when it could be prerendered. **Why:** Prerendering pre-builds static HTML pages for better SEO and faster initial load. SSR renders components on the server for improved performance on the first load, which is crucial for user experience and search engine crawlers. **Example (SolidStart):** """javascript // solid.config.js import solid from "solid-start/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [solid()], }); """ """javascript // app.jsx import { Routes, Route } from "@solidjs/router"; import Home from "./pages/Home"; import About from "./pages/About"; export default function App() { return ( <Routes> <Route path="/" component={Home} /> <Route path="/about" component={About} /> </Routes> ); } """ **Explanation:** - SolidStart simplifies SSR and prerendering for SolidJS applications. - "solid-start/vite" integrates SolidStart's features into the Vite build process. - You can define routes and components like a standard SolidJS app, and SolidStart handles the server-side rendering. ## 2. CI/CD Pipelines ### 2.1. Automate Builds and Deployments **Do This:** Implement a CI/CD pipeline using tools like GitHub Actions, GitLab CI, CircleCI, or Jenkins to automate the build, test, and deployment processes. **Don't Do This:** Manually build and deploy your application, which is error-prone and time-consuming. **Why:** CI/CD pipelines ensure consistent and reliable deployments, reduce the risk of human error, and allow for faster iteration cycles. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 - run: npm install - run: npm run build - name: Deploy to Firebase Hosting uses: w9jds/firebase-action@releases/v6 with: args: deploy --only hosting env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }} """ **Explanation:** * This workflow is triggered when code is pushed to the "main" branch. * It sets up Node.js, installs dependencies, builds the Solid.js application, and deploys it to Firebase Hosting. * "secrets.FIREBASE_TOKEN" and "secrets.FIREBASE_PROJECT_ID" are stored securely in GitHub Secrets. ### 2.2. Implement Automated Testing **Do This:** Include automated tests (unit, integration, and end-to-end) in your CI/CD pipeline. **Don't Do This:** Deploy code without thorough testing, which can lead to bugs and regressions in production. **Why:** Automated tests ensure code quality, catch errors early, and prevent regressions, leading to a more stable and reliable application. **Example (Jest and Solid Testing Library):** """javascript // src/components/Counter.test.jsx import { render, fireEvent } from '@solidjs/testing-library'; import Counter from './Counter'; describe('Counter Component', () => { it('increments the counter when the button is clicked', async () => { const { getByText } = render(() => <Counter />); const incrementButton = getByText('Increment'); const counterValue = getByText('0'); // Assuming initial value is 0. fireEvent.click(incrementButton); await (() => expect(getByText('1')).toBeVisible()); fireEvent.click(incrementButton); await (() => expect(getByText('2')).toBeVisible()); }); }); """ **Explanation:** * Uses "@solidjs/testing-library" for rendering and interacting with Solid.js components. * "render" mounts the component for testing. * "fireEvent.click" simulates a button click. * "getByText" retrieves elements based on their text content. * The test asserts that the counter value increments correctly after each click. ### 2.3. Use Version Control **Do This:** Use a version control system like Git to track changes to your codebase. **Don't Do This:** Directly modify production code without proper version control. **Why:** Version control enables collaboration, allows you to revert to previous versions, and provides a history of changes, which is essential for managing complex projects. ### 2.4. Implement Feature Flags **Do This:** Use feature flags to control the release of new features to a subset of users or to enable/disable features without deploying new code. **Don't Do This:** Deploy new features directly to all users without proper testing or monitoring. **Why:** Feature flags reduce the risk of introducing bugs or performance issues to all users at once. **Example (using a basic feature flag in a Solid component):** """jsx import { createSignal } from 'solid-js'; const FEATURE_FLAG_NEW_UI = import.meta.env.VITE_FEATURE_NEW_UI === 'true'; function MyComponent() { return ( <div> {FEATURE_FLAG_NEW_UI ? ( <p>New UI is enabled!</p> ) : ( <p>Old UI is active.</p> )} </div> ); } export default MyComponent; // .env VITE_FEATURE_NEW_UI=true """ **Explanation** * We read a flag from the environment ("VITE_FEATURE_NEW_UI"). Note the "=== 'true'" comparison is important, since env variables are strings. * We use a ternary operator to conditionally render different UI elements depending on whether the flag is enabled or not. ## 3. Production Environment ### 3.1. Use a CDN **Do This:** Serve static assets (JavaScript, CSS, images) from a Content Delivery Network (CDN). **Don't Do This:** Serve static assets directly from your origin server, which can increase latency and reduce performance. **Why:** CDNs distribute your content across multiple servers globally, reducing latency and improving loading speeds for users around the world. ### 3.2. Configure Caching **Do This:** Configure appropriate caching headers (e.g., "Cache-Control", "Expires") for static assets and API responses. **Don't Do This:** Use overly aggressive or ineffective caching strategies, which can lead to stale content or unnecessary requests. **Why:** Caching reduces the load on your server, improves response times, and enhances the user experience. ### 3.3. Monitor Performance and Errors **Do This:** Implement monitoring and error tracking using tools like Sentry, New Relic, or Datadog. **Don't Do This:** Deploy code without proper monitoring, which makes it difficult to identify and resolve issues in production. **Why:** Monitoring and error tracking provide insights into the performance and stability of your application, allowing you to proactively identify and resolve issues. **Example (Sentry):** """javascript // src/index.jsx or main.jsx import * as Sentry from "@sentry/solid"; Sentry.init({ dsn: "YOUR_SENTRY_DSN", integrations: [ new Sentry.BrowserTracing(), new Sentry.Replay() ], // Performance Monitoring tracesSampleRate: 0.1, // Capture 10% of transactions for performance traces. // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to lower it at first. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. }); import App from './App'; import { render } from 'solid-js/web'; render(() => <App />, document.getElementById('root')); """ **Explanation:** * "Sentry.init" initializes Sentry with your DSN (Data Source Name). * "BrowserTracing" integration enables performance monitoring. * "Replay" integration enables session replay features to visually see what the user experienced when an error occurred. * "tracesSampleRate" controls the sampling rate for performance traces. * "replaysSessionSampleRate" controls the sampling rate for session replays. * "replaysOnErrorSampleRate" allows you to capture replays specifically when errors occur. * Wrap the application rendering in "Sentry.withProfiler" to profile Solid.js components. ### 3.4. Implement Logging **Do This:** Implement structured logging throughout your application. **Don't Do This:** Rely solely on "console.log" statements, which are difficult to manage in production. **Why:** Structured logging provides valuable context for debugging and troubleshooting issues in production. **Example:** """javascript import log from 'loglevel'; // Use a logging library like loglevel log.setLevel(log.levels.DEBUG); // Set the log level based on environment function MyComponent() { return( <button onClick={() => { log.debug("Button clicked"); }}> Click me </button> ); } export default MyComponent; """ ### 3.5. Secure Your Application **Do This:** Implement security best practices, such as input validation, output encoding, and protection against common web vulnerabilities like XSS and CSRF. **Don't Do This:** Neglect security considerations, which can leave your application vulnerable to attacks. **Why:** Security is essential for protecting user data and preventing malicious activity. Solid.js is not immune to common WebApp vulnerabilities and should be assessed regularly. ### 3.6. Proper Error Handling **Do This:** Implement global error boundaries using Solid's "<ErrorBoundary>" component and proactively catch errors to prevent application crashes. **Don't Do This:** Allow unhandled exceptions to propagate, leading to a broken user experience. **Why:** Error boundaries and proactive error handling improve the resilience of your application by gracefully handling unexpected errors. **Example:** """jsx import { ErrorBoundary } from 'solid-js'; import MyComponent from './MyComponent'; function App() { return ( <ErrorBoundary fallback={error => <p>An error occurred: {error.message}</p>}> <MyComponent /> </ErrorBoundary> ); } export default App; """ ## 4. Solid.js Specific Considerations ### 4.1. Leverage Solid.js's Reactive Nature **Do This:** Understand and leverage Solid.js's reactive primitives effectively in your build and deployment setup. For example, optimize data fetching and updates to minimize unnecessary re-renders. **Don't Do This:** Treat Solid.js like other virtual DOM libraries (like react). **Why:** Solid.js's fine-grained reactivity allows for precise updates, which can significantly improve performance when data changes frequently. ### 4.2 Hydration considerations in SolidJS **Do This:** If using SSR, pay careful attention to hydration optimization when the server-rendered content loads client-side. **Don't Do This:** Neglect hydration and cause full re-renders. Ensure data consistency to prevent hydration mismatches. **Why:** Efficient hydration is crucial for optimal SSR performance. Avoid unnecessary re-renders and data mismatches that harm the user experience. ### 4.3 Component Library Versioning **Do This:** Use Semantic Versioning (SemVer) and clear commit messages for any SolidJS component library to ensure that consumers of your components can safely upgrade, or explicitly understand the updates they're getting. **Don't Do This:** Make breaking changes in minor or patch releases to component libraries as this can affect many consumers. **Why:** Managing versions correctly, especially in component libraries, promotes maintainability, reduces dependency conflicts, and eases integrations for other SolidJS projects. ## 5. Security Considerations Specific to Solid.js * **Avoid Insecurely Rendered Content**: Like all frontend frameworks, be careful when rendering user-provided content that is not properly sanitized. Use appropriate escaping techniques and consider libraries like DOMPurify to prevent XSS attacks. * **Secure API Endpoints**: While not specific to Solid.js but a general security practice, ensure that all your backend API endpoints that your Solid.js applications interact with are secure and follow security best practices. * **Dependency Management**: Regularly audit your dependencies for known vulnerabilities using tools like "npm audit" or "yarn audit". * **Secrets Management**: Use environment variables and secure storage solutions to manage API keys, tokens, and other sensitive information. Never commit sensitive information directly to your codebase. * **Rate Limiting**: Implement rate limiting on your backend APIs to protect against abuse and DoS attacks. * **Content Security Policy (CSP)**: Configure a strong Content Security Policy (CSP) to prevent XSS attacks by controlling the resources that the browser is allowed to load. * **Subresource Integrity (SRI)**: Use SRI to ensure that your application loads unmodified versions of third-party resources from CDNs. * **Regular Security Audits**: Conduct regular security audits and penetration testing to identify and address potential vulnerabilities in your application. These standards provide a comprehensive guide for deploying and managing Solid.js applications, focusing on performance, reliability, and security. Adhering to these practices will help you build and maintain robust Solid.js applications that deliver a great user experience.
# Core Architecture Standards for Solid.js This document outlines the core architectural standards for Solid.js projects. It provides guidelines for structuring applications, managing state, organizing components, and leveraging Solid.js's reactivity model efficiently. These standards are designed to promote maintainability, readability, performance, and scalability in your Solid.js applications. ## 1. Project Structure and Organization A well-defined project structure is crucial for maintainability, especially as the application grows. **Standard:** Organize your project using a feature-based or domain-based structure, grouping related components, utilities, and styles together. **Why:** This approach increases cohesion and reduces coupling, making it easier to locate, understand, and modify code related to a specific feature. **Do This:** """ src/ components/ FeatureA/ FeatureA.tsx // Main component FeatureA.module.css // Component-specific styles (CSS Modules) FeatureA.spec.tsx // Unit tests utils.ts // Utility functions specific to FeatureA FeatureB/ ... contexts/ // Global state management with contexts AppContext.tsx hooks/ // Custom hooks for reusable logic useDataFetching.ts pages/ // Top-level route components/pages HomePage.tsx AboutPage.tsx services/ // API communication modules api.ts styles/ // Global styles global.css utils/ // General utility functions dateUtils.ts formatUtils.ts App.tsx // Root component index.tsx // Entry point vite-env.d.ts """ **Don't Do This:** """ src/ components/ // Unorganized dump of all components ComponentA.tsx ComponentB.tsx ... utils/ // Unrelated utility functions mixed together api.ts dateUtils.ts formatUtils.ts App.tsx index.tsx """ **Explanation:** The recommended structure promotes modularity. Feature-specific logic resides within its folder, keeping individual components lean and focused. Placing pages under "/pages" clarifies the routing structure of the application. The "/services" directory encapsulates API interactions. Use CSS modules to scope styles to individual components, avoiding naming conflicts. **Code Example:** """typescript // src/components/FeatureA/FeatureA.tsx import styles from './FeatureA.module.css'; // CSS Modules interface FeatureAProps { data: string; } function FeatureA(props: FeatureAProps) { return ( <div class={styles.container}> {/* Access styles using styles.className */} <p>Feature A: {props.data}</p> </div> ); } export default FeatureA; // src/components/FeatureA/FeatureA.module.css .container { background-color: #f0f0f0; padding: 10px; border: 1px solid #ccc; } """ ## 2. Component Architecture Solid.js components should be small, focused, and reusable. Leverage the reactivity system for efficient updates. **Standard:** Create functional components that are reactive and focused on a single responsibility. Prefer composition over inheritance. **Why:** Functional components improve code clarity and testability. Composition allows you to build complex UIs by combining smaller, reusable components. **Do This:** """typescript // Small, focused component interface ButtonProps { onClick: () => void; children: any; } function Button(props: ButtonProps) { return <button onClick={props.onClick}>{props.children}</button>; } """ """typescript // Composing components import Button from './Button'; interface CardProps { title: string; description: string; } function Card(props: CardProps) { return ( <div> <h2>{props.title}</h2> <p>{props.description}</p> <Button onClick={() => alert('Clicked!')}>Learn More</Button> </div> ); } """ **Don't Do This:** * Creating monolithic components with too much logic. * Using class components (functional components are preferred in Solid.js). * Relying on inheritance. **Explanation:** Solid.js favors functional components due to their simplicity and performance characteristics. Composition promotes reusability and flexibility. Keep components small and focused on a single task to improve maintainability. **Code Example:** """typescript // Example using Solid's reactivity import { createSignal, JSX } from 'solid-js'; function Counter(): JSX.Element { const [count, setCount] = createSignal(0); const increment = () => setCount(count() + 1); return ( <div> <p>Count: {count()}</p> <button onClick={increment}>Increment</button> </div> ); } export default Counter; """ ## 3. State Management Choose a state management strategy that aligns with the complexity of your application. Solid.js provides built-in primitives, but for larger applications, consider libraries like Zustand or Jotai. **Standard:** Use "createSignal" for local component state. Consider using contexts for application-wide state or specialized state management libraries like Zustand or Jotai for more complex needs. **Why:** "createSignal" is the fundamental reactive primitive in Solid.js. Contexts provide a simple way to share state across components. Zustand and Jotai offer scalability and performance optimizations for large applications. **Do This:** """typescript // Local state with createSignal import { createSignal } from 'solid-js'; function MyComponent() { const [name, setName] = createSignal('Initial Name'); const updateName = (newName: string) => { setName(newName); }; return ( <div> <p>Name: {name()}</p> <input type="text" value={name()} onInput={(e) => updateName(e.currentTarget.value)} /> </div> ); } """ """typescript // Global state with context import { createContext, useContext, createSignal, JSX } from 'solid-js'; interface AppContextType { theme: [string, (theme: string) => void]; } const AppContext = createContext<AppContextType>({} as AppContextType); interface AppProviderProps { children: JSX.Element } function AppProvider(props: AppProviderProps) { const theme = createSignal('light'); return ( <AppContext.Provider value={{ theme }}> {props.children} </AppContext.Provider> ); } function useApp() { return useContext(AppContext); } import { ParentComponent } from './ParentComponent'; function App() { return ( <AppProvider> <ParentComponent /> </AppProvider> ); } export default App; //Example usage inside ParentComponent.tsx import { useApp } from '../AppContext'; function ParentComponent() { const {theme} = useApp(); return( <> Value via context: {theme[0]()} </> ) } """ """typescript // Example Zustand Usage import create from 'zustand'; interface BearState { bears: number; increase: (by: number) => void; } const useBearStore = create<BearState>((set) => ({ bears: 0, increase: (by: number) => set((state) => ({ bears: state.bears + by })), })); function BearCounter() { const bears = useBearStore((state) => state.bears); return <h1>{bears} around here ...</h1>; } function IncreasePopulation() { const increase = useBearStore((state) => state.increase); return <button onClick={() => increase(1)}>one up</button>; } function App() { return ( <> <BearCounter /> <IncreasePopulation /> </> ); } """ **Don't Do This:** * Overusing global state for component-specific data. * Mutating state directly without using setter functions from "createSignal". * Ignoring the performance implications of excessive state updates. **Explanation:** Choose the state management strategy based on the scale and complexity of the application. "createSignal" is ideal for local state, while contexts and Zustand are beneficial for managing global state and side effects. Avoid direct mutation of signal values, always use the setter function to trigger reactivity. **Code Example:** """typescript // Correct way to update a signal import { createSignal } from 'solid-js'; function UpdateSignalExample() { const [count, setCount] = createSignal(0); const increment = () => { setCount(count() + 1); // Correct way to update }; return ( <div> <p>Count: {count()}</p> <button onClick={increment}>Increment</button> </div> ); } """ ## 4. Asynchronous Operations and Data Fetching Handle asynchronous operations gracefully using asynchronous functions and error handling. Solid.js provides tools like "createResource" for managing data fetching. **Standard:** Use "createResource" for data fetching. Handle loading and error states appropriately. Encapsulate API calls in dedicated services. **Why:** "createResource" simplifies data fetching, automatically manages loading and error states, and integrates seamlessly with Solid's reactivity system. Dedicated services improve code organization and reusability. **Do This:** """typescript // Using createResource for data fetching import { createResource } from 'solid-js'; interface Post { id: number; title: string; body: string; } async function fetchPosts(): Promise<Post[]> { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!response.ok) { throw new Error('Failed to fetch posts'); } return response.json(); } function PostsList() { const [posts, { mutate, refetch }] = createResource(fetchPosts); return ( <div> {posts.loading ? ( <p>Loading...</p> ) : posts.error ? ( <p>Error: {posts.error.message}</p> ) : ( <ul> {posts().map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> )} <button onClick={refetch}>Refresh</button> </div> ); } """ """typescript // Encapsulating API calls in a service // src/services/api.ts async function getPosts(): Promise<Post[]> { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!response.ok) { throw new Error('Failed to fetch posts'); } return response.json(); } export const api = { getPosts, }; // Usage in a component: import { createResource } from 'solid-js'; import { api } from '../services/api'; function PostsList() { const [posts, { mutate, refetch }] = createResource(api.getPosts); // ... rest of the component } """ **Don't Do This:** * Fetching data directly in components without handling loading/error states. * Repeating API calls in multiple components. * Ignoring error handling during API requests. **Explanation:** "createResource" provides a declarative way to handle asynchronous data fetching. Encapsulate API interactions within a service layer to avoid code duplication and promote separation of concerns. Ensure robust error handling to provide a better user experience. **Code Example:** """typescript // Handling data fetching with error boundary import { createResource, ErrorBoundary } from 'solid-js'; interface Post { id: number; title: string; body: string; } async function fetchPosts(): Promise<Post[]> { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!response.ok) { throw new Error("Failed to fetch posts"); } return response.json(); } function PostsList() { const [posts] = createResource(fetchPosts); return ( <ul> {posts()?.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } function App() { return ( <ErrorBoundary fallback={error => <p>Something went wrong: {error.message}</p>}> <PostsList /> </ErrorBoundary> ); } export default App; """ ## 5. Reactivity and Performance Solid.js's reactivity model is highly efficient. Understand how to leverage it effectively to avoid unnecessary re-renders and optimize performance. **Standard:** Optimize reactivity by creating signals only when necessary. Avoid unnecessary computations within reactive contexts. Use "createMemo" for derived values. Use "untrack" to read signals without creating a dependency. **Why:** Efficient reactivity leads to faster updates and better overall application performance. "createMemo" helps avoid redundant computations. "untrack" provides fine-grained control over dependency tracking. **Do This:** """typescript // Using createMemo for derived values import { createSignal, createMemo } from 'solid-js'; function FullNameComponent() { const [firstName, setFirstName] = createSignal('John'); const [lastName, setLastName] = createSignal('Doe'); const fullName = createMemo(() => "${firstName()} ${lastName()}"); // Only updates when firstName or lastName change return ( <div> <p>Full Name: {fullName()}</p> <input type="text" value={firstName()} onInput={(e) => setFirstName(e.currentTarget.value)} /> <input type="text" value={lastName()} onInput={(e) => setLastName(e.currentTarget.value)} /> </div> ); } """ """typescript // Using untrack to avoid unnecessary dependencies import { createSignal, untrack } from 'solid-js'; function UntrackExample() { const [count, setCount] = createSignal(0); const [message, setMessage] = createSignal('Initial Message'); const handleClick = () => { untrack(() => { // Read count without creating a dependency console.log("Button clicked. Current count: ${count()}"); }); setMessage('Button was clicked!'); }; return ( <div> <p>Count: {count()}</p> <p>Message: {message()}</p> <button onClick={handleClick}>Click me</button> </div> ); } """ **Don't Do This:** * Performing expensive calculations directly within component render functions. * Creating signals unnecessarily. * Ignoring potential performance bottlenecks caused by excessive re-renders. **Explanation:** "createMemo" helps to avoid redundant computations by caching the result and only re-evaluating when its dependencies change. "untrack" lets you read signals without causing side effects. These tools enhance the performance of your applications. **Code Example:** """typescript // Optimizing list rendering using keyed list import { createSignal, For } from 'solid-js'; interface Item { id: number; text: string; } function KeyedListExample() { const [items, setItems] = createSignal<Item[]>([ { id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' }, { id: 3, text: 'Item 3' }, ]); const addItem = () => { setItems([...items(), { id: Date.now(), text: "New Item ${Date.now()}" }]); }; return ( <div> <button onClick={addItem}>Add Item</button> <ul> <For each={items()} key={(item) => item.id}> {(item) => <li>{item.text}</li>} </For> </ul> </div> ); } """ ## 6. Error Handling Robust error handling is important for creating reliable applications. **Standard:** Use "ErrorBoundary" components to catch errors gracefully. Implement centralized error logging. Provide user-friendly error messages. **Why:** "ErrorBoundary" provides a declarative way to handle errors within components. Centralized logging helps track and debug errors. User-friendly messages improve the user experience. **Do This:** """typescript // Using ErrorBoundary import { ErrorBoundary } from 'solid-js'; function MyComponent() { throw new Error('Something went wrong!'); // Simulate an error return <p>This will not be rendered.</p>; } """ """typescript function App() { return ( <ErrorBoundary fallback={error => <p>An error occurred: {error.message}</p>}> <MyComponent /> </ErrorBoundary> ); } export default App; """ **Don't Do This:** * Ignoring errors. * Displaying technical error details to users. * Failing to log errors for debugging. **Explanation:** "ErrorBoundary" prevents component errors from crashing the entire application. Ensure error messages are displayed correctly and don't expose sensitive information. **Code Example:** Implement an ErrorLogger service for error logging. """typescript class ErrorLogger { static log(error: any, componentStack?: string) { console.error('Error', error); if (componentStack) { console.error('Component Stack', componentStack) } //Optional: Send the error to a server-side logging service such as Sentry, LogRocket, etc. //Example //Sentry.captureException(error, { // contexts: { // react: {componentStack}, // }, //}); } } export default ErrorLogger; """ ## 7. Code Style and Formatting Maintain consistent code style and formatting for readability and maintainability. **Standard:** Use Prettier and ESLint with configured Solid.js-specific rules. Enforce consistent naming conventions for variables and components. Add comments and documentation to explain complex logic. **Why:** Consistent code style improves readability and reduces the cognitive load for developers. Linting tools help catch potential errors early. **Do This:** * Configure Prettier and ESLint specifically for Solid.js projects. * Use descriptive variable and function names. * Write concise and informative comments. """json // .prettierrc.json { "semi": true, "trailingComma": "all", "singleQuote": true, "printWidth": 120, "tabWidth": 2 } """ """ // .eslintrc.js module.exports = { parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint', 'solid'], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:solid/recommended', 'prettier', // Make sure "prettier" is the last element in this array. ], rules: { // Add custom rules here }, }; """ **Don't Do This:** * Ignoring code style guidelines. * Using inconsistent naming conventions. * Writing complex code without comments or documentation. **Explanation:** Tools like Prettier and ESLint help automate code formatting and catch potential errors. Consistency results in more understandable and maintainable code. **Code Example:** """typescript // Example of a well-documented function /** * Formats a date string into a user-friendly format. * @param dateString The date string to format. * @returns A formatted date string. * @throws Error if the date string is invalid. */ function formatDate(dateString: string): string { try { const date = new Date(dateString); return date.toLocaleDateString(); // Format the date } catch (error) { console.error('Invalid date string:', dateString); throw new Error('Invalid date string provided.'); } } """ ## 8. Testing Write comprehensive tests to ensure the reliability of your application. **Standard:** Use Jest or Vitest for unit and integration tests. Aim for high test coverage, particularly in critical code paths. Write tests that are readable, maintainable, and focused on testing specific behavior. **Why:** Testing ensures that the application functions as expected and reduces the risk of introducing bugs. **Do This:** * Write unit tests for individual components and functions. * Write integration tests for testing interactions between components. * Use mocking to isolate components during testing. """typescript // Example unit test using Vitest import { describe, it, expect } from 'vitest'; import { render, fireEvent } from '@solidjs/testing-library'; import Counter from './Counter'; describe('Counter Component', () => { it('should increment the count when the button is clicked', async () => { const { getByText } = render(() => <Counter />); const incrementButton = getByText('Increment'); const countElement = getByText('Count: 0'); await fireEvent.click(incrementButton); expect(getByText('Count: 1')).toBeDefined(); }); }); """ **Don't Do This:** * Skipping tests for critical functionality. * Writing tests that are tightly coupled to implementation details. * Ignoring test failures. **Explanation:** Testing is paramount for writing robust, maintainable and reliable code. Focus on writing tests that target specific behaviors to prevent regressions. By adhering to these architectural standards, your Solid.js projects will be easier to maintain, test, and scale over time.
# Component Design Standards for Solid.js This document outlines the component design standards for Solid.js, providing guidelines for creating reusable, maintainable, and performant components. It is intended to guide developers and inform AI coding assistants. ## 1. Component Architecture and Structure ### 1.1. Standard: Single Responsibility Principle **Do This:** Each component should have a clear, single purpose. Decompose complex functionalities into smaller, composable components. **Don't Do This:** Avoid creating "God Components" that handle multiple unrelated responsibilities. **Why:** Adhering to the Single Responsibility Principle (SRP) promotes modularity, testability, and reusability. Smaller, focused components are easier to understand, maintain, and reuse across different parts of the application. """jsx // Good: Separated component for displaying user info function UserInfo({ user }) { return ( <div> <h2>{user.name}</h2> <p>Email: {user.email}</p> </div> ); } // Good: Separated component for displaying user avatar function UserAvatar({ user }) { return ( <img src={user.avatarUrl} alt={user.name} /> ); } function UserProfile({ user }) { return ( <div> <UserAvatar user={user} /> <UserInfo user={user} /> </div> ); } // Bad: Component rendering avatar and info together, less reusable. function UserProfileCombined({user}) { return ( <div> <img src={user.avatarUrl} alt={user.name} /> <h2>{user.name}</h2> <p>Email: {user.email}</p> </div> ); } """ ### 1.2. Standard: Component Composition **Do This:** Favor composition over inheritance. Use props to configure components and pass children to create flexible UI structures. **Don't Do This:** Rely heavily on class inheritance or complex conditional rendering within a single component. **Why:** Composition promotes code reuse and avoids the tight coupling associated with inheritance. It makes components more adaptable to different contexts. """jsx // Good: Composing a Layout component with children function Layout({ children }) { return ( <div className="layout"> <header>Header</header> <main>{children}</main> <footer>Footer</footer> </div> ); } function HomePage() { return ( <Layout> <h1>Welcome to the Home Page</h1> <p>Some content here.</p> </Layout> ); } // Bad: Trying to handle layout within the page component. function HomePageCombined() { return ( <div className="layout"> <header>Header</header> <main> <h1>Welcome to the Home Page</h1> <p>Some content here.</p> </main> <footer>Footer</footer> </div> ); } """ ### 1.3. Standard: Named Exports **Do This:** Use named exports for components to improve discoverability and refactoring. **Don't Do This:** Rely solely on default exports, especially in larger projects. **Why:** Named exports make it easier to identify and import specific components, improving code readability and maintainability. They also facilitate tree shaking and reduce bundle size. """jsx // Good: Named export export function Button({ children, onClick }) { return <button onClick={onClick}>{children}</button>; } // Bad: Default export (less discoverable) export default function Button({ children, onClick }) { return <button onClick={onClick}>{children}</button>; } // Usage of named import import { Button } from './components/Button'; """ ## 2. Component Implementation Details ### 2.1. Standard: Prop Types and Validation **Do This:** Define prop types using TypeScript or PropTypes to ensure data integrity and provide helpful error messages during development. **Don't Do This:** Skip prop type definitions, especially for components that accept complex or critical data. **Why:** Prop type validation helps catch errors early, improves code reliability, and makes components easier to understand. TypeScript provides static type checking, while PropTypes offers runtime validation. """tsx // TypeScript Example interface ButtonProps { children: string; onClick: () => void; disabled?: boolean; // Optional prop } function Button(props: ButtonProps) { return ( <button onClick={props.onClick} disabled={props.disabled}> {props.children} </button> ); } // PropTypes Example (less common with TypeScript, but still valid) import PropTypes from 'prop-types'; function LegacyButton({ children, onClick }) { return <button onClick={onClick}>{children}</button>; } LegacyButton.propTypes = { children: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, }; // Usage: function App() { const handleClick = () => { alert("Button clicked!"); }; return ( <Button onClick={handleClick} children="Click Me" /> ); } """ ### 2.2. Standard: Controlled vs. Uncontrolled Components **Do This:** Choose between controlled and uncontrolled components based on your use case. Consider using controlled components when you need fine-grained control over input values and validation. Use uncontrolled components for simpler cases. **Don't Do This:** Mix controlled and uncontrolled patterns within the same component without a clear reason. **Why:** Controlled components provide a single source of truth for input values, making it easier to implement validation and complex logic. Uncontrolled components can simplify development for basic forms. """jsx // Good: Controlled component import { createSignal } from 'solid-js'; function ControlledInput() { const [inputValue, setInputValue] = createSignal(''); const handleChange = (event) => { setInputValue(event.target.value); }; return ( <div> <input type="text" value={inputValue()} onChange={handleChange} /> <p>Value: {inputValue()}</p> </div> ); } // Good: Uncontrolled component function UncontrolledInput() { const handleSubmit = (event) => { event.preventDefault(); const value = event.target.elements.myInput.value; alert("Uncontrolled input value: ${value}"); }; return ( <form onSubmit={handleSubmit}> <input type="text" name="myInput" /> <button type="submit">Submit</button> </form> ); } """ ### 2.3. Standard: Handling Side Effects **Do This:** Use effects ("createEffect", "onMount", "onCleanup") to manage side effects such as data fetching, DOM manipulation, and subscriptions. **Don't Do This:** Perform side effects directly within the component's render function or outside effects. **Why:** Effects allow you to isolate and manage side effects, preventing unexpected behavior and improving performance. They also provide mechanisms for cleanup, which is essential for avoiding memory leaks. """jsx // Good: Using createEffect for data fetching import { createSignal, createEffect, onMount } from 'solid-js'; function DataFetcher() { const [data, setData] = createSignal(null); const [loading, setLoading] = createSignal(true); const fetchData = async () => { try { const response = await fetch('https://api.example.com/data'); const result = await response.json(); setData(result); } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } }; onMount(() => { fetchData(); }); return ( <div> {loading() ? <p>Loading...</p> : <pre>{JSON.stringify(data(), null, 2)}</pre>} </div> ); } // Using onCleanup to unsubscribe from an event import { createSignal, createEffect, onMount, onCleanup } from 'solid-js'; function EventListenerComponent() { const [count, setCount] = createSignal(0); onMount(() => { const increment = () => { setCount(prev => prev + 1); }; window.addEventListener('click', increment); onCleanup(() => { window.removeEventListener('click', increment); console.log('Event listener removed!'); }); }); return ( <div> <p>Count: {count()}</p> </div> ); } """ ### 2.4. Standard: Using Refs **Do This:** Employ refs ("createRef") to access DOM elements or component instances directly when necessary. **Don't Do This:** Overuse refs. Typically data should be managed with signals. Avoid direct DOM manipulation if the framework data binding can handle it. **Why:** Refs provide a way to interact with the underlying DOM or component instances, which can be useful for focus management, measuring elements, or integrating with third-party libraries. """jsx // Good: Using a ref for focus management import { createRef, onMount } from 'solid-js'; function FocusableInput() { const inputRef = createRef(); onMount(() => { inputRef.current.focus(); }); return <input type="text" ref={inputRef} />; } """ ### 2.5. Standard: Performance Optimization **Do This:** Utilize "memo" and "batch" to prevent unnecessary re-renders of components and efficiently update multiple signals. Understand memoization limitations and avoid over-optimization. **Don't Do This:** Neglect performance considerations, especially when dealing with large datasets or complex UI interactions. Wrap every component with "memo" without a good reason. **Why:** "memo" helps optimize performance by memoizing components and preventing re-renders when the props haven't changed. "batch" allows to change multiple signals in a single update, reducing the number of re-renders trigged. """jsx // Good: Using memo to prevent unnecessary re-renders import { createSignal, memo } from 'solid-js'; function DisplayValue({ value }) { console.log('DisplayValue rendered'); // Check when it renders return <p>Value: {value}</p>; } const MemoizedDisplayValue = memo(DisplayValue); function App() { const [count, setCount] = createSignal(0); return ( <div> <button onClick={() => setCount(count() + 1)}>Increment</button> <MemoizedDisplayValue value={count()} /> </div> ); } // Good: Using batch to update multiple signals. import { createSignal, batch } from 'solid-js'; function MultiUpdate() { const [firstName, setFirstName] = createSignal('John'); const [lastName, setLastName] = createSignal('Doe'); const updateName = () => { batch(() => { setFirstName('Jane'); setLastName('Smith'); }); console.log('Signals updated'); }; return ( <div> <p>First Name: {firstName()}</p> <p>Last Name: {lastName()}</p> <button onClick={updateName}>Update Name</button> </div> ); } """ ### 2.6. Standard: Context API **Do This:** Use the Context API ("createContext", "useContext") for sharing data between components without prop drilling. **Don't Do This:** Overuse Context API for all data management. It is better for app-wide configuration or shared state that many components need. Avoid using context for passing props down a single branch of components. **Why:** Context API simplifies data sharing in complex component trees, improving code readability and maintainability. """jsx // Good: Using Context API import { createContext, useContext, createSignal } from 'solid-js'; // Create a context const ThemeContext = createContext({ theme: 'light', setTheme: () => {} }); // Create a provider component function ThemeProvider({ children }) { const [theme, setTheme] = createSignal('light'); const value = { theme: theme(), setTheme: setTheme }; return ( <ThemeContext.Provider value={value}> {children} </ThemeContext.Provider> ); } // Create a consumer component function ThemedComponent() { const themeContext = useContext(ThemeContext); const toggleTheme = () => { themeContext.setTheme(themeContext.theme === 'light' ? 'dark' : 'light'); }; return ( <div style={{ background: themeContext.theme === 'light' ? '#fff' : '#333', color: themeContext.theme === 'light' ? '#000' : '#fff' }}> <p>Current theme: {themeContext.theme}</p> <button onClick={toggleTheme}>Toggle Theme</button> </div> ); } // Usage in App root: function App() { return ( <ThemeProvider> <ThemedComponent /> </ThemeProvider> ); } """ ### 2.7. Standard: Fragments **Do This:** Use Fragments ("<>...</>") to group multiple elements without adding an extra DOM node. **Don't Do This:** Wrap components in unnecessary "<div>" elements just to satisfy the requirement of returning a single root element. **Why:** Fragments prevent the creation of extra DOM nodes, which can improve performance and simplify the DOM structure. """jsx // Good: Using Fragments function MyComponent() { return ( <> <h1>Title</h1> <p>Content</p> </> ); } // Bad: Unnecessary div function MyComponentBad() { return ( <div> <h1>Title</h1> <p>Content</p> </div> ); } """ ## 3. Component Testing ### 3.1. Standard: Unit Testing **Do This:** Write unit tests for individual components to verify their functionality and ensure they behave as expected. Use a testing library like Jest or Vitest with Solid Testing Library. **Don't Do This:** Skip unit tests, especially for complex or critical components, as this increases the risk of regressions and makes it harder to refactor code. **Why:** Unit tests provide a safety net for your code, making it easier to identify and fix bugs early in the development process. They also serve as documentation for how components are intended to be used. """javascript // Example Unit Test - using Vitest & solid-testing-library import { render, screen } from 'solid-testing-library'; import { createSignal } from 'solid-js'; import { describe, it, expect } from 'vitest'; function Counter() { const [count, setCount] = createSignal(0); return ( <div> <p>Count: {count()}</p> <button onClick={() => setCount(count() + 1)}>Increment</button> </div> ); } describe('Counter Component', () => { it('should increment the count when the button is clicked', async () => { render(() => <Counter />); const incrementButton = screen.getByText('Increment'); expect(screen.getByText('Count: 0')).toBeInTheDocument(); await incrementButton.click(); expect(screen.getByText('Count: 1')).toBeInTheDocument(); }); }); """ ### 3.2. Standard: Integration Testing **Do This:** Write integration tests to verify that components work correctly together and that the application behaves as expected. **Don't Do This:** Rely solely on unit tests, as they may not catch issues that arise from interactions between components. **Why:** Integration tests ensure that different parts of the application work together correctly, reducing the risk of integration issues. ### 3.3. Standard: End-to-End Testing **Do This:** Write end-to-end (E2E) tests to verify the entire application flow, from user interaction to data persistence. Use a tool like Cypress or Playwright. **Don't Do This:** Neglect E2E tests, as they provide the most comprehensive coverage and ensure that the application works correctly in a real-world environment. **Why:** E2E tests simulate real user behavior, ensuring that the application works correctly from start to finish. They help catch issues that may not be apparent from unit or integration tests. ## 4. Styling Components ### 4.1. Standard: CSS Modules or Styled Components **Do This:** Use CSS Modules or Styled Components to scope styles to individual components, preventing naming collisions and improving code maintainability. **Don't Do This:** Use global CSS classes without a clear naming convention, as this can lead to style conflicts and make it harder to refactor code. **Why:** CSS Modules and Styled Components provide a way to encapsulate styles within components, preventing naming collisions and improving code organization. """jsx // Good: CSS Modules import styles from './MyComponent.module.css'; function MyComponent() { return ( <div className={styles.container}> <h1 className={styles.title}>Title</h1> <p className={styles.content}>Content</p> </div> ); } """ """jsx // Example: Styled Components import styled from 'styled-components'; const StyledContainer = styled.div" background-color: #f0f0f0; padding: 20px; border-radius: 5px; "; const StyledTitle = styled.h1" color: #333; font-size: 24px; "; function MyComponent() { return ( <StyledContainer> <StyledTitle>Hello, Styled Components!</StyledTitle> <p>This is a styled component example.</p> </StyledContainer> ); } """ ### 4.2. Standard: BEM Naming Convention (if using Standard CSS) **Do This:** If using standard CSS, follow the BEM (Block, Element, Modifier) naming convention to create clear and maintainable CSS classes. **Don't Do This:** Use ambiguous or inconsistent CSS class names, as this can make it harder to understand and maintain the styles. **Why:** BEM provides a clear and consistent naming convention for CSS classes, improving code readability and maintainability. """css /* Good: BEM Naming */ .block {} .block__element {} .block__element--modifier {} /* Bad: Ambiguous Naming */ .title {} .content {} """ ## 5. Accessibility ### 5.1. Standard: ARIA Attributes **Do This:** Use ARIA attributes to provide semantic information to assistive technologies, making the application more accessible to users with disabilities. **Don't Do This:** Neglect accessibility considerations, as this can exclude users with disabilities from using the application. **Why:** ARIA attributes provide a way to enhance the accessibility of web applications, making them more usable for people with disabilities. """jsx // Good: Using ARIA attributes function AccessibleButton({ onClick, children }) { return ( <button onClick={onClick} aria-label="Click me"> {children} </button> ); } """ ### 5.2. Standard: Semantic HTML **Do This:** Use semantic HTML elements to structure the application, providing meaning to the content and improving accessibility. **Don't Do This:** Use generic "<div>" or "<span>" elements for all content, as this can make the application harder to understand and navigate. **Why:** Semantic HTML elements provide meaning to the content, making it easier for assistive technologies and search engines to understand the structure of the application. """html <!-- Good: Semantic HTML --> <nav> <ul> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> </ul> </nav> <article> <h1>Title</h1> <p>Content</p> </article> """ ## 6. Solid.js Specific Best Practices These are component design considerations that leverage Solid.js' strengths: ### 6.1. Standard: Granular Updates with Signals **Do This:** Utilize the fine-grained reactivity of Solid.js by ensuring signals are the only sources of state within components. This enables precise DOM updates. **Don't Do This:** Mutate data structures directly, or use approaches that force broad component re-renders, negating Solid's performance advantages. **Why:** Solid.js is incredibly efficient because it only updates the specific parts of the DOM that have changed. By relying on signals, you tap into this reactivity system optimally. """jsx import { createSignal } from "solid-js"; function MyComponent() { const [name, setName] = createSignal("Initial Name"); return ( <div> <p>Name: {name()}</p> <button onClick={() => setName("New Name")}>Change Name</button> </div> ); } """ ### 6.2. Standard: Avoid Unnecessary Abstraction **Do This:** Keep components simple and focused, avoiding over-engineering abstractions too early. Solid.js excels at direct DOM manipulation and optimized updates. **Don't Do This:** Force complex compositional patterns if they don't translate to benefits in performance or maintainability. Simplicity can be a virtue in Solid.js. **Why:** Solid.js already handles many performance concerns under the hood. Overly complex component structures can sometimes hinder, rather than help. ### 6.3 Standard: Use Solid's Control Flow Components for Rendering Lists and Conditionals **Do This:** Use Solid's "<Show>", "<For>", and "<Switch>" components rather than relying on .map() and ternary operators for conditional rendering, as they hook into the reactivity system more efficiently. **Don't Do This:** Fall back upon array methods and ternary operators for conditionals unless you're sure there will be few state changes and updates to these areas. **Why:** Solid's control flow components optimize rendering and re-rendering compared to more generic JavaScript constructs """jsx import { createSignal, For, Show } from "solid-js"; function ListComponent() { const [items, setItems] = createSignal(["Item 1", "Item 2", "Item 3"]); const [showList, setShowList] = createSignal(true); return ( <> <Show when={showList()}> <ul> <For each={items()}>{(item) => <li>{item}</li>}</For> </ul> </Show> <button onClick={() => setShowList(!showList())}>Toggle List</button> </> ); } """ These standards aim to provide a comprehensive guide to building well-structured, maintainable, and performant Solid.js components, taking advantage of the framework's unique reactive model and emphasizing best practices for modern web development.
# Performance Optimization Standards for Solid.js This document outlines coding standards for optimizing performance in Solid.js applications. It provides guidelines, best practices, and code examples to help developers build fast, responsive, and efficient applications utilizing the benefits of Solid.js reactivity at its best. ## 1. Leveraging Solid's Reactivity ### 1.1 Fine-Grained Reactivity **Standard:** Utilize Solid's fine-grained reactivity system to minimize unnecessary re-renders and computations. **Why:** Solid.js uses signals, derived signals (memos), and effects to automatically track dependencies and update only the components that need to be updated. This approach drastically reduces wasted computation time compared to virtual DOM frameworks that always re-render the template starting from a large component and diff the resulting DOM. **Do This:** * Favor signals for managing state that changes over time. * Use memos to derive computed values from signals efficiently. * Use effects to perform side effects that react to signal changes. * Structure components to isolate reactive updates to the smallest possible DOM nodes. **Don't Do This:** * Rely on global state management libraries that bypass Solid's reactivity. * Over-use effects for general computations better suited for memos. * Manually manipulate the DOM – let Solid.js handle updates. **Code Example:** """jsx import { createSignal, createMemo, createEffect } from 'solid-js'; import { render } from 'solid-js/web'; function MyComponent() { const [count, setCount] = createSignal(0); const doubledCount = createMemo(() => count() * 2); createEffect(() => { console.log('Count changed:', count()); }); return ( <div> <p>Count: {count()}</p> <p>Doubled Count: {doubledCount()}</p> <button onClick={() => setCount(count() + 1)}>Increment</button> </div> ); } render(() => <MyComponent />, document.getElementById('root')); """ In this example, only the text nodes displaying "count" and "doubledCount" will update when "count" changes. The effect only runs when "count" updates ensuring only necessary side effects occur. ### 1.2 Avoid Unnecessary Global State Updates **Standard:** Only trigger signal updates when new values differ from the previous ones, especially in global stores. **Why:** Updating signals unnecessarily can lead to useless re-renders and effect triggers, even if the resulting DOM remains the same. Solid will prevent rerunning effects when values haven't changed, but reducing signal updates saves on the equality check. **Do This:** * Implement a shallow comparison before updating signals with potentially unchanged values, particularly in complex objects. * Use the "setState" pattern introduced in Solid.js to merge updates, avoiding replacing the entire state object in global stores. * Structure global state to minimize dependencies between different parts of the application. **Don't Do This:** * Blindly update global signals without checking for changes. * Rely on deeply nested global state structures that propagate updates across unrelated components. **Code Example:** """jsx import { createSignal } from 'solid-js'; const [user, setUser] = createSignal({ id: 1, name: 'Initial User', address: { city: 'Some City' } }); function updateUserCity(newCity) { setUser(prevUser => { if (prevUser.address.city === newCity) { return prevUser; // Avoid unnecessary signal update } return { ...prevUser, address: { ...prevUser.address, city: newCity } }; }); } //Later, on a button handler <button onClick={() => updateUserCity('New City')}>Update City</button> """ This example updates only the "city" property of the "address" object within the "user" signal *if* the city is different. It carefully utilizes the "setUser" updater function to avoid unnecessary re-renders. ### 1.3 Memoization vs. Derived Signals **Standard:** Understand the distinction between memoization ("createMemo") and derived signals, and choose the appropriate approach. **Why:** "createMemo" caches the result of a computation and only re-executes it when its dependencies change. This is perfect for derived values that are consumed by the UI. A derived signal is returned directly from the source signal. Using the right tool for the right job contributes to performance. **Do This:** * Use "createMemo" for UI-bound, costly computations based on signals. Especially computations that trigger DOM updates. * Favor derived signals for internal computations or temporary values in functions. * Review and optimize existing memos if your application is underperforming. **Don't Do This:** * Overuse "createMemo" for cheap computations (direct signal access is usually faster). * Neglect to update memos when their underlying dependencies change (causing stale data). * Chain several memos together, creating long dependency chains. **Code Example:** """jsx import { createSignal, createMemo } from 'solid-js'; function ComplexComponent() { const [input, setInput] = createSignal(''); const processedInput = createMemo(() => { console.log("Processing input (expensive op)"); let temp = input().toUpperCase(); // Simulate expensive string processing operation for (let i = 0; i < 1000; i++) { temp += i.toString(); } return temp; }); return ( <div> <input type="text" value={input()} onInput={(e) => setInput(e.currentTarget.value)} /> <p>Processed Input: {processedInput()}</p> </div> ); } export default ComplexComponent; """ In this example, the "processedInput" memo ensures that the expensive string processing is only executed when the "input" signal actually changes, preventing unnecessary re-computations. ## 2. Component Design Patterns for Performance ### 2.1 List Virtualization **Standard:** Implement list virtualization for rendering large lists of data. **Why:** Rendering a very long list can be slow because of the high number of elements that need to be created and rendered in the beginning. List virtualization only renders the part of the list that's currently visible and renders more elements as the user scrolls. **Do This:** * Use libraries like "solid-virtual" for efficient list virtualization. * Calculate the height of each list item accurately for accurate virtualization calculations. * Implement placeholders or loading indicators for items that are not yet rendered. **Don't Do This:** * Render thousands of items at once without virtualization. * Use imprecise or variable item heights, causing rendering glitches. **Code Example:** """jsx import { createSignal } from 'solid-js'; import { render } from 'solid-js/web'; import { VirtualList } from 'solid-virtual'; function VirtualizedList() { const [items, setItems] = createSignal(Array.from({ length: 1000 }, (_, i) => "Item ${i}")); return ( <VirtualList items={items()} itemSize={50} // Estimated height of each item > {(item) => ( <div style={{ height: '50px', borderBottom: '1px solid #ccc' }}>{item}</div> )} </VirtualList> ); } render(() => <VirtualizedList />, document.getElementById('root')); """ This example utilizes the "solid-virtual" library to efficiently render a long list of items with a fixed height, only rendering visible elements. ### 2.2 Code Splitting and Lazy Loading **Standard:** Implement code splitting to reduce initial load time and lazily load components as needed. **Why:** Splitting your code into smaller chunks allows the browser to download and parse only the code required for the initial view, improving the time to interactive. Lazy loading defers the loading of non-critical components until they are actually needed, further reducing the initial payload. **Do This:** * Utilize Solid's "lazy" function for dynamic imports of components. * Group related components into logical bundles for splitting. * Use suspense to display a loading indicator while lazy components are loading. **Don't Do This:** * Put all your code into a single, massive bundle. * Lazily load components that are essential for the initial view. * Neglect to provide a suspense fallback, causing a blank screen while loading. **Code Example:** """jsx import { lazy, Suspense } from 'solid-js'; import { Route, Routes } from '@solidjs/router'; const Home = lazy(() => import('./Home')); const About = lazy(() => import('./About')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> ); } export default App; """ This example shows how to use "lazy" along with "<Suspense>" from Solid.js to lazy load components when requested inside the "Routes" component. Using a suspense fallback will handle loading states of each lazy-loaded component. ### 2.3 Pre-rendering & Server-Side Rendering (SSR) **Standard:** Consider pre-rendering or SSR for improved initial performance and SEO. **Why:** Pre-rendering generates static HTML at build time, which can be served immediately to the user, reducing the time to first contentful paint (FCP) and improving SEO. SSR renders the application on the server for the first request, providing similar benefits to pre-rendering but allowing for more dynamic content. **Do This:** * Use a framework like Solid Start for SSR and static site generation. * Analyze the needs of your application to determine if pre-rendering or SSR is the most suitable approach. * Implement proper caching strategies for SSR to minimize server load. **Don't Do This:** * Use SSR without proper caching, causing excessive server load. * Use SSR for purely static content that would be better served by pre-rendering. * Overcomplicate SSR with unnecessary features or dependencies. **Code Example (Solid Start):** Solid Start automatically handles routing, SSR and static site generation. """jsx // Filename: src/routes/index.tsx import { createSignal } from "solid-js"; export default function Index() { const [count, setCount] = createSignal(0); return ( <section> <h1>Hello world!</h1> <p> <button onClick={() => setCount(count() + 1)}> {count()} </button> </p> </section> ); } """ ## 3. Data Fetching Optimization ### 3.1 Caching Data Requests **Standard:** Implement client-side caching for data requests. **Why:** Repeatedly fetching the same data from an API is inefficient. Caching data on the client can significantly reduce network requests and improve perceived performance, specially for data rarely modified. **Do This:** * Use a simple in-memory cache for short-lived data. * Use "localStorage" or "sessionStorage" for persistent caching (use with caution). * Use a dedicated caching library like "swr" or implement your own caching mechanism using signals and memos. * Employ cache invalidation strategies to update cached data when necessary. **Don't Do This:** * Cache sensitive data in "localStorage" without proper encryption. * Cache data indefinitely without invalidation, causing stale data to be displayed. * Rely solely on browser caching without implementing a client-side caching layer. **Code Example:** """jsx import { createSignal, createEffect } from 'solid-js'; function useCachedData(url, fetcher) { const [data, setData] = createSignal(null); const [isLoading, setIsLoading] = createSignal(false); const [error, setError] = createSignal(null); const cache = new Map(); // Simple in-memory cache createEffect(() => { async function fetchData() { setIsLoading(true); try { if (cache.has(url)) { console.log('Data from cache'); setData(cache.get(url)); } else { const result = await fetcher(url); cache.set(url, result); setData(result); console.log('Data from API'); } } catch (e) { setError(e); } finally { setIsLoading(false); } } fetchData(); }); return { data, isLoading, error }; } async function myFetcher(url) { const response = await fetch(url); //Example return response.json(); } // Usage function MyComponent() { const { data, isLoading, error } = useCachedData('https://api.example.com/data', myFetcher); if (isLoading()) return <p>Loading...</p>; if (error()) return <p>Error: {error().message}</p>; if (!data()) return <p>No data available.</p>; return ( <div> {/* Render your data here */} <pre>{JSON.stringify(data(), null, 2)}</pre> </div> ); } """ This example shows a basic "useCachedData" hook that fetches data from an API and caches it in memory. If the data is already cached, it returns the cached value instead of making a new API request. ### 3.2 Request Prioritization **Standard:** Prioritize data requests to improve perceived performance. **Why:** Fetching critical data first allows the user to see meaningful content sooner. **Do This:** * Use techniques like "Promise.all" to fetch non-critical data in parallel. * Defer fetching of low-priority data until after the initial view is rendered. * Use "fetchPriority" attribute to instruct the browser to prioritize loading of specific network resources. **Don't Do This:** * Block the rendering of the initial view while waiting for all data to load. * Unnecessarily chain requests, creating a waterfall effect. * Ignore the priority of different data requests. **Code Example:** """jsx import { createSignal, createEffect } from 'solid-js'; function prioritizeFetch() { const [primaryData, setPrimaryData] = createSignal(null); const [secondaryData, setSecondaryData] = createSignal(null); createEffect(() => { async function fetchPrimary() { const response = await fetch('/api/primary'); setPrimaryData(await response.json()); } async function fetchSecondary() { const response = await fetch('/api/secondary'); setSecondaryData(await response.json()); } // Start fetching primary data immediately fetchPrimary(); // Fetch secondary data after the component has rendered (using setTimeout 0) setTimeout(fetchSecondary, 0); }); return { primaryData, secondaryData }; } function MyComponent() { const { primaryData, secondaryData } = prioritizeFetch(); return ( <div> <h1>{primaryData()?.title || 'Loading...'}</h1> <p>{secondaryData()?.description || 'Loading more content...'}</p> </div> ); } """ In this example, the primary data is fetched immediately, while the secondary data is fetched after a small delay using "setTimeout(...,0)". This will render primary data sooner for better a user experience. ### 3.3 Batching API Requests **Standard:** Combine multiple API requests into a single request when possible. **Why:** Reducing the number of network requests can significantly improve performance, especially in scenarios where multiple small pieces of data are needed. **Do This:** * Use GraphQL or similar technologies to fetch multiple related data points in a single query. * Implement batch endpoints on your backend to handle multiple requests in a single API call. * Use libraries like "axios" to group fetch requests. **Don't Do This:** * Make an excessive number of small API requests when a single batched request would suffice. * Over-complicate batching logic with unnecessary complexity. * Send unnecessarily large payloads in batched requests. ## 4. DOM Manipulation ### 4.1 Efficient DOM Updates in Effects **Standard:** Ensure effect blocks perform minimal DOM manipulations. **Why:** While Solid is efficient, DOM manipulation is still relatively slow. Minimize the scope of necessary operations to improve the overall responsiveness. **Do This:** * Update only the specific DOM nodes that need to be changed within an effect. * Avoid unnecessary DOM reads, as they can trigger layout thrashing. * Throttle or debounce effects that trigger frequently to avoid overloading the browser. Use utility libraries like "lodash" that support throttling and debouncing to avoid re-implementing and testing those solutions. **Don't Do This:** * Rely on effects to manipulate large portions of the DOM unnecessarily. * Perform synchronous DOM reads and writes within the same effect, causing layout thrashing. * Trigger effects at excessively high frequencies. **Code Example:** """jsx import { createSignal, createEffect } from 'solid-js'; function EfficientDOMUpdates() { const [text, setText] = createSignal('Initial Text'); createEffect(() => { const element = document.getElementById('my-element'); // Get the element ONCE, outside the update cycle if (element) { element.textContent = text(); // Efficiently updates the text content } }); return ( <div> <p id="my-element">{text()}</p> <input type="text" value={text()} onInput={(e) => setText(e.currentTarget.value)} /> </div> ); } """ This example retrieves the DOM element only once, then updates its text content directly within the effect, avoiding unnecessary re-renders and DOM manipulations. The element retrieval can be done only once since the element exists on initial render. ### 4.2 Avoiding DOM thrashing **Standard:** Minimize synchronous DOM reads immediately followed by DOM writes to avoid layout thrashing. **Why:** When the browser is forced to recalculate styles and layout due to interleaved reads and writes, performance degrades significantly. **Do this:** * Batch your DOM reads and writes. First performs all the reads needed to base your logic on and then proceeds with all writes. * Use "requestAnimationFrame" API to read and write to DOM. **Don't Do This:** * Mix synchronous DOM reads and writes in your logic. **Code Example:** """jsx import { createSignal, createEffect } from 'solid-js'; function AvoidDOMThrashing() { const [width, setWidth] = createSignal('200'); createEffect(() => { requestAnimationFrame(() => { // All reads here... const element = document.getElementById('thrashing-element'); // Get the element ONCE, outside the update cycle if (element) { const elementWidth = element.offsetWidth; console.log("Element width: ${elementWidth}"); // All writes here... element.style.width = "${width()}px"; // Synchronously sets the element width } }); }); return ( <div> <p id="thrashing-element" style={{width: "200px", height: "50px", backgroundColor: "red"}}></p> <input type="number" value={width()} onInput={(e) => setWidth(e.currentTarget.value)} /> </div> ); } """ ### 4.3 Template Literal Optimizations **Standard:** Use template literals or tagged template literals efficiently to construct dynamic strings or HTML. **Why:** Building strings for DOM manipulation can be slow if performed incorrectly. Template literals are generally more efficient than string concatenation, but tagged template literals offer more advanced optimization possibilities. **Do This:** * Use template literals for simple string interpolation. * Use tagged template literals with memoization to cache the generated DOM structure for repeated usage. * Minimize string operations within template literals. **Don't Do This:** * Rely on expensive string concatenation operations for dynamic content. * Overuse tagged template literals for simple string interpolations. **Code Example:** """jsx import { createSignal, createMemo } from 'solid-js'; function TemplateOptimizations() { const [name, setName] = createSignal('World'); // Basic template literal const greeting = "Hello, ${name()}!"; return ( <div> <p>{greeting}</p> <input type="text" value={name()} onInput={(e) => setName(e.currentTarget.value)} /> </div> ); } """ ## 5. Memory Management ### 5.1 Properly Disposing of Resources **Standard:** Dispose of resources (e.g., timers, event listeners, subscriptions) when they are no longer needed. **Why:** Failure to dispose of resources can lead to memory leaks, negatively impacting performance and potentially crashing the application in the long run. **Do This:** * Use the "onCleanup" hook to dispose of resources when a component unmounts. * Clear timers using "clearInterval" or "clearTimeout". * Unsubscribe from event listeners using "removeEventListener". * Dispose of subscriptions to external data sources. **Don't Do This:** * Ignore the cleanup of resources, relying on garbage collection to handle everything. * Create global resources without proper cleanup mechanisms. * Leak resources across component unmounts. **Code Example:** """jsx import { createSignal, createEffect, onCleanup } from 'solid-js'; function ResourceCleanup() { const [count, setCount] = createSignal(0); createEffect(() => { const intervalId = setInterval(() => { setCount(prev => prev + 1); }, 1000); onCleanup(() => { clearInterval(intervalId); console.log('Interval cleared.'); }); }); return ( <div> <p>Count: {count()}</p> </div> ); } """ In this example, the "clearInterval" call ensures that the interval is cleared when the component unmounts, preventing a memory leak. ### 5.2 Managing Large Data Structures **Standard:** Efficiently manage large data structures to minimize memory consumption. **Why:** Loading and processing large datasets can quickly consume memory, leading to performance issues and browser crashes. **Do This:** * Use techniques like pagination or infinite scrolling to load data in chunks. * Utilize data structures that are optimized for memory efficiency (e.g., Typed Arrays). * Consider using a database or specialized data storage solution for extremely large datasets. **Don't Do This:** * Load entire datasets into memory at once. * Store large data structures in global variables without proper management. * Use inefficient data structures for large datasets. ## 6. Tooling and Auditing ### 6.1 Profiling with Solid Devtools **Standard:** Utilize the Solid Devtools to profile your application and identify performance bottlenecks. **Why:** The Solid Devtools provide valuable insights into the reactivity graph, component rendering, and overall performance of your application, helping you identify areas for improvement. **Do This:** * Install the Solid Devtools browser extension. * Use the profiler to record and analyze component rendering times. * Examine the reactivity graph to understand the data flow. * Identify and optimize expensive computations or unnecessary re-renders. **Don't Do This:** * Ignore the insights provided by the Solid Devtools. * Rely solely on guesswork when diagnosing performance problems. * Neglect to profile your application before deploying to production. ### 6.2 Lighthouse Audits **Standard:** Regularly run Lighthouse audits to assess the performance of your application. **Why:** Lighthouse provides a comprehensive set of performance metrics and recommendations for improvement, covering areas such as loading speed, accessibility, and SEO. **Do This:** * Run Lighthouse audits in Chrome Devtools or using the command-line tool. * Address the recommendations provided by Lighthouse to improve overall performance. * Pay attention to key metrics such as First Contentful Paint (FCP), Largest Contentful Paint (LCP), and Time to Interactive (TTI). **Don't Do This:** * Ignore the warnings and errors reported by Lighthouse. * Focus solely on achieving a perfect Lighthouse score without considering the actual user experience. * Neglect to run Lighthouse audits after making significant changes to your application. ### 6.3 Performance Budgeting **Standard:** Define and enforce a performance budget for your application. **Why:** A performance budget sets clear targets for key performance metrics, helping you maintain a consistently fast and responsive application throughout the development lifecycle. **Do This:** * Define performance budgets for metrics such as page load time, bundle size, and time to interactive. * Use tools like "Bundle Analyzer" and "Lighthouse" to track your progress against the budget. * Integrate performance checks into your CI/CD pipeline to prevent regressions. **Don't Do This:** * Set unrealistic or overly ambitious performance budgets. * Ignore performance budgets once they have been defined. * Fail to enforce performance budgets throughout the development process. By adhering to these standards, developers can build high-performance Solid.js applications that provide a superior user experience while minimizing resource consumption.