# Code Style and Conventions Standards for Solid.js
This document outlines the coding style and conventions standards for Solid.js projects. Adhering to these guidelines will improve code readability, maintainability, consistency, and overall project quality. It's designed to be consumed by developers and used to guide AI coding assistants.
## 1. General Formatting
### 1.1. Indentation
**Do This:**
* Use 2 spaces for indentation.
* Configure your editor to automatically convert tabs to spaces.
**Don't Do This:**
* Use tabs for indentation.
* Use a different number of spaces for indentation (e.g., 4 spaces).
**Why:** Consistent indentation improves readability and reduces the likelihood of syntax errors. 2-space indentation is commonly preferred for its balance between conciseness and visual clarity.
**Example:**
"""javascript
// Correct indentation
function MyComponent() {
const [count, setCount] = createSignal(0);
return (
<p>Count: {count()}</p>
setCount(count() + 1)}>Increment
);
}
// Incorrect indentation
function MyComponent() {
const [count, setCount] = createSignal(0);
return (
<p>Count: {count()}</p>
setCount(count() + 1)}>Increment
);
}
"""
### 1.2. Line Length
**Do This:**
* Aim for a maximum line length of 120 characters.
* Break long lines after operators or commas to improve readability.
**Don't Do This:**
* Exceed the maximum line length without a clear reason, such as a URL.
* Break lines arbitrarily without considering readability.
**Why:** Limiting line length enhances readability, particularly on smaller screens and in code review tools.
**Example:**
"""javascript
// Correct line breaking
const veryLongVariableName = someFunction(
parameter1,
parameter2,
extremelyLongParameter3
);
// Incorrect line breaking
const veryLongVariableName =
someFunction(parameter1, parameter2, extremelyLongParameter3);
"""
### 1.3. Whitespace
**Do This:**
* Use a single space around operators (e.g., "=", "+", "-", "*", "/").
* Use a single space after commas in argument lists and objects.
* Use a blank line to separate logical blocks of code.
* Add a newline at the end of each file.
**Don't Do This:**
* Omit spaces around operators or commas.
* Use excessive blank lines.
**Why:** Consistent whitespace makes the code easier to scan and reduces visual clutter.
**Example:**
"""javascript
// Correct whitespace
const x = 1 + 2;
const obj = { a: 1, b: 2 };
function myFunction(a, b) {
return a + b;
}
// Incorrect whitespace
const x=1+2;
const obj={a:1,b:2};
function myFunction(a,b){
return a+b;
}
"""
### 1.4. Semicolons
**Do This:**
* Always use semicolons at the end of statements. While automatic semicolon insertion (ASI) exists, explicit semicolons prevent unexpected behavior and ensure consistency.
**Don't Do This:**
* Rely on ASI to insert semicolons automatically.
**Why:** Explicit semicolons make the code less ambiguous and improve reliability.
**Example:**
"""javascript
// Correct use of semicolons
const x = 1;
console.log(x);
// Incorrect use of semicolons (relying on ASI)
const x = 1
console.log(x) // Potential issues
"""
### 1.5. Quotes
**Do This:**
* Use single quotes ("'") for string literals unless the string contains single quotes, in which case use double quotes ("""). Use template literals (" "" ") for string interpolation.
**Don't Do This:**
* Inconsistently switch between single and double quotes.
**Why:** Consistency makes it easier to scan code. Template literals offer more flexibility for handling variables within strings.
**Example:**
"""javascript
// Correct quote usage
const name = 'John Doe';
const message = "He's a great developer.";
const greeting = "Hello, ${name}!";
// Incorrect quote usage
const name = "John Doe";
const message = 'He's a great developer.';
"""
## 2. Naming Conventions
### 2.1. Variables and Constants
**Do This:**
* Use "camelCase" for variable names.
* Use "PascalCase" for component names.
* Use "UPPER_SNAKE_CASE" for constants.
* Prefix boolean variables with "is", "has", or "should" (e.g., "isLoading", "hasError", "shouldUpdate").
* Use descriptive and meaningful names.
**Don't Do This:**
* Use single-letter variable names (except for loop counters).
* Use cryptic or ambiguous names.
* Use "snake_case" for variable names.
* Mutate constants.
**Why:** Clear naming improves code comprehension and reduces the risk of errors.
**Example:**
"""javascript
// Correct naming
const isLoading = createSignal(false);
const userName = 'John Doe';
const MAX_RETRIES = 3;
function MyComponent() {
// ...
}
// Incorrect naming
const a = createSignal(false);
const nm = 'John Doe';
const maxretries = 3;
function mycomponent() {
// ...
}
"""
### 2.2. Functions and Components
**Do This:**
* Use "camelCase" for function names.
* Use "PascalCase" for Solid.js components.
* Use verbs for function names (e.g., "getData", "calculateTotal").
* Name components based on their responsibility (e.g., "UserList", "ProductCard").
**Don't Do This:**
* Start component names with lowercase letters.
* Use vague or generic function names.
**Why:** Consistent naming helps to differentiate functions from components and understand their purpose at a glance.
**Example:**
"""javascript
// Correct naming
function calculateTotal(items) {
// ...
}
function UserList() {
// ...
}
// Incorrect naming
function calculatetotal(items) {
// ...
}
function userlist() {
// ...
}
"""
### 2.3. Props and Event Handlers
**Do This:**
* Use "camelCase" for props.
* Prefix event handler props with "on" (e.g., "onClick", "onChange").
* Pass props explicitly instead of relying on global state if possible.
**Don't Do This:**
* Use "snake_case" for props.
* Omit the "on" prefix for event handlers.
**Why:** Standardized naming promotes consistency and makes event handling more predictable.
**Example:**
"""javascript
// Correct naming
function MyButton({ onClick, label }) {
return {label};
}
// Incorrect naming
function MyButton({ onclick, button_label }) {
return {button_label};
}
"""
### 2.4. Signals, Stores, and Derived Values
**Do This:**
* Name Signals with clear, descriptive nouns representing the data they hold (e.g., "count", "userName", "productList").
* Understand that Signals are functions, and thus must be invoked to access their value (e.g., "count()", "userName()").
* Name Stores using the store paradigm, suggesting that they hold multiple related state values.
* Create Stores with the "createStore" function exposed by Solid, and be explicit about how they conform to the interface of the store.
* If derived values depend on other signals or stores, name them appropriately to describe this dependency, or their derived status.
**Don't Do This:**
* Use unclear abbreviations or generic names for Signals or Stores, as this reduces readability
* Mistake Signals for regular variables, as this often leads to confusion such as missed invocation.
* Mutate values in Stores directly, as this can lead to unexpected or un reactive behaviour
**Why:** Solid makes heavy use of state management features. Accurate naming promotes clarity and removes confusion about how data is stored and transferred in the application. Signals that are not invoked are a very common error for newcomers. Explicit Store interface helps ensure type safety for the data they hold.
**Example:**
"""javascript
// Correct naming of Solid data structures
const [count, setCount] = createSignal(0);
const COUNTER_MAX = 100;
function incrementCount(){
setCount(c => Math.min(c+1, COUNTER_MAX);)
}
const [user, setUser] = createStore({
firstName: "John",
lastName: "Doe",
age: 30,
});
const fullName = createMemo(() => "${user.firstName} ${user.lastName}");
// Incorrect naming of Solid data structures
const a = createSignal(0);
function incrementA(){
a = a + 1; // This does not work!
}
const [u, setU] = createStore({
f: "John",
l: "Doe",
a: 30,
});
"""
## 3. Component Structure and Organization
### 3.1. Component Size
**Do This:**
* Keep components small and focused on a single responsibility.
* Extract complex logic into separate functions or custom hooks.
* Break down large components into smaller, reusable components.
**Don't Do This:**
* Create massive components that are difficult to understand and maintain.
**Why:** Smaller components are easier to reason about, test, and reuse.
**Example:**
"""javascript
// Good: Separate concern-based components
function UserList({ users }) {
return (
{users.map(user => (
))}
);
}
function UserItem({ user }) {
return {user.name};
}
// Bad: Single large "God" component
function AllUsers({users}){
//A lot of logic here related to fetching the users, handling errors, rendering, etc
return(
//Complex JSX Structure
...
)
}
"""
### 3.2. Props and Context
**Do This:**
* Pass data down to child components using props.
* Use Context API for sharing data that is needed by many components deeper in the tree (e.g., theme, authentication). Use sparingly.
* Use destructuring for accessing props.
**Don't Do This:**
* Pass unnecessary props.
* Overuse Context.
* Mutate props directly within a component.
**Why:** Props ensure data flow is explicit and predictable. Context can simplify data sharing but should be used judiciously to avoid tight coupling.
**Example:**
"""javascript
// Correct prop usage
function UserProfile({ user }) {
return (
{user.name}
<p>{user.email}</p>
);
}
// Correct Context API use
const ThemeContext = createContext('light');
function App() {
return (
);
}
function MyComponent() {
const theme = useContext(ThemeContext);
return Content;
}
"""
### 3.3. Component Composition
**Do This:**
* Favor composition over inheritance. Compose components to create more complex UIs.
* Use slots (children props) to allow parent components to inject content into child components.
**Don't Do This:**
* Create deeply nested component hierarchies.
**Why:** Composition provides more flexibility and reusability compared to inheritance.
**Example:**
"""javascript
// Correct Composition
function Card({ children, title }) {
return (
{title}
{children}
);
}
function MyComponent() {
return (
<p>This is the card content.</p>
);
}
"""
### 3.4. SOLID Principles for Components
**Do This:**
* Adhere to the SOLID principles of object-oriented design where applicable to component design.
* Specifically, favor single responsibility principle to ensure each component has a clear, focused purpose.
**Don't Do This:**
* Create single components with multiple responsibilities and functionalities.
**Why:** Proper adherence to SOLID principles helps decouple components and avoid unmaintainable code patterns.
**Example:**
"""javascript
//Good: Each component has a clear purpose
function Card({ title, children }) {
return (
{title}
{children}
);
}
function CardContent({text}){
return(
<p>{text}</p>
);
}
function App(){
return(
);
}
//Bad: Components are too complex to understand
function BigCard({title, text}){
return(
{title}
<p>{text}</p>
);
}
"""
## 4. Specific Solid.js Considerations
### 4.1. Mutability and Reactivity
**Do This:**
* Use "createSignal" for simple reactive values. Signals provide getter/setter pairs with fine-grained dependency tracking.
* Use "createStore" for complex reactive objects. Stores provide nested reactivity with immutable updates.
* Use "createMemo" for derived values that depend on signals or stores. Memos cache the result and only update when the dependencies change.
* Avoid direct DOM manipulation. Let Solid.js handle the updates.
* Understand the difference between "Signal()" which returns the value of the Signal and just "Signal" which allows passing it to a child.
**Don't Do This:**
* Mutate signals or stores directly.
* Rely on side effects for updating the UI.
* Overuse "createEffect". Prefer "createMemo" for derived values.
* Manually update the DOM.
**Why:** Solid.js reactivity relies on fine-grained dependency tracking. Direct mutations can break reactivity and lead to unpredictable behavior.
**Example:**
"""javascript
// Correct reactivity
import { createSignal, createEffect } from 'solid-js';
function MyComponent() {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log('Count changed:', count());
});
return setCount(count()+1)}>Increment;
}
// Incorrect reactivity
import { createSignal, createEffect } from 'solid-js';
function MyComponent() {
let [count, setCount] = createSignal(0);
createEffect(() => {
console.log('Count changed:', count); // WRONG - "count" is a function!
});
return count++ ; setCount(count)}>Increment; //WRONG - Directly mutating count breaks reactivity.
}
"""
### 4.2. JSX and Fragments
**Do This:**
* Wrap multiple adjacent JSX elements in a fragment ("<>...") or a single parent element.
* Use short syntax for fragments ("<>...") instead of "...".
* Use array mapping carefully, consider that SolidJS only renders the differences between the rendered states!
"""JavaScript
// Good Style:
{myArray.map(item => )}
//Not good style (will re-render the entire list on changes)
{myArray.map(item => <> )}
"""
**Don't Do This:**
* Return multiple adjacent JSX elements without wrapping them in a fragment or parent element.
**Why:** JSX syntax requires a single root element. Fragments provide a way to group multiple elements without introducing an extra DOM node. Using keys when mapping aids reactivity.
**Example:**
"""javascript
// Correct fragment usage
function MyComponent() {
return (
<>
Title
<p>Content</p>
);
}
// Incorrect fragment usage
function MyComponent() {
return (
Title
<p>Content</p> // Error: Adjacent JSX elements must be wrapped in a parent element.
);
}
"""
### 4.3. Event Handling
**Do This:**
* Use arrow functions for inline event handlers to avoid binding issues.
* Pass event objects explicitly to event handlers.
* Typescript users should use accurate typing in their interfaces, e.g. onClick?: (e: MouseEvent) => void
**Don't Do This:**
* Use "this" inside event handlers.
* Prevent default behavior unnecessarily.
**Why:** Arrow functions lexically bind "this", which avoids confusion. Explicit event handling makes the intent clear. Preventing default behavior should only be done when truly necessary.
**Example:**
"""javascript
// Correct event handling
function MyButton({ onClick }) {
return onClick(e)}>Click Me;
}
// Incorrect event handling
function MyButton({ onClick }) {
return Click Me; //this binding issues are possible
}
"""
### 4.4. Asynchronous Operations
**Do This:**
* Use "async/await" for handling asynchronous operations.
* Implement try/catch blocks for error handling.
* Use Solid's resource system ("createResource") for data fetching.
**Don't Do This:**
* Use callbacks excessively.
* Ignore errors in asynchronous operations.
* Mutate state directly during asynchronous operations.
**Why:** "async/await" simplifies asynchronous code and reduces the risk of callback hell. Error handling is crucial for preventing unexpected behavior. "createResource" helps to manage state and handles loading and error states.
**Example:**
"""javascript
// Correct asynchronous operation
import { createResource } from 'solid-js';
async function fetchData() {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
return response.json();
}
function MyComponent() {
const [data, { mutate, refetch }] = createResource(fetchData);
return (
{data.loading ? <p>Loading...</p> : null}
{data.error ? <p>Error: {data.error.message}</p> : null}
{data() ? <pre>{JSON.stringify(data(), null, 2)}</pre> : null}
Refresh
);
}
// Incorrect asynchronous operation(bad) -> callback hell
import { createResource } from 'solid-js';
function MyComponent() {
const [data, { mutate, refetch }] = createResource(
() => {
fetch('https://api.example.com/data').then(response => {
response.json().then(json => {
//Do something with the Json, setState, etc.
})
});
});
return (
{data.loading ? <p>Loading...</p> : null}
{data.error ? <p>Error: {data.error.message}</p> : null}
{data() ? <pre>{JSON.stringify(data(), null, 2)}</pre> : null}
Refresh
);
}
"""
#### 4.5 DOM properties versus HTML attributes
**Do This:**
* Understand that HTML attributes initialize DOM properties, but they do not directly update in two-way binding in Solid.
* Use the "prop:" prefix in JSX when you need to bind directly to a DOM property instead of an attribute.
* Use correct casing for properties, understanding that unlike attributes.
* Consider using functions rather than strings for complex properties such as "classList".
**Don't Do This:**
* Mistake HTML attributes and DOM properties, assuming changes to one reflects in the other.
* Write to HTML attributes thinking the UI will update reactively.
* Forget that properties use camelCase (e.g., "htmlFor", "className") while attributes use kebab-case (e.g., "for", "class").
**Why:** Using DOM properties provides control and optimization, but they must be targeted with correct casing and syntax.
**Example:**
"""javascript
// Good: setting HTML properties on an element
function MyTextInput() {
return (
);
}
// Bad: setting the HTML attribute
function MyTextInput(){
//Attribute is only set once and does not change.
}
//Also fine
//Good: Setting the element's "classList" property with a function, making it reactive to changes
function ToggleGreenButton(props){
return(
{ return {red: props.isRed(), green : !props.isRed()}}}>
I am a button that can toggle red and green.
);
}
"""
## 5. Error Handling
### 5.1. Try/Catch Blocks
**Do This:**
* Wrap potentially failing code in "try/catch" blocks.
* Log errors to the console or an error-tracking service.
* Display user-friendly error messages.
**Don't Do This:**
* Ignore errors or swallow exceptions silently.
**Why:** Proper error handling is essential for preventing application crashes and providing a good user experience.
**Example:**
"""javascript
// Correct error handling
function MyComponent() {
try {
const data = fetchData();
return <pre>{JSON.stringify(data, null, 2)}</pre>;
} catch (error) {
console.error('Error fetching data:', error);
return <p>An error occurred while fetching data.</p>;
}
}
"""
### 5.2. Error Boundaries
**Do This:**
* Use error boundaries to catch errors in child components.
* Display a fallback UI when an error occurs.
**Don't Do This:**
* Let errors propagate to the root of the application and crash it.
**Why:** Error boundaries provide a way to gracefully handle errors that occur during rendering.
**Example:**
"""javascript
// Implement Error Boundary (note, this is a simplified example, a more robust error boundary solution is recommended)
import { createSignal } from 'solid-js';
function ErrorBoundary({ children, fallback }) {
const [hasError, setHasError] = createSignal(false);
return (
<>
{hasError() ? fallback : children}
);
}
function MyComponent() {
return (
An error occurred in this component.<p></p>}>
);
}
function ChildComponent() {
throw new Error('Something went wrong in the child component.');
}
"""
## 6. Testing
### 6.1. Unit Tests
**Do This:**
* Write unit tests for all components, functions, and hooks.
* Use a testing framework such as Jest or Vitest.
* Test different scenarios, including edge cases and error conditions.
* Aim for high code coverage.
**Don't Do This:**
* Skip testing for trivial components or functions.
* Write tests that are too tightly coupled to the implementation details.
**Why:** Unit tests ensure that the code works as expected and prevent regressions.
**Example:**
"""javascript
// Example using Jest
import { render, screen } from '@solidjs/testing-library';
import { createSignal } from 'solid-js';
import MyComponent from './MyComponent';
import '@testing-library/jest-dom';
describe('MyComponent', () => {
it('renders the correct count', () => {
const [count, setCount] = createSignal(0);
render(() => );
});
});
"""
### 6.2. Integration Tests
**Do This:**
* Write integration tests to verify the interaction between different components and services.
* Test data flow and state management.
**Don't Do This:**
* Rely solely on unit tests without integration tests.
**Why:** Integration tests ensure that the different parts of the application work together correctly.
## 7. Documentation
### 7.1. Code Comments
**Do This:**
* Write clear and concise comments to explain complex logic.
* Document the purpose of components, functions, and hooks.
* Use JSDoc-style comments for generating API documentation.
**Don't Do This:**
* Write obvious or redundant comments.
* Leave commented-out code in the codebase.
* Let comments become outdated.
**Why:** Code comments help other developers understand the code and maintain it more easily.
**Example:**
"""javascript
// Correct code comments
/**
* Calculates the total price of items in the cart.
* @param {Array} items - An array of items with price and quantity properties.
* @returns {number} The total price.
*/
function calculateTotal(items) {
// ...
}
"""
### 7.2. README Files
**Do This:**
* Include a README file in each project and component library.
* Describe the purpose, usage, and configuration of the project or library.
* Provide clear installation and usage instructions.
**Don't Do This:**
* Omit a README file.
* Write incomplete or outdated documentation.
**Why:** README files provide essential information for developers who are using or contributing to the project.
## 8. Security
### 8.1. Input Validation
**Do This:**
* Validate all user inputs to prevent injection attacks.
* Sanitize data before displaying it in the UI.
**Don't Do This:**
* Trust user inputs directly.
**Why:** Input validation prevents malicious code from being injected into the application.
### 8.2. Authentication and Authorization
**Do This:**
* Implement proper authentication and authorization mechanisms.
* Store passwords securely using hashing and salting.
* Avoid storing sensitive data in the client-side code.
**Don't Do This:**
* Rely on client-side validation for security.
* Store passwords in plain text.
**Why:** Authentication and authorization protect sensitive data and prevent unauthorized access to the application.
### 8.3. Dependency Management
**Do This:**
* Keep dependencies up to date.
* Regularly audit dependencies for security vulnerabilities.
* Use a dependency manager such as npm or yarn.
**Don't Do This:**
* Use outdated or vulnerable dependencies.
**Why:** Dependency management ensures that the application is protected against known vulnerabilities.
Adhering to these coding standards will help create a more maintainable, readable, and robust Solid.js codebase. Remember that these guidelines are a starting point and should be adapted to the specific needs of your project.
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.