# Tooling and Ecosystem Standards for Bun
This document outlines the coding standards and best practices related to tooling and ecosystem usage when developing with Bun. Adhering to these standards will result in more maintainable, performant, and secure applications.
## 1. Package Management and Dependencies
### 1.1. Bun's Native Package Manager
Bun includes its own native package manager, which is significantly faster than "npm" or "yarn".
**Do This:**
* Use "bun install" for installing dependencies.
* Use "bun add " to add new dependencies.
* Use "bun remove " to remove dependencies.
**Don't Do This:**
* Avoid using "npm" or "yarn" unless absolutely necessary due to specific package compatibility issues.
**Why:**
Bun's package manager is optimized for speed and efficiency within the Bun runtime, leading to faster installation times and reduced overhead.
**Example:**
"""bash
# Install dependencies
bun install
# Add a new dependency
bun add lodash
# Remove a dependency
bun remove lodash
"""
### 1.2. Dependency Versions
**Do This:**
* Use semantic versioning (semver) ranges (e.g., "^1.2.3" or "~1.2.3") in "package.json" to allow for minor and patch updates.
* Regularly update dependencies using "bun update".
* Use "bun lockb" to ensure deterministic builds across environments.
**Don't Do This:**
* Avoid using exact versions (e.g., "1.2.3") unless absolutely necessary to prevent unexpected breaking changes.
* Avoid directly modifying "bun.lockb"—use "bun update" instead.
**Why:**
Semver ranges allow for automatic updates within compatible versions, while "bun lockb" ensures that all environments use the same dependency versions, preventing inconsistencies.
**Example:**
"""json
// package.json
{
"dependencies": {
"lodash": "^4.17.0",
"express": "~4.18.0"
}
}
"""
"""bash
# Update dependencies
bun update
# Create lockfile
bun lockb
"""
### 1.3. Development Dependencies
**Do This:**
* Use "--dev" flag when installing development-only dependencies (e.g., testing libraries, linters).
* Group development dependencies under the "devDependencies" section in "package.json".
**Don't Do This:**
* Avoid installing development dependencies as regular dependencies as this increases the production bundle size.
**Why:**
Separating development dependencies ensures that these tools are not included in the production build, reducing its size and improving performance.
**Example:**
"""bash
# Install a development dependency
bun add --dev eslint
// package.json
{
"devDependencies": {
"eslint": "^8.0.0"
}
}
"""
## 2. Transpilation and Bundling
### 2.1. Native TypeScript Support
Bun has built-in support for TypeScript, so no extra configuration is required in most cases.
**Do This:**
* Leverage Bun's native TypeScript support for faster transpilation.
* Use "tsconfig.json" to customize TypeScript compilation options.
* Use ".ts", ".tsx" extensions for Typescript files.
**Don't Do This:**
* Avoid using external tools for basic TypeScript transpilation unless you need very specific transformations.
* Avoid unnecessary type assertions or "any" types, strive for full type coverage and safety.
**Why:**
Native support eliminates the overhead of external transpilers, reducing build times and simplifying the development process. TypeScript enforces stronger type checking, reducing runtime errors and improving code quality.
**Example:**
"""typescript
// index.ts
function greet(name: string): string {
return "Hello, ${name}!";
}
console.log(greet("Bun"));
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"jsx": "react-jsx" // If using React
}
}
// running the code
bun index.ts
"""
### 2.2. Bundling with Bun
Bun can also act as a bundler similar to webpack or esbuild.
**Do This:**
* Use "bun build" to bundle your application for production.
* Configure bundling options in "bunfig.toml" or through command-line arguments.
* Leverage tree shaking by using ES module syntax to eliminate dead code.
* Consider code splitting where appropriate for larger applications to reduce initial load times.
**Don't Do This:**
* Avoid including unnecessary files or dependencies in the bundle.
* Overcomplicate bundling configurations; leverage Bun defaults where possible.
**Why:**
Bundling optimizes code for deployment by minimizing file sizes and improving loading times.
**Example:**
"""toml
# bunfig.toml
[build]
entryPoints = ["./src/index.ts"]
outdir = "./dist"
minify = true
format = "esm"
"""
"""bash
# Build the application
bun build
"""
### 2.3. ES Modules
**Do This:**
* Use ES modules ("import" and "export") for modularizing code.
* Ensure files have ".js", ".ts", ".jsx", or ".tsx" extensions when using ES modules.
**Don't Do This:**
* Avoid using CommonJS ("require") unless interfacing with legacy code.
* Mix ES modules and CommonJS in the same file.
**Why:**
ES modules are the modern standard for JavaScript modules, offering better static analysis and tree-shaking capabilities. Bun natively supports ES modules, providing seamless integration and performance benefits.
**Example:**
"""typescript
// utils.ts
export function add(a: number, b: number): number {
return a + b;
}
// index.ts
import { add } from './utils';
console.log(add(2, 3));
"""
## 3. Linting and Formatting
### 3.1. ESLint and Prettier
**Do This:**
* Integrate ESLint for linting and Prettier for code formatting.
* Use a shared configuration file (e.g., ".eslintrc.js", ".prettierrc.js") to enforce consistent coding styles.
* Configure ESLint and Prettier to run automatically on file save or commit using IDE extensions or Git hooks.
**Don't Do This:**
* Ignore linting or formatting errors.
* Use inconsistent coding styles across the codebase.
**Why:**
Linting and formatting tools ensure code consistency and help catch potential errors early in the development process, improving code readability and maintainability.
**Example:**
"""bash
# Install eslint and prettier
bun add --dev eslint prettier eslint-config-prettier eslint-plugin-prettier
# .eslintrc.js
module.exports = {
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
env: {
node: true,
es6: true,
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
rules: {},
};
// .prettierrc.js
module.exports = {
semi: true,
trailingComma: 'es5',
singleQuote: true,
printWidth: 100,
};
"""
### 3.2. Editor Configuration
**Do This:**
* Configure your code editor (e.g., VS Code) to automatically format code on save.
* Install ESLint and Prettier extensions for real-time linting and formatting feedback.
* Use editorconfig to maintain consistent coding styles among different editors.
**Don't Do This:**
* Rely solely on manual formatting.
* Ignore editor warnings or suggestions.
**Why:**
Editor integration automates the linting and formatting process, ensuring that code is always consistent and error-free.
**Example:**
"""json
// .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
"""
## 4. Testing
### 4.1. Testing Frameworks
**Do This:**
* Use a testing framework like Jest, Mocha, or Vitest for writing unit and integration tests.
* Write tests for all critical functionality.
* Use clear and descriptive test names.
* Aim for high test coverage.
* Consider using Bun's built-in test runner through "bun test".
**Don't Do This:**
* Skip writing tests for complex or critical logic.
* Write overly complex or brittle tests.
* Rely solely on manual testing.
**Why:**
Automated tests ensure that code behaves as expected and help prevent regressions when making changes.
**Example:**
"""bash
# Using Bun's test runner
// math.test.ts or math.test.js
import { expect, test } from 'bun:test';
import { add } from './math';
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
// Run tests
bun test
"""
### 4.2. Test-Driven Development (TDD)
**Do This:**
* Consider using TDD to write tests before implementing the actual code.
* Follow the red-green-refactor cycle.
**Don't Do This:**
* Write tests only after the code is complete.
**Why:**
TDD helps ensure that code is testable and meets the specified requirements.
## 5. Debugging
### 5.1. Debugging Tools
**Do This:**
* Use Bun's built-in debugger or integrate with VS Code's debugger.
* Use "console.log" or "console.debug" statements sparingly for debugging purposes.
* Use breakpoints to step through code execution.
**Don't Do This:**
* Leave debugging statements in production code.
* Rely solely on trial-and-error debugging.
**Why:**
Debugging tools help identify and fix errors quickly and efficiently.
**Example:**
"""bash
# Debugging with VS Code
// launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "launch",
"name": "Debug Bun Script",
"program": "${workspaceFolder}/index.ts",
"cwd": "${workspaceFolder}"
}
]
}
"""
### 5.2. Logging
**Do This:**
* Use a logging library like "pino" or "winston" for structured logging; if "console.log" is used, keep it consistent.
* Log important events, errors, and warnings.
* Use different log levels (e.g., debug, info, warn, error) to categorize log messages.
* Implement centralized logging for handling and analysis on production systems.
**Don't Do This:**
* Log sensitive information (e.g., passwords, API keys).
* Over-log, creating excessive noise in the logs.
**Why:**
Logging provides valuable insights into the application's behavior and helps diagnose issues in production environments.
**Example:**
"""bash
bun add pino
"""
"""typescript
// logger.ts
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
});
export default logger;
// app.ts
import logger from './logger';
logger.info('Application started');
"""
## 6. Environment Variables and Configuration
### 6.1. Environment Variables
**Do This:**
* Use environment variables for configuring the application (e.g., API keys, database URLs).
* Load environment variables from a ".env" file using a library like "dotenv" or "env-var".
* Validate environment variables at application startup.
**Don't Do This:**
* Hardcode configuration values in the code.
* Store sensitive information in version control.
**Why:**
Environment variables allow for easy configuration changes without modifying the code and help keep sensitive information secure.
**Example:**
"""bash
# Install env-var
bun add env-var
# .env
API_KEY=your_api_key
PORT=3000
"""
"""typescript
// config.ts
import { env } from 'env-var';
const config = {
apiKey: env('API_KEY').required().asString(),
port: env('PORT').default(3000).asPortNumber(),
};
export default config;
// app.ts
import config from './config';
console.log(config.apiKey);
console.log("Server listening on port ${config.port}");
"""
### 6.2. Configuration Files
For more complex configuration, use structured configuration files such as TOML or YAML.
**Do This:**
* Use TOML for Bun configurations (e.g., "bunfig.toml").
* Implement validation and schema for configuration files.
**Don't Do This:**
* Mix configuration types unnecessarily.
* Avoid clear documentation for each setting.
**Why:** Structured configuration files offer improved readability and maintainability, especially for complex configurations.
## 7. Code Documentation
### 7.1. JSDoc
**Do This:**
* Use JSDoc comments to document functions, classes, and modules.
* Include descriptions, parameter types, and return types in JSDoc comments.
* Generate API documentation using tools like TypeDoc.
**Don't Do This:**
* Skip documenting code, especially complex or public-facing APIs.
* Write vague or incomplete documentation.
**Why:**
Code documentation makes it easier for developers to understand and use the code, improving collaboration and maintainability.
**Example:**
"""typescript
/**
* Adds two numbers together.
*
* @param {number} a - The first number.
* @param {number} b - The second number.
* @returns {number} The sum of the two numbers.
*/
export function add(a: number, b: number): number {
return a + b;
}
"""
## 8. Security
### 8.1. Dependency Security
**Do This:**
* Regularly scan dependencies for known vulnerabilities using "bun audit".
* Update vulnerable dependencies to the latest secure versions.
* Use a Software Bill of Materials (SBOM) to keep track of the dependencies.
**Don't Do This:**
* Ignore vulnerability warnings.
* Use outdated or unmaintained dependencies.
**Why:**
Dependency vulnerabilities can expose the application to security risks. Regularly scanning and updating dependencies helps mitigate these risks.
**Example:**
"""bash
# Audit dependencies
bun audit
"""
### 8.2. Input Validation
**Do This:**
* Validate all user inputs to prevent injection attacks.
* Sanitize inputs before using them in database queries or rendering them in the UI.
* Use a library like "joi" or "zod" for input validation.
**Don't Do This:**
* Trust user inputs without validation.
* Pass unsanitized inputs directly to database queries.
**Why:**
Input validation prevents malicious users from injecting code or data into the application, protecting it from security threats.
### 8.3. Secrets Management
**Do This**:
* Store API keys, database passwords, and other secrets using an environment variable manager like Doppler or Hashicorp Vault.
* Avoid hardcoding sensitive credentials directly in the application code.
**Why**: Keeping secrets separate from your code is an important security measure.
## 9. Performance Optimization
### 9.1. Profiling Tools
**Do This:**
* Use Bun's profiling tools to identify performance bottlenecks.
* Measure the performance of critical code paths.
* Optimize slow code paths using techniques like caching, memoization, or algorithmic improvements.
**Don't Do This:**
* Make performance optimizations without measuring their impact.
* Optimize prematurely.
**Why:**
Profiling tools provide insights into the application's performance and help identify areas that can be improved.
### 9.2. Code Splitting
**Do This:**
* Use code splitting to break up large bundles into smaller chunks that can be loaded on demand.
* Use dynamic imports to load modules asynchronously.
**Don't Do This:**
* Create overly granular chunks, which can increase the number of HTTP requests.
**Why:**
Code splitting reduces the initial load time of the application and improves its responsiveness.
## 10. Collaboration and Version Control
### 10.1. Git
**Do This:**
* Use Git for version control.
* Create meaningful commit messages.
* Use branches for developing new features or fixing bugs.
* Follow a consistent branching strategy (e.g., Gitflow).
* Review code before merging it into the main branch.
**Don't Do This:**
* Commit directly to the main branch without review.
* Commit large changes without breaking them into smaller, more manageable commits.
* Commit sensitive information (e.g., passwords, API keys) to version control.
**Why:**
Version control allows for tracking changes to the codebase, collaborating with other developers, and reverting to previous versions if necessary.
### 10.2. Code Reviews
**Do This:**
* Conduct thorough code reviews before merging changes.
* Focus on code quality, security, and performance during code reviews.
* Provide constructive feedback.
**Why:**
Code reviews help catch errors, improve code quality, and share knowledge among team members.
By adhering to these coding standards, you can ensure that your Bun projects are maintainable, performant, and secure. This, in turn, fosters consistent and high-quality code across your development team.
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'
# Component Design Standards for Bun This document outlines coding standards specifically for component design in Bun. It aims to provide guidance for creating reusable, maintainable, and performant components within the Bun runtime environment. These standards are designed to be used both by developers and AI coding assistants. ## 1. Component Architecture & Philosophy ### 1.1. Embrace Functional Components and Reactivity **Standard:** Favor functional components over class-based components. Leverage Bun's native support for reactivity and its compatibility with JavaScript frameworks utilizing reactivity, such as React and Preact. **Do This:** Use functional components with hooks for managing state and side effects. **Don't Do This:** Rely primarily on class-based components unless specifically justified by framework requirements or legacy codebases. **Why:** Functional components are generally easier to read, test, and maintain. They promote a more declarative style of programming. By leveraging reactivity, components can efficiently update only the necessary parts of the UI when data changes. **Example:** """javascript // Functional component using React with Bun import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); } export default Counter; """ ### 1.2. Single Responsibility Principle (SRP) **Standard:** Each component should have a single, well-defined responsibility. **Do This:** Decompose complex components into smaller, more focused components. **Don't Do This:** Create "god components" that handle multiple unrelated tasks. **Why:** SRP improves code readability, maintainability, and testability. It also promotes reusability, as smaller components can be composed in different ways to create new functionality. **Example:** Instead of a single "UserProfile" component that fetches user data, displays profile information, and handles profile editing, break it down into: * "UserFetch": Fetches user data. * "UserProfileDisplay": Displays user profile information. * "UserProfileEditor": Handles profile editing. ### 1.3. Composition over Inheritance **Standard:** Prefer composition over inheritance for code reuse. **Do This:** Create components that can be composed together to create more complex functionality. **Don't Do This:** Rely heavily on inheritance hierarchies to share code between components. **Why:** Composition is more flexible and less prone to the fragile base class problem. It allows you to combine components in various ways without creating tight coupling. Inheritance creates tight coupling making refactoring more difficult. **Example:** """javascript // Composition using React function Button(props) { return ( <button className={"button ${props.className}"} onClick={props.onClick}> {props.children} </button> ); } function PrimaryButton(props) { return ( <Button className="primary-button" {...props}> {props.children} </Button> ); } // Usage <PrimaryButton onClick={() => console.log('Clicked!')}>Click Me</PrimaryButton> """ Here "PrimaryButton" *composes* "Button" instead of inheriting from it. The CSS class styles the "Button" element. ### 1.4. API Design: Props and Events **Standard:** Design clear and concise component APIs using props for configuration and events for communication. **Do This:** * Use descriptive prop names. * Define prop types with "propTypes" or TypeScript. * Pass data down via props. * Communicate events up via callbacks. * Use default prop values where appropriate. **Don't Do This:** * Rely on global state directly within components (except for specific framework-managed contexts). * Mutate props directly. * Create overly complex prop structures. **Why:** Well-defined component APIs make components easier to understand, use, and test. They also reduce the risk of unexpected behavior. Clear interfaces are critical for maintaining a modular architecture. **Example (React with Prop Types):** """javascript import React from 'react'; import PropTypes from 'prop-types'; function Greeting(props) { return <p>Hello, {props.name}!</p>; } Greeting.propTypes = { name: PropTypes.string.isRequired, }; Greeting.defaultProps = { name: 'Guest', }; export default Greeting; """ ## 2. Component Implementation Details ### 2.1. State Management **Standard:** Use appropriate state management techniques based on component complexity and scope. **Do This:** * Use local component state for simple, isolated state. * Use context/providers for sharing state between related components. * Leverage state management libraries (e.g., Redux, Zustand, Jotai) for complex, application-wide state. **Don't Do This:** * Overuse global state for local component concerns. * Mutate state directly without using "setState" or appropriate state management hooks. **Why:** Proper state management ensures predictable component behavior and prevents performance issues. **Example (React with useState hook):** """javascript import React, { useState } from 'react'; function Toggle() { const [isOn, setIsOn] = useState(false); const toggle = () => { setIsOn(!isOn); }; return ( <button onClick={toggle}> {isOn ? 'On' : 'Off'} </button> ); } export default Toggle; """ ### 2.2. Rendering Optimization **Standard:** Optimize component rendering for performance. **Do This:** * Use "React.memo" or similar techniques to prevent unnecessary re-renders. * Implement "shouldComponentUpdate" (if using class components) or "useMemo" to control render updates. * Use virtualization techniques for large lists. * Debounce or throttle event handlers for performance-sensitive updates. **Don't Do This:** * Force re-renders unnecessarily. * Perform heavy calculations or expensive operations during rendering. **Why:** Optimizing rendering prevents performance bottlenecks, especially in complex UIs. **Example (React with React.memo):** """javascript import React from 'react'; function DisplayName({ name }) { console.log("DisplayName re-rendered!"); // For debugging return <p>Name: {name}</p>; } export default React.memo(DisplayName, (prevProps, nextProps) => { // Return true if props are equal, preventing re-render. return prevProps.name === nextProps.name; }); //Usage function App() { const [name, setName] = React.useState("Initial Name"); return ( <div> <DisplayName name={name} /> <button onClick={() => setName("New Name")}>Change Name</button> </div> ); } """ In this example, the "DisplayName" component will only re-render if the "name" prop changes. ### 2.3. Asynchronous Operations **Standard:** Handle asynchronous operations within components correctly. **Do This:** * Use "async/await" syntax for cleaner asynchronous code. * Handle errors appropriately with "try/catch" blocks or ".catch()" methods. * Use "useEffect" hook in React for performing side effects (including asynchronous operations) after rendering. **Don't Do This:** * Ignore errors from asynchronous operations. * Update component state after the component has been unmounted (leading to potential memory leaks). Use "AbortController" to manage asynchronous operations and cancel them when the component unmounts. **Why:** Proper asynchronous handling ensures that components behave predictably and avoids common errors like race conditions and memory leaks. **Example (React with useEffect and async/await):** """javascript import React, { useState, useEffect } from 'react'; function DataFetcher({ url }) { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { setIsLoading(true); try { const response = await fetch(url); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } const json = await response.json(); setData(json); } catch (e) { setError(e); } finally { setIsLoading(false); } }; fetchData(); }, [url]); // useEffect depends on the 'url' prop if (isLoading) { return <p>Loading...</p>; } if (error) { return <p>Error: {error.message}</p>; } return ( <div> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); } export default DataFetcher; """ ### 2.4. Accessibility (a11y) **Standard:** Build accessible components that are usable by people with disabilities. **Do This:** * Use semantic HTML elements. * Provide alternative text for images. * Ensure proper keyboard navigation. * Use ARIA attributes to enhance accessibility. * Test components with accessibility tools (e.g., Axe). **Don't Do This:** * Rely solely on visual cues. * Use generic elements (e.g., "<div>", "<span>") without appropriate semantic roles. **Why:** Accessibility makes your application usable by a wider audience and is often a legal requirement. **Example (Accessible Button):** """javascript import React from 'react'; function AccessibleButton({ onClick, children, ariaLabel }) { return ( <button onClick={onClick} aria-label={ariaLabel}> {children} </button> ); } export default AccessibleButton; """ ### 2.5. Testing **Standard:** Write unit tests, integration tests, and end-to-end tests for your components. **Do This:** * Use testing frameworks like Jest or Vitest. * Test component behavior in various scenarios. * Use mocking to isolate components during testing. * Strive for high test coverage. * Write tests for edge cases. **Don't Do This:** * Skip testing complex components. * Write tests that are too tightly coupled to implementation details. * Rely solely on manual testing. **Why:** Testing ensures that your components function correctly and reduces the risk of regressions. Automated tests are crucial for maintainability and continuous integration. Bun is compatible with all Javascript testing frameworks and runners. **Example (Jest test for Counter Component):** """javascript import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Counter from './Counter'; test('increments count when button is clicked', () => { render(<Counter />); const incrementButton = screen.getByText('Increment'); fireEvent.click(incrementButton); const countElement = screen.getByText('Count: 1'); expect(countElement).toBeInTheDocument(); }); """ ## 3. Bun-Specific Considerations ### 3.1. Performance Benefits **Standard:** Leverage Bun's performance advantages when designing components. **Do This:** * Use Bun's fast file system APIs for component data loading scenarios (where applicable and appropriate for design context). * Benchmark different component implementations to identify performance bottlenecks. * Use Bun's faster startup time to your advantage when rendering server-side components. **Don't Do This:** * Assume that Bun automatically optimizes all component code; profiling and optimization are still necessary. * Neglect standard web performance optimizations (e.g., code splitting, lazy loading). **Why:** Bun's performance improvements can significantly improve the responsiveness and user experience of your components. However, you still need to write efficient code and apply standard best practices. ### 3.2. Ecosystem Compatibility **Standard:** Ensure compatibility with the broader JavaScript ecosystem. **Do This:** * Use standard ES modules. * Follow established npm/yarn package management practices. * Leverage existing React, Preact, or other framework component libraries (if applicable). **Don't Do This:** * Rely on non-standard Bun-specific features unless absolutely necessary. * Create custom component ecosystems that are incompatible with existing tools and libraries. **Why:** Maintaining ecosystem compatibility ensures that your components can be easily reused and integrated with other projects. ### 3.3. Server Components in Bun **Standard:** Utilize server components wisely, especially when using React Server Components. **Do This:** * Fetch data directly on the server for improved security and performance. * Keep server components close to the data source and separate from client-side interactivity. * Take advantages of the server's file system and database access capabilities. **Don't Do This:** * Introduce client-side dependencies in server components (they don't execute on the client). * Handle UI interactivity and state management on the server. They should be in client components. **Example:** """javascript // Server Component (fetching data) import { Database } from 'bun:sqlite' async function getProducts() { const db = new Database('mydb.sqlite'); const products = db.query("SELECT * FROM products").all(); db.close(); return products } export default async function ProductList() { const products = await getProducts(); return ( <ul> {products.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> ); } """ ### 3.4. Bun Plugins for Components **Standard:** Create and use Bun plugins to extend component functionality. **Do This:** * Use Bun plugins to transform component code during the build process. * Create plugins for tasks such as CSS-in-JS compilation, image optimization, and code minification. * Publish your component plugins to the Bun package registry. **Don't Do This:** * Overuse plugins for simple component transformations. * Create plugins that modify component behavior at runtime. **Example:** """javascript // bun-plugin-example.js export default { name: "example-plugin", setup(build) { build.onLoad({ filter: /\.css$/ }, async (args) => { // Transform CSS files const contents = await Bun.file(args.path).text(); const transformedContents = transformCSS(contents); // Implement your CSS transformation logic return { contents: transformedContents, loader: "css" }; }); }, }; // bunfig.toml [plugins] "example-plugin" = "./bun-plugin-example.js" """ ## 4. Common Anti-Patterns * **God Components:** Components responsible for too many unrelated tasks. * **Prop Drilling:** Passing props through multiple layers of components that don't need them. Use context or state management libraries to avoid this. * **Tight Coupling:** Components that are highly dependent on each other, making it difficult to reuse or modify them independently. * **Ignoring Error Handling:** Failing to handle errors in asynchronous operations or other potentially failing code. * **Over-optimization:** Spending too much time optimizing components that are not performance bottlenecks. Measure before optimizing. * **Lack of Testing:** Failing to write tests for components, leading to regressions and difficult maintenance. * **Direct DOM Manipulation:** Directly manipulating the DOM without using the framework's facilities unless there is a very specific and compelling reason to do so. Increases complexity and risk. * **Global State Abuse:** Using global state for localized component concerns. This creates unnecessary dependencies. ## 5. Conclusion These component design standards for Bun provide a foundation for building high-quality, maintainable, and performant applications. By following these guidelines, developers can create components that are easy to understand, reuse, test, and extend. Remember that the best standards are those which are adopted and followed by the team. So, ensure the standards are clear, concise and readily available when needed. Regularly review this document and iterate on it to adapt to the ever-evolving landscape of web development.
# Performance Optimization Standards for Bun This document outlines performance optimization standards for Bun projects. It provides guidelines for writing efficient and performant code, covering various aspects from architectural considerations to low-level implementation details. These standards aim to improve application speed, responsiveness, and resource usage, leveraging Bun's features and capabilities. ## 1. Architectural Considerations ### 1.1. Modular Design **Standard:** Break down large applications into smaller, independent modules with well-defined interfaces. * **Do This:** Use ES modules or CommonJS modules to organize code. * **Don't Do This:** Create monolithic files with tightly coupled code. **Why:** Modules improve code organization, testability, and reusability. They also allow for selective loading of code, reducing initial load times. **Example:** """javascript // math.js export function add(a, b) { return a + b; } // app.js import { add } from './math.js'; console.log(add(5, 3)); """ ### 1.2. Microservices Architecture **Standard:** Consider microservices for large-scale applications where independent services can be scaled and deployed separately. * **Do This:** Design microservices with clear API boundaries and efficient communication protocols (e.g., HTTP/2, gRPC). * **Don't Do This:** Build a single, large application that's difficult to scale and maintain. **Why:** Microservices enhance scalability, fault isolation, and deployment flexibility. ### 1.3. Caching Strategies **Standard:** Implement caching at various levels (e.g., browser, CDN, server) to reduce latency and server load. * **Do This:** Use HTTP caching headers, CDN caching, and in-memory caching. Leverage Bun's built-in caching capabilities where applicable. * **Don't Do This:** Overlook caching opportunities or use overly aggressive caching strategies. **Why:** Caching reduces network requests and server processing, improving response times. **Example:** """javascript // Setting HTTP caching headers (using Elysia.js) import { Elysia } from 'elysia' const app = new Elysia() .get('/', () => 'Hello Elysia', { response: { headers: { 'Cache-Control': 'public, max-age=3600' // 1 hour cache } } }) .listen(3000) """ ### 1.4. Connection Pooling **Standard:** Employ connection pooling for database or other resource connections to minimize connection overhead. * **Do This:** Use a connection pool library (e.g., "pg" for PostgreSQL, "mysql2" for MySQL) to manage connections. Bun's built-in SQLite support automatically handles connection pooling. * **Don't Do This:** Create a new connection for each request. **Why:** Connection pooling reduces the overhead of establishing and closing connections, improving performance. **Example (PostgreSQL with "pg"):** """javascript import { Pool } from 'pg'; const pool = new Pool({ user: 'dbuser', host: 'localhost', database: 'mydb', password: 'secretpassword', port: 5432, max: 20, // Maximum number of clients in the pool idleTimeoutMillis: 30000, // Close idle clients after 30 seconds connectionTimeoutMillis: 2000, // Return an error after 2 seconds if connection could not be established }); async function queryDatabase(sql, params) { const client = await pool.connect(); try { const result = await client.query(sql, params); return result; } finally { client.release(); // Release the client back to the pool } } // Example usage queryDatabase('SELECT * FROM users WHERE id = $1', [1]) .then(result => console.log(result.rows)) .catch(err => console.error(err)); """ ## 2. Code-Level Optimization ### 2.1. Efficient Data Structures **Standard:** Choose appropriate data structures for specific tasks to minimize time and space complexity. * **Do This:** Use Maps for key-value lookups, Sets for unique value collections, and Arrays when order matters. * **Don't Do This:** Use Arrays when key-value lookups are frequent or perform unnecessary iterations. **Why:** Selecting the right data structure can significantly improve algorithm performance. **Example:** """javascript // Using a Map for efficient key-value lookup const userMap = new Map(); userMap.set('123', { name: 'Alice', age: 30 }); const user = userMap.get('123'); // O(1) lookup // Using a Set for unique value collection const uniqueIds = new Set(); uniqueIds.add(1); uniqueIds.add(2); uniqueIds.add(1); // Duplicate is ignored """ ### 2.2. Minimize Object Creation **Standard:** Reduce unnecessary object creation to minimize garbage collection overhead. * **Do This:** Reuse objects when possible, use object pools, and avoid creating objects in tight loops. * **Don't Do This:** Create new objects for every iteration in a loop or store large amounts of unneeded data in objects. **Why:** Excessive object creation leads to frequent garbage collection cycles, which can pause execution. **Example:** """javascript // Reusing an object const point = { x: 0, y: 0 }; for (let i = 0; i < 1000; i++) { point.x = i; point.y = i * 2; // Use the point object } // Object Pooling (Manual Implementation - Consider using a library for production) class ObjectPool { constructor(objectFactory, size) { this.pool = []; this.objectFactory = objectFactory; this.size = size; this.initialize(); } initialize() { for (let i = 0; i < this.size; i++) { this.pool.push(this.objectFactory()); } } acquire() { if (this.pool.length > 0) { return this.pool.pop(); } else { // Pool is empty, create a new object if necessary return this.objectFactory(); } } release(object) { this.pool.push(object); } } //usage const myObjectPool = new ObjectPool(() => ({x: 0, y: 0}), 10); const obj = myObjectPool.acquire(); obj.x = 5; obj.y = 10; myObjectPool.release(obj); """ ### 2.3. Optimize Loops **Standard:** Write efficient loops to minimize the number of iterations and operations within the loop. * **Do This:** Cache loop invariants, unroll loops, and use optimized loop constructs (e.g., "for...of" vs. "for...in"). * **Don't Do This:** Perform expensive operations within loops, access array lengths in each iteration, or use inefficient loop types. **Why:** Loop optimization can significantly reduce execution time for repetitive tasks. **Example:** """javascript // Caching loop invariant const arr = [1, 2, 3, 4, 5]; const len = arr.length; // Cache the length for (let i = 0; i < len; i++) { console.log(arr[i]); } // Optimized for...of loop: for (const item of arr) { console.log(item); } """ ### 2.4. String Concatenation **Standard:** Use efficient string concatenation methods to minimize memory allocation and copying. * **Do This:** Use template literals or array joins for large strings. Avoid repeated string concatenation using the "+" operator. * **Don't Do This:** Use the "+" operator for concatenating many strings, creating unnecessary intermediate strings. **Why:** String concatenation can be expensive, especially for large strings. Template literals and array joins are more efficient. Bun also optimizes string handling more efficiently than traditional Node.js. **Example:** """javascript // Efficient string concatenation with template literals const name = 'Alice'; const age = 30; const message = "Hello, my name is ${name} and I am ${age} years old."; // Efficient string concatenation with array joins const parts = ['Hello', 'my name is', name, 'and I am', age, 'years old.']; const message2 = parts.join(' '); """ ### 2.5. Regular Expression Optimization **Standard:** Optimize regular expressions for performance by minimizing backtracking and using appropriate flags (e.g., "i" for case-insensitive matching). * **Do This:** Use specific patterns, avoid using ".*" at the beginning of expressions, and precompile regular expressions for reuse. * **Don't Do This:** Use overly complex regular expressions or create new regular expressions in loops. **Why:** Inefficient regular expressions can cause performance bottlenecks. **Example:** """javascript // Precompiling a regular expression const regex = /pattern/g; // 'g' for global, precompiled function testString(str) { return regex.test(str); } console.log(testString('pattern')); """ ### 2.6 Asynchronous operations **Standard:** Utilize Bun's fast "async/await" and native Promise implementations effectively. Avoid blocking the event loop. * **Do This:** Use "async/await" for concise asynchronous code. Take advantage of Bun's optimized Promise implementation. Employ "Bun.sleep()" for non-blocking delays when needed. Consider using "Bun.spawn" for executing external commands concurrently without blocking. * **Don't Do This:** Use callback-hell or block the event loop with synchronous operations, especially when dealing with network requests or file I/O. **Why:** Asynchronous programming allows for non-blocking operations, which are crucial for maintaining responsiveness in I/O-bound applications. Bun's fast event loop and optimized Promise implementation make async operations more performant. "Bun.sleep()" pauses execution without blocking the main thread, and "Bun.spawn" enables efficient execution of external commands, crucial for performance in task orchestration. **Example:** """javascript // Async operation with async/await async function fetchData() { try { const response = await fetch('https://example.com/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } } // Usage fetchData().then(data => { if (data) { console.log('Data:', data); } }); //Example of non-blocking delay async function delayedAction() { console.log("Starting action..."); await Bun.sleep(2000); // Wait for 2 seconds without blocking console.log("Action completed!"); } delayedAction(); //Example using Bun.spawn for concurrent task execution async function executeCommand(command) { const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe", }); const output = await new Response(proc.stdout).text(); const errorOutput = await new Response(proc.stderr).text(); const exitCode = await proc.exited; return { output, errorOutput, exitCode }; } async function main() { console.log("Starting concurrent command execution..."); const { output, errorOutput, exitCode } = await executeCommand(["ls", "-l"]); if (exitCode === 0) { console.log("Command output:", output); } else { console.error("Command failed with error:", errorOutput); } } main(); """ ## 3. Bun-Specific Optimizations ### 3.1. Native Modules (FFI) **Standard:** Use Bun's native module capabilities (Foreign Function Interface - FFI) for performance-critical tasks by writing code directly in Zig (Bun's underlying language) or other languages like C/C++. * **Do This:** Utilize FFI to implement computationally intensive operations or when leveraging existing native libraries. * **Don't Do This:** Overuse FFI for tasks that can be efficiently handled in JavaScript or Typescript, as the overhead of context switching can negate the benefits. **Why:** FFI allows you to bypass JavaScript's performance limitations by executing code directly in native languages, which can be significantly faster for CPU-bound tasks. This is particularly beneficial in Bun due to its efficient interop capabilities with Zig. **Example:** """zig // example.zig const std = @import("std"); export fn add(a: i32, b: i32) i32 { return a + b; } """ """typescript // index.ts const lib = new Bun.FFI({ dlopen: { // Use dlopen for maximum compatibility, even if less performant linux: "./example.so", darwin: "./example.dylib", windows: "./example.dll", }, symbols: { add: { args: [Bun.FFI.i32, Bun.FFI.i32], returns: Bun.FFI.i32, }, }, }); const result = lib.symbols.add(5, 3); console.log(result); // Output: 8 """ ### 3.2. Bun.Transpiler **Standard:** Leverage "Bun.Transpiler" for ahead-of-time (AOT) compilation of TypeScript and JSX to JavaScript. * **Do This:** Utilize "Bun.Transpiler" in your build process to pre-compile code, especially for server-side rendering or computationally intensive tasks, improving startup time and runtime performance. * **Don't Do This:** Rely solely on just-in-time (JIT) compilation for production deployments, as it can introduce performance overhead during runtime. **Why:** AOT compilation with "Bun.Transpiler" reduces the need for runtime compilation or transpilation, leading to faster execution and reduced CPU usage, especially during the initial stages of application execution. **Example:** """typescript // Compile TypeScript to JavaScript const transpiler = new Bun.Transpiler({ loader: 'ts', }); const tsCode = " function add(a: number, b: number): number { return a + b; } console.log(add(5, 3)); "; const jsCode = await transpiler.transform(tsCode); console.log(jsCode); // Compile JSX to JavaScript. const jsxCode = " function MyComponent() { return ( <div> <h1>Hello, world!</h1> </div> ); } "; const transpilerJSX = new Bun.Transpiler({ loader: "jsx", }); const jsCodeJSX = await transpilerJSX.transform(jsxCode); console.log(jsCodeJSX); """ ### 3.3. Bun.serve **Standard:** Usethe "Bun.serve" API for high-performance web servers. * **Do This:** Utilize "Bun.serve" for creating web servers with its optimized HTTP implementation, WebSocket support, and TLS integration. * **Don't Do This:** Rely on traditional Node.js frameworks like Express.js for new Bun projects unless backward compatibility is a firm requirement. **Why:** "Bun.serve" is designed for speed and efficiency within the Bun runtime, delivering significant performance improvements compared to traditional Node.js web frameworks due to its optimized architecture and low-level access. **Example:** """typescript // Serving static files and dynamic content const server = Bun.serve({ port: 3000, async fetch(req) { const url = new URL(req.url); if (url.pathname === '/') { return new Response('Hello, Bun!'); } if (url.pathname.startsWith('/static')) { const filePath = '.' + url.pathname; const file = Bun.file(filePath); return new Response(file); } return new Response('404 Not Found', { status: 404 }); }, error(error) { console.error(error); return new Response("Internal Server Error", { status: 500 }); }, }); console.log("Server listening on port ${server.port}"); """ ### 3.4. SQLite Integration **Standard:** Harness Bun's native SQLite integration for performant database operations. * **Do This:** Leverage the built-in SQLite client when dealing with lightweight data storage needs, especially for read-heavy workloads. * **Don't Do This:** Neglect optimizing SQLite queries or schema design, as these considerably impact performance. For very large or complex data scenarios, consider other database solutions and connection pooling as above. **Why:** Bun's native SQLite integration offers excellent performance for simple data storage use cases. Operations are generally very fast when reading. **Example:** """typescript import { Database } from 'bun:sqlite'; // Use ":memory:" for an in-memory database const db = new Database(':memory:'); db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);'); // Insert data const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?);'); insert.run('Alice', 'alice@example.com'); insert.run('Bob', 'bob@example.com'); // Query data const query = db.prepare('SELECT * FROM users;'); const users = query.all(); console.log(users); db.close(); """ ## 4. Memory Management ### 4.1. Explicitly Release Resources **Standard:** Ensure that resources like file handles, database connections, and allocated memory are explicitly released when they are no longer needed. * **Do This:** Use "try...finally" blocks to ensure resources are released even if errors occur. Close database connections after use if not using connection pooling. * **Don't Do This:** Rely solely on garbage collection to release resources, as it can be unpredictable and lead to resource leaks. **Why:** Explicit resource management prevents memory leaks and ensures that system resources are available for other processes. **Example:** """typescript import { open, close } from 'node:fs/promises'; async function readFile(filePath) { let fileHandle = null; try { fileHandle = await open(filePath, 'r'); const contents = await fileHandle.readFile({ encoding: 'utf8' }); return contents; } catch (error) { console.error('Error reading file:', error); return null; } finally { if (fileHandle) { await fileHandle.close(); console.log('File handle closed.'); } } } readFile('example.txt').then(contents => { if (contents) { console.log('File contents:', contents); } }); """ ### 4.2. Avoid Global Variables **Standard:** Minimize the use of global variables to avoid unintentional memory retention and naming conflicts. * **Do This:** Use closures, modules, or classes to encapsulate variables and limit their scope. * **Don't Do This:** Declare large objects or arrays as global variables. **Why:** Global variables can lead to increased memory usage and make it harder to reason about the state of the application. ### 4.3. Weak References **Standard:** Use WeakRef and WeakMap objects when holding references to objects that should not prevent garbage collection. * **Do This:** Employ WeakRef for caching or memoization of expensive computations when the underlying data can be reclaimed by the garbage collector. Use WeakMap to associate data which are needed only when the original key objects are alive. * **Don't Do This:** Overuse weak references, as they can make debugging more difficult and introduce unpredictable behavior if not managed carefully. **Why:** Weak references allow you to hold references to objects without preventing them from being garbage collected, which is useful for caching and other memory-sensitive operations. **Example:** """typescript // Using WeakRef for caching const cache = new Map(); function getCachedData(key, expensiveComputation) { if (cache.has(key)) { const weakRef = cache.get(key); const cachedData = weakRef.deref(); //Dereference, return either undefined or the original value if (cachedData) { console.log('Using cached data'); return cachedData; } } const data = expensiveComputation(key); cache.set(key, new WeakRef(data)); console.log('Computed and cached data'); return data; } // Example Usage function computeData(key) { console.log('Computing data for key:', key); // Simulate an expensive computation return "Expensive data for ${key}"; } let data1 = getCachedData('key1', computeData); // Computes and caches the data let data2 = getCachedData('key1', computeData); // Retrieves data from cache //Simulate a garbage collection global.gc(); let data3 = getCachedData('key1', computeData); //Recomputes after GC """ ## 5. Monitoring and Profiling ### 5.1. Performance Monitoring Tools. **Standard:** Integrate performance monitoring tools to track application performance metrics and identify bottlenecks. * **Do This:** Use tools like Bun's built-in profiler, "console.time" and "console.timeEnd", or third-party APM solutions (like DataDog). * **Don't Do This:** Ignore performance metrics or rely solely on anecdotal evidence to identify performance issues. **Why:** Performance monitoring provides insights into application behavior and helps identify areas for optimization. **Example:** """javascript // Using console.time and console.timeEnd console.time('myFunction'); // Code to be measured for (let i = 0; i < 100000; i++) { //Some task } console.timeEnd('myFunction'); // Outputs the time taken //Simple Bun-specific profiler example Bun.profile(() => { //Code to profile for (let i = 0; i < 1000000; i++) { //Some task } }, { output: "profile.json" }) """ ### 5.2. Profiling Techniques **Standard:** Use profiling techniques (e.g., CPU profiling, memory profiling) to identify performance bottlenecks and memory leaks. * **Do This:** Use the built-in Node.js Inspector, Chrome DevTools, or third-party profiling tools to capture performance data. * **Don't Do This:** Profile code without a specific goal or fail to analyze profiling data. **Why:** Profiling helps pinpoint the exact lines of code that are causing performance issues. ## 6. Testing and Continuous Integration ### 6.1. Performance Testing **Standard:** Implement performance tests to measure and track application performance over time. * **Do This:** Use tools like Jest or k6 to write performance tests that simulate real-world scenarios. * **Don't Do This:** Neglect performance testing or rely solely on functional tests. **Why:** Performance tests help ensure that performance does not degrade with code changes. ### 6.2. Continuous Integration **Standard:** Integrate performance testing into the continuous integration (CI) pipeline. * **Do This:** Run performance tests automatically on each commit or pull request and set performance budgets to prevent regressions. * **Don't Do This:** Run performance tests manually or skip them during CI. **Why:** Continuous integration ensures that performance is continuously monitored and that regressions are detected early. By adhering to these performance optimization standards, Bun developers can build efficient, scalable, and performant applications that deliver exceptional user experiences while maximizing resource utilization. Remember to stay up-to-date with the latest version of Bun and its features to leverage the latest performance improvements.
# Core Architecture Standards for Bun This document outlines core architectural standards for Bun projects, focusing on project structure, fundamental patterns, and organization principles to ensure maintainability, performance, and security. These standards are designed to leverage the latest features of Bun and promote consistency across development teams. ## 1. Project Structure and Organization A well-defined project structure greatly enhances maintainability and collaboration. Bun projects should adhere to a modular and component-based organization. ### 1.1. Standard Directory Layout **Do This:** Adopt a consistent directory structure across all Bun projects. A recommended structure is: """ bun-project/ ├── src/ # Source code directory │ ├── components/ # Reusable, independent UI components │ ├── models/ # Data models and schemas │ ├── services/ # Business logic and external API interactions │ ├── utils/ # Utility functions and helpers │ ├── types/ # TypeScript type definitions │ ├── app.ts # Main application entry point │ └── router.ts # Route definitions (if using a custom router) ├── public/ # Static assets (HTML, CSS, images) ├── test/ # Unit and integration tests │ ├── components/ │ ├── models/ │ └── services/ ├── .env # Environment variables (using "bunfig.toml" is preferred) ├── bun.lockb # Dependency lockfile ├── bunfig.toml # Bun configuration file ├── README.md # Project documentation └── tsconfig.json # TypeScript configuration """ **Don't Do This:** Use a flat or haphazard directory structure. Avoid placing all files in a single "src/" directory without logical separation. **Why:** Promotes modularity, easy navigation, and clear separation of concerns. Consistent structure simplifies onboarding for new developers. **Example:** A server-side rendered React component within this structure: """ bun-project/ └── src/ └── components/ └── UserProfile.tsx """ """typescript // src/components/UserProfile.tsx import React from 'react'; interface UserProfileProps { username: string; bio: string; } const UserProfile: React.FC<UserProfileProps> = ({ username, bio }) => { return ( <div> <h2>{username}</h2> <p>{bio}</p> </div> ); }; export default UserProfile; """ ### 1.2. Modular Design **Do This:** Break down the application into self-contained modules (components, services, etc.). Favor small, focused modules over large, monolithic ones. Use ES modules ("import"/"export") for dependency management. **Don't Do This:** Create tightly coupled modules with excessive dependencies on each other. Avoid global state and side effects as much as possible. **Why:** Modularity improves code reusability, testability, and maintainability. It also allows for easier parallel development and feature isolation. **Example:** Example of using ES modules: """typescript // src/services/UserService.ts const fetchUser = async (id: string) => { const response = await fetch("https://api.example.com/users/${id}"); return await response.json(); }; export { fetchUser }; """ """typescript // src/components/UserProfile.tsx import { fetchUser } from '../services/UserService'; // ... component logic using fetchUser """ ### 1.3. Configuration Management **Do This:** Utilize "bunfig.toml" for project configuration. Store environment-specific variables in ".env" files (or, better yet, use a secrets management system for production). Access environment variables using "Bun.env". **Don't Do This:** Hardcode configuration values directly in the source code. Commit ".env" files to version control (especially those containing secrets). **Why:** Decouples configuration from code, allowing different environments (development, staging, production) to be easily managed without code changes. Using "bunfig.toml" allows for specific bun configurations. **Example:** "bunfig.toml" configuration: """toml # bunfig.toml debug = true port = 3000 [test] database_url = "postgresql://user:password@host:port/testdb" """ """typescript // src/app.ts const debugMode = Bun.env.DEBUG === 'true'; const port = parseInt(Bun.env.PORT || '3000'); console.log("Running in debug mode: ${debugMode}"); console.log("Server listening on port ${port}"); """ ### 1.4 Typescript Usage **Do This**: Employ TypeScript extensively to catch type-related errors early. Create and utilise types to increase the readibility and reliablity of your code. **Don't Do This**: Avoid introducing "any" types without due cause, as this reduces the benfits offered by typescript. **Why**: Typescript enables earlier error checking, increased reliabilty, and improved readibility. **Example:** """typescript interface User { id: number, name: string, email: string } async function getUser(url : string) : Promise<User> { const response = await fetch(url); const user = await response.json() as User; return user; } """ ## 2. Architectural Patterns Choosing the appropriate architectural pattern dramatically impacts an application’s characteristics. Given Bun's strengths, certain patterns are well-suited. ### 2.1. Layered Architecture **Do This:** Organize the application into distinct layers (e.g., presentation, application, domain, infrastructure). Each layer should have a specific responsibility and only depend on the layers below it. **Don't Do This:** Create circular dependencies between layers or allow layers to bypass intermediate layers. **Why:** Layered architecture promotes separation of concerns, making it easier to understand, test, and modify different parts of the application independently. **Example:** * **Presentation Layer:** Handles user interface and input/output. (React components, API endpoints) * **Application Layer:** Orchestrates the application logic, invoking services and coordinating data flow. (Route handlers, controller logic) * **Domain Layer:** Contains the core business logic and domain entities. (Business rules, validation logic) * **Infrastructure Layer:** Provides access to external resources (databases, APIs, file system). (Database connections, API clients) A simplified example: """typescript // src/presentation/UserController.ts import { UserService } from '../application/UserService'; class UserController { private userService: UserService; constructor(userService: UserService) { this.userService = userService; } async getUser(req: Request, res: Response) { const userId = req.params.id; const user = await this.userService.getUserById(userId); res.json(user); } } // src/application/UserService.ts import { UserRepository } from '../domain/UserRepository'; class UserService { private userRepository: UserRepository; constructor(userRepository: UserRepository) { this.userRepository = userRepository; } async getUserById(id: string) { return this.userRepository.findById(id); } } // src/domain/UserRepository.ts interface UserRepository { findById(id: string): Promise<User | null>; } // src/infrastructure/PostgresUserRepository.ts import { UserRepository } from '../domain/UserRepository'; class PostgresUserRepository implements UserRepository { async findById(id: string) { // Database query using Bun's SQLite or PostgreSQL support // Example depends on the chosen database library return { id, name: 'Example User' }; // Replace with actual implementation } } """ ### 2.2. Microservices Architecture (Considerations) **Do This:** If the application is complex and needs to scale independently, consider a microservices architecture. Each microservice should handle a specific business capability. Use asynchronous communication (e.g., message queues) to decouple services. **Don't Do This:** Start with a microservices architecture for simple applications. Avoid tight coupling between microservices (e.g., shared databases). **Why:** Microservices allow for independent deployment, scaling, and technology choices. Provides fault isolation if one microservice fails. **Example:** Consider a simple e-commerce application. Separate microservices could handle: * **Product Catalog:** Manages product information. * **Order Management:** Handles order creation and processing. * **User Authentication:** Manages user accounts and authentication. These microservices could communicate via HTTP APIs or a message queue like Redis or RabbitMQ. ### 2.3. API Gateway **Do This:** Use an API gateway to route requests to the appropriate microservice, handle authentication, and provide other cross-cutting concerns (e.g., rate limiting, logging). Consider using Bun.serve to create a simple API gateway. **Don't Do This:** Expose microservices directly to the outside world without a gateway. **Why:** The API gateway provides a single entry point for the application, simplifying client interactions and improving security. **Example:** """typescript // api-gateway.ts async function handleRequest(req: Request): Response { const url = new URL(req.url); const pathname = url.pathname; if (pathname.startsWith("/products")) { // Forward to product catalog return await fetch("http://product-catalog-service:3001" + pathname, { method: req.method, headers: req.headers, body: req.body, }); } else if (pathname.startsWith("/orders")) { // Forward to order management service return await fetch("http://order-management-service:3002" + pathname, { method: req.method, headers: req.headers, body: req.body, }); } else { return new Response("Not Found", { status: 404 }); } } Bun.serve({ port: 3000, fetch: handleRequest, }); """ ## 3. Coding Practices Adhering to consistent coding practices enhances code readability, maintainability, and collaboration. ### 3.1. Immutability **Do This:** Favor immutable data structures and operations. Use "const" for variables that should not be reassigned. Avoid mutating arrays and objects directly; use methods like "map", "filter", "reduce", and the spread operator ("...") to create new instances. **Don't Do This:** Modify data structures in place. Rely on mutable state, especially in concurrent scenarios. **Why:** Immutability simplifies reasoning about code, prevents unexpected side effects, and improves performance in certain cases. **Example:** """typescript // Mutable approach (bad) const numbers = [1, 2, 3]; numbers.push(4); // Mutates the original array console.log(numbers); // [1, 2, 3, 4] // Immutable approach (good) const numbers2 = [1, 2, 3]; const newNumbers = [...numbers2, 4]; // Creates a new array console.log(numbers2); // [1, 2, 3] console.log(newNumbers); // [1, 2, 3, 4] """ ### 3.2. Asynchronous Programming **Do This:** Use "async/await" for asynchronous operations. Handle errors using "try/catch" blocks. Leverage Bun's built-in async APIs (e.g., "Bun.file()", "Bun.serve()"). Utilize Promises for handling asynchronous results and rejections. **Don't Do This:** Rely on callbacks or nested callbacks (callback hell). Ignore potential errors in asynchronous operations. **Why:** "async/await" makes asynchronous code easier to read and reason about. Proper error handling prevents unhandled exceptions and improves application stability. **Example:** """typescript // Using async/await async function readFile(filePath: string): Promise<string> { try { const file = Bun.file(filePath); const text = await file.text(); return text; } catch (error) { console.error("Error reading file: ${error}"); throw error; // Re-throw the error to be handled upstream } } // Calling the function readFile('my-file.txt') .then(content => console.log(content)) .catch(error => console.error('Failed to read file')); """ ### 3.3 Concurrent programming **Do This:** Use the "Bun.spawn" API, or worker threads to run your code concurrently. **Don't Do This** Block the main thread of your application. **Why:** Concurrent programming allows for better application responsiveness, increased throughput, and improved resource utilization. **Example** """typescript //spawn example const { stdout, stderr, exited } = Bun.spawn(['ls', '-l']); const output = await new Response(stdout).text(); console.log(output); """ ### 3.4. Error Handling **Do This:** Implement robust error handling using "try...catch" blocks for synchronous code and ".catch()" for Promises. Log errors with sufficient context (e.g., timestamp, error message, stack trace). Use custom error classes to provide more specific error information. **Don't Do This:** Swallow errors without logging or handling them. Rely on default error messages without providing context. **Why:** Proper error handling prevents application crashes, provides valuable debugging information, and improves user experience. **Example:** """typescript class CustomError extends Error { constructor(message: string, public code: string) { super(message); this.name = 'CustomError'; } } async function processData(data: any) { try { if (!data) { throw new CustomError('Data is null or undefined', 'DATA_INVALID'); } // ... process data } catch (error: any) { console.error("Error processing data: ${error.message}", { code: error.code, stack: error.stack, timestamp: new Date().toISOString(), }); throw error; // Re-throw the error for upstream handling } } """ ## 4. Security Best Practices Building secure applications is paramount. These guidelines focus on minimizing common vulnerabilities in Bun environments. ### 4.1. Input Validation **Do This:** Validate all user inputs to prevent injection attacks (e.g., SQL injection, XSS). Sanitize inputs before using them in queries or displaying them in the UI. Use established validation libraries (e.g., Joi, validator.js). **Don't Do This:** Trust user inputs without validation. Directly concatenate user inputs into SQL queries or HTML. **Why:** Input validation prevents malicious users from injecting arbitrary code or data into the application. **Example:** """typescript //Using zod for validation: import { z } from "zod"; const UserSchema = z.object({ username: z.string().min(3).max(20), email: z.string().email(), password: z.string().min(8), }); function createUser(userData: any) { try { const validatedData = UserSchema.parse(userData); // ... create user in database using validatedData } catch (error: any) { console.error("Validation error:", error.errors); throw error; } } """ ### 4.2. Authentication and Authorization **Do This:** Implement secure authentication mechanisms (e.g., JWT, OAuth 2.0) to verify user identities. Use authorization to control access to resources based on user roles and permissions. Store passwords securely using bcrypt or Argon2. **Don't Do This:** Store passwords in plain text. Implement custom authentication logic without using established security protocols. Grant excessive permissions to users. **Why:** Authentication and authorization protect sensitive data and prevent unauthorized access to application resources. **Example:** """typescript // Authenticating a user and creating a JWT. This is simple and should be expanded upon utilizing secure environment // variables and error handling import { sign } from 'jsonwebtoken'; async function loginUser(username: string, password: string) { //Query user in DB with details const payload = { username: username, id: '123' } const token = await sign(payload, 'shhhhh'); return token; } """ ### 4.3. Dependency Management **Do This:** Regularly update dependencies to patch security vulnerabilities. Use "bun audit" or "npm audit" to identify and fix known vulnerabilities. Lock dependencies using "bun.lockb" (or "package-lock.json" with npm) to ensure consistent builds. **Don't Do This:** Use outdated dependencies with known security vulnerabilities. Ignore security audit warnings. **Why:** Keeping dependencies up-to-date mitigates the risk of exploiting known vulnerabilities in third-party libraries. ### 4.4. Secrets Management **Do This:** Store sensitive information (e.g., API keys, database passwords) in environment variables or a dedicated secrets management system (e.g., HashiCorp Vault, AWS Secrets Manager). Never commit secrets to version control. **Don't Do This:** Hardcode secrets in the source code or configuration files. Store secrets in easily accessible locations (e.g., ".env" files in production). **Why:** Secure secrets management protects sensitive information from unauthorized access. ## 5. Performance Optimization Bun is designed for performance. Maximize its potential by following these guidelines. ### 5.1. Code Splitting **Do This:** Use code splitting to reduce the initial load time of the application. Load only the necessary code for each page or feature. **Don't Do This:** Bundle the entire application into a single large JavaScript file. **Why:** Code splitting improves application performance by reducing the amount of code that needs to be downloaded and parsed initially. **Example:** Dynamic imports in React: """typescript // Example inside a React component import React, { useState, useEffect } from 'react'; function MyComponent() { const [module, setModule] = useState<any>(null); useEffect(() => { import('./HeavyComponent') // This triggers code splitting .then(HeavyComponent => { setModule(() => HeavyComponent.default); }); }, []); if (!module) { return <div>Loading...</div>; } const HeavyComponent = module; return <HeavyComponent />; } export default MyComponent; """ ### 5.2. Caching **Do This**: Implement caching strategies to store frequently accessed data in memory or on disk. Use HTTP caching headers to leverage browser caching. Consider using a dedicated caching service like Redis or Memcached for more complex caching requirements. Leverage Bun's efficient file system APIs for disk-based caching. **Don't Do This:** Cache sensitive data without proper security measures. Invalidate the cache frequently or never, leading to stale data. **Why:** Caching reduces latency and improves application responsiveness by serving data from a cache instead of fetching it from a slower data source. **Example:** Basic in-memory caching: """typescript const cache = new Map(); async function getData(key: string, fetchFunction: () => Promise<any>) { if (cache.has(key)) { console.log('Serving from cache'); return cache.get(key); } const data = await fetchFunction(); cache.set(key, data); return data; } async function fetchFromAPI() { //Simulate data fetching: await new Promise(r => setTimeout(r, 2000)); return {data : 'Fetched Data!'}; } async function main() { const result = await getData('myKey', fetchFromAPI); console.log(result); const cachedResult = await getData('myKey', fetchFromAPI); console.log(cachedResult); } main(); """ ### 5.3. Efficient Data Structures and Algorithms **Do this:** Utilise appropriate data structures and effective algorithms. **Don't do this:** Ignore the time complexity of your algorithms. **Why:** Efficient data structures allow for faster program execution. ## 6. Testing Thorough testing is crucial for identifying and fixing bugs early in the development process. ### 6.1. Unit Tests **Do This:** Write unit tests for individual modules and functions to verify their correctness in isolation. Use a testing framework like Jest, Mocha, or Vitest (Vitest often integrates well with a Bun-based workflow). Aim for high code coverage. **Don't Do This:** Skip unit tests or write superficial tests that don't cover all edge cases. **Why:** Unit tests provide fast feedback on code changes and prevent regressions. **Example:** Jest unit test: """typescript //Example add function: function add(a : number, b : number) : number { return a + b; } // add.test.ts import { add } from './add'; describe('add', () => { it('should return the sum of two numbers', () => { expect(add(1, 2)).toBe(3); expect(add(-1, 1)).toBe(0); expect(add(0, 0)).toBe(0); }); }); """ ### 6.2. Integration Tests **Do This:** Write integration tests to verify the interaction between different modules or services. Test the integration with external APIs and databases. **Don't Do This:** Skip integration tests or rely solely on unit tests. **Why:** Integration tests ensure that the different parts of the application work together correctly. ### 6.3. End-to-End Tests **Do This** Write end-to-end tests to simulate user interactions and verify the application's functionality from the user's perspective. Use tools like Playwright or Cypress. **Don't Do This:** Skip end-to-end tests or rely solely on unit and integration tests. **Why:** End-to-end tests provide confidence that the application works as expected in a real-world environment. ## 7. Documentation Comprehensive documentation is essential for maintainability and knowledge sharing. ### 7.1. Code Comments **Do This:** Write clear and concise comments to explain complex logic, algorithms, and design decisions. Follow a consistent commenting style. **Don't Do This:** Write unnecessary comments that state the obvious or repeat the code. **Why:** Code comments help other developers (and your future self) understand the code more easily. ### 7.2. API Documentation **Do This:** Generate API documentation using tools like JSDoc or Swagger/OpenAPI. Describe the purpose, parameters, and return values of each API endpoint. **Don't Do This:** Skip API documentation or write incomplete documentation. **Why:** API documentation makes it easier for other developers to consume the API. ### 7.3. README **Do This**: Write a comprehensive README file for each project. Include: Project description, how to get started (installation, configuration), how to run tests and how to deploy. **Don't Do This:** Leave the README empty, or include a basic description. **Why:** Improves project usability. By adhering to these core architecture standards, Bun projects can be built to be more maintainable, performant, and secure. This guide should be considered a living document, subject to updates as Bun evolves and best practices emerge.
# State Management Standards for Bun This document outlines standards for managing application state in Bun projects. It focuses on maintainability, performance, reactivity, and security, providing actionable guidelines and illustrative examples. ## 1. Principles of State Management in Bun State management in Bun applications involves orchestrating data flow, reactivity, and application state changes. Effective state management leads to predictable application behavior, easier debugging, and scalable architectures. Bun’s speed and Javascript/Typescript compatibility provide a robust foundation for various state management approaches. ### 1.1. Immutability **Standard:** Treat application state as immutable whenever possible. Modify state through explicit updates that create new state objects rather than mutating existing ones. **Why:** Immutability simplifies debugging, change detection, and concurrency management. Mutation makes it difficult to track state changes and can lead to unexpected side effects. **Do This:** """typescript // Preferred: Creating a new object const oldState = { count: 0 }; const newState = { ...oldState, count: oldState.count + 1 }; // Using a library like Immer import { produce } from "immer"; const baseState = { deeply: { nested: { obj: { count: 0 } } } }; const nextState = produce(baseState, (draft) => { draft.deeply.nested.obj.count += 1; }); """ **Don't Do This:** """typescript // Avoid: Mutating the original object const state = { count: 0 }; state.count = 1; // Direct mutation """ ### 1.2. Unidirectional Data Flow **Standard:** Enforce a clear, unidirectional data flow. State changes should originate from a single source and propagate downwards through the component tree or application modules. **Why:** Unidirectional data flow simplifies reasoning about application behavior and makes debugging easier. Avoid bidirectional data flow, which can result in entangled dependencies and unpredictable state updates. **Do This:** Define clear actions or events that trigger state updates, and ensure these updates are handled in a predictable manner. **Don't Do This:** Allow components to directly modify the state of other components or external data stores. Create central data stores that multiple components can mutate unexpectedly. ### 1.3. Centralized vs. Decentralized State **Standard:** Choose the appropriate state management strategy based on the complexity and scale of your application. Centralized state management is suitable for complex applications, while decentralized state management may be adequate for smaller applications. **Why:** Centralized state offers a single source of truth, simplifies debugging, and streamlines data flow, but can introduce complexity. Decentralized state is flexible for smaller applications, but harder manage in large, collaborative codebases. **Do This:** * For small to medium-sized applications, consider React's built-in "useState" and "useContext" hooks or lightweight libraries like Zustand. * For large and complex applications, use state management libraries like Redux, Zustand, or Jotai. **Don't Do This:** * Over-engineer state management for small applications by using complex centralized state management solutions. * Underestimate the complexity of state management for larger applications by relying solely on component-level state. ### 1.4. Predictable Side Effects **Standard:** Isolate and manage side effects (e.g., API calls, timers, DOM manipulations) explicitly. **Why:** Side effects can make application state unpredictable and difficult to test. Managing them predictably improves code maintainability and reliability. **Do This:** * Use middleware in libraries like Redux (e.g., Redux Thunk, Redux Saga) to handle asynchronous actions. * Use React's "useEffect" hook to manage side effects within components. **Don't Do This:** * Perform side effects directly within component render functions or state reducers. * Ignore error handling when dealing with side effects. ## 2. State Management Approaches in Bun Bun's compatibility with JavaScript and TypeScript allows it to fully support a variety of state management paradigms. Choosing the right one depends on your project size, complexity, and architectural preferences. ### 2.1. Global State Management Libraries #### 2.1.1. Redux **Standard:** Use Redux for complex, large-scale applications requiring predictable state management and centralized control. **Why:** Redux provides a well-defined architecture (store, actions, reducers) and encourages immutability, making it easier to reason about application state. Middleware support facilitates handling asynchronous actions and side effects. **Do This:** """typescript // actions.ts export const increment = () => ({ type: 'INCREMENT' }); export const decrement = () => ({ type: 'DECREMENT' }); // reducers.ts const initialState = { count: 0 }; const reducer = (state = initialState, action: any) => { switch (action.type) { case 'INCREMENT': return { ...state, count: state.count + 1 }; case 'DECREMENT': return { ...state, count: state.count - 1 }; default: return state; } }; export default reducer; // store.ts import { createStore } from 'redux'; import reducer from './reducers'; const store = createStore(reducer); export default store; // Component Example import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement } from './actions'; function Counter() { const count = useSelector((state: any) => state.count); const dispatch = useDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch(increment())}>Increment</button> <button onClick={() => dispatch(decrement())}>Decrement</button> </div> ); } export default Counter; """ **Don't Do This:** * Avoid using Redux for trivial applications, as it introduces unnecessary boilerplate. * Mutate state directly within reducers; always return a new state object. * Place complex business logic within components, keep that logic inside actions and/or middleware. #### 2.1.2. Zustand **Standard:** Use Zustand for simpler state management with minimal boilerplate, particularly in React applications. **Why:** Zustand offers a straightforward API, reduces boilerplate, and simplifies state mutations using Immer integration. It is a good choice for projects where Redux is too complex, but component-level state is insufficient. **Do This:** """typescript import { create } from 'zustand'; interface CounterState { count: number; increment: () => void; decrement: () => void; } const useCounterStore = create<CounterState>((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), })); // Component Example function Counter() { const { count, increment, decrement } = useCounterStore(); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } export default Counter; """ **Don't Do This:** * Don't overuse global state in Zustand for component-specific state. * Avoid complex state logic directly in components. Define state updates within the store's set function. #### 2.1.3. Jotai **Standard:** Use Jotai for fine-grained state management with a focus on simplicity and performance. **Why:** Jotai uses an atomic approach, where each piece of state is represented as a "atom." It offers excellent performance due to its minimal re-renders and integrates well with React. **Do This:** """typescript import { atom, useAtom } from 'jotai'; const countAtom = atom(0); // Component Example function Counter() { const [count, setCount] = useAtom(countAtom); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setCount(count - 1)}>Decrement</button> </div> ); } export default Counter; """ **Don't Do This:** * Avoid overusing atomic state if related pieces of state are frequently updated together. Consider grouping them within a single atom for efficiency. * Don't directly mutate the value of an atom; always use the "setCount" function. ### 2.2. Component-Local State **Standard:** Use React's "useState" and "useReducer" hooks for managing state within individual components, particularly for simple, localized state. **Why:** Component-local state simplifies state management for smaller components and avoids the overhead of global state management libraries. **Do This:** """typescript import { useState, useReducer } from 'react'; function Counter() { const [count, setCount] = useState(0); const reducer = (state: number, action: { type: 'increment' | 'decrement' }) => { switch (action.type) { case 'increment': return state + 1; case 'decrement': return state - 1; default: return state; } }; const [state, dispatch] = useReducer(reducer, 0); return ( <div> <p>Count (useState): {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <p>Count (useReducer): {state}</p> <button onClick={() => dispatch({ type: 'increment' })}>Increment (useReducer)</button> </div> ); } export default Counter; """ **Don't Do This:** * Avoid prop drilling by passing state down through multiple layers of components. Consider using "useContext" or a global state management library. * Overuse component-local state for state that needs to be shared across multiple components or application modules. ### 2.3. Context API **Standard:** Use React's Context API for sharing state across a component tree without prop drilling. **Why:** The Context API provides a simple way to share state between components without manually passing props through each level of the tree. **Do This:** """typescript import React, { createContext, useContext, useState } from 'react'; interface ThemeContextType { theme: string; toggleTheme: () => void; } const ThemeContext = createContext<ThemeContextType | undefined>(undefined); interface ThemeProviderProps { children: React.ReactNode; } function ThemeProvider({ children }: ThemeProviderProps) { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); }; const value: ThemeContextType = { theme, toggleTheme, }; return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; } function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; } // Component Example function ThemedComponent() { const { theme, toggleTheme } = useTheme(); return ( <div style={{ backgroundColor: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}> <p>Current Theme: {theme}</p> <button onClick={toggleTheme}>Toggle Theme</button> </div> ); } function App() { return ( <ThemeProvider> <ThemedComponent /> </ThemeProvider> ); } export default App; """ **Don't Do This:** * Avoid managing complex, frequently changing state with the Context API, as it can trigger unnecessary re-renders. Consider using a global state management library for more granular control over updates. * Don't update the context value unnecessarily. Make sure the components using the Context API are optimized to avoid re-rendering when the context value changes. This is particularly important if the context value contains large or complex objects ## 3. Performance Considerations Efficient state management is crucial for building high-performance Bun applications. Overly complex or inefficient state management can lead to performance bottlenecks and a poor user experience. ### 3.1. Minimizing Re-renders **Standard:** Minimize unnecessary component re-renders by optimizing state updates and using memoization techniques. **Why:** Frequent re-renders can degrade performance, particularly in complex UIs. Ensuring that components only re-render when their relevant state changes improves responsiveness. **Do This:** * Use "React.memo" to memoize functional components based on their props. * Use "useMemo" and "useCallback" hooks to memoize computed values and event handlers. * Use selectors in Redux to compute derived data and prevent unnecessary re-renders. * Use "shouldComponentUpdate" (for class components) to prevent re-renders based on prop and state changes. **Don't Do This:** * Avoid passing new objects or functions as props to memoized components, as this will cause them to re-render regardless of the actual prop values. * Don't skip memoization entirely; analyze the render performance of your components and memoize those that are causing bottlenecks. ### 3.2. Lazy Initialization **Standard:** Initialize state lazily when it is not immediately needed, especially for expensive or asynchronous operations. **Why:** Lazy initialization improves application startup time and reduces unnecessary computations. **Do This:** * Use the lazy initialization form of "useState": """typescript const [data, setData] = useState(() => { // Perform expensive operation here return computeInitialData(); }); """ * Implement lazy loading of modules or components that are not immediately visible on the screen. **Don't Do This:** * Perform expensive operations during component initialization that block the UI thread and degrade performance. ### 3.3. Batch State Updates **Standard:** Batch multiple state updates into a single update to reduce the number of re-renders. **Why:** Batching updates minimizes the overhead associated with each state update and improves rendering performance. **Do This:** * Use "ReactDOM.unstable_batchedUpdates" or "setTimeout" to batch multiple state updates within a single event handler. """typescript import ReactDOM from 'react-dom'; function handleClick() { ReactDOM.unstable_batchedUpdates(() => { setCount(count + 1); setOtherValue(otherValue + 1); }); } """ * Use "useReducer" to combine multiple state updates into a single dispatch. **Don't Do This:** * Avoid triggering multiple state updates within a tight loop or event handler without batching them, as this can lead to performance issues. ## 4. Security Best Practices for State Management Secure state management is essential to protect sensitive data and prevent vulnerabilities in Bun applications. Following security best practices helps mitigate risks and ensure the integrity of your application. ### 4.1. Storing Sensitive Data **Standard:** Avoid storing sensitive data (e.g., passwords, API keys, personal information) directly in application state, especially in client-side state. **Why:** Client-side state is often exposed to the client and can be easily accessed by malicious actors. Storing sensitive data in client-side state increases the risk of data breaches. **Do This:** * Store sensitive data on the server and only expose it to the client as needed using secure APIs. * Use encrypted storage mechanisms (e.g., browser cookies with the "HttpOnly" and "Secure" flags) for sensitive data that must be stored on the client. * Use environment variables to store API keys and configuration settings, and avoid hardcoding them directly in the source code. **Don't Do This:** * Store passwords, API keys, or other sensitive data directly in component state or global state. * Expose sensitive data in client-side logs or error messages. * Commit sensitive data to version control systems. ### 4.2. Input Validation and Sanitization **Standard:** Validate and sanitize all user inputs before storing them in application state or sending them to the server. **Why:** Input validation and sanitization prevent cross-site scripting (XSS) attacks and other security vulnerabilities by ensuring that user inputs are safe and do not contain malicious code. **Do This:** * Use input validation libraries (e.g., Joi, Yup) to validate user inputs against predefined schemas. * Use sanitization libraries (e.g., DOMPurify) to sanitize user inputs and remove any potentially malicious code. * Encode user inputs properly when displaying them in the UI to prevent XSS attacks. **Don't Do This:** * Trust user inputs implicitly without validation or sanitization. * Insert user inputs directly into the DOM without encoding them. * Disable XSS protection in your application. ### 4.3. Secure API Communication **Standard:** Use HTTPS for all API communication to encrypt data in transit and prevent eavesdropping. **Why:** HTTPS ensures that data exchanged between the client and server is encrypted, protecting it from interception and tampering. **Do This:** * Obtain and configure SSL/TLS certificates for your server. * Enforce HTTPS by redirecting all HTTP requests to HTTPS. * Use secure API authentication mechanisms (e.g., JWT, OAuth) to verify the identity of clients and protect API endpoints. **Don't Do This:** * Use HTTP for API communication, especially when transmitting sensitive data. * Store API keys or authentication tokens in client-side code. * Disable SSL/TLS encryption on your server. ### 4.4. State Injection **Standard:** Avoid directly injecting state from external sources (query parameters, local storage) into your state management without proper validation **Why:** Directly injecting state from external sources without validation can lead to unexpected or malicious behavior, especially if the external source is controlled by an attacker. **Do This:** * Create a validation schema for data that can be injected. * Properly handle any exceptions raised by the validation process. * Do not trust any inputs that have not been validated. **Don't Do This:** * Trust that data from external sources is in a secure manner without validation * Inject code into application state that is not validated first. ## 5. Error Handling and Logging Proper error handling and logging are crucial for maintaining the stability and reliability of Bun applications. ### 5.1. Centralized Error Handling **Standard:** Implement a centralized error handling mechanism to catch and handle errors consistently across your application. **Why:** Catching errors in one place makes it easier to log and manage the errors. Implement graceful fallbacks when errors occur. **Do This:** * Create a global error boundary component to catch and handle errors from all child components. * Use "try...catch" blocks to catch errors in asynchronous operations and state updates. * Log errors to a centralized logging service for analysis and monitoring. **Don't Do This:** * Let unhandled exceptions crash the application. * Ignore errors silently without logging them. ### 5.2. Logging **Standard**: Implement robust logging practices to track state changes, API requests, and other important events in your application. **Why**: Comprehensive logs make it easy to analyze runtime issues. Logging best practice involves setting the proper logging level i.e. debug, info, warn or error as appropriate. **Do This:** * Use structured logging to capture state changes. * Correlate logs from React components from Bun server components. **Don't Do This:** * Store sensitive information in logs. * Depend on console logging for production applications. ### 5.3. State Recovery **Standard**: Implement state recovery mechanisms to restore the application to a stable state after an error occurs. **Why**: Crash the application leads to data loss and bad user experience. State recovery could involve reloading the application, redirecting to the default state, or using a previous state in session storage. **Do This:** * Implement rollback logic. * Persist state from session storage. Handle errors that occur. **Don't Do This:** * Leave the application in bad state after an error. * Store sensitive information in local or session storage. ## 6. Testing State Management Thoroughly testing your state management logic is essential for ensuring the correctness and reliability of your Bun applications. ### 6.1. Unit Testing **Standard**: Write unit tests for all state reducers, actions, and selectors to verify their behavior in isolation. **Why**: Ensure the application is accurate and prevent regression bugs. Writing the unit tests in isolation also validates if components are loosely coupled. **Do This:** * Use testing libraries to test state management. * Mock dependencies to ensure stability. **Don't Do This:** * Neglect writing unit tests to state management components. * Implement the application without unit tests. ### 6.2. Integration Testing **Standard**: Write integration tests to verify the interaction between state management and the React components. **Why**: Check and ensure the interaction between state management and components is working as expected. Verify functional flows and logic integration across various components. **Do This:** * Use testing libraries like Playwright to write integration tests. * Mock API requests and other dependencies to ensure stability. **Don't Do This:** * Fail to test the integration components. ### 6.3 End-to-End (E2E) Testing **Standard**: Write complete E2E tests to simulate user interactions and verify the application as a whole. **Why**: Catch bugs early and allow developers to ship new features quickly and efficiently. Write the functional flows as the user would experience. **Do This:** * Use end-to-end testing fameworks such as Selenium to test components. * Make sure E2E tests cover the most important user flows. **Don't Do This:** * Neglect complete E2E testing. * Skimp on E2E testing to save on expenses. * Rely only on manual testing. These standards provide a comprehensive guide for managing state in your Bun applications. Adhering to these guidelines will result in cleaner, more maintainable, secure, and performant code. By following these guidelines and best practices, you can build scalable and enjoyable Bun applications.
# Testing Methodologies Standards for Bun This document outlines the testing standards for Bun projects, covering unit, integration, and end-to-end testing strategies. It aims to ensure code quality, reliability, and maintainability while leveraging Bun's unique features. ## 1. General Testing Principles ### 1.1. Test Pyramid * **Do This:** Follow the test pyramid concept, prioritizing unit tests, followed by integration tests, and lastly, end-to-end tests. * **Don't Do This:** Rely heavily on end-to-end tests at the expense of unit and integration tests. This leads to slow feedback loops and difficult debugging. * **Why:** The test pyramid ensures a balanced testing strategy. Unit tests are fast and isolate problems, while integration and end-to-end tests verify interactions between components and the system as a whole. ### 1.2. Test-Driven Development (TDD) * **Do This:** Write tests *before* implementing the code. * **Don't Do This:** Write tests after the code is already written, or skip tests altogether. * **Why:** TDD forces you to think about the desired behavior and interface of your code before implementation, leading to better design and testability. ### 1.3. Test Coverage * **Do This:** Aim for high test coverage (e.g., 80% or higher), but prioritize testing critical business logic and complex code paths. Use Bun's built-in test coverage tools to measure coverage. * **Don't Do This:** Strive for 100% coverage at all costs. This can lead to writing superficial tests that don't add value and become difficult to maintain. * **Why:** Test coverage provides a metric for assessing the extent to which your code is tested. Focus should be on meaningful tests, not just achieving a specific number. ### 1.4. Test Naming and Organization * **Do This:** Use descriptive test names that clearly indicate what is being tested. Organize tests logically within your project structure. Match component/module folder structure. * **Don't Do This:** Use vague or ambiguous test names. Dump all tests into a single folder. * **Why:** Clear naming and organization make it easier to understand the purpose of tests and navigate the test suite. ## 2. Unit Testing ### 2.1. Scope and Purpose * **Do This:** Unit test individual functions, classes, or modules in isolation, mocking dependencies. * **Don't Do This:** Test interactions between different parts of the system in unit tests. That's the job of integration tests. * **Why:** Unit tests are fast, reliable, and isolate problems to specific units of code. ### 2.2. Assertion Libraries * **Do This:** Use a robust assertion library like "expect" (built-in to Bun) or "chai". * **Don't Do This:** Write custom assertion logic. * **Why:** Assertion libraries provide a clear and consistent way to express expectations in your tests. ### 2.3. Mocking and Stubbing * **Do This:** Use mocking libraries like "sinon" or "jest" (compatible with Bun) to isolate the unit under test. Focus on stubbing external dependencies and data to control inputs. * **Don't Do This:** Over-mock, replace parts that are not relevant to the target test or mock the target of the test itself. * **Why:** Mocking allows you to test units of code in isolation without relying on external systems. ### 2.4. Example: Unit Testing a Simple Function """typescript // src/utils.ts export function add(a: number, b: number): number { return a + b; } // test/utils.test.ts import { add } from '../src/utils'; import { expect, it, describe } from 'bun:test'; describe('add', () => { it('should return the sum of two numbers', () => { expect(add(2, 3)).toBe(5); }); it('should handle negative numbers', () => { expect(add(-1, 5)).toBe(4); }); it('should handle zero', () => { expect(add(0, 0)).toBe(0); }); }); """ ### 2.5. Example: Unit Testing Asynchronous Code """typescript // src/api.ts export async function fetchData(url: string): Promise<any> { const response = await fetch(url); return response.json(); } // test/api.test.ts import { fetchData } from '../src/api'; import { expect, it, describe, mock } from 'bun:test'; describe('fetchData', () => { it('should fetch data from the API', async () => { const mockResponse = { data: 'test data' }; global.fetch = mock(() => Promise.resolve({ json: () => Promise.resolve(mockResponse), }) as any ); const data = await fetchData('https://example.com/api'); expect(data).toEqual(mockResponse); }); it('should handle errors', async () => { global.fetch = mock(() => Promise.reject(new Error('Network error')) as any); await expect(fetchData('https://example.com/api')).rejects.toThrow('Network error'); }); }); """ ### 2.6. Anti-Patterns in Unit Testing * **Testing implementation details:** Unit tests should focus on the *behavior* of the code, not its implementation. Avoid tests that depend on specific internal logic. * **Skipping error handling:** Make sure to test how your code handles errors and edge cases. * **Using real dependencies:** Avoid hitting real databases, APIs, or other external systems in unit tests. Use mocks, stubs, or test doubles. ## 3. Integration Testing ### 3.1. Scope and Purpose * **Do This:** Integration tests verify the interactions between different modules or components within your system. Often focuses on the 'seams' between components/services, or between a service and its database. * **Don't Do This:** Test individual units of code in isolation. Test the entire system end-to-end. * **Why:** Integration tests ensure that different parts of your application work together correctly. ### 3.2. Database Integration * **Do This:** Use test databases for integration tests. Seed the database with known data before running tests. Clean up the database after tests. Tools like "testcontainers" may also be useful. * **Don't Do This:** Run integration tests against production databases. * **Why:** Isolation and data control are crucial for reliable and repeatable integration tests. ### 3.3. API Integration * **Do This:** Test interactions with external APIs. Mock external API responses when possible, or use a sandbox environment. * **Don't Do This:** Rely on the availability and stability of live external APIs for every test run. * **Why:** External APIs can be unreliable. Mocking or using a sandbox minimizes the impact on your test suite. ### 3.4. Example: Integration Testing with a Database """typescript // src/user-service.ts import { Database } from 'bun:sqlite'; export class UserService { private readonly db: Database; constructor(db: Database) { this.db = db; this.db.exec(" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE ) "); } async createUser(name: string, email: string): Promise<number> { const query = this.db.query("INSERT INTO users (name, email) VALUES (?, ?) RETURNING id"); const result = query.get(name, email) as { id: number } | undefined; if (!result) { throw new Error("Failed to create user"); } return result.id; } async getUser(id: number): Promise<{ id: number; name: string; email: string } | undefined> { const query = this.db.query("SELECT id, name, email FROM users WHERE id = ?"); return query.get(id) as { id: number; name: string; email: string } | undefined; } } // test/user-service.test.ts import { UserService } from '../src/user-service'; import { Database } from 'bun:sqlite'; import { expect, it, describe, beforeAll, afterAll } from 'bun:test'; describe('UserService', () => { let db: Database; let userService: UserService; beforeAll(() => { db = new Database(':memory:'); // In-memory database for testing userService = new UserService(db); }); afterAll(() => { db.close(); }); it('should create a user', async () => { const userId = await userService.createUser('John Doe', 'john.doe@example.com'); expect(userId).toBeGreaterThan(0); const user = await userService.getUser(userId); expect(user).toEqual({ id: userId, name: 'John Doe', email: 'john.doe@example.com' }); }); }); """ ### 3.5. Example: Integration Testing with an HTTP API """typescript // src/product-service.ts import { fetch } from 'undici'; // 'undici' is the recommended "fetch" implementation for Bun export class ProductService { private readonly apiUrl: string; constructor(apiUrl: string) { this.apiUrl = apiUrl; } async getProduct(id: number): Promise<any> { const response = await fetch("${this.apiUrl}/products/${id}"); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } } // test/product-service.test.ts import { ProductService } from '../src/product-service'; import { expect, it, describe, mock } from 'bun:test'; describe('ProductService', () => { it('should fetch a product', async () => { const mockProduct = { id: 1, name: 'Test Product', price: 10 }; global.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockProduct), } as any) ); const productService = new ProductService('https://api.example.com'); const product = await productService.getProduct(1); expect(product).toEqual(mockProduct); }); it('should handle errors when fetching a product', async () => { global.fetch = mock(() => Promise.resolve({ ok: false, status: 500, } as any) ); const productService = new ProductService('https://api.example.com'); await expect(productService.getProduct(1)).rejects.toThrowError("HTTP error! status: 500"); }); }); """ ### 3.6. Anti-Patterns in Integration Testing * **Testing too much:** Integration tests should focus on verifying the interaction between components but shouldn't be end-to-end tests. * **Using inconsistent test data:** Ensure that your test data is consistent and well-defined. Use factories or fixtures to create test data. * **Ignoring dependencies:** Understand and manage the dependencies of your integration tests. ## 4. End-to-End (E2E) Testing ### 4.1. Scope and Purpose * **Do This:** E2E tests verify that the entire system works as expected from the user's perspective, simulating real user workflows. * **Don't Do This:** Use E2E tests to test individual units of code or interactions between components. * **Why:** E2E tests provide the highest level of confidence that your application is working correctly. ### 4.2. Testing Tools * **Do This:** Use modern E2E testing tools like Playwright or Cypress. These tools provide features like browser automation, test recording, and detailed reporting, and have been tested with Bun. * **Don't Do This:** Use outdated or unreliable testing tools that make it difficult to write and maintain E2E tests. * **Why:** Modern E2E testing tools offer a better developer experience and more robust testing capabilities. ### 4.3. Test Environment * **Do This:** Run E2E tests in a dedicated test environment that closely mirrors production. Use environment variables or configuration files to manage test-specific settings. * **Don't Do This:** Run E2E tests against production environments. * **Why:** A dedicated test environment ensures isolation and prevents unintended side effects. ### 4.4. Example: E2E Testing with Playwright First, install Playwright: """bash bun add -d playwright bun playwright install """ """typescript // playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', // Replace with your app's URL trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], webServer: { command: 'bun run dev', // Replace with your start command url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); """ """typescript // tests/example.spec.ts import { test, expect } from '@playwright/test'; test('has title', async ({ page }) => { await page.goto('/'); await expect(page).toHaveTitle(/Bun App/); // Replace with your title }); test('get started link', async ({ page }) => { await page.goto('/'); await page.getByRole('link', { name: 'Get started' }).click(); await expect(page).toHaveURL(/.*intro/); }); """ To run these tests: """bash bun test """ ### 4.5. Anti-Patterns in E2E Testing * **Flaky tests:** E2E tests can be flaky due to timing issues or external dependencies. Implement retries and timeouts to mitigate flakiness. * **Over-reliance on UI elements:** Avoid relying too heavily on specific UI element locators. Use more robust techniques like testing based on content or semantic roles. * **Ignoring accessibility:** Make sure your E2E tests verify the accessibility of your application. ## 5. Specific Bun Considerations ### 5.1. Bun's Built-in Test Runner * **Do This:** Leverage Bun's built-in test runner for unit and integration tests where possible. It is optimized for speed and integrates well with the Bun ecosystem. * **Don't Do This:** Avoid using Bun's test runner if you require advanced features like code coverage reporting or mocking that may be better supported by other tools. Bun has built-in code coverage now so this is less relevant. * **Why:** Bun's test runner is fast and easy to use. ### 5.2. Compatibility with Existing Testing Frameworks * **Do This:** Use existing testing frameworks like Jest or Mocha if you have existing test suites or require features not yet available in Bun's built-in test runner. The frameworks are generally compatible. * **Don't Do This:** Avoid using older versions of these frameworks that may not be fully compatible with Bun. * **Why:** Leverage existing investments in testing frameworks and libraries where possible. ### 5.3. Bun's Performance * **Do This:** Keep in mind that Bun is fast and can cause tests to run too quickly if your system isn't warmed up and ready. You *may* need to add some small delays in tests that rely on async operations. Profile tests to identify performance bottlenecks. * **Don't Do This:** Blindly add delays to your tests just for the sake of it. Debug performance issues systematically. * **Why:** Bun's speed can expose performance issues in your tests that might not be apparent in other environments. ### 5.4. Utilizing Bun's Features in Tests * **Do This:** Utilize features like Bun's built-in SQLite support for testing database interactions or Bun's "fetch" implementation in integration tests, which reduces dependencies. * **Don't Do This:** Overcomplicate tests by re-implementing functionalities that Bun already provides efficiently and optimized. * **Why:** Leverage Bun's features to simplify testing code and improve performance. ### 5.5. Example: Performance Testing While Bun doesn't have a dedicated performance testing framework *built-in*, you can leverage its speed and "console.time" for simple benchmarks and performance analysis. """typescript import { expect, it, describe } from 'bun:test'; describe('Performance Test', () => { it('Should perform operation within acceptable time', async () => { const operation = async () => { console.time('operation'); // Simulate some work let sum = 0; for (let i = 0; i < 1000000; i++) { sum += i; } console.timeEnd('operation'); return sum; }; const startTime = Date.now(); const result = await operation(); const endTime = Date.now(); const duration = endTime - startTime; console.log("Operation result: ${result}"); console.log("Duration: ${duration}ms"); expect(duration).toBeLessThan(500); // Example threshold - Adjust as needed }); }); """ **Note:** This is a basic example. For more comprehensive performance testing, consider using dedicated tools like "autocannon" or artillery within Bun. ## 6. Continuous Integration/Continuous Deployment (CI/CD) ### 6.1. Automated Testing * **Do This:** Integrate all tests (unit, integration, E2E) into your CI/CD pipeline. Run tests automatically on every commit or pull request. * **Don't Do This:** Rely on manual testing only. * **Why:** Automated testing provides early feedback and prevents regressions. ### 6.2. Reporting and Monitoring * **Do This:** Use a test reporting tool to track test results and identify failing tests. Monitor test execution time and identify slow tests. This can often be integrated as part of your CI/CD Pipeline automatically. * **Don't Do This:** Ignore failing tests or let test suites become slow and unreliable. * **Why:** Test reporting and monitoring helps you maintain a healthy test suite and identify potential problems early on. ### 6.3. Git Hooks * **Do This:** Use git hooks to run linters, formatters, and unit tests before commits, ensuring code consistency and preventing broken code from being committed. * **Don't Do This:** Skip pre-commit checks for speed. These checks can prevent a lot of problems early! * **Why:** Catch code quality issues before they are pushed to the repository. By following these testing standards, you can ensure the quality, reliability, and maintainability of your Bun projects.