# State Management Standards for Qwik
This document outlines the best practices for managing state in Qwik applications. Proper state management is crucial for building performant, maintainable, and scalable Qwik applications. It covers approaches to managing application state, controlling data flow, and leveraging Qwik's reactivity features. These standards are based on the latest Qwik features and recommendations.
## 1. Core Principles of State Management in Qwik
### 1.1. Location Strategy
**Standard:** Choose the appropriate scope and location for your state. State should live as close as possible to the components that need it, but elevated to a shared parent when multiple components need to access and update the same data. Aim for a balanced approach to avoid over-centralization or unnecessary prop drilling.
**Why:** Efficient state management in Qwik hinges on locality. Local state simplifies component logic and reduces the chances of unnecessary re-renders. Global state, while convenient, should be reserved for data truly needed across the entire application.
**Do This:**
* Use component-level state with "useStore()" when the state is only relevant to that component.
* Elevate state to a shared parent component when multiple child components need to share the state.
* Use global state sparingly, typically only for application-wide configurations or user authentication.
**Don't Do This:**
* Avoid excessive prop drilling, passing state down through multiple layers of components. This leads to tightly coupled and difficult-to-maintain code.
* Don't store component-specific state in a global store, causing unnecessary updates across the entire application.
* Abuse global state. Over-reliance on global state increases coupling and makes it hard to reason about data flow.
**Example (Component-Level):**
"""tsx
import { component$, useStore } from '@builder.io/qwik';
export const Counter = component$(() => {
const store = useStore({
count: 0,
increment: () => {
store.count++;
},
});
return (
<>
<p>Count: {store.count}</p>
Increment
);
});
"""
**Example (Shared Parent):**
"""tsx
import { component$, useStore, $, useContext, createContextId } from '@builder.io/qwik';
interface UserContextType {
username: string;
updateUsername: (newUsername: string) => void;
}
const UserContext = createContextId('user-context');
export const UserProvider = component$((props: { children: any }) => {
const store = useStore({
username: "Guest",
updateUsername: $((newUsername: string) => {
store.username = newUsername;
})
});
return (
{props.children}
);
});
const UsernameDisplay = component$(() => {
const user = useContext(UserContext);
return <p>Username: {user.username}</p>;
});
const UsernameUpdater = component$(() => {
const user = useContext(UserContext);
const handleInputChange = $((event: Event) => {
const inputElement = event.target as HTMLInputElement;
user.updateUsername(inputElement.value);
});
return (
);
});
export const App = component$(() => {
return (
);
});
"""
### 1.2. Immutability
**Standard:** Treat state as immutable whenever possible. Instead of modifying state directly, create new copies with the desired changes.
**Why:** Immutability simplifies debugging, enables efficient change detection, and prevents unexpected side effects. Qwik's reactivity system works best with immutable data.
**Do This:**
* Use the spread operator ("...") or "Object.assign()" to create new objects with updated properties.
* Use array methods like "map()", "filter()", and "slice()" to create new arrays instead of modifying them directly.
* Consider using immutable data structures from libraries like Immer for complex state updates.
**Don't Do This:**
* Directly modify properties of state objects (e.g., "state.property = newValue").
* Use array methods like "push()", "pop()", "splice()", or "sort()" that mutate the original array.
**Example (Immutable Update):**
"""tsx
import { component$, useStore } from '@builder.io/qwik';
interface Item {
id: number;
name: string;
completed: boolean;
}
export const ItemList = component$(() => {
const store = useStore<{ items: Item[] }>({
items: [{ id: 1, name: 'Learn Qwik', completed: false }],
});
const toggleComplete = $((id: number) => {
store.items = store.items.map((item) =>
item.id === id ? { ...item, completed: !item.completed } : item
);
});
return (
{store.items.map((item) => (
toggleComplete(item.id)}
/>
{item.name}
))}
);
});
"""
### 1.3. Serialization
**Standard:** Ensure that your state can be serialized. Qwik relies on serialization for resumability.
**Why:** Qwik serializes the state of your application to HTML, which is then used to "resume" the application on the client. Non-serializable state will be lost when the application is resumed, leading to unexpected behavior.
**Do This:**
* Store only primitive values (strings, numbers, booleans), plain objects, and arrays in your state.
* Avoid storing classes, functions, or instances of custom objects directly in the state.
* If you need to store complex data, serialize it to a string or a primitive value before storing it in the state.
**Don't Do This:**
* Store DOM elements or other non-serializable objects directly in the state.
* Store functions or closures in the state.
* Assume that anything you put into the state will survive a page reload without proper serialization.
**Example (Serialization):**
"""tsx
import { component$, useStore } from '@builder.io/qwik';
interface SerializableData {
name: string;
value: number;
}
export const DataComponent = component$(() => {
const store = useStore<{ data: string | null }>({
data: localStorage.getItem('myData') || null,
});
const updateData = $((newData: SerializableData) => {
const serializedData = JSON.stringify(newData);
store.data = serializedData;
localStorage.setItem('myData', serializedData);
});
return (
<>
updateData({ name: 'Example', value: 42 })}>
Update Data
<p>Data: {store.data}</p>
);
});
"""
## 2. Qwik's State Management Tools
### 2.1. "useStore()"
**Standard:** Utilize "useStore()" for managing component-level state.
**Why:** "useStore()" provides a simple and efficient way to create reactive state within a component. It automatically tracks changes to the state and triggers re-renders when necessary. It also supports serialization for resumability.
**Do This:**
* Use "useStore()" to create the initial state within a component.
* Update the state by modifying the properties of the store object.
* Use the "$()" function wrap code that modifies the store to ensure proper tracking of mutations.
**Don't Do This:**
* Try use other React hooks for state management. Qwik's model avoids the use of the virtual DOM, and therefore useState, useEffect etc. are incompatible.
* Forget to wrap mutation logic within "$()". This is critical for Qwik's reactivity.
**Example:**
"""tsx
import { component$, useStore, $ } from '@builder.io/qwik';
export const MyComponent = component$(() => {
const store = useStore({
name: 'Initial Name',
updateName: $((newName: string) => {
store.name = newName;
}),
});
return (
<>
<p>Name: {store.name}</p>
store.updateName('New Name')}>Update Name
);
});
"""
### 2.2. "$()" and Event Handlers
**Standard:** Utilize "$()" to wrap event handlers and functions that modify state and are not directly invoked in the JSX.
**Why:** "$()" tells Qwik to serialize and lazy load the wrapped code. This is essential for reducing the initial JavaScript payload and improving performance.
**Do This:**
* Wrap all event handlers with "$()" when they are not directly used in JSX.
* Wrap functions that are called from event handlers and modify state with "$()".
* Consider using "useClientEffect$" (and similar client-only hooks) to run code only on the client, if hydration is not immediately necessary.
**Don't Do This:**
* Forget to wrap event handlers with "$()", especially when they modify the state. This can lead to hydration issues and break resumability.
* Overuse "$()" for code that doesn't need to be serialized or lazy-loaded.
**Example:**
"""tsx
import { component$, useStore, $ } from '@builder.io/qwik';
export const MyComponent = component$(() => {
const store = useStore({
message: 'Initial Message',
updateMessage: $((newMessage: string) => {
store.message = newMessage;
}),
});
const handleClick = $(() => {
store.updateMessage('New Message from Click');
});
return (
<>
<p>Message: {store.message}</p>
Update Message
);
});
"""
### 2.3 "useContextProvider" and "useContext"
**Standard:** When state needs to be shared between components that are not direct parent/child, use Qwik's context API.
**Why:** Like React, the context API allows sharing reactive state throughout a component tree without prop drilling. Also, unlike React's "useContext", Qwik's "useContext" hook can be used server-side.
**Do This:**
* Create a context ID using "createContextId()".
* Use "useContextProvider()" to provide a value from a parent component, at the right level within your application.
* Use "useContext()" to consume data from the context in any descendent.
**Don't Do This:**
* Overuse context. When state can be simply passed using props, do so. Excessive context makes reasoning about the state within a component harder.
* Modify context values directly. Follow the principal of immutability to make debugging and reasoning about state easier.
**Example:**
"""tsx
// context.ts
import { createContextId } from '@builder.io/qwik';
export const MyContext = createContextId<{ value: string }>('my-context');
// component.tsx
import { component$, useContextProvider, useContext, useStore } from '@builder.io/qwik';
import { MyContext } from './context';
export const ParentComponent = component$((props: { children: any }) => {
// You can also pass existing server state into the context.
const store = useStore({ value: 'Server Name' });
useContextProvider(MyContext, store);
return props.children;
});
export const ChildComponent = component$(() => {
const myContext = useContext(MyContext);
return <p>Value from context: {myContext.value}</p>;
});
// App.tsx
export const App = component$(() => {
return ()
});
"""
## 3. Data Fetching and State
### 3.1. "useResource$()"
**Standard:** Utilize "useResource$()" for asynchronous data fetching.
**Why:** "useResource$()" integrates seamlessly with Qwik's resumability system and provides a reactive way to manage the loading and error states of data fetching operations on the server.
**Do This:**
* Use "useResource$()" to wrap asynchronous functions that fetch data.
* Access the data using the ".value" property of the resource.
* Handle loading and error states gracefully.
**Don't Do This:**
* Perform data fetching directly within components without using "useResource$()".
* Ignore error states, resulting in unhandled exceptions and a poor user experience.
**Example:**
"""tsx
import { component$, useResource$ } from '@builder.io/qwik';
export const DataFetcher = component$(() => {
const dataResource = useResource$(async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
return await response.json();
});
return (
<>
{dataResource.value ? (
<p>Data: {dataResource.value.title}</p>
) : dataResource.error ? (
<p>Error: {dataResource.error.message}</p>
) : (
<p>Loading...</p>
)}
);
});
"""
### 3.2. Caching Strategies
**Standard:** Implement appropriate caching strategies for fetched data.
**Why:** Caching reduces the number of API calls, improves performance, and reduces load on the server.
**Do This:**
* Use browser caching (e.g., "Cache-Control" headers) for static assets and API responses.
* Use a client-side caching library (e.g., "lru-cache") for frequently accessed data.
* Implement server-side caching using technologies like Redis or Memcached for data that is shared across multiple users.
* Incorporate stale-while-revalidate for near-instant loading with background updates.
**Don't Do This:**
* Cache sensitive data without proper security measures.
* Cache data indefinitely without a proper expiration strategy.
* Ignore cache invalidation, resulting in stale data being displayed to the user.
**Example (Client-Side Caching):**
While the client might directly implement with "localStorage" as shown previously.
"""typescript
import { cache } from '@builder.io/qwik';
const fetchData = cache(
async (url: string) => {
const res = await fetch(url);
return res.json();
},
{
maxAge: 60 * 60 * 1000, // 1 hour
}
);
export const MyComponent = component$(() => {
const dataResource = useResource$(async () => {
return await fetchData('https://jsonplaceholder.typicode.com/todos/1');
});
return (
<>
{dataResource.value ? (
<p>Data: {dataResource.value.title}</p>
) : dataResource.error ? (
<p>Error: {dataResource.error.message}</p>
) : (
<p>Loading...</p>
)}
);
});
"""
## 4. Global State Management
### 4.1. When to Use Global State
**Standard:** Use global state only when necessary.
**Why:** Global state increases the complexity of the application and makes it harder to reason about data flow. It should be reserved for data that is truly needed across the entire application.
**Do This:**
* Use global state for application-wide configuration settings.
* Use global state for user authentication status and user profile information.
* Consider using a global store for managing shared data that is accessed by multiple components.
* Qwik prefers the use of server data and "useContext" to manage dependencies.
**Don't Do This:**
* Use global state for component-specific data.
* Overuse global state, resulting in tight coupling and difficult-to-maintain code.
### 4.2. Global Context Injection
**Standard:** When global state is necessary, use Qwik's context to inject data into components.
**Why:** Context allows you to provide data to a subtree of components without having to manually pass props at every level. It provides server and client capabilities out of the box.
**Do This:**
* Define a context using "createContextId()".
* Provide the context value using "useContextProvider()" at the root of the application or at the appropriate level in the component tree.
* Consume the context value using "useContext()" in the components that need it.
**Don't Do This:**
* Modify the context value directly, which can lead to unexpected behavior.
* Create too many contexts, resulting in a complex and difficult-to-manage application.
**Example (Global Context):**
"""tsx
// app-context.ts
import { createContextId } from '@builder.io/qwik';
export interface AppContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
export const AppContext = createContextId('app-context');
// app.tsx
import { component$, useContextProvider, useStore, $, useContext } from '@builder.io/qwik';
import { AppContext } from './app-context';
export const App = component$((props: { children: any }) => {
const store = useStore({
theme: 'light',
toggleTheme: $((() => {
store.theme = store.theme === 'light' ? 'dark' : 'light';
}),
});
useContextProvider(AppContext, store);
return (
<>
{props.children}
);
});
const ThemeProvider = component$((props: { children: any }) => {
const context = useContext(AppContext);
return (
{props.children}
)
})
"""
## 5. Advanced State Management Patterns
### 5.1. State Machines
**Standard:** Consider using state machines for managing complex state transitions.
**Why:** State machines provide a structured and predictable way to manage complex state logic. They can help to prevent invalid state transitions and simplify debugging.
**Do This:**
* Use a state machine library like "xstate" to define the states, events, and transitions of your application.
* Integrate the state machine with Qwik's reactivity system to trigger re-renders when the state changes.
**Don't Do This:**
* Attempt to manage complex state logic manually, which can lead to bugs and difficult-to-maintain code.
* Overuse state machines for simple state logic that can be easily managed with "useStore()".
### 5.2. Signals (Experimental)
**Standard:** Signals are still considered experimental within Qwik. Use with caution.
**Why:** Signals offer a fine-grained reactivity system that can improve performance by minimizing unnecessary re-renders. They allow you to track changes to individual values within a store, rather than the entire store object.
**Do This:**
* Evaluate them where performance is critical.
* Carefully measure performance differences between "useStore()" and signals before committing to a large-scale refactoring.
**Don't Do This:**
Rely on them in production without thorough testing.
Use signals without understanding their implications of Qwik's resumability system.
## 6. Security Considerations
### 6.1. Preventing XSS Attacks
**Standard:** Sanitize data before storing it in the state.
**Why:** Storing unsanitized data in the state can make your application vulnerable to cross-site scripting (XSS) attacks. If an attacker can inject malicious JavaScript code into the state, they can potentially steal user data, hijack user sessions, or deface your application.
**Do This:**
* Sanitize user input using a library like DOMPurify before storing it in the state.
* Use Qwik's built-in escaping mechanisms to prevent XSS attacks.
**Don't Do This:**
* Store unsanitized user input directly in the state.
* Disable Qwik's built-in escaping mechanisms without understanding the security implications.
### 6.2. Protecting Sensitive Data
**Standard:** Protect sensitive data stored in the state.
**Why:** Sensitive data, such as passwords, API keys, and user authentication tokens, should be protected from unauthorized access. Storing sensitive data in the wrong place can expose it to potential attacks.
**Do This:**
* Avoid storing sensitive data in the client-side state whenever possible.
* If you must store sensitive data in the client-side state, encrypt it using a strong encryption algorithm.
* Store sensitive data on the server and access it through secure APIs.
**Don't Do This:**
* Store sensitive data in plain text in the client-side state.
* Expose sensitive data in URLs or in the browser's history.
## 7. Testing
### 7.1 Unit Testing
**Standard:** Write unit tests for components that manage state.
**Why:** State management logic can become quite complex, so automated tests are critical for ensuring the reliability of your application. To test a Qwik component with state:
**Do This:**
* Ensure the state updates as expected when the component interacts with outside services.
* Confirm that the UI reflects the state, after any updates or changes. You can use tools like "jest", "vitest", or "playwright" to test Qwik components.
### 7.2 Component Integration Testing
**Standard:** Implement component integration tests for complete stateful components.
**Why:** Although unit tests are important, integration tests test a bigger piece of the application, specifically the parts responsible for state management.
**Do This:**
* Make sure that user input updates app state.
* Confirm that any async calls update app state properly.
* Verify that the system behaves reproducibly for a specific set of inputs and preconditions.
## 8. Conclusion
Effective state management is critical for building robust and maintainable Qwik applications. By following these coding standards, developers can ensure that their applications are performant, secure, and easy to understand. By adhering to these principles, Qwik developers can leverage the framework's key strengths, ensuring excellent performance and a smooth development experience.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Testing Methodologies Standards for Qwik This document outlines the standards for testing methodologies in Qwik applications. Adhering to these standards ensures code quality, maintainability, and reliability within a Qwik project, specifically taking advantage of Qwik's unique features like resumability and lazy-loading. ## 1. General Testing Principles ### 1.1 Test Pyramid **Do This:** Follow the test pyramid strategy, emphasizing unit tests, followed by integration tests, and then end-to-end (E2E) tests. **Don't Do This:** Over-rely on E2E tests at the expense of unit and integration tests. **Why:** A well-balanced test pyramid reduces testing costs, increases confidence in the codebase, and enables faster feedback loops. Unit tests are the fastest and cheapest to run; focusing on them catches issues early. Excessive E2E tests are slow, brittle, and expensive to maintain. ### 1.2 Test-Driven Development (TDD) **Do This:** Consider implementing TDD, where tests are written before the actual code. **Don't Do This:** Write tests as an afterthought. **Why:** TDD forces you to think about the requirements and API design upfront, leading to cleaner, more testable code. ### 1.3 Test Organization **Do This:** Colocate tests with the component/module they are testing (e.g., "src/components/MyComponent/my-component.tsx" and "src/components/MyComponent/my-component.test.tsx"). Use a clear naming convention. **Don't Do This:** Place all tests in a single "tests/" directory without a logical structure. **Why:** Co-location improves discoverability and maintainability. It makes it easier to find and update tests when the component changes. ## 2. Unit Testing ### 2.1 Focus **Do This:** Unit tests should focus on testing individual functions, components, and services in isolation. Mock dependencies to avoid external dependencies and side effects. **Don't Do This:** Write unit tests that rely on external APIs, databases, or the DOM (except for rendering tests, which should be limited). **Why:** Unit tests provide fast feedback on the correctness of individual units of code. Isolation ensures that tests are not affected by external factors, making them more reliable. Qwik's resumability model means you need to specifically test the state management. ### 2.2 Tools **Do This:** Use Jest or Vitest as the primary unit testing frameworks. Utilize "@testing-library/react" (or "@testing-library/preact" under the hood in Qwik) for component rendering and interaction testing. Consider "msw" for mocking API calls. **Don't Do This:** Rely on outdated testing libraries or frameworks. **Why:** Jest and Vitest offer excellent performance, mocking capabilities, and a rich ecosystem of plugins. "@testing-library/react" encourages testing components from a user's perspective, improving test quality. "msw" gives a powerful framework for testing interactions. ### 2.3 Component Rendering Tests **Do This:** Use "@testing-library/react" to render components and assert on the rendered output. Focus on testing the component's public API and behavior. **Don't Do This:** Test implementation details or internal state directly. **Why:** Testing components from a user's perspective makes tests more resilient to changes in implementation details. Changes to the UI are more likely to require test updates than internal logic changes. **Example:** """tsx // src/components/Counter/counter.tsx import { component$, useSignal, $, useStylesScoped$ } from '@builder.io/qwik'; import styles from './counter.css?inline'; export const Counter = component$(() => { useStylesScoped$(styles); const count = useSignal(0); const increment = $(() => { count.value++; }); return ( <div class="counter"> <button onClick$={increment}>Increment</button> <span>{count.value}</span> </div> ); }); // src/components/Counter/counter.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { h } from '@builder.io/qwik'; import { Counter } from './counter'; import { test, expect } from 'vitest'; test('increments the count on button click', async () => { render(h(Counter, {})); // Use h (Qwik's hyperscript) const button = await screen.findByText('Increment'); const countDisplay = screen.getByText('0'); fireEvent.click(button); expect(countDisplay).toHaveTextContent('1'); }); """ ### 2.4 Testing Qwik Specific Features **Do This:** Pay special attention to testing code within "$()" functions and "use*" hooks, since these are where the most Qwik-specific logic resides. Test your "routeLoader$" and "routeAction$" handlers effectively, mocking network requests and verifying state updates. **Don't Do This:** Assume Qwik's magic will automatically make everything work flawlessly. Write generic React tests that don't account for Qwik's resumability. **Why:** The "$()" functions and "use*" hooks are the core of the Qwik framework. Proper testing here makes sure the framework works correctly. ### 2.5 Mocking **Do This:** Use mocking appropriately to isolate units of code. Tools like "jest.mock" or "vi.mock" can be invaluable. Mock API calls using "msw" wherever possible. **Don't Do This:** Over-mock, which can lead to tests that don't reflect real-world behavior. ### 2.6 Example: Service Unit Test """typescript // src/services/api.ts export const fetchUserData = async (userId: string) => { const response = await fetch("/api/users/${userId}"); if (!response.ok) { throw new Error('Failed to fetch user data'); } return response.json(); }; // src/services/api.test.ts import { fetchUserData } from './api'; import { vi, expect, test } from 'vitest'; test('fetchUserData fetches user data successfully', async () => { const mockResponse = { id: '123', name: 'Test User' }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockResponse), }); const userData = await fetchUserData('123'); expect(userData).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledWith('/api/users/123'); }); test('fetchUserData throws an error when the request fails', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, }); await expect(fetchUserData('123')).rejects.toThrow('Failed to fetch user data'); }); """ ## 3. Integration Testing ### 3.1 Focus **Do This:** Integration tests should verify the interaction between different parts of the application, such as components working with services, or different modules interacting. **Don't Do This:** Treat integration tests as either large unit tests or small E2E tests. They should have a distinct purpose. **Why:** Integration tests ensure that different parts of the application work together correctly. ### 3.2 Tools **Do This:** Continue using Jest or Vitest for integration tests. Consider using "msw" to mock external API dependencies, and a lightweight testing server. **Don't Do This:** Attempting to use only unit testing tools **Why:** Leveraging good tools helps. ### 3.3 Component Integration Tests **Do This:** Test scenarios where multiple components interact. For instance, testing a form component that uses a service to submit data. **Don't Do This:** Primarily using only Jest or Vitest. **Example:** """tsx // src/components/Form/form.tsx import { component$, useSignal, $, useStylesScoped$ } from '@builder.io/qwik'; import { submitFormData } from '../../services/api'; //Example API call import styles from './form.css?inline'; export const Form = component$(() => { useStylesScoped$(styles); const name = useSignal(''); const email = useSignal(''); const submissionResult = useSignal<string | null>(null); const handleSubmit = $(async () => { try { await submitFormData({ name: name.value, email: email.value }); submissionResult.value = 'Form submitted successfully!'; } catch (error: any) { submissionResult.value = "Submission failed: ${error.message}"; } }); return ( <form onSubmit$={handleSubmit}> <div> <label htmlFor="name">Name:</label> <input type="text" id="name" value={name.value} onChange$={(e) => (name.value = e.target.value)} /> </div> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" value={email.value} onChange$={(e) => (email.value = e.target.value)} /> </div> <button type="submit">Submit</button> {submissionResult.value && <div>{submissionResult.value}</div>} </form> ); }); // src/components/Form/form.test.tsx import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { h } from '@builder.io/qwik'; import { Form } from './form'; import { submitFormData } from '../../services/api'; //Example API call import { vi, expect, test } from 'vitest'; vi.mock('../../services/api'); // Mock the submitFormData function test('submits the form and displays success message', async () => { (submitFormData as any).mockResolvedValue(undefined); // Mock successful submission render(h(Form, {})); const nameInput = screen.getByLabelText('Name:'); const emailInput = screen.getByLabelText('Email:'); const submitButton = screen.getByText('Submit'); fireEvent.change(nameInput, { target: { value: 'Test Name' } }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText('Form submitted successfully!')).toBeInTheDocument(); }); expect(submitFormData).toHaveBeenCalledWith({ name: 'Test Name', email: 'test@example.com' }); }); test('displays an error message when submission fails', async () => { (submitFormData as any).mockRejectedValue(new Error('Submission failed!')); // Mock failed submission render(h(Form, {})); const nameInput = screen.getByLabelText('Name:'); const emailInput = screen.getByLabelText('Email:'); const submitButton = screen.getByText('Submit'); fireEvent.change(nameInput, { target: { value: 'Test Name' } }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText('Submission failed: Submission failed!')).toBeInTheDocument(); }); }); """ ### 3.4 Testing "routeLoader$" and "routeAction$" **Do This:** Write integration tests that verify the data fetching and mutation logic within your "routeLoader$" and "routeAction$" functions. Mock any backend dependencies. Verify that the UI updates correctly after the data loads or the action is performed. **Don't Do This:** Skip testing these critical server-side functions. **Why:** "routeLoader$" and "routeAction$" are the main entry points for data loading and form submissions in Qwik apps. Testing them makes sure that the application's data flow works correctly. ## 4. End-to-End (E2E) Testing ### 4.1 Focus **Do This:** E2E tests should simulate real user interactions with the application, covering critical user flows. Keep E2E tests focused on the most important functionality. **Don't Do This:** Use E2E tests to cover every possible scenario. This will lead to slow, brittle, and difficult-to-maintain test suites. **Why:** E2E tests provide the highest level of confidence in the application's correctness, but also the highest cost. They should focus on validating the core functionality from the user's perspective. ### 4.2 Tools **Do This:** Use Playwright or Cypress for E2E testing. These tools offer excellent browser automation capabilities and are well-suited for testing modern web applications. **Don't Do This:** Attempt manual testing when it can be automated. **Why:** These are industry leading tools to use ### 4.3 Test Environment **Do This:** Ensure that the E2E tests run against a dedicated test environment that is as close to production as possible. Seed the database with test data before each test run. **Don't Do This:** Run E2E tests against a development or production environment. **Why:** Running tests against a dedicated environment prevents interference with development and protects production data. ### 4.4 Writing Stable E2E Tests **Do This:** Write tests that are resilient to UI changes. Use semantic selectors (e.g., "data-testid") instead of CSS selectors that are likely to change. Wait for elements to be visible and interactable before interacting with them. Mock any backend dependencies that would cause inconsistencies. **Don't Do This:** Write tests that rely on specific CSS class names or IDs. Introduce artificial delays (e.g., "setTimeout") to wait for elements to load. Don't use "data-testid" at all. **Why:** Tests relying on CSS class are brittle. ### 4.5 Example E2E Test (Playwright) """typescript // tests/e2e/home.spec.ts import { test, expect } from '@playwright/test'; test('loads the homepage and displays the title', async ({ page }) => { await page.goto('/'); await expect(page.locator('h1')).toHaveText('Welcome to Qwik!'); }); test('navigates to the About page', async ({ page }) => { await page.goto('/'); await page.click('text=About'); await expect(page).toHaveURL('/about'); await expect(page.locator('h1')).toHaveText('About Us'); }); test('interacts with the counter', async ({ page }) => { await page.goto('/'); await page.getByRole('button', { name: 'Increment' }).click(); await expect(page.locator('span')).toHaveText('1'); }); """ ## 5. Types of Tests for Qwik Qwik applications benefit from tests specifically designed to address their unique characteristics: * **Resumability Tests:** Verify that the application state is correctly serialized and deserialized during resumability. Focus on testing state updates for components. * **Lazy-Loading Tests:** Verify that components and modules are lazy-loaded as expected, improving initial load time. This can be done by monitoring network requests during E2E tests. * **Optimizer Tests:** Verify that the Qwik optimizer is correctly splitting and chunking code, minimizing the initial download size. ## 6. Code Coverage **Do This:** Strive for high code coverage, but don't treat it as the only goal. Code coverage should be used as a tool to identify untested areas of the code, not as a measure of test quality. Aim for a coverage of at least 80%. **Don't Do This:** Aim for 100% code coverage at the expense of writing meaningful tests. **Why:** High code coverage reduces the risk of undiscovered bugs, but it doesn't guarantee that the code is correct. Meaningful tests that cover important use cases are more valuable than tests written solely to increase code coverage. ## 7. Continuous Integration (CI) **Do This:** Integrate tests into a CI pipeline to automatically run tests on every commit and pull request. **Don't Do This:** Rely on manual testing or running tests only before deployment. **Why:** CI ensures that tests are run consistently and provides early feedback on code changes, preventing regressions and improving code quality. ## 8. Test Data Management **Do This:** Use realistic test data that covers various scenarios, including edge cases and error conditions. **Don't Do This:** Use unrealistic or trivial test data that doesn't accurately reflect real-world usage. **Why:** Realistic test data helps uncover potential issues that might not be apparent with simple test cases. ## 9. Accessibility Testing **Do This:** Include accessibility testing in your testing strategy. Ensure the application meets accessibility standards. **Don't Do This:** Ignore accessibility concerns during the software development process. ## 10. Performance Testing **Do This:** Incorporate performance testing to identify and address performance bottlenecks. **Don't Do This:** Neglect performance testing until late in the development cycle. ## 11. Security Testing **Do This:** Conduct security tests to identify and mitigate security vulnerabilities. **Don't Do This:** Assume your application is inherently secure without proper security testing. These standards are designed to provide a solid foundation for testing Qwik applications. By following these guidelines, teams can ensure that they deliver high-quality, reliable, and maintainable software.
# Tooling and Ecosystem Standards for Qwik This document outlines the recommended tooling and ecosystem standards for Qwik development. Adhering to these guidelines ensures a consistent, maintainable, performant, and secure codebase. It's aimed at Qwik developers and serves as context for AI coding assistants. ## 1. Development Environment Setup ### 1.1 Use Node.js LTS and a Package Manager **Standard:** Use the latest LTS version of Node.js and a package manager like npm, pnpm or yarn. Node.js is used to execute Qwik Optimizer and Qwik City. **Why:** Utilizing the latest LTS version of Node.js guarantees stability, performance improvements, and access to the latest JavaScript features. A package manager ensures consistent dependency management across environments. **Do This:** * Install the latest LTS version of Node.js. Always download official Node binaries instead of package managers. * Use npm, pnpm or yarn for package management. pnpm is recommended for workspace projects. **Don't Do This:** * Use outdated Node.js versions. * Avoid global installations of dependencies. **Example:** """bash # Check Node.js version node -v # Check npm version npm -v #Check pnpm version pnpm -v #Check yarn version yarn -v """ ### 1.2 Editor and IDE Configuration **Standard:** Configure an editor or IDE with Qwik-specific extensions and pre-commit hooks for better consistency. **Why:** Proper editor configuration enhances developer productivity, catches errors early, and enforces consistent code style. Pre-commit hooks automate code formatting and linting before committing. **Do This:** * Use VS Code with the official Qwik extension. It provides syntax highlighting, code completion, and Qwik-specific diagnostics and quick fixes. * Install ESLint and Prettier extensions and configure them to format code on save. * Set up pre-commit hooks using Husky and lint-staged (or similar tools) to run linters and formatters before each commit. **Don't Do This:** * Rely solely on manual formatting. * Skip code reviews or automated checks. **Example:** ".vscode/settings.json": """json { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact" ], "files.eol": "\n" } """ "package.json": """json { "devDependencies": { "eslint": "^8.0.0", "eslint-config-qwik": "^1.0.0", "husky": "^8.0.0", "lint-staged": "^13.0.0", "prettier": "^2.0.0" }, "lint-staged": { "*.{js,jsx,ts,tsx,md,json}": "prettier --write" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "prettier": { "arrowParens": "always", "semi": false, "trailingComma": "all", "singleQuote": true } } """ ### 1.3 Qwik CLI **Standard:** Utilize the Qwik CLI for creating and managing projects. **Why:** The Qwik CLI simplifies project setup, component generation, and building production-ready applications. **Do This:** * Install the Qwik CLI globally: "npm install -g @builder.io/qwik-city". * Use "qwik new" to scaffold new Qwik projects. * Use "qwik add" to add integrations like TailwindCSS or Cloudflare. * Leverage the CLI for building: "npm run build" **Don't Do This:** * Manually configure project structures where CLI tools are available. **Example:** """bash # Create a new Qwik City project npm create qwik@latest my-qwik-app # Add TailwindCSS integration to an existing Qwik City project cd my-qwik-app npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p """ ## 2. Linting and Formatting ### 2.1 ESLint Configuration **Standard:** Use ESLint with recommended Qwik-specific rules. **Why:** ESLint helps catch syntax errors, enforce code style, and ensure code quality throughout the project. Qwik-specific rules enforce best practices related to Qwik APIs and patterns. **Do This:** * Install ESLint and the "eslint-config-qwik" package. * Extend the recommended Qwik configuration in your ".eslintrc.js" file. * Configure ESLint to run on file save within your editor. * Address all linting errors and warnings. **Don't Do This:** * Disable or ignore ESLint rules without justification. * Commit code with linting errors. **Example:** ".eslintrc.js": """javascript module.exports = { root: true, extends: ['qwik', 'prettier'], // Removed 'plugin:@typescript-eslint/recommended' parserOptions: { ecmaVersion: 'latest', sourceType: 'module', }, rules: { // Add custom rules here. For example: // 'no-console': 'warn', }, }; """ ### 2.2 Prettier Configuration **Standard:** Use Prettier for consistent code formatting. **Why:** Prettier automatically formats code based on a predefined set of rules, ensuring a uniform style across the project. It removes subjective formatting debates. **Do This:** * Install Prettier. * Create a ".prettierrc.js" file in the root of your project. * Configure Prettier rules to enforce consistent formatting conventions (e.g., single quotes, trailing commas). * Integrate Prettier with your editor and pre-commit hooks. **Don't Do This:** * Use inconsistent formatting conventions. * Override Prettier's formatting without a clear reason. **Example:** ".prettierrc.js": """javascript module.exports = { arrowParens: 'always', semi: false, trailingComma: 'all', singleQuote: true, printWidth: 100, //Consider this option as it becomes more common }; """ ### 2.3 TypeScript Integration **Standard**: Use TypeScript for type safety and improved code maintainability. **Why**: TypeScript enforces static typing, which helps catch errors during development, improves code readability, and facilitates easier refactoring. **Do This**: * Install TypeScript as a dev dependency: "npm install --save-dev typescript". * Configure TypeScript to enforce strict type checking by enabling "strict" mode in "tsconfig.json". * Define types for all components, services, and data structures. **Don't Do This**: * Use "any" type excessively. * Ignore TypeScript errors and warnings. **Example**: "tsconfig.json": """json { "compilerOptions": { "target": "esnext", "module": "esnext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, "baseUrl": ".", "paths": { "*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules"] } """ ## 3. Package Management and Dependencies ### 3.1 Dependency Versioning **Standard:** Specify dependency versions precisely using semantic versioning (semver). **Why:** Semver ensures predictable dependency updates and prevents unexpected breaking changes. **Do This:** * Use specific version numbers or version ranges with caution (e.g., "^1.2.3", "~1.2.3"). Consider exact pinning for library codebases. * Regularly review and update dependencies to benefit from bug fixes and new features. **Don't Do This:** * Use wildcard version ranges (e.g., "*"). * Ignore dependency update notifications. **Example:** "package.json": """json { "dependencies": { "@builder.io/qwik": "1.4.0", "@builder.io/qwik-city": "1.4.0" }, "devDependencies": { "typescript": "~5.0.0" } } """ ### 3.2 Managing Dependencies **Standard:** Favor ES Modules-compatible packages to leverage tree-shaking. **Why:** Using ES Modules allows the bundler to include only the necessary code, reducing the bundle size and improving performance. **Do This:** * Prefer packages that provide ES Module versions. * Use dynamic "import()" for optional dependencies or large modules. * Analyze bundle size using tools like "rollup-plugin-visualizer". **Don't Do This:** * Use CommonJS modules in new projects or when ES Module alternatives are available. * Import entire libraries when only a subset of functions is needed. **Example:** """typescript // Use dynamic import for optional dependencies async function loadAnalytics() { if (process.env.NODE_ENV === 'production') { const analytics = await import('analytics'); analytics.init({ /* config */ }); } } """ ### 3.3 Recommended Qwik Libraries **Standard:** Utilize the Qwik ecosystem libraries when appropriate. **Why:** Qwik ecoystems provides a well-tested and optimized foundation for common tasks. **Do This:** * Use "@builder.io/qwik-city" for routing and server-side rendering. * Explore "@builder.io/qwik-react" for integrating React components into Qwik applications. * Utilize "@qwikdev/headroom" for header visibility management **Don't Do This:** * Reinvent the wheel by creating functionality already available in the Qwik ecosystem (after evaluation). * Use libraries without understanding their impact on loading and performance. ### 3.4 Handling Environment Variables **Standard**: Use environment variables for configuration to avoid hardcoding sensitive information and to configure different environments. **Why**: Environment variables keep configuration separate from code, enhancing security and flexibility across different deployment environments. **Do This**: * Use ".env" files for local development using libraries like "dotenv". * Access environment variables using "process.env.VARIABLE_NAME". * Define environment variables in deployment environments (e.g., Netlify, Vercel). **Don't Do This**: * Commit ".env" files to version control. * Hardcode sensitive information (API keys, database credentials) in the code. **Example**: """bash # .env file API_KEY=your_api_key BASE_URL=https://api.example.com """ """typescript // Accessing in Qwik component import { $, useStore } from '@builder.io/qwik'; export const MyComponent = component$(() => { const apiKey = process.env.API_KEY; const baseUrl = process.env.BASE_URL; // ... use apiKey and baseUrl in the component }); """ ## 4. Testing ### 4.1 Testing Frameworks **Standard**: Employ testing frameworks such as Jest or Vitest for writing unit and integration tests. **Why**: Testing frameworks provide a structure for writing and running tests, enabling automated verification of component and application behavior. **Do This**: * Set up Jest or Vitest with appropriate Qwik-aware configurations. * Write unit tests for individual components to verify their behavior in isolation. * Write integration tests to ensure that components work correctly together. * Aim for high test coverage to catch bugs early in the development cycle. **Don't Do This**: * Skip writing tests or postpone testing until late in the development cycle. * Write tests that are brittle and tightly coupled to implementation details. **Example**: """javascript // Example test using Vitest import { test, expect } from 'vitest'; import { MyComponent } from './my-component'; import { render } from '@builder.io/qwik/testing'; test('MyComponent renders correctly', async () => { const { container } = await render(<MyComponent />); expect(container.textContent).toContain('Hello, Qwik!'); }); """ ### 4.2 End-to-End (E2E) Testing **Standard**: Utilize E2E testing tools like Cypress or Playwright to simulate user interactions and verify application functionality. **Why**: E2E tests validate the entire application flow, ensuring that all components and services work together as expected from the user's perspective. **Do This**: * Set up Cypress or Playwright to automate browser-based testing. * Write E2E tests that simulate common user scenarios. * Run E2E tests in a continuous integration environment to catch integration issues early. **Don't Do This**: * Rely solely on manual testing without automated E2E tests. * Ignore E2E test failures or postpone fixing them. **Example:** """javascript // Example test using Playwright import { test, expect } from '@playwright/test'; test('Homepage has title', async ({ page }) => { await page.goto('http://localhost:3000/'); await expect(page).toHaveTitle("Welcome to Qwik City!"); }); """ ## 5 Continuous Integration and Deployment ### 5.1 CI/CD Pipelines **Standard**: Set up CI/CD pipelines using platforms like GitHub Actions, GitLab CI, or Jenkins to automate building, testing, and deploying applications. **Why**: CI/CD pipelines automate software delivery, reducing manual effort, improving reliability, and accelerating the release cycle. **Do This**: * Configure CI/CD pipelines to trigger on code commits and pull requests. * Run linters, formatters, and tests as part of the CI/CD pipeline. * Automate the deployment process to staging and production environments. **Don't Do This**: * Manually build, test, and deploy applications. * Skip CI/CD pipelines or neglect to maintain them. **Example**: ".github/workflows/deploy.yml": """yaml 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' # Use a supported Node version - run: npm install - run: npm run build - name: Deploy to Netlify uses: netlify/actions/deploy@v1 env: NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} PRODUCTION_DEPLOY: true """ ### 5.2 Cloud Platforms **Standard**: Deploy Qwik applications to cloud platforms like Netlify, Vercel, Cloudflare Pages, or AWS Amplify for optimal performance and scalability. **Why**: Cloud platforms offer features such as edge caching, serverless functions, and automated deployments, which enhance application performance and scalability. **Do This**: * Choose a cloud platform that aligns with your application's requirements. * Configure deployment settings to optimize performance (e.g., enable CDN, configure caching). * Monitor application performance using cloud platform monitoring tools. **Don't Do This**: * Host applications on outdated infrastructure. * Ignore performance monitoring or fail to address performance issues. ## 6. Code Analysis and Monitoring ### 6.1 Static Code Analysis **Standard**: Utilize static code analysis tools such as SonarQube or CodeClimate to identify potential code quality issues. **Why**: Static code analysis helps detect bugs, security vulnerabilities, and code smells early in the development cycle. **Do This**: * Integrate static code analysis tools into the CI/CD pipeline. * Enforce code quality rules and address violations promptly. **Don't Do This**: * Ignore static code analysis findings or postpone fixing them. ### 6.2 Performance Monitoring **Standard**: Implement performance monitoring using tools like Sentry or New Relic to track application performance in production. **Why**: Performance monitoring allows you to identify bottlenecks, diagnose performance issues in real-time, and optimize application performance. **Do This**: * Integrate performance monitoring tools into the application. * Set up alerts to notify you of performance regressions or errors. * Regularly review performance metrics and optimize code accordingly. **Don't Do This**: * Neglect to monitor performance. **Example**: """typescript // Integrating Sentry into a Qwik application import * as Sentry from '@sentry/qwik'; import { init } from '@qwik-city/vite'; init({ dsn: 'YOUR_SENTRY_DSN', integrations: [ new Sentry.BrowserTracing(), new Sentry.Replay(), ], // Performance Monitoring tracesSampleRate: 0.1, // Capture 10% of transactions for performance monitoring. // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. }); """ ## 7. Security ### 7.1 Dependency Security Scanning **Standard**: Use tools like "npm audit", "yarn audit", or Snyk to scan dependencies for security vulnerabilities. **Why**: Dependency scanning identifies known vulnerabilities in third-party libraries, helping you avoid including insecure code in your application. **Do This**: * Run dependency scans regularly as part of the CI/CD pipeline. * Update vulnerable dependencies promptly. * Consider using a dependency firewall to block the introduction of vulnerable dependencies. **Don't Do This**: * Ignore dependency security vulnerabilities. * Fail to update vulnerable dependencies. ### 7.2 Code Security Audits **Standard**: Conduct code security audits to identify potential security flaws in the codebase. **Why**: Code security audits reveal vulnerabilities such as injection flaws, authentication issues, and cross-site scripting (XSS) vulnerabilities, helping you prevent security breaches. **Do This**: * Engage security experts to perform code security audits. * Address all identified security vulnerabilities promptly. * Implement security best practices, such as input validation, output encoding, and secure authentication. ### 7.3 Secure Configuration **Standard**: Follow secure configuration practices to protect sensitive data and prevent unauthorized access. **Why**: Secure configuration helps prevent accidental exposure of sensitive data and reduces the risk of security breaches. **Do This**: * Use environment variables to store sensitive configuration data. * Encrypt sensitive data at rest and in transit. * Implement access controls and authentication mechanisms to restrict unauthorized access.
# Component Design Standards for Qwik This document outlines the coding standards for component design in Qwik, aiming to promote maintainability, reusability, and performance. Adhering to these guidelines ensures efficient collaboration and a robust codebase in accordance with the latest Qwik features and best practices. ## 1. Component Architecture ### 1.1. Component Hierarchy and Structure * **Do This:** Favor a component-based architecture with a clear hierarchy. Break down complex UI into smaller, reusable components. * **Don't Do This:** Create monolithic components that handle too many responsibilities. **Why:** A well-defined component hierarchy simplifies maintenance and promotes reusability. Breaking down complex UIs enables focus on specific features and improve readability. It also encourages efficient code splitting and lazy loading in Qwik. **Example:** """typescript // Good: Separated concerns // card.tsx import { component$, $, useContext } from '@builder.io/qwik'; import { ThemeContext } from './theme-context'; export const Card = component$(() => { const theme = useContext(ThemeContext); return ( <div class={"card ${theme.value}"}> <CardHeader /> <CardBody /> </div> ); }); // card-header.tsx import { component$ } from '@builder.io/qwik'; export const CardHeader = component$(() => { return ( <header> <h3>Card Title</h3> </header> ); }); // card-body.tsx import { component$ } from '@builder.io/qwik'; export const CardBody = component$(() => { return ( <div> <p>Card content goes here.</p> </div> ); }); // theme-context.tsx import { createContextId } from '@builder.io/qwik'; export const ThemeContext = createContextId<'light' | 'dark'>('theme'); """ """typescript // Bad: Monolithic component import { component$ } from '@builder.io/qwik'; export const MonolithicCard = component$(() => { return ( <div class="card"> <header> <h3>Card Title</h3> </header> <div> <p>Card content goes here.</p> </div> </div> ); }); """ ### 1.2. Smart vs. Dumb Components (Presentational vs. Container Components) * **Do This:** Distinguish between smart (container) and dumb (presentational) components. Smart components handle data fetching, state management, and business logic. Dumb components primarily focus on rendering UI based on props. * **Don't Do This:** Mix data handling and UI rendering logic within a single component. **Why:** This separation enhances reusability, testability, and maintainability. Presentational components become easily adaptable and restylable for different contexts, and container components facilitate easier state management and testing. **Example:** """typescript // Smart Component (Container) - data fetching import { component$, useStore, useTask$ } from '@builder.io/qwik'; import { PresentationalList } from './presentational-list'; export const SmartList = component$(() => { const state = useStore({ items: [] }); useTask$(async ({ track }) => { track(() => state); // Re-run when state changes const fetchData = async () => { const response = await fetch('/api/items'); state.items = await response.json(); }; fetchData(); }); return <PresentationalList items={state.items} />; }) // Dumb Component (Presentational) - UI rendering import { component$ } from '@builder.io/qwik'; interface Props { items: any[]; } export const PresentationalList = component$<Props>(({ items }) => { return ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }); """ ### 1.3. State Management Strategy * **Do This:** Favor Qwik's "useStore" and "useSignal" for local component state. "useContext" when sharing state between components that may not neccessarily be parent and child. Consider more advanced state management solutions like Redux or Zustand (though typically not needed) only for complex applications with global state needs. * **Don't Do This:** Over-engineer state management with complex solutions for simple component state. Mutations should be predictable and avoid hidden side-effects. **Why:** Using Qwik's built-in state management tools ("useStore", "useSignal", "useContext", and "useTask$") simplifies development and provides performance benefits due to Qwik's resumability feature. External state management libraries introduce additional complexity and may not align with Qwik's core principles. **Example:** """typescript // Using useStore import { component$, useStore } from '@builder.io/qwik'; export const Counter = component$(() => { const state = useStore({ count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick$={() => state.count++}>Increment</button> </div> ); }); """ """typescript // Using useContext import { component$, useContext, createContextId } from '@builder.io/qwik'; const MyContext = createContextId<{ value: string }>('my-context'); export const ProviderComponent = component$(() => { return ( <MyContext.Provider value={{ value: 'Hello from context!' }}> <ConsumerComponent /> </MyContext.Provider> ); }); export const ConsumerComponent = component$(() => { const contextValue = useContext(MyContext); return <p>{contextValue.value}</p>; }); """ ### 1.4. Prop Drilling Avoidance * **Do This:** Favor "useContext" to share state with deep child components instead of passing props down through multiple levels. * **Don't Do This:** Prop drilling, particularly when the intermediate components do not utilize prop values. **Why:** Reduces the complexity and coupling of intermediate components, simplifying code and improving maintainability. Context offers a direct way for deeply nested components to access needed data. **Example:** (refer to the "useContext" example above) ## 2. Component Implementation ### 2.1. Functional Components * **Do This:** Use functional components with Qwik's "component$" for all new components. Embrace immutability when working with props and state. * **Don't Do This:** Use class-based components or mutable data structures without a clear reason. **Why:** Functional components with hooks are simpler to read, write, and test. They promote immutability which makes state management easier and avoids unexpected side effects. **Example:** """typescript import { component$, useSignal } from '@builder.io/qwik'; export const MyComponent = component$(() => { const name = useSignal('Initial Name'); return ( <div> <p>Hello, {name.value}!</p> <input type="text" value={name.value} onInput$={(event) => (name.value = (event.target as HTMLInputElement).value)} /> </div> ); }); """ ### 2.2. Props and Events * **Do This:** Define prop types clearly using interfaces or types. Use Qwik's "Prop<T>" to ensure type safety. Design events to be predictable and consistent. Use "$" to define event handlers. * **Don't Do This:** Avoid "any" type for props. Don't introduce side effects within event handlers without proper handling. **Why:** Type safety prevents runtime errors and improves code maintainability. Clear event handling reduces unexpected behavior and improves component predictability. **Example:** """typescript import { component$, Prop } from '@builder.io/qwik'; interface Props { name: string; age: number; onClick: Prop<() => void>; // Correctly typed event handler } export const UserProfile = component$<Props>(({ name, age, onClick }) => { return ( <div> <p>Name: {name}</p> <p>Age: {age}</p> <button onClick$={onClick}>Click Me</button> </div> ); }); """ """typescript // Usage import { component$ } from '@builder.io/qwik'; import { UserProfile } from './user-profile'; export const ParentComponent = component$(() => { const handleClick = () => { alert('Button clicked!'); }; return <UserProfile name="John Doe" age={30} onClick={handleClick} />; }); """ ### 2.3. Code-Splitting * **Do This:** Utilize Qwik's automatic code-splitting capabilities by breaking down larger components into smaller, lazy-loaded modules, keeping components small and focus on a specific task. * **Don't Do This:** Bundle all component code into a single large file, which can hurt initial load times. **Why:** Code splitting can improve the user experience for initial load times. **Example:** """typescript // Dynamically import components import { component$, Dynamic } from '@builder.io/qwik'; export const ParentComponent = component$(() => { return ( <div> <p>Parent Component</p> <Dynamic import={() => import('./lazy-component')} /> </div> ); }); // lazy-component.tsx import { component$ } from '@builder.io/qwik'; export default component$(() => { return <p>Lazy Loaded Component!</p>; }); """ ### 2.4. Styling * **Do This:** Use CSS Modules, Styled Components with Qwik Optimizer, or other CSS-in-JS solutions for component-specific styling. Scope styles to components to avoid naming conflicts. * **Don't Do This:** Use global CSS styles that can inadvertently affect other parts of the application. **Why:** Component-specific styling enhances maintainability and prevents unexpected style collisions. Use CSS Modules or CSS-in-JS to encapsulate styles within components. **Example (CSS Modules):** """typescript // my-component.tsx import { component$ } from '@builder.io/qwik'; import styles from './my-component.module.css'; export const MyComponent = component$(() => { return ( <div class={styles.container}> <h1 class={styles.title}>Hello, Qwik!</h1> </div> ); }); // my-component.module.css .container { background-color: #f0f0f0; padding: 20px; } .title { color: #333; } """ ### 2.5. Accessibility * **Do This:** Ensure components are accessible by providing proper semantic HTML, ARIA attributes, and keyboard navigation. Test components with screen readers. * **Don't Do This:** Ignore accessibility considerations, leading to components unusable by people with disabilities. **Why:** Accessibility is crucial for creating inclusive applications. Providing correct semantic structure and ARIA attributes ensures screen readers and other assistive technologies can properly interpret the content. **Example:** """typescript import { component$ } from '@builder.io/qwik'; export const AccessibleButton = component$(() => { return ( <button aria-label="Close dialog" onClick$={() => alert('Button clicked!')}> Close </button> ); }); """ ### 2.6. Naming Conventions * **Do This:** Use PascalCase for component names (e.g., "MyComponent"). Use camelCase for props and event handlers (e.g., "onClick", "userName"). * **Don't Do This:** Use inconsistent or cryptic names that obscure the component's purpose. **Why:** Consistent naming conventions improve code readability and maintainability. **Example:** """typescript import { component$ } from '@builder.io/qwik'; interface Props { userName: string; onClick: () => void; } export const UserProfileCard = component$<Props>(({ userName, onClick }) => { return ( <div> <p>Welcome, {userName}!</p> <button onClick$={onClick}>View Profile</button> </div> ); }); """ ### 2.7. Component Composition * **Do This:** Leverage component composition techniques to create flexible components. Use the "children" prop for content projection. * **Don't Do This:** Create rigid components that are difficult to adapt to changing requirements. **Why:** Component composition facilitates code reuse and allows components to be configured in various ways. **Example:** """typescript import { component$, Slot } from '@builder.io/qwik'; export const Card = component$(() => { return ( <div class="card"> <div class="card-header"> <Slot name="header" /> </div> <div class="card-body"> <Slot /> </div> <div class="card-footer"> <Slot name="footer" /> </div> </div> ); }); // Usage: import { component$ } from '@builder.io/qwik'; import { Card } from './card'; export const MyPage = component$(() => { return ( <Card> This is the card body content. <div q:slot="header"> <h2>Card Title</h2> </div> <div q:slot="footer"> <button>Save</button> </div> </Card> ); }); """ ## 3. Performance Optimization ### 3.1. Minimizing Re-renders * **Do This:** Use "useClientEffect$" and "useTask$" judiciously to minimize re-renders. Track only necessary dependencies to avoid triggering unnecessary updates. Freeze props to prevent changes. * **Don't Do This:** Rely on naive state updates that cause frequent re-renders. **Why:** Minimizing re-renders improves application's overall responsiveness. "useClientEffect$" is only executed in the client and can execute code that depends on the rendered state. **Example:** """typescript import { component$, useSignal, useTask$ } from '@builder.io/qwik'; export const OptimizedComponent = component$(() => { const count = useSignal(0); useTask$(({ track }) => { // Track only the count value track(() => count.value); console.log('Count updated:', count.value); }); return ( <div> <p>Count: {count.value}</p> <button onClick$={() => count.value++}>Increment</button> </div> ); }); """ ### 3.2. Serialization * **Do This:** Properly serialize data passed as props to ensure resumability. Avoid passing complex, non-serializable data. * **Don't Do This:** Pass functions or complex objects that cannot be serialized, which violates Qwik's resumability principle. **Why:** Serialization is crucial for Qwik's resumability, which allows the application to pause and resume on the client. **Example:** """typescript import { component$ } from '@builder.io/qwik'; interface Props { message: string; // Serializable data } export const SerializableComponent = component$<Props>(({ message }) => { return <p>{message}</p>; }); """ ### 3.3. Lazy Loading with "useVisibleTask$" * **Do This:** Use "useVisibleTask$" for tasks that should only start when the component is visible, preventing unnecessary computations before the component is needed. * **Don't Do This:** Perform heavy computations on component initialization regardless of visibility. **Why:** "useVisibleTask$" only starts when the component is visible, avoiding unnecessary computations and improving initial load times. """typescript import { component$, useVisibleTask$ } from '@builder.io/qwik'; export const VisibleComponent = component$(() => { useVisibleTask$(({ track }) => { // Track relevant state track(() => true); // Example, replace with actual dependency console.log('Component is visible!'); }); return <p>Visible Component</p>; }); """ ## 4. Testing ### 4.1. Component Tests * **Do This:** Write unit tests for individual components to ensure they render correctly and behave as expected. Use snapshot testing to catch unexpected UI changes. Integration testing is still necessary but is less relevant to this standard. * **Don't Do This:** Skip component tests, relying solely on end-to-end tests. **Why:** Unit tests provide rapid feedback on component behavior, making it easier to identify and fix bugs early in the development process. **Example:** (using Jest and Qwik's testing utilities) """typescript // my-component.test.tsx import { render } from '@builder.io/qwik/testing'; import { MyComponent } from './my-component'; describe('MyComponent', () => { it('should render correctly', async () => { const { container } = await render(<MyComponent />); expect(container.textContent).toContain('Hello, Qwik!'); }); }); """ ### 4.2. Types and Contracts * **Do This:** use Typescript and type checking to make sure integration is secure. Follow a test-driven approach. * **Don't Do This:** Avoid Typescript or testing because of time constraints. **Why:** Avoid errors and ensure integration. Save time in debugging. ## 5. Security ### 5.1. Input Validation * **Do This:** Validate all user inputs within components to prevent security vulnerabilities such as cross-site scripting (XSS) and injection attacks. * **Don't Do This:** Trust user inputs without validation, leading to potential security exploits. **Why:** Input validation is crucial for preventing malicious data from entering your application. **Example:** """typescript import { component$, useSignal } from '@builder.io/qwik'; export const ValidatedInput = component$(() => { const inputValue = useSignal(''); const sanitizedValue = useSignal(''); const handleChange = (event: Event) => { const input = (event.target as HTMLInputElement).value; // Sanitize input value (example: remove HTML tags) const sanitized = input.replace(/<[^>]*>/g, ''); inputValue.value = input; sanitizedValue.value = sanitized; }; return ( <div> <input type="text" value={inputValue.value} onInput$={handleChange} /> <p>Original Input: {inputValue.value}</p> <p>Sanitized Input: {sanitizedValue.value}</p> </div> ); }); """ ### 5.2. Secure Data Handling * **Do This:** Use secure methods for storing and transmitting sensitive data. Avoid storing sensitive data in local storage or cookies without proper encryption * **Don't Do This:** Expose sensitive data directly in the client-side code. **Why:** Protecting sensitive data is paramount. Secure data handling practices are essential for maintaining user privacy and security. ### 5.3. Avoiding Server-Side Rendering Vulnerabilities * **Do This:** Sanitize data properly during SSR to avoid injection vulnerabilities. * **Don't Do This:** Trust all data during SSR. **Why:** When using SSR, the server needs to be just as secured as the client against vulnerabilities.
# Core Architecture Standards for Qwik This document outlines the core architectural standards for Qwik applications. It focuses on establishing a robust and maintainable foundation for Qwik projects, emphasizing key architectural patterns, project structure, and organization principles that align with Qwik's unique resumability and performance characteristics. This document aims to guide developers in building scalable, efficient, and maintainable Qwik applications. ## 1. Fundamental Architectural Patterns ### 1.1. Component-Based Architecture **Standard:** Embrace a component-based architecture where the application UI is decomposed into reusable and independent components. * **Do This:** Design interfaces and define clear contracts for components, promoting modularity and testability. * **Don't Do This:** Create monolithic components that handle excessive responsibilities and dependencies. **Why:** Component-based architecture promotes code reuse, simplifies testing, and enhances maintainability. **Example:** """tsx // Good: Small, focused component import { component$ } from '@builder.io/qwik'; export const Button = component$((props: { label: string, onClick$: () => void }) => { return <button onClick$={props.onClick$}>{props.label}</button>; }); // Bad: Large, complex component with multiple responsibilities export const ComplexComponent = component$(() => { // ... lots of logic and multiple UI elements return ( <div> {/* ... */} </div> ); }); """ ### 1.2. Reactive Programming **Standard:** Leverage Qwik's built-in reactivity features for managing application state and data flow. * **Do This:** Utilize signals ("useSignal", "useStore") effectively to create reactive data bindings. * **Don't Do This:** Directly manipulate the DOM or rely on global mutable state. **Why:** Reactive programming with signals enhances performance, reduces boilerplate, and makes the application more predictable. **Example:** """tsx // Correct usage of signals import { component$, useSignal } from '@builder.io/qwik'; export const Counter = component$(() => { const count = useSignal(0); return ( <div> <button onClick$={() => count.value++}>Increment</button> {count.value} </div> ); }); // Anti-pattern: Directly manipulating the DOM (Avoid) export const CounterBad = component$(() => { return ( <div> <button onClick$={() => { const element = document.getElementById('counter'); if (element) element.textContent = parseInt(element.textContent || '0') + 1 + "";} }>Increment</button> <span id='counter'>0</span> </div> ); }); """ ### 1.3. State Management **Standard:** Adopt a well-defined state management strategy, utilizing "useStore" for complex state requirements and "useSignal" for simple, transient state. * **Do This:** Organize store data in a structured manner that aligns with the application's data model. * **Don't Do This:** Overuse global state when component-level state suffices. **Why:** Centralized state management promotes data consistency, simplifies debugging, and improves the maintainability of complex applications. **Example:** """tsx // Using useStore (for Complex State) import { component$, useStore } from '@builder.io/qwik'; interface User { name: string; age: number; } export const UserProfile = component$(() => { const user = useStore<User>({ name: 'John Doe', age: 30, }); return ( <div> <p>Name: {user.name}</p> <p>Age: {user.age}</p> </div> ); }); """ ### 1.4. Resumability **Standard:** Leverage Qwik's core principle of resumability to enhance application performance and reduce initial load time. * **Do This:** Design the application to minimize the amount of JavaScript that needs to be downloaded and executed upfront. * **Don't Do This:** Load lots of unnecessary javascript which are not used on initial load. **Why:** Resumability significantly improves the user experience by providing faster initial page loads and improved time-to-interactive. **Example:** """tsx // Correct : The event handler is not serialized. It will only be bundled and transmitted to the browser when user performs interaction on this component in the browser. import { component$, useSignal } from '@builder.io/qwik'; export const DelayedCounter = component$(() => { const count = useSignal(0); return ( <div> <button onClick$={() => count.value++}>Increment</button> <span>{count.value}</span> </div> ); }); """ ### 1.5. Folder Structure **Standard:** Follow a clear and consistent project structure to organize components, services, and other application assets. * **Do This:** Organize code into logical folders like "components", "services", "routes", and "utils". * **Don't Do This:** Place all files in a single directory or use inconsistent naming conventions. **Why:** Consistent project structure enhances navigability and maintainability. **Example:** """ src/ ├── components/ # Reusable UI components │ ├── Button/ │ │ ├── Button.tsx │ │ └── Button.css │ └── Card/ │ ├── Card.tsx │ └── Card.css ├── pages/ # Route-specific components │ ├── index.tsx │ └── about.tsx ├── services/ # Business logic and data fetching │ └── api.ts ├── utils/ # Utility functions and helpers │ └── helpers.ts └── root.tsx # Root qwik component. Defines the sites layout """ ## 2. Project Organization Principles ### 2.1. Separation of Concerns **Standard:** Maintain a clear separation of concerns (SoC) between UI, logic, and data management. * **Do This:** Move business logic away from UI. Create dedicated services (e.g. "api.ts") for data fetching. * **Don't Do This:** Mix UI rendering with complex calculations or data manipulation. **Why:** SoC simplifies debugging, facilitates testing, and promotes code reuse. **Example:** """tsx // Good: Business logic separated from UI import { component$ } from '@builder.io/qwik'; import { fetchUserData } from './services/api'; export const UserProfile = component$(() => { const userData = fetchUserData(); // Asynchronous data fetching return ( <div> <p>Name: {userData.name}</p> <p>Age: {userData.age}</p> </div> ); }); // In services/api.ts export const fetchUserData = async () => { //Logic relating to fetching from API endpoint. return { name: 'Example', age: 25 }; }; // Bad: Mixing UI rendering with data manipulation export const UserProfileBad = component$(() => { const userData = {name: 'Example', age: 25}; //data hardcoded const processedData = "${userData.name} - ${userData.age}"; return ( <div> {processedData} </div> ); }); """ ### 2.2. Atomic Design **Standard:** Consider adopting the principles of Atomic Design to structure components. * **Do This:** Organize components into atoms, molecules, organisms, templates, and pages. * **Don't Do This:** Create ad-hoc component structures without a clear organizing principle. **Why:** Atomic Design provides a structured approach for building UIs, enhancing component reuse and maintainability. **Example:** """ components/ ├── atoms/ # Smallest, indivisible components (e.g., buttons, labels, inputs) │ ├── Button.tsx │ └── Input.tsx ├── molecules/ # Simple combinations of atoms (e.g., input with label) │ └── SearchBar.tsx ├── organisms/ # Relatively complex sections of the interface (e.g., a header) │ └── Header.tsx ├── templates/ # Page-level layouts that stitch organisms together │ └── HomeTemplate.tsx └── pages/ # Actual pages using templates └── index.tsx """ ### 2.3. Context API for Global Data **Standard:** Use the Context API for sharing data that is considered "global" for a tree of Qwik components. * **Do This:** Make a new context with "useContextProvider" to share data for components that are nested deeply. * **Don't Do This:** Share data by passing props through several layers of components ("prop drilling"). **Why:** Context API helps in ensuring maintainability for sharing "global" data in React applications. **Example:** """tsx //Create the context import { createContextId } from "@builder.io/qwik"; interface ThemeContextType { theme?: "light" | "dark"; setTheme?: (theme: ThemeContextType["theme"]) => void; } export const ThemeContext = createContextId<ThemeContextType>("theme-context"); //Wrap children with theme context import { component$, useContextProvider, useSignal } from "@builder.io/qwik"; import { ThemeContext } from "./theme-context"; export const ThemeProvider = component$((props: { children: any }) => { const themeSig = useSignal<ThemeContextType["theme"]>("light"); useContextProvider(ThemeContext, { theme: themeSig.value, setTheme: (theme) => { themeSig.value = theme; }, }); return props.children; }); //Consume theme from a deeply nested component import { component$, useContext } from "@builder.io/qwik"; import { ThemeContext } from "./theme-context"; export const DeeplyNestedComponent = component$(() => { const themeContext = useContext(ThemeContext); return ( <div> Theme: {themeContext.theme} <button onClick$={() => themeContext.setTheme?.(themeContext.theme === "light" ? "dark" : "light")}> Toggle Theme </button> </div> ); }); """ ## 3. Qwik-Specific Considerations ### 3.1 Optimizer usage **Standard:** Ensure that Qwik's optimizer is configured correctly to remove unused code, inline critical CSS, and perform other performance optimizations. * **Do This:** Use the Qwik CLI to initiate projects. This will automatically configures the optimizer. * **Don't Do This:** Skip this step when creating a production build. **Why:** The optimizer minimizes the bundle size, improves initial load performance, and reduces time to interactive (TTI). ### 3.2 Prefetching and lazy loading **Standard:** Strategically implement prefetching for critical resources and lazy loading for non-critical assets. * **Do This:** Employ "useClientEffect$" and "useVisibleTask$" to prefetch data or assets needed shortly after initial load. * **Don't Do This:** Eagerly load all assets upfront. **Why:** Prefetching and lazy loading optimize resource utilization and further enhance performance. **Example:** """tsx //Component is visible load data import { component$, useVisibleTask$ } from '@builder.io/qwik'; import { fetchData } from './services/api'; export const DataComponent = component$(() => { useVisibleTask$(async () => { const data = await fetchData(); // Fetch data when the component becomes visible // use the data }); return ( <div> {/* Render the Data */} </div> ); }); """ ### 3.3 Using Qwik City **Standard:** When building full-fledged applications, leverage Qwik City for routing, data loading, and server-side rendering (SSR). * **Do This:** Follow Qwik City's conventions for defining routes, layouts, and data endpoints. * **Don't Do This:** Manually handle routing and server-side logic in complex applications. **Why:** Qwik City automates common tasks, improving development speed, performance, and SEO. ### 3.4 File system routing **Standard:** Use the Qwik City's file system routing as a key aspect of the core architecture of the Qwik application. * **Do This:** Create files inside the "/routes" directory. These files will then be available as routes in the Qwik App. * **Don't Do This:** Create routes in any other way other than creating files inside the "/routes" directory and expect the routes to work. **Why:** File system routing allows developers to create routes in a Qwik application using the file system, rather than manual route creation. **Example:** """text src/ └── routes/ ├── index.tsx # -> / ├── about/ # -> /about/ └── contact.tsx # -> /contact """ ## 4. Security Best Practices ### 4.1. Secure Coding Practices **Standard:** Adhere to secure coding practices to prevent common web vulnerabilities. * **Do This:** Sanitize user inputs, implement proper authentication and authorization, and prevent cross-site scripting (XSS) and cross-site request forgery (CSRF) attacks. * **Don't Do This:** Trust user input without validation or expose sensitive information in client-side code. **Why:** Security vulnerabilities can compromise the application's integrity and user data. ### 4.2. Environment Variables **Standard:** Securely manage environment variables to avoid exposing sensitive information in source code. * **Do This:** Use ".env" files for development environments and configure environment variables in production deployments. * **Don't Do This:** Hard-code API keys or passwords directly in the source code. **Why:** Environment variables help keep sensitive data separate from the code base. ### 4.3. Dependency Management **Standard:** Manage dependencies effectively and keep packages up to date. * **Do This:** Use a package manager (e.g., npm, yarn, pnpm) to manage dependencies and regularly update packages to address security vulnerabilities. * **Don't Do This:** Use outdated or unmaintained packages. **Why:** Vulnerable dependencies can introduce security risks. ## 5. Common Anti-Patterns * **Over-reliance on Global State:** Avoid using global state excessively. Prefer component-level state or context when possible. * **Direct DOM Manipulation:** Avoid directly manipulating the DOM. Use Qwik's reactive features instead. * **Ignoring Qwik’s Resumability:** Neglecting to optimize for resumability can negate many of Qwik's performance benefits. * **Complex Components:** Large, overly complex components are difficult to maintain and test. Break them down into smaller, reusable parts. * **Inconsistent Folder Structure:** A disorganized folder structure makes it difficult to navigate and maintain the codebase. ## 6. Continuous Improvement This document will be updated periodically to reflect the latest Qwik features, best practices, and community standards. Contributors are encouraged to suggest improvements and refinements to ensure this document remains a valuable resource for Qwik developers.
# Code Style and Conventions Standards for Qwik This document outlines the coding style and conventions standards for Qwik projects, aiming to ensure consistency, readability, and maintainability across the codebase. These standards are designed to be used in conjunction with other rules in this series (e.g., architecture, security), providing a comprehensive guide for Qwik development. These conventions help create code that is easy to understand, debug, and extend, while also leveraging Qwik's unique features for optimal performance. ## 1. General Formatting Consistent formatting is crucial for code readability. Qwik projects should adhere to the following general formatting rules: ### 1.1. Whitespace * **Do This:** * Use 2 spaces for indentation. This helps make your code more readable on smaller screens and promotes a consistent visual structure. """typescript // Good: 2 spaces for indentation export const MyComponent = component$(() => { return ( <div> <p>Hello, Qwik!</p> </div> ); }); """ * **Don't Do This:** * Avoid using tabs for indentation. This can lead to inconsistent formatting across different editors and environments. """typescript // Bad: Tabs for indentation export const MyComponent = component$(() => { return ( <div> <p>Hello, Qwik!</p> </div> ); }); """ * **Why:** Consistent indentation improves code readability and reduces visual clutter. Using spaces ensures consistency across different development environments. ### 1.2. Line Length * **Do This:** * Limit lines to a maximum of 120 characters. Longer lines can be difficult to read and manage. """typescript // Good: Lines under 120 characters export const LongComponentName = component$(() => { return ( <div style={{ width: '100%', backgroundColor: 'lightblue', padding: '10px' }}> <p>This is a component with a long name and some styling.</p> </div> ); }); """ * **Don't Do This:** * Avoid excessively long lines that wrap around the editor. """typescript // Bad: Lines exceeding 120 characters export const LongComponentName = component$(() => { return ( <div style={{ width: '100%', backgroundColor: 'lightblue', padding: '10px', border: '1px solid gray', margin: '5px' }}> <p>This is a component with a long name and some styling. It should be avoided to exceed line limits.</p> </div> ); }); """ * **Why:** Limiting line length improves readability, especially on smaller screens or when comparing different versions of code. ### 1.3. Blank Lines * **Do This:** * Use blank lines to separate logical sections of code. This visually groups related code and improves readability. """typescript // Good: Blank lines for separation export const MyComponent = component$(() => { // Data fetching logic const data = useStore({ name: 'Qwik' }); // Rendering the component return ( <div> <p>Hello, {data.name}!</p> </div> ); }); """ * **Don't Do This:** * Avoid excessive blank lines that create large gaps in the code. """typescript // Bad: Excessive blank lines export const MyComponent = component$(() => { // Data fetching logic const data = useStore({ name: 'Qwik' }); // Rendering the component return ( <div> <p>Hello, {data.name}!</p> </div> ); }); """ * **Why:** Judicious use of blank lines helps to visually organize code into logical blocks, making it easier to understand the structure and flow. ### 1.4. Trailing Commas * **Do This:** * Include trailing commas in multi-line object literals, array literals, and function parameter lists. This simplifies adding, removing, or reordering items. """typescript // Good: Trailing commas const config = { name: 'My App', version: '1.0.0', author: 'John Doe', // Trailing comma }; """ * **Don't Do This:** * Omit trailing commas, as this can lead to unnecessary diffs in version control systems """typescript // Bad: Missing trailing comma const config = { name: 'My App', version: '1.0.0', author: 'John Doe' }; """ * **Why:** Trailing commas make version control diffs cleaner when adding, removing, or reordering items in lists and objects. ### 1.5. Ending Files * **Do This:** * End every file on a newline. ## 2. Naming Conventions Clear and consistent naming conventions are essential for understanding the purpose of different elements in the codebase. ### 2.1. Variables * **Do This:** * Use camelCase for variable names. This convention is widely used in JavaScript and makes variables easily identifiable. """typescript // Good: camelCase variable names const userName = 'John Doe'; const itemCount = 10; """ * **Don't Do This:** * Avoid snake_case or PascalCase for variable names. """typescript // Bad: snake_case variable names const user_name = 'John Doe'; // Bad: PascalCase variable names const UserName = 'John Doe'; """ * **Why:** camelCase is the standard convention in JavaScript and enhances code consistency. ### 2.2. Constants * **Do This:** * Use UPPER_SNAKE_CASE for constants. This clearly distinguishes constants from variables. """typescript // Good: UPPER_SNAKE_CASE constants const API_URL = 'https://example.com/api'; const MAX_ITEMS = 20; """ * **Don't Do This:** * Avoid camelCase or PascalCase for constants. """typescript // Bad: camelCase constants const apiUrl = 'https://example.com/api'; // Bad: PascalCase constants const ApiUrl = 'https://example.com/api'; """ * **Why:** Using UPPER_SNAKE_CASE for constants makes it immediately clear that these values should not be modified. ### 2.3. Functions * **Do This:** * Use camelCase for function names. This maintains consistency with variable naming. """typescript // Good: camelCase function names function calculateTotal(price: number, quantity: number): number { return price * quantity; } const handleClick = () => { console.log('Button clicked!'); }; """ * **Don't Do This:** * Avoid PascalCase or snake_case for function names. """typescript // Bad: PascalCase function names function CalculateTotal(price: number, quantity: number): number { return price * quantity; } // Bad: snake_case function names function calculate_total(price: number, quantity: number): number { return price * quantity; } """ * **Why:** camelCase is the standard convention in JavaScript and enhances code consistency. ### 2.4. Components * **Do This:** * Use PascalCase for component names. This distinguishes components from regular functions or variables. """typescript // Good: PascalCase component names export const MyComponent = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid camelCase or snake_case for component names. """typescript // Bad: camelCase component names export const myComponent = component$(() => { return <p>Hello, Qwik!</p>; }); // Bad: snake_case component names export const my_component = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Why:** PascalCase is the standard convention for React components (and adopted by Qwik), ensuring consistent naming across the codebase. ### 2.5. Files and Directories * **Do This:** * Use kebab-case for file and directory names related to components or routes. Consistent naming makes the project structure more predictable. """ // Good: kebab-case file and directory names src/ └── components/ ├── my-component/ │ ├── my-component.tsx │ └── my-component.css └── other-component/ ├── other-component.tsx └── other-component.css """ * **Don't Do This:** * Avoid camelCase or snake_case for file and directory names. """ // Bad: camelCase file and directory names src/ └── components/ ├── myComponent/ │ ├── myComponent.tsx │ └── myComponent.css └── otherComponent/ ├── otherComponent.tsx └── otherComponent.css """ * **Why:** kebab-case is a common convention for file and directory names in web development, contributing to a more consistent project structure. ## 3. Qwik-Specific Conventions Qwik introduces specific concepts like "component$", "useStore", and "useTask$". Following specific conventions for these elements is essential. ### 3.1. "component$" * **Do This:** * Use "component$" for creating components that leverage Qwik's resumability features. Always lazy-load components using "component$". """typescript // Good: Using component$ for lazy-loaded components import { component$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid using regular function declarations for creating components that should be resumable. Also, avoid defining components inline with other logic. """typescript // Bad: Not using component$ export function OldComponent() { return <p>Hello, Old Qwik!</p>; } """ * **Why:** "component$" ensures that your components are lazy-loaded and resumable, improving performance and initial load time. ### 3.2. "useStore" * **Do This:** * Use "useStore" for managing component state that needs to be persisted across resumability boundaries. """typescript // Good: Using useStore for component state import { component$, useStore } from '@builder.io/qwik'; export const MyComponent = component$(() => { const store = useStore({ name: 'Qwik', count: 0, }); return ( <div> <p>Hello, {store.name}! Count: {store.count}</p> </div> ); }); """ * **Don't Do This:** * Avoid using regular "useState" from React, as it might not be compatible with Qwik's resumability features. """typescript // Bad: Using useState (React) import { component$ } from '@builder.io/qwik'; import { useState } from 'react'; export const MyComponent = component$(() => { const [name, setName] = useState('Qwik'); // This is wrong in Qwik. return ( <div> <p>Hello, {name}!</p> </div> ); }); """ * **Why:** "useStore" is designed to work with Qwik's resumability, ensuring that your component state is correctly persisted and restored. It provides efficient serialization and deserialization for optimal performance. ### 3.3. "useTask$" * **Do This:** * Use "useTask$" to perform side effects that need to run after rendering, and to re-run them whenever specific dependencies change. """typescript // Good: Using useTask$ for side effects import { component$, useTask$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { useTask$(() => { console.log('Component rendered!'); }); return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid using "useClientEffect$" for tasks better suited for execution after server-side rendering or during rehydration on the client, as "useClientEffect$" runs only on the client. Direct DOM manipulation within components (except within "useTask$") """typescript // Bad: Using useClientEffect$ when not needed import { component$, useClientEffect$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { useClientEffect$(() => { console.log('Component rendered on client!'); }); return <p>Hello, Qwik!</p>; }); """ * **Why:** "useTask$" ensures that side effects are properly handled during server-side rendering and client-side rehydration, maintaining consistency and avoiding unexpected behavior. ### 3.4 "useSignal" * **Do This:** * Use "useSignal" when you need reactivity at a more granular level, rather than for entire component state. Signals can be used for tracking very specific values that trigger updates. """typescript import { component$, useSignal } from '@builder.io/qwik'; export const MyComponent = component$(() => { const count = useSignal(0); return ( <div> <p>Count: {count.value}</p> <button onClick$={() => count.value++}>Increment</button> </div> ); }); """ * **Don't Do This:** * Overuse signals for managing large chunks of component state. For larger states, "useStore" is typically more appropriate. * **Why:** "useSignal" provides fine-grained reactivity with minimal overhead, perfect for specific values. However, for complex state management, "useStore" is better suited. ### 3.5. Props * **Do This:** * Explicitly define prop types using interfaces. This provides type safety and enhances code maintainability. """typescript import { component$ } from '@builder.io/qwik'; interface MyComponentProps { name: string; age: number; } export const MyComponent = component$((props: MyComponentProps) => { return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Don't Do This:** * Avoid using "any" for prop types, as this removes type safety and increases the risk of errors. """typescript import { component$ } from '@builder.io/qwik'; export const MyComponent = component$((props: any) => { // Avoid 'any' return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Why:** Explicit prop types improve code clarity and prevent type-related errors, leading to more robust and maintainable components. ## 4. Stylistic Consistency Maintaining stylistic consistency across the codebase improves readability and reduces cognitive load for developers. ### 4.1. Quotes * **Do This:** * Use single quotes (') for JSX attributes and double quotes (") for strings, but choose a single one and adhere to it for the entire project. This is a common convention in JavaScript and HTML. """typescript // Good: Single quotes for attributes, double quotes for strings export const MyComponent = component$(() => { return ( <div className="my-class"> {/*className uses double quotes (but single quotes are also fine if all is consistent)*/} <p>Hello, Qwik!</p> </div> ); }); """ * **Don't Do This:** * Inconsistently use single and double quotes in JSX attributes or strings, leading to visual clutter and reduced readability. """typescript // Bad: Inconsistent quote usage export const MyComponent = component$(() => { return ( <div className='my-class'> {/*className uses single quotes*/} <p>Hello, Qwik!</p> </div> ); }); """ * **Why:** Consistent quote usage improves code readability and reduces visual noise. ### 4.2. Arrow Functions * **Do This:** * Prefer arrow functions for concise function expressions, especially in functional components and callbacks. """typescript // Good: Using arrow functions const handleClick = () => { console.log('Button clicked!'); }; export const MyComponent = component$(() => { return <button onClick$={handleClick}>Click me</button>; }); """ * **Don't Do This:** * Use traditional function declarations when arrow functions would be more concise and readable. """typescript // Bad: Using traditional function declaration function handleClick() { console.log('Button clicked!'); } export const MyComponent = component$(() => { return <button onClick$={handleClick}>Click me</button>; }); """ * **Why:** Arrow functions are more concise and have lexical "this" binding, making them ideal for functional components and callbacks. ### 4.3. Object and Array Literals * **Do This:** * Use concise syntax for object and array literals. This improves readability and reduces boilerplate code. """typescript // Good: Concise object and array literals const user = { name: 'John', age: 30 }; const numbers = [1, 2, 3, 4, 5]; """ * **Don't Do This:** * Use verbose or outdated syntax for object and array literals. """typescript // Bad: Verbose object and array literals const user = new Object(); user.name = 'John'; user.age = 30; const numbers = new Array(); numbers[0] = 1; numbers[1] = 2; """ * **Why:** Concise syntax for object and array literals makes the code more readable and maintainable. ### 4.4. Ternary Operators * **Do This:** * Use ternary operators for simple conditional expressions. This makes the code more concise and readable. """typescript // Good: Using ternary operator const isLoggedIn = true; const message = isLoggedIn ? 'Welcome!' : 'Please log in.'; """ * **Don't Do This:** * Use ternary operators for complex conditional logic, as this can reduce readability. """typescript // Bad: Complex ternary operator const isLoggedIn = true; const userRole = 'admin'; const message = isLoggedIn ? userRole === 'admin' ? 'Welcome, Admin!' : 'Welcome, User!' : 'Please log in.'; """ * **Why:** Ternary operators provide a concise way to express simple conditional logic. For more complex logic, use "if" statements for better readability. ## 5. Error Handling Effective error handling is crucial for building robust and maintainable applications. ### 5.1. Try-Catch Blocks * **Do This:** * Use "try-catch" blocks to handle potential errors that might occur during runtime, especially when dealing with asynchronous operations or external APIs. """typescript // Good: Using try-catch block async function fetchData() { try { const response = await fetch('https://example.com/api/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } } """ * **Don't Do This:** * Ignore potential errors without handling them, as this can lead to unexpected behavior and difficult debugging. """typescript // Bad: Ignoring potential errors async function fetchData() { const response = await fetch('https://example.com/api/data'); const data = await response.json(); // If fetch call fails, exception will halt execution return data; } """ * **Why:** "try-catch" blocks allow you to gracefully handle errors, preventing the application from crashing and providing informative error messages. ### 5.2. Error Boundaries * **Do This:** * Implement error boundaries in your Qwik components to catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the component. """typescript // Good: Error Boundary Component import { component$, ErrorBoundary } from '@builder.io/qwik'; interface Props { children: JSX.Element; } export const ErrorBoundaryComponent = component$((props: Props) => { return ( <ErrorBoundary> {props.children} <template q:fallback> <div> <h2>Something went wrong.</h2> <p>Please try again later.</p> </div> </template> </ErrorBoundary> ); }); """ * **Don't Do This:** * Rely solely on global error handlers, as they might not provide sufficient context and can make it difficult to isolate the source of the error. Avoid letting errors crash the entire application. * **Why:** Error boundaries ensure that errors in one part of the application do not affect other parts, improving the overall user experience and maintainability. ### 5.3 Handling Promises * **Do This:** * Always handle promise rejections, either with ".catch()" or by using "async/await" within a "try...catch" block. Unhandled promise rejections can lead to unhandled exceptions and unexpected behavior. """typescript // Good: Handling promise rejections with .catch() fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error fetching data:', error)); // Good: Handling promise rejections with async/await and try...catch async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); } catch (error) { console.error('Error fetching data:', error); } } """ * **Don't Do This:** * Ignore promise rejections. This can mask errors and lead to unexpected behavior in your application. """typescript // Bad: Ignoring promise rejections fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)); // What if the fetch fails? """ * **Why:** Proper promise rejection handling ensures that errors are caught and handled, leading to a more stable and reliable application. ## 6. Comments and Documentation Clear and concise comments are essential for understanding the purpose and functionality of the code. ### 6.1. Code Comments * **Do This:** * Add comments to explain complex or non-obvious logic. Comments should provide context, explain the purpose of the code, and guide the reader through the implementation. """typescript // Good: Code comments /** * Calculates the discount amount based on the user's membership level. * @param price The original price of the item. * @param membershipLevel The user's membership level (e.g., 'Basic', 'Premium', 'Gold'). * @returns The discount amount. */ function calculateDiscount(price: number, membershipLevel: string): number { let discountPercentage = 0; switch (membershipLevel) { case 'Premium': discountPercentage = 0.1; // 10% discount for Premium members break; case 'Gold': discountPercentage = 0.2; // 20% discount for Gold members break; default: discountPercentage = 0; // No discount for other membership levels } return price * discountPercentage; } """ * **Don't Do This:** * Add redundant comments that simply restate what the code already does. Avoid also using comments to document obvious code. """typescript // Bad: Redundant comments const x = 5; // Assign 5 to x => This isn't descriptive, this is only restating! """ * **Why:** Comments should provide valuable information that is not immediately apparent from the code itself. They should explain the *why* and not just the *what*. ### 6.2. JSDoc Comments * **Do This:** * Use JSDoc-style comments at the beginning of functions and components to describe parameters, return values, and any side effects. This allows IDEs and documentation generators to provide better assistance and documentation. """typescript /** * Fetches user data from the API. * @param id The ID of the user to fetch. * @returns A promise that resolves with the user data, or rejects with an error. */ async function fetchUserData(id:number): Promise<any> { //... } """ ### 6.3. Component Documentation * **Do This:** * Include a comment block at the top of each component file that describes the purpose of the component, its props, and any other relevant information. """typescript // Good: Component documentation /** * A component that displays a user's profile. * * @param props The props for the component. * @param props.name The name of the user. * @param props.age The age of the user. */ import { component$ } from '@builder.io/qwik'; interface MyComponentProps { name: string; age: number; } export const MyComponent = component$((props: MyComponentProps) => { return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Don't Do This:** * Omit component documentation, as this can make it difficult for other developers to understand the purpose and usage of the component. * **Why:** Clear component documentation facilitates code reuse and collaboration, helping other developers quickly understand how to use the component and what to expect from it. ## 7. Imports and Exports Properly managing imports and exports is critical for maintaining a clear and modular codebase. ### 7.1. Absolute Imports * **Do This:** * Favor absolute imports over relative imports, especially for modules in deeply nested directories. Absolute imports make it easier to refactor code and move files without breaking import paths. """typescript // Good: Absolute import import { MyComponent } from 'src/components/my-component'; """ * **Don't Do This:** * Use relative imports that rely on the file's location within the directory structure, as this can make the code more brittle and difficult to refactor. """typescript // Bad: Relative import import { MyComponent } from '../../components/my-component'; """ * **Why:** Absolute imports provide a more stable and predictable way to reference modules, reducing the risk of broken imports during refactoring. They also improve code readability by making it clear where modules are located within the project structure. ### 7.2. Grouping Imports * **Do This:** * Group imports from the same module together and separate them from imports from other modules. This improves code readability and makes it easier to identify dependencies. """typescript // Good: Grouped imports // Qwik imports import { component$, useStore } from '@builder.io/qwik'; // Component imports import { Button } from './button'; import { Input } from './input'; // Utility imports import { formatData } from 'src/utils/format'; import { validateForm } from 'src/utils/validation'; """ * **Don't Do This:** * Scatter imports throughout the file or mix them haphazardly, as this can make it more difficult to understand the dependencies of the code. """typescript // Bad: Scattered imports import { Button } from './button'; import { component$ } from '@builder.io/qwik'; import { formatData } from 'src/utils/format'; import { useStore } from '@builder.io/qwik'; import { Input } from './input'; import { validateForm } from 'src/utils/validation'; """ * **Why:** Grouping imports improves code readability and makes it easier to identify the dependencies of the code at a glance. ### 7.3. Named Exports vs. Default Exports * **Do This:** * Primarily use named exports. They are more explicit and prevent naming collisions in larger projects. When the module is the component itself, the name should match the component name and the filename. """typescript // Good: Named export export const MyComponent = component$(() => { // ... }); """ * **Don't Do This:** * Avoid default exports unless there is one primary export from the module (e.g., a single utility function). It’s harder to trace where default exports originate. """typescript // Bad: Default export export default component$(() => { // ... }); """ * **Why:** Named exports make it clear what is being exported from a module, reducing ambiguity and potential naming conflicts. These code style and convention standards are designed to help Qwik developers write code that is consistent, readable, and maintainable. By following these guidelines, development teams can ensure that their codebases are easy to understand, debug, and extend, while also leveraging Qwik's unique features for optimal performance.