# State Management Standards for Rollup
This document outlines the coding standards for state management within Rollup projects. It aims to provide a comprehensive guide for developers to write maintainable, performant, and secure code when handling application state in Rollup modules and plugins.
It focuses on modern JavaScript and Rollup conventions, using up-to-date examples and best practices. This guide will help teams ensure consistent architecture, data flow, and reactivity across their Rollup ecosystem.
## 1. Principles of State Management in Rollup
State management in Rollup differs from traditional frontend frameworks like React or Vue. Rollup is a *module bundler*, meaning it *combines* your code and *doesn't* directly manage runtime application state. However, the modules Rollup bundles *do*, and so how your modules are structured to manage their internal state is important for overall application design. We need to consider:
* **Module Scoping:** Rollup encourages modularity. Modules should manage their own state, minimizing global state.
* **Data Flow:** Understand how data flows through your application components and how this affects the bundling process.
* **Side Effects**: Rollup enables side effects within modules, so handle them with care.
### 1.1. Standard: Prefer Module-Level Scoping
* **Do This:** Encapsulate state within individual modules to promote reusability and prevent naming conflicts.
* **Don't Do This:** Rely heavily on global variables or singletons for state, as these can lead to unintended side effects and make debugging difficult.
**Why?** Module-level scope reduces the risk of variable collisions and improves code maintainability by encapsulating logic and state. Global variables make code harder to reason about because any part of the application can modify them unexpectedly.
"""javascript
// Good: module using internal state
let counter = 0;
export function increment() {
counter++;
return counter;
}
export function decrement() {
counter--;
return counter;
}
"""
"""javascript
// Bad: using a global variable
window.globalCounter = 0; // Avoid!
export function increment() {
window.globalCounter++;
return window.globalCounter;
}
"""
### 1.2. Standard: Be Explicit About Data Flow
* **Do This:** Clearly define how data enters and exits your modules using function parameters and return values of the imported functions.
* **Don't Do This:** Depend on implicit or magical state updates, which can make the application logic difficult to follow.
**Why?** Explicit data flow increases code predictability, making it easier to trace and debug data-related issues. Predictable data flow is crucial for maintainability.
"""javascript
// Good: Explicit data flow
export function updateState(state, changes) {
return { ...state, ...changes };
}
// Usage:
import { updateState } from './state-management';
let currentState = { name: 'Initial', value: 0 };
const newState = updateState(currentState, { value: 1 });
console.log(newState); // { name: 'Initial', value: 1 }
"""
"""javascript
// Bad: Implicit state update
let internalState = { name: 'Initial', value: 0 };
export function updateValue(newValue) {
internalState.value = newValue; // Avoid direct mutation!
}
"""
### 1.3. Standard: Control Side Effects
* **Do This:** Isolate side effects (such as modifying external state) to specific functions and modules.
* **Don't Do This:** Allow side effects to occur randomly throughout your code, as this makes debugging and testing far more difficult.
**Why?** Controlling side effects helps in debugging and testing. By isolating side effects, you can quickly identify and fix issues when unexpected behavior occurs. Testing becomes more reliable and predictable.
"""javascript
// Good: Controlled side effect within a module
import { log } from './logger';
export function processData(data) {
// Perform some calculations
const result = data * 2;
// Log the result (side effect)
log("Processed data: ${result}");
return result;
}
"""
"""javascript
// Bad: Random side effect - using console.log calls directly
export function processData(data) {
const result = data * 2;
console.log("Processing data: ${result}"); // Side effect interspersed. Avoid!
return result;
}
"""
## 2. Implementing State Management Patterns
The specific approach to state management depends heavily on the complexity of your application and how your individual modules will interact. Rollup itself doesn't enforce a specific pattern, so you choose the one that best fits your needs.
### 2.1. Standard: Plain JavaScript Objects
* **Do This:** Use standard JavaScript objects to store and manage simple state within modules. This approach works well for small to medium-sized applications. Focus on immutability where feasible.
* **Don't Do This:** Overcomplicate with state management libraries if basic objects suffice.
**Why?** Using plain JavaScript objects reduces dependencies and keeps the code lightweight. It is perfect for scenarios where external libraries would just add unnecessary overhead.
"""javascript
// Example: Managing state with JavaScript objects (immutably)
let initialState = {
count: 0,
name: 'Example'
};
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'UPDATE_NAME':
return { ...state, name: action.payload };
default:
return state;
}
}
// Usage
let currentState = initialState;
currentState = reducer(currentState, { type: 'INCREMENT' });
currentState = reducer(currentState, { type: 'UPDATE_NAME', payload: 'New Name' });
console.log(currentState); // { count: 1, name: 'New Name' }
"""
### 2.2. Standard: Event Emitters
* **Do This:** Implement an event emitter pattern to create reactive state using custom events. This is useful for decoupling modules and handling asynchronous updates.
* **Don't Do This:** Overuse event emitters for synchronous state updates; consider direct function calls for simpler cases.
**Why?** Event emitters allow different modules to react to state changes without direct dependencies, promoting looser coupling. This decoupling makes code easier to modify and extend.
"""javascript
// Example: Event emitter implementation
import { EventEmitter } from 'events';
class Store extends EventEmitter {
constructor(initialState) {
super();
this.state = initialState;
}
getState() {
return this.state;
}
update(newState) {
this.state = { ...this.state, ...newState };
this.emit('stateChanged', this.state);
}
}
const store = new Store({ count: 0 });
// Subscribe to state changes
store.on('stateChanged', (newState) => {
console.log('State changed:', newState);
});
// Update the state
store.update({ count: 1 });
"""
### 2.3. Standard: RxJS Observables
* **Do This:** Use RxJS Observables for managing complex asynchronous state and data streams.
* **Don't Do This:** Implement complex custom solutions when RxJS can handle the scenarios more efficiently. This is particularly useful for real-time data processing.
**Why?** RxJS provides a powerful and flexible way to handle asynchronous data streams. Observables simplify complex logic with operators for filtering, mapping, and combining data, making your code more reactive and efficient.
"""javascript
// Example: RxJS implementation
import { BehaviorSubject } from 'rxjs';
class Store {
constructor(initialState) {
this.state$ = new BehaviorSubject(initialState);
}
getState() {
return this.state$.getValue();
}
update(newState) {
this.state$.next({ ...this.state$.getValue(), ...newState });
}
subscribe(callback) {
return this.state$.subscribe(callback);
}
}
const store = new Store({ count: 0 });
// Subscribe to state changes
const subscription = store.subscribe((newState) => {
console.log('RxJS State changed:', newState);
});
// Update the state
store.update({ count: 1 });
// Unsubscribe when not needed.
subscription.unsubscribe();
"""
## 3. State Management Within Rollup Plugins
Rollup plugins can maintain their internal state, which can be helpful for caching or managing plugin-specific settings.
### 3.1. Standard: Encapsulate Plugin State
* **Do This:** Use closures or classes to encapsulate the plugin's state.
* **Don't Do This:** Rely on global variables to store plugin state, which could clash with other plugins or application code.
**Why?** Encapsulation ensures that the plugin's internal state does not interfere with other parts of the application or other plugins, preventing unexpected side effects.
"""javascript
// Example: Plugin state using closures
function myPlugin() {
let internalState = {}; // Encapsulated state
return {
name: 'my-plugin',
transform(code, id) {
// Access and/or modify the internal state
internalState[id] = code.length;
// Example of side effect (logging plugin activity)
console.log("File ${id} transformed. Code length: ${internalState[id]}");
return code;
},
buildEnd() {
console.log('Plugin build completed. Processed file sizes:', internalState);
}
};
}
export default myPlugin;
"""
### 3.2. Standard: Use Plugin Context
* **Do This:** Leverage the "this" context within plugin lifecycle hooks to share state and methods. Rollup plugins that use ES Module syntax bind "this" to a plugin context object.
* **Don't Do This:** Directly modify the configuration object, as it could lead to unexpected behavior.
**Why?** The plugin context facilitates sharing data and utility functions between different hooks within the plugin, promoting a consistent and maintainable structure.
"""javascript
// Example: Sharing state using plugin context
function myPlugin() {
return {
name: 'my-plugin',
options(options) {
this.sharedState = { count: 0 };
return options;
},
transform(code, id) {
this.sharedState.count++;
console.log("Transforming ${id}. Count: ${this.sharedState.count}");
return code;
},
buildEnd() {
console.log("Total files transformed: ${this.sharedState.count}");
}
};
}
export default myPlugin;
"""
### 3.3. Standard: Be Mindful of Persistent State
* **Do This:** Ensure that any persistent state within the plugin (e.g., cached data) is properly managed and does not lead to memory leaks.
* **Don't Do This:** Accumulate state indefinitely without clearing or updating it, especially when dealing with large datasets. Always clean up resources appropriately.
**Why?** Properly managing persistent state inside a plugin prevents memory leaks and ensures that the plugin behaves efficiently, especially in long-running or watch-mode builds.
"""javascript
// Example: Caching data within a Rollup plugin
import { createHash } from 'crypto';
function cachingPlugin() {
const cache = new Map();
return {
name: 'caching-plugin',
transform(code, id) {
const hash = createHash('sha256').update(code).digest('hex');
if (cache.has(id) && cache.get(id).hash === hash) {
console.log("[cache] Returning cached version of ${id}");
return cache.get(id).code;
}
// Process the code (in this example, just converting to uppercase)
const transformedCode = code.toUpperCase();
// Store the transformed code in the cache
cache.set(id, { code: transformedCode, hash: hash });
console.log("[cache] Caching transformed version of ${id}");
return transformedCode;
}
};
}
export default cachingPlugin;
"""
## 4. Asynchronous State Management
Rollup plugins often perform asynchronous operations, such as reading files or making network requests. Proper state management is crucial to handle these operations correctly.
### 4.1. Standard: Use Async/Await
* **Do This:** Utilize "async" and "await" to manage asynchronous state updates in a readable and maintainable way.
* **Don't Do This:** Rely on callbacks or promises without proper error handling, which can lead to difficult-to-debug issues.
**Why?** "async/await" makes asynchronous code look and behave a bit more like synchronous code, which improves readability. It also provides easier error handling with "try/catch" blocks.
"""javascript
// Example: Asynchronous plugin using async/await
import { readFile } from 'fs/promises';
function asyncPlugin() {
let state = { filesRead: 0 };
return {
name: 'async-plugin',
async load(id) {
try {
const content = await readFile(id, 'utf-8');
state.filesRead++;
console.log("[async plugin] Read file ${id}. Total files read: ${state.filesRead}");
return content;
} catch (error) {
this.error("Failed to read file ${id}: ${error.message}");
return null;
}
}
};
}
export default asyncPlugin;
"""
### 4.2. Standard: Handle Errors Robustly
* **Do This:** Implement error-handling mechanisms (e.g., "try/catch" blocks) to catch and manage exceptions that may occur during asynchronous operations. Specifically using the plugin context's "this.warn" and "this.error" methods.
* **Don't Do This:** Ignore possible errors, as this can cause your plugin to fail silently or produce unexpected results.
**Why?** Robust error handling prevents the plugin from crashing or producing incorrect output, improving the overall stability and reliability of the build process.
"""javascript
// Example: Robust error handling in an async plugin
import { readFile } from 'fs/promises';
function errorHandlingPlugin() {
return {
name: 'error-handling-plugin',
async load(id) {
try {
const content = await readFile(id, 'utf-8');
return content;
} catch (error) {
this.error("[error-handling-plugin] Failed to read file ${id}: ${error.message}");
return null; // Important to return null to halt processing of this file.
}
}
};
}
export default errorHandlingPlugin;
"""
## 5. Security Considerations
When managing state, especially in plugins that handle user-provided data, security is paramount.
### 5.1. Standard: Validate and Sanitize Data
* **Do This:** Validate and sanitize all external data to prevent common security vulnerabilities such as cross-site scripting (XSS) or injection attacks.
* **Don't Do This:** Directly use external data without proper validation, as this can expose your plugin and application to security risks.
**Why?** Validating and sanitizing data ensures that only safe and expected data is processed, mitigating potential security threats and ensuring the integrity of the build process.
"""javascript
// Example: Data validation and sanitization
import { createHash } from 'crypto';
function securePlugin() {
return {
name: 'secure-plugin',
transform(code, id) {
// Validate the file ID
if (!isValidFileId(id)) {
this.warn("[secure-plugin] Invalid file ID: ${id}");
return null;
}
// Sanitize the code content
const sanitizedCode = sanitize(code);
// Generate a hash of the sanitized code
const hash = createHash('sha256').update(sanitizedCode).digest('hex');
console.log("[secure-plugin] Processed and secured file ${id}, hash: ${hash}");
return sanitizedCode;
}
};
function isValidFileId(id) {
// Implement your validation logic here
return typeof id === 'string' && id.length > 0;
}
function sanitize(code) {
// Implement your code sanitization logic here (e.g., escaping HTML entities)
return code.replace(//g, '>');
}
}
export default securePlugin;
"""
### 5.2. Standard: Avoid Storing Sensitive Information
* **Do This:** Avoid storing sensitive information (e.g., API keys, passwords) directly in the plugin's state. If you must store such data, encrypt it and manage access carefully. Consider environment variables.
* **Don't Do This:** Hardcode sensitive information or store it in plain text within the plugin's codebase or state.
**Why?** Preventing the storage of sensitive information minimizes the risk of data breaches and security compromises. Use secure configuration management practices, such as environment variables or secure credential stores.
### 5.3. Standard: Use Secure Dependencies
* **Do This:** Regularly update your dependencies to patch security vulnerabilities. Scan your dependencies for known security issues using tools like "npm audit" or "yarn audit".
* **Don't Do This:** Use outdated or unmaintained dependencies, as they may contain known security vulnerabilities that can be exploited.
**Why?** Keeping dependencies up to date and actively scanning for vulnerabilities reduces the risk of introducing security flaws into your plugin and application. Regularly audit and update dependencies to maintain a secure environment.
## 6. Conclusion
Adhering to these coding standards ensures clean, maintainable, and secure code for state management in Rollup. By embracing modularity, explicit data flow, and robust error handling, developers can build plugins and applications that are manageable and reliable.
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'
# Security Best Practices Standards for Rollup This document outlines security best practices for developing Rollup plugins, configurations, and build processes. Adhering to these standards will help mitigate potential vulnerabilities and ensure the integrity of bundled code. ## 1. Input Validation and Sanitization ### 1.1. Validate Plugin Options **Standard:** Always validate and sanitize options passed to Rollup plugins to prevent injection attacks and unexpected behavior. **Why:** Unvalidated options can be exploited to execute arbitrary code or manipulate the build process. This is particularly crucial when options involve file paths or external resources. **Do This:** """javascript // rollup-plugin-example.js import { createFilter } from '@rollup/pluginutils'; import { resolve, isAbsolute } from 'path'; function examplePlugin(options = {}) { const { include, exclude, customOption } = options; // Validate 'include' and 'exclude' using @rollup/pluginutils createFilter const filter = createFilter(include, exclude); // Validate 'customOption' if (typeof customOption !== 'string') { throw new Error('customOption must be a string.'); } if (customOption.length > 100) { // Arbitrary length limit throw new Error('customOption is too long.'); } // Sanitize 'customOption' against Regular Expression Denial of Service (ReDoS). const safeCustomOption = customOption.replace(/([.*+?^${}()|[\]\\])/g, '\\$1'); return { name: 'example', transform(code, id) { if (!filter(id)) return null; // Use safeCustomOption instead of customOption here // Avoid using eval or Function constructor with user-supplied data // e.g., const result = new Function('return ' + safeCustomOption)(); // AVOID! // Instead rely on known function calls with limited scope const transformedCode = code + "\n// Transformed with safeCustomOption: ${safeCustomOption}"; return transformedCode; } }; } export default examplePlugin; """ **Don't Do This:** """javascript // BAD: Unvalidated option usage function examplePlugin(options = {}) { return { name: 'example', transform(code, id) { // Potential vulnerability: using options.customCode directly return code + options.customCode; } }; } """ **Anti-Pattern:** Directly using plugin options without any validation or sanitization. Relying solely on user-provided data can lead to catastrophic vulnerabilities. ### 1.2. Validate Resolved File Paths **Standard:** Validate and normalize file paths before using them to access the file system. Use "path.resolve()" and "path.normalize()" to mitigate path traversal vulnerabilities. **Why:** Path traversal vulnerabilities can allow attackers to read or write arbitrary files on the system. **Do This:** """javascript import { createFilter } from '@rollup/pluginutils'; import { resolve, normalize, isAbsolute } from 'path'; import { readFileSync } from 'fs'; function securePlugin(options = {}) { const { filePath } = options; if (!filePath) { throw new Error('filePath is required'); } // Ensure the path is absolute and normalized const absolutePath = resolve(filePath); const normalizedPath = normalize(absolutePath); // Check if the resolved path is within the expected directory const baseDir = process.cwd(); // Or a more specific expected directory if (!normalizedPath.startsWith(baseDir)) { throw new Error('filePath must be within the project directory'); } return { name: 'secure-plugin', load() { try { const content = readFileSync(normalizedPath, 'utf-8'); return content; } catch (error) { this.error("Failed to read file: ${error.message}"); } } }; } export default securePlugin; """ **Don't Do This:** """javascript // BAD: Unvalidated file path import { readFileSync } from 'fs'; function insecurePlugin(options = {}) { return { name: 'insecure-plugin', load() { try { // Potential path traversal vulnerability const content = readFileSync(options.filePath, 'utf-8'); return content; } catch (error) { this.error("Failed to read file: ${error.message}"); } } }; } """ **Anti-Pattern:** Directly using user-supplied file paths without validation, particularly in functions like "fs.readFileSync" or dynamic imports. ## 2. Code Injection Prevention ### 2.1. Avoid "eval()" and "Function()" Constructor **Standard:** Never use "eval()" or the "Function()" constructor with user-supplied data. **Why:** These can execute arbitrary code, leading to severe security vulnerabilities. **Do This:** Rely on alternative methods such as lookup tables, switch statements, or specialized libraries for dynamic code execution. """javascript // Instead of using eval, use object lookup: const operations = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b, }; function calculate(operationName, a, b) { const operation = operations[operationName]; if (!operation) { throw new Error("Invalid operation: ${operationName}"); } return operation(a, b); } console.log(calculate('add', 5, 3)); // Output: 8 """ **Don't Do This:** """javascript // BAD: Using eval() function executeCode(userInput) { // Extremely dangerous eval(userInput); } """ **Anti-Pattern:** Using "eval()" or "Function()" to execute code derived from user input or external sources. ### 2.2. Template Literals and Dynamic Strings **Standard:** When constructing dynamic strings, particularly for code generation, use careful escaping and validation to prevent code injection. Consider using templating engines with automatic escaping. **Why:** Incorrectly constructed dynamic strings can allow attackers to inject malicious code snippets. **Do This:** """javascript function createSafeString(input) { // Escape potentially harmful characters const escapedInput = input.replace(/["${}]/g, '\\$&'); return "console.log(\"${escapedInput}\");"; } const userInput = 'Hello ${() => alert("XSS")}"'; const safeCode = createSafeString(userInput); console.log(safeCode); // Output: console.log("Hello \${() => alert("XSS")}\""); // This output is safe to include in generated code. """ **Don't Do This:** """javascript // BAD: Unescaped template literals function createInsecureString(input) { return "console.log(\"${input}\");"; } const userInput = 'Hello ${() => alert("XSS")}"'; const insecureCode = createInsecureString(userInput); // BAD OUTPUT: console.log("Hello ${() => alert("XSS")}")" """ **Anti-Pattern:** Directly embedding user input or external data into template literals without proper escaping. ## 3. Dependency Management ### 3.1. Use Lockfiles **Standard:** Always use lockfiles (e.g., "package-lock.json", "yarn.lock", "pnpm-lock.yaml") to ensure consistent dependency versions across environments. **Why:** Lockfiles prevent unexpected version upgrades that could introduce vulnerabilities. **Do This:** Regularly commit and update your lockfiles. Use "npm ci" or "yarn install --frozen-lockfile" in CI/CD environments to enforce the lockfile. ### 3.2. Regularly Audit Dependencies **Standard:** Regularly audit your project's dependencies for known vulnerabilities using tools like "npm audit", "yarn audit", or "snyk". **Why:** Dependencies can contain security flaws that can compromise your application. **Do This:** """bash npm audit # or yarn audit # or snyk test """ Address any identified vulnerabilities by updating dependencies or applying patches. ### 3.3. Avoid Unnecessary Dependencies **Standard:** Keep your project's dependency count to a minimum. Only include dependencies that are truly necessary. **Why:** Each dependency increases the attack surface of your application. **Do This:** Regularly review your dependencies and remove any that are no longer needed. Consider alternatives that provide similar functionality without adding dependencies. **Anti-Pattern:** Adding dependencies without thoroughly evaluating their necessity and security implications. ### 3.4. Pin Dependency Versions **Standard:** Pin the specific versions of your core dependencies in "package.json". This helps prevent unexpected breaking changes or the introduction of vulnerabilities from patch or minor version updates. **Why:** Using version ranges like "^1.0.0" can introduce unexpected changes from minor or patch updates that might contain security flaws. Pinning versions provides predictability. **Do This:** """json // package.json { "dependencies": { "lodash": "4.17.21", "axios": "0.27.2" } } """ This ensures you always use the exact versions you've tested. Security updates should be applied by consciously updating the version number and retesting. Consider using tools like "npm-check-updates" to help manage updates. **Don't Do This:** """json // BAD - Using version ranges { "dependencies": { "lodash": "^4.0.0", "axios": "~0.20.0" } } """ **Anti-Pattern:** Using wide version ranges for dependencies without careful oversight and testing of updates. This exposes your project to potential vulnerabilities from upstream changes. ## 4. Secure Build Process ### 4.1. Environment Variables for Secrets **Standard:** Store sensitive information such as API keys and database passwords in environment variables, not directly in your code or configuration files. **Why:** Environment variables are less likely to be accidentally committed to version control. **Do This:** """javascript // rollup.config.js import replace from '@rollup/plugin-replace'; export default { // ... other configurations plugins: [ replace({ preventAssignment: true, values: { 'process.env.API_KEY': JSON.stringify(process.env.API_KEY), }, }), ], }; """ Set the "API_KEY" environment variable in your deployment environment. **Don't Do This:** """javascript // BAD: Hardcoding API key const apiKey = 'YOUR_API_KEY'; """ **Anti-Pattern:** Hardcoding sensitive information directly in your code or configuration files. ### 4.2. Secure Third-Party Plugins **Standard:** Thoroughly vet any third-party Rollup plugins before using them in your project. Ensure they are actively maintained, have a good reputation, and do not exhibit any suspicious behavior. **Why:** Malicious or poorly written plugins can introduce vulnerabilities into your build process and bundled code. **Do This:** * Check the plugin's source code on GitHub or npm. * Read the plugin's documentation and issue tracker. * Look for any security audits or vulnerability reports. * Test the plugin in a sandboxed environment before using it in production. ### 4.3. Minimize Build Dependencies **Standard:** Reduce the number of dependencies required during the build process to minimize the risk of introducing vulnerabilities. **Why:** Build dependencies can introduce security flaws and increase the attack surface. **Do This:** Use tools like "esbuild" or "swc" for faster and more secure builds. Use tools such as "webpack-stats-duplicates" to check for package duplication that can be safely removed. ### 4.4. CI/CD Security **Standard:** Implement robust CI/CD pipelines that include security scanning, vulnerability checks, and automated testing. **Why:** Automating security checks helps identify and prevent vulnerabilities from being introduced into your codebase. **Do This:** * Integrate tools like "npm audit", "yarn audit", or "snyk" into your CI/CD pipeline. * Run static analysis tools to identify potential security flaws in your code. * Perform penetration testing on your application before deploying to production. ## 5. Output Sanitization ### 5.1. Prevent Cross-Site Scripting (XSS) **Standard:** Sanitize any data that is dynamically inserted into HTML to prevent XSS attacks. **Why:** XSS attacks can allow attackers to inject malicious scripts into your application. **Do This:** * Use a templating engine (e.g., Handlebars, Mustache) with automatic escaping. * Use a library specifically designed for sanitizing HTML (e.g., DOMPurify). * Apply output encoding to prevent the interpretation of HTML tags. **Don't Do This:** """javascript // BAD: Directly inserting data into HTML element.innerHTML = userInput; """ **Anti-Pattern:** Directly inserting user input into HTML without sanitization. ### 5.2. Content Security Policy (CSP) **Standard:** Implement a Content Security Policy (CSP) to restrict the sources from which the browser can load resources. **Why:** CSP helps mitigate XSS attacks by preventing the execution of untrusted scripts. **Do This:** Configure your server to send the "Content-Security-Policy" header with appropriate directives. Example: """ Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' https://example.com """ This policy allows resources to be loaded only from the same origin and from "https://example.com". ## 6. Secure Plugin Development ### 6.1. Principle of Least Privilege **Standard:** Plugins should request only the minimum permissions or access they need to perform their intended function. **Why:** Reduces the potential damage if a plugin is compromised. **Do This:** Carefully define the scope of a plugin and avoid requesting unnecessary access to the file system, network, or other resources. ### 6.2. Data Encryption **Standard:** If a plugin handles sensitive data (e.g., API keys, passwords), encrypt the data at rest and in transit. **Why:** Protects sensitive data from unauthorized access. **Do This:** Use established encryption libraries (e.g., "crypto" in Node.js) and follow industry best practices for key management. ### 6.3. Secure Communication **Standard:** If a plugin communicates with external services, use HTTPS and validate the server's certificate. **Why:** Prevents man-in-the-middle attacks and ensures the integrity of data transmitted over the network. **Do This:** Use the "https" module in Node.js and configure it to verify the server's certificate. ### 6.4 Handling Plugin Errors **Standard:** Plugins must gracefully handle errors and avoid exposing sensitive information in error messages. **Why:** Error messages can inadvertently reveal details about the system or application that can be exploited by attackers. **Do This:** Log errors to a secure location, but avoid displaying them directly to users. Use generic error messages that do not reveal sensitive information. """javascript // Secure error handling example try { // Dangerous operation } catch (error) { console.error('An error occurred during processing.'); // Generic error message // Securely log the full error details to a file or monitoring system // Important: DO NOT expose sensitive details to the user. } """ **Don't Do This:** """javascript // Insecure error handling try { // Dangerous operation } catch (error) { console.error("Operation failed: ${error.message}"); // Exposes internal details // May expose file paths, secrets, etc. } """ **Anti-Pattern:** Leaking sensitive information in error messages or logs. ## 7. Regular Security Updates **Standard:** Keep Rollup, your plugins, and all dependencies up to date with the latest security patches. **Why:** Security vulnerabilities are constantly being discovered, and updates often contain fixes for these vulnerabilities. **Do This:** Regularly update your dependencies using "npm update", "yarn upgrade", or "pnpm update". Subscribe to security advisories for Rollup and your dependencies to stay informed of any new vulnerabilities. ## 8. Testing and Auditing ### 8.1 Unit Tests **Standard:** Implement thorough unit tests to verify the security of your Rollup plugins and configurations. These tests should specifically target potential vulnerabilities such as input validation, code injection, and path traversal. **Why:** Unit tests help identify and prevent security flaws early in the development process. **Do This:** Use a testing framework like Jest or Mocha to write unit tests that cover all critical security aspects of your code. ### 8.2 Static Analysis **Standard:** Use static analysis tools to automatically identify potential security vulnerabilities in your codebase. **Why:** Static analysis can detect common security flaws that might be missed during manual code reviews. **Do This:** Integrate static analysis tools like ESLint with security plugins (e.g., "eslint-plugin-security") into your development workflow. ### 8.3 Security Audits **Standard:** Conduct regular security audits of your Rollup plugins and configurations. These audits should be performed by experienced security professionals. **Why:** Security audits can identify vulnerabilities that might not be apparent through automated testing or static analysis. **Do This:** Engage a reputable security firm to perform a comprehensive audit of your code. Address any findings promptly and thoroughly. ## 9. Handling Secrets ### 9.1 Secure Storage **Standard:** Secrets should be stored securely and accessed only when necessary. **Why:** Exposed secrets can lead to unauthorized access and data breaches. **Do This:** Use a dedicated secrets management solution like HashiCorp Vault or AWS Secrets Manager to store and manage secrets. Use environment variables for local development and ensure that these are not checked into version control. ### 9.2 Least Privilege **Standard:** Access to secrets should be granted based on the principle of least privilege. **Why:** Limiting access to secrets reduces the risk of unauthorized disclosure. **Do This:** Grant only the necessary access to secrets to individual developers, applications, and services. Use fine-grained access controls to restrict access to specific secrets based on need. ### 9.3 Rotation **Standard:** Secrets should be regularly rotated to limit the impact of a potential compromise. **Why:** Rotating secrets reduces the window of opportunity for attackers to use compromised credentials. **Do This:** Implement a process for regularly rotating secrets on a predefined schedule. Automate the rotation process where possible. By adhering to these security best practices, you can significantly reduce the risk of vulnerabilities in your Rollup projects and ensure the integrity of your bundled code. Always stay informed of the latest security threats and update your practices accordingly.
# Component Design Standards for Rollup This document outlines the coding standards for component design in Rollup projects. It's intended to guide developers in writing reusable, maintainable, and performant code, specifically within the Rollup ecosystem. These standards are tailored to reflect the latest best practices and features of Rollup projects. ## 1. Principles of Component Design in Rollup ### 1.1. Single Responsibility Principle (SRP) * **Standard:** Each Rollup plugin or transform should have a single, well-defined purpose. * **Do This:** Create separate plugins for different concerns like code minification, adding banners, and handling specific file types. * **Don't Do This:** Bundle multiple unrelated functionalities into a single, monolithic plugin. * **Why:** Promotes modularity, testability, and easier maintenance. Changes to one aspect don't inadvertently affect other parts of the system. """javascript // Do This: Separate plugins for different tasks // rollup.config.js import minify from 'rollup-plugin-terser'; import banner from 'rollup-plugin-banner'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'umd', name: 'MyModule' }, plugins: [ banner({ banner: '/* My Awesome Library */' }), minify() ] }; """ ### 1.2. Abstraction and Encapsulation * **Standard:** Abstract away complex implementation details within modules and plugins. Expose clear, well-defined interfaces. * **Do This:** Create a plugin that hides the complexity of a specific transformation process and provides simple options for customization. * **Don't Do This:** Expose internal workings or rely on undocumented behavior within the Rollup configuration. * **Why:** Simplifies usage, protects against accidental breakage caused by internal changes, and makes it easier to swap out implementations. """javascript // Do This: Encapsulate complex logic in a plugin // my-custom-plugin.js export default function myCustomPlugin(options = {}) { const { pattern, replacement } = options; return { name: 'my-custom-plugin', transform(code, id) { if (id.endsWith('.svelte')) return null; // don't run on svelte files if (!pattern || !replacement) return code; return code.replace(pattern, replacement); } }; } // rollup.config.js import myCustomPlugin from './my-custom-plugin.js'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es' }, plugins: [ myCustomPlugin({ pattern: /__VERSION__/g, replacement: '1.2.3' }) ] }; """ ### 1.3. Don't Repeat Yourself (DRY) * **Standard:** Avoid duplicating code. Extract common functionality into reusable modules or helper functions. * **Do This:** If multiple plugins need to parse similar configuration options, create a shared utility function to handle the parsing. * **Don't Do This:** Copy and paste the same parsing logic into each plugin that needs it. * **Why:** Reduces redundancy, making code easier to update and less prone to errors. """javascript // Do This: Share utility functions // utils.js export function parseOptions(options) { // Logic to parse and validate options const parsedOptions = { ...options }; // basic example return parsedOptions; } // plugin1.js import { parseOptions } from './utils.js'; export default function plugin1(options = {}) { const parsed = parseOptions(options); // ... } // plugin2.js import { parseOptions } from './utils.js'; export default function plugin2(options = {}) { const parsed = parseOptions(options); // ... } """ ### 1.4. Composition over Inheritance * **Standard:** Favor composing plugin functionalities from smaller, independent plugins over creating complex inheritance hierarchies. * **Do This:** Create individual plugins for specific transformations and combine them in the Rollup configuration. * **Don't Do This:** Create a base plugin class with a complex inheritance structure for different transformation types. * **Why:** Promotes flexibility and reduces coupling between plugins. It's easier to mix and match functionality as needed. """javascript // Do This: Compose plugins // rollup.config.js import pluginA from './plugin-a.js'; import pluginB from './plugin-b.js'; import pluginC from './plugin-c.js'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es' }, plugins: [ pluginA(), pluginB(), pluginC() ] }; """ ## 2. Creating Reusable Components (Plugins) ### 2.1. Plugin Structure * **Standard:** Follow the standard Rollup plugin structure, including a name and relevant lifecycle hooks. * **Do This:** Ensure your plugin exports a function that returns an object with a "name" property and appropriate lifecycle hooks (e.g., "transform", "renderChunk"). * **Don't Do This:** Export a simple object or directly modify the Rollup configuration. """javascript // Do This: Standard plugin structure export default function myPlugin(options = {}) { return { name: 'my-plugin', // Required: The name of the plugin transform(code, id) { // Optional: Transform code here }, renderChunk(code, chunk, options, meta) { // Optional: Alter the final chunk } }; } """ ### 2.2. Configuration Options * **Standard:** Design configuration options that are intuitive, well-documented, and validated. * **Do This:** Use descriptive option names, provide default values, and validate the types and values of received options. Utilize a schema validator library if necessary. * **Don't Do This:** Use obscure option names, assume default values, or fail to validate configuration options. * **Why:** Improves usability and prevents unexpected behavior due to invalid configurations. """javascript // Do This: Validate and provide defaults for options import { isString } from 'lodash-es'; // Or any other utility library export default function myPlugin(options = {}) { const { message = 'Hello', include } = options; if (!isString(message)) { throw new Error('message option must be a string'); } return { name: 'my-plugin', transform(code, id) { if (include && !id.includes(include)) { return null; } return "console.log("${message}");\n${code}"; } }; } // rollup.config.js import myPlugin from './my-plugin.js'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es' }, plugins: [ myPlugin({ message: 'Custom Message', // Valid string include: 'src/' }) ] }; """ ### 2.3. Handling Dependencies * **Standard:** Declare and manage plugin dependencies explicitly. * **Do This:** Specify peer dependencies to avoid version conflicts with the consuming projects. Consider using "rollup-plugin-node-resolve" to resolve external dependencies of your plugin if required. * **Don't Do This:** Bundle dependencies directly into the plugin if they are also likely to be used in the consuming project. * **Why:** Avoids dependency conflicts and ensures predictable behavior. """json // Do This: Package.json with peer dependencies { "name": "my-rollup-plugin", "version": "1.0.0", "peerDependencies": { "lodash-es": "^4.0.0", // example "rollup": "^4.0.0" // Specify minimum supported Rollup version }, "devDependencies": { "rollup": "^4.0.0", "lodash-es": "^4.0.0" } } // rollup.config.js (in the consuming project) import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import myPlugin from 'my-rollup-plugin'; // Assumes installed from npm export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es' }, plugins: [ resolve(), // Resolve node_modules commonjs(), // Convert CommonJS to ES modules myPlugin() // Use the plugin ] }; """ It's generally best practice to include "@rollup/plugin-node-resolve" and "@rollup/plugin-commonjs" in the *consumer's* "rollup.config.js" file, NOT bundled within the plugin itself. This provides the consumer with full control over versions and configuration. ### 2.4. Error Handling and Logging * **Standard:** Implement robust error handling and provide informative logging for debugging. * **Do This:** Use "this.error()" and "this.warn()" provided by Rollup to report errors and warnings. Provide context-specific messages and, when possible, include code snippets related to the error. * **Don't Do This:** Throw generic errors or rely on "console.log" for debugging in a production environment. Avoid verbose logs unless specifically enabled through an option. * **Why:** Aids in debugging and provides users with actionable information about potential issues. """javascript // Do This: Use this.error() and this.warn() export default function myPlugin(options = {}) { return { name: 'my-plugin', transform(code, id) { try { // Some potentially error-prone operation if (code.includes('invalid code')) { this.error({ message: 'Invalid code detected.', id }); // Add file ID // Or use this.error(new Error('Detailed error information')) } // ... } catch (error) { this.warn("Problem during transform: ${error.message}"); } return code; } }; } """ ### 2.5. Testing * **Standard:** Write comprehensive unit and integration tests for plugins. * **Do This:** Use a testing framework like Jest or Mocha and a Rollup testing utility to verify plugin functionality. Automate the tests using CI/CD. * **Don't Do This:** Rely on manual testing or skip testing altogether. * **Why:** Ensures reliability and prevents regressions. """javascript // Do This: Example test (using Jest) // my-plugin.test.js import { rollup } from 'rollup'; import myPlugin from './my-plugin.js'; import fs from 'node:fs/promises'; async function buildAndRun(options) { const bundle = await rollup({ input: 'test/fixtures/input.js', // Create a simple input file plugins: [myPlugin(options)] }); const outputOptions = { format: 'es' }; const { output } = await bundle.generate(outputOptions); return output[0].code; } it('should transform code correctly', async () => { const result = await buildAndRun({ message: 'Testing!' }); expect(result).toContain('Testing!'); }); it('should not transform files if include option is specified', async () => { await fs.writeFile('test/fixtures/input.js', 'console.log("hello");') const result = await buildAndRun({ message: 'Testing!', include: 'other' }); expect(result).toContain('console.log("hello");'); expect(result).not.toContain('Testing!'); }); """ Create testable output artifacts. Use "fs.writeFile("testfile.txt", code)" to save generated files into a "test" directory, then use node's "fs/promises" to read those files back in for comparison. This allows more accurate testing of different output configurations. ### 2.6. Documentation * **Standard:** Provide clear and comprehensive documentation for each plugin. * **Do This:** Include a README file with a description of the plugin, installation instructions, configuration options, usage examples, and contribution guidelines. Document public interfaces with JSDoc-style comments. * **Don't Do This:** Omit documentation or provide incomplete or outdated information. * **Why:** Makes the plugin easier to understand and use. ## 3. Modern Approaches and Patterns ### 3.1. ES Modules * **Standard:** Use ES modules for plugin development. * **Do This:** Use "export default function myPlugin() {}" for plugin definitions and "import" statements for dependencies. * **Don't Do This:** Use CommonJS modules ("module.exports", "require") unless absolutely necessary. * **Why:** ES modules are the standard for modern JavaScript development and provide better static analysis and tree-shaking capabilities. ### 3.2. Async/Await * **Standard:** Use "async/await" for asynchronous operations. * **Do This:** Use "async" functions with "await" to handle asynchronous tasks like file I/O or network requests. * **Don't Do This:** Use callbacks or promises directly unless you have a specific reason to do so, which is rare. * **Why:** Improves code readability and simplifies asynchronous control flow. """javascript // Do This: Async/await for asset loading import { readFile } from 'node:fs/promises'; export default function myPlugin(options = {}) { return { name: 'my-plugin', async load(id) { if (id.endsWith('template.html')) { try { const template = await readFile(id, 'utf-8'); return "export default ${JSON.stringify(template)};"; } catch (error) { this.error("Failed to load template: ${error.message}"); } } } }; } """ ### 3.3. Virtual Modules * **Standard:** Utilize "this.emitFile" for creating virtual modules within Rollup. * **Do This:** For dynamically generated code (e.g., from templates or schemas), use "this.emitFile" to inject them as virtual modules into the bundle. Specify "type: 'asset'" if it needs to be preserved as a standalone file. * **Don't Do This:** Directly manipulate the file system. * **Why:** Keeps intermediate files in memory, improving performance and cleanliness. """javascript // Do This: Create virtual modules export default function myPlugin() { return { name: 'my-plugin', buildStart() { const generatedCode = "export const value = ${Math.random()};"; this.emitFile({ type: 'chunk', // Or 'asset' if you want a file id: 'generated-module', name: 'generated', fileName: 'generated.js', code: generatedCode }); }, resolveId(source) { if (source === 'generated-module') { return 'generated-module'; // Resolve to the virtual module ID } return null; } }; } // In your .js files import { value } from 'generated-module' // will load it in """ ### 3.4 Source Maps * **Standard**: Ensure source maps are properly generated and handled by plugins. * **Do This**: When doing transformations, update the associated sourcemap using libraries like "magic-string" or similar utility. Rollup automatically chains sourcemaps from different plugins, so ensure your modifications preserve this chain. * **Don't do This**: Modifying code without adjusting the sourcemap as this will make debugging very hard. This is especially important for code generation plugins which create new files. * **Why**: Proper source map handling makes debugging transformed code much easier. """javascript import MagicString from 'magic-string'; export default function sourcemapPlugin() { return { name: "sourcemap-plugin", transform(code, id) { const magicString = new MagicString(code); magicString.prepend('/* This code was modified by sourcemap-plugin */\n'); magicString.append('\n/* End of modification by sourcemap-plugin */'); const map = magicString.generateMap({ source: id, includeContent:true }); //true is important return { code: magicString.toString(), map: map }; } } } """ ## 4. Security Considerations ### 4.1. Malicious Code Injection * **Standard:** Sanitize and validate any user-provided input that is used in code generation or transformations. * **Do This:** Use secure coding practices to prevent code injection vulnerabilities, especially when handling user-provided configuration options. When building output, escape strings properly. * **Don't Do This:** Directly insert user input into code without validation or sanitization. * **Why:** Prevents malicious code from being injected into the final bundle. ### 4.2. Dependency Vulnerabilities * **Standard:** Regularly audit and update dependencies to address known vulnerabilities. * **Do This:** Use tools like "npm audit" or "yarn audit" to identify and fix dependency vulnerabilities. Keep Rollup and its plugins updated. * **Don't Do This:** Ignore security warnings or use outdated dependencies. * **Why:** Reduces the risk of security exploits. ## 5. Performance Optimization ### 5.1. Minimize Plugin Overhead * **Standard:** Only use necessary transformations. * **Do This:** Be aware of the performance cost of unnecessary operations. Aim to create plugins performant and only use the ones that add proper value. * **Don't Do This:** Apply a kitchen sink of transformations without thinking if they actually add value. * **Why:** Avoid useless operation and spend CPU cycles unnecesarily ### 5.2. Leverage asynchronous operations * **Standard:** Parallelise long operations. * **Do This:** Whenever suitable, use "Promise.all" to parallelise operations happening over multiple files. Ensure that processing of the different files is independent or otherwise apply proper synchronisation with mutexes or other appropriate mechanisms. * **Don't Do This:** Perform intensive tasks in synchronised manner that slows down build process. * **Why:** Avoid bottlenecks and improve build times of the project ### 5.3 Code Splitting * **Standard**: Use code splitting to reduce bundle sizes. * **Do This**: Use the 'dynamic import' syntax and configure Rollup to create separate chunks for different parts of the application. This strategy can significantly improve the initial load time by only delivering necessary code when the application starts with deferred loading. * **Don't Do This**: Include all code into one large bundle. * **Why**: Improves initial load time and overall performance, especially for large applications. """javascript // Example: Dynamic import for code splitting async function loadComponent() { const { default: component } = await import('./my-component.js'); // Use the dynamically loaded component document.body.appendChild(component); } loadComponent(); """ ### 5.4 Cache Results * **Standard:** Cache intermediate results within plugins to reduce unnecessary recomputation. * **Do This:** Implement caching strategies, especially for operations that depend on external resources or computationally intensive calculations. Use the "this.cache" API to store the value. * **Don't Do This:** Recompute results unnecessarily on every build. * **Why:** Drastically improves performance of incremental builds. """javascript export default function cachePlugin(options = {}) { return { name: 'cache-plugin', transform(code, id) { const cachedResult = this.cache.get(id); if (cachedResult) { return cachedResult; } // Perform transformation const transformedCode = code + "// Modified by cache plugin" // very simple example; this.cache.set(id, transformedCode); return transformedCode; } }; } """ These component design standards provide a strong foundation for developing high-quality Rollup plugins and applications, which are important for maintainability, performance, and security. Following these practices will result in code that is easier to understand, debug and reuse, leading to more productive development workflows.
# Code Style and Conventions Standards for Rollup This document outlines the code style and conventions to be followed when contributing to Rollup or developing Rollup-based projects. Adhering to these standards ensures consistency, readability, maintainability, and performance across the codebase. ## 1. General Principles * **Consistency:** Maintain a consistent style throughout the codebase. Use automated tools like ESLint and Prettier to enforce these standards. * **Readability:** Write code that is easy to understand and follow. Use meaningful names, comments, and proper indentation. * **Maintainability:** Design code that is easy to modify, extend, and debug. Follow modular design principles and avoid unnecessary complexity. * **Performance:** Optimize code for performance. Consider the impact of your code on bundle size and execution speed. * **Security:** Write secure code. Be aware of potential vulnerabilities and take steps to mitigate them. Use secure coding practices and follow security best practices. ## 2. Formatting ### 2.1. Whitespace * **Indentation:** Use 2 spaces for indentation. Avoid tabs. """javascript // Do This function foo() { console.log('Hello'); } // Don't Do This function bar() { console.log('Hello'); } """ *Why:* Consistent indentation enhances readability and reduces visual clutter. * **Line Length:** Limit lines to a maximum of 120 characters. *Why:* Longer lines are harder to read and can cause horizontal scrolling in some editors. * **Blank Lines:** Use blank lines to separate logical sections of code, such as function definitions, loops, and conditional statements. """javascript // Do This function processData(data) { // Process the data const processedData = data.map(item => item * 2); // Return the processed data return processedData; } // Don't Do This function processData(data){const processedData=data.map(item=>item*2);return processedData;} """ *Why:* Blank lines improve readability by visually separating different parts of the code. * **Trailing Whitespace:** Remove trailing whitespace at the end of lines. *Why:* Trailing whitespace is invisible but can cause unnecessary changes in diffs. * **Whitespace Around Operators:** Use spaces around operators. """javascript // Do This const x = 1 + 2; const y = a * (b - c); // Don't Do This const x=1+2; const y=a*(b-c); """ *Why:* Improves readability by visually separating operators from operands. ### 2.2. Curly Braces * **Placement:** Place opening curly braces on the same line as the statement. """javascript // Do This function foo() { console.log('Hello'); } if (condition) { console.log('True'); } // Don't Do This function foo() { console.log('Hello'); } if (condition) { console.log('True'); } """ *Why:* This style is more compact and easier to read. * **Single-Line Blocks:** For single-line blocks, curly braces are optional but **strongly encouraged** for clarity and to prevent errors when modifying the code later. """javascript // Do This if (condition) { console.log('Condition is true'); } // Discouraged (but allowed if very simple): if (condition) console.log('Condition is true'); """ *Why:* Adding curly braces makes the code more explicit and reduces the risk of introducing bugs. ### 2.3. Quotes * **Strings:** Use single quotes ("'") for strings. """javascript // Do This const name = 'John Doe'; // Don't Do This const name = "John Doe"; """ *Why:* Single quotes are generally preferred for simple strings as they are more readable and consistent. However, template literals should be used where appropriate for string interpolation or multi-line strings. ### 2.4. Semicolons * **Always Use Semicolons:** Always include semicolons at the end of statements. """javascript // Do This const message = 'Hello, world!'; console.log(message); // Don't Do This (relying on ASI) const message = 'Hello, world!' console.log(message) """ *Why:* While JavaScript has Automatic Semicolon Insertion (ASI), relying on it can lead to unexpected behavior and errors. Explicit semicolons make the code more predictable and less prone to bugs. ## 3. Naming Conventions ### 3.1. General * **Descriptive Names:** Use descriptive and meaningful names for variables, functions, and classes. """javascript // Do This const userAge = 30; function calculateArea(width, height) { return width * height; } // Don't Do This const a = 30; function calc(w, h) { return w * h; } """ *Why:* Descriptive names make the code easier to understand and maintain. * **Avoid Abbreviations:** Avoid abbreviations unless they are widely understood in the specific context. """javascript // Do This const maximumFileSize = 1024; // KB // Avoid const maxFS = 1024; """ *Why:* Abbreviations can make the code harder to understand, especially for newcomers. ### 3.2. Variables * **"const" and "let":** Use "const" for variables that should not be reassigned. Use "let" for variables that need to be reassigned. Avoid "var". """javascript // Do This const apiKey = 'YOUR_API_KEY'; let counter = 0; counter++; // Don't Do This var apiKey = 'YOUR_API_KEY'; var counter = 0; """ *Why:* "const" and "let" provide better scoping and help prevent accidental reassignment, leading to more reliable code. "var" has function scoping, which can lead to confusion and bugs. * **Camel Case:** Use camel case for variable names. """javascript // Do This const firstName = 'John'; const lastName = 'Doe'; // Don't Do This const first_name = 'John'; const LastName = 'Doe'; """ *Why:* Camel case is the standard convention for variable names in JavaScript and improves readability. ### 3.3. Functions * **Camel Case:** Use camel case for function names. """javascript // Do This function calculateSum(a, b) { return a + b; } // Don't Do This function CalculateSum(a, b) { return a + b; } """ *Why:* Camel case improves readability and consistency with other variable names. * **Descriptive Verbs:** Use descriptive verbs for function names. """javascript // Do This function getUserById(id) { // ... } function validateInput(input) { // ... } // Don't Do This function user(id) { // ... } function check(input) { // ... } """ *Why:* Clear verb-based names make the function's purpose immediately apparent. ### 3.4. Classes * **Pascal Case:** Use Pascal case for class names. """javascript // Do This class UserAccount { constructor(name, email) { this.name = name; this.email = email; } } // Don't Do This class userAccount { constructor(name, email) { this.name = name; this.email = email; } } """ *Why:* Pascal case is the standard convention for class names in JavaScript. ### 3.5. Constants * **Upper Case with Underscores:** Use upper case with underscores for constant names. """javascript // Do This const API_URL = 'https://example.com/api'; const MAX_RETRIES = 3; // Don't Do This const apiUrl = 'https://example.com/api'; const maxRetries = 3; """ *Why:* This convention clearly distinguishes constants from variables and improves readability. Constants should be declared with "const", making them immutable. ## 4. Code Style Specific to Rollup ### 4.1. Module Imports and Exports * **Explicit Imports:** Use explicit imports to specify which modules or members are being imported. """javascript // Do This import { rollup } from 'rollup'; import commonjs from '@rollup/plugin-commonjs'; // Don't Do This import * as rollup from 'rollup'; // Avoid wildcard imports """ *Why:* Explicit imports make the code more readable and help avoid potential naming conflicts. They also allow for better tree-shaking by only importing the necessary code. * **Named Exports:** Prefer named exports over default exports. """javascript // Do This export function myFunction() { // ... } export const myConstant = 42; // Don't Do This (generally) export default function() { // ... } """ *Why:* Named exports make it clear what is being exported from a module. Also, they prevent naming collisions and enable the TypeScript compiler to provide more accurate type checking. *When Default Exports are Acceptable:* Default exports can be suitable when a module primarily exports a single function, class, or value (e.g., a React component). Consider readability on a case-by-case basis, but consistency within a project is key. * **Consistent Use of File Extensions:** When importing local modules, consistently use file extensions (e.g., ".js", ".ts"). This improves clarity and can avoid resolution issues, particularly in environments with differing module resolution configurations. """javascript // Do This import { something } from './my-module.js'; // Okay (if consistent across the project and module resolution configured correctly) import { something } from './my-module'; """ *Why:* While omitting the extension may work in some configurations, explicitly including it enhances clarity and reduces ambiguity. This is especially helpful when working with different module systems or bundlers beyond Rollup. ### 4.2 Rollup Plugin Development * **Plugin Structure:** Rollup plugins should be structured as functions that return an object with lifecycle hooks. """javascript // Do This import { createFilter } from '@rollup/pluginutils'; function myPlugin(options = {}) { const filter = createFilter(options.include, options.exclude); return { name: 'my-plugin', transform(code, id) { if (!filter(id)) return null; // Modify the code const modifiedCode = code.replace('foo', 'bar'); return { code: modifiedCode, map: null // Sourcemap (optional) }; } }; } export default myPlugin; """ *Why:* This structure is the standard for Rollup plugins and allows Rollup to properly manage the plugin's lifecycle. Using "@rollup/pluginutils" for filtering is also recommended. * **Name Property:** Every Rollup plugin should have a "name" property. """javascript // Do This return { name: 'my-plugin', // ... }; // Don't Do This return { // ... }; """ *Why:* The "name" property is used for logging and debugging purposes, helping identify the plugin responsible for specific transformations. * **Asynchronous Operations:** Use "async/await" for asynchronous operations in plugin hooks. """javascript // Do This async transform(code, id) { const result = await someAsyncOperation(code); return { code: result, map: null }; } // Don't Do This (using Promises directly without async/await) transform(code, id) { return someAsyncOperation(code).then(result => ({ code: result, map: null })); } """ *Why:* "async/await" makes asynchronous code easier to read and reason about, improving maintainability. * **Error Handling:** Implement proper error handling in plugin hooks. """javascript // Do This async transform(code, id) { try { const result = await someAsyncOperation(code); return { code: result, map: null }; } catch (error) { this.error("Error processing ${id}: ${error.message}"); } } // Don't Do This (ignoring potential errors) async transform(code, id) { const result = await someAsyncOperation(code); return { code: result, map: null }; } """ *Why:* Proper error handling prevents unexpected crashes and provides informative error messages, making debugging easier. Use "this.error" (from the plugin context) to report build errors to Rollup. * **Sourcemaps:** Generate sourcemaps when modifying code in plugin hooks. """javascript //With existing sourcemap import { SourceMap } from 'rollup'; async transform(code, id) { try { const result = await someTransformationLibrary.transform(code, {sourceMaps: true}); return { code: result.code, map: result.map //Existing sourcemap }; } catch (error) { this.error("Error processing ${id}: ${error.message}"); } } //Generating new Sourcemap import { SourceMap } from 'rollup'; async transform(code, id) { try { const result = await someTransformationLibrary.transform(code); const map = new SourceMap({ file: id }); map.addFile({ content: code }); return { code: result.code, map: map.toString() //New sourcemap as string }; } catch (error) { this.error("Error processing ${id}: ${error.message}"); } } """ *Why:* Sourcemaps allow developers to debug the original source code even after it has been transformed by Rollup. This greatly enhances the developer experience, especially for complex projects. ### 4.3. Configuration Files (rollup.config.js/ts) * **Clear and Concise Configuration:** Keep Rollup configuration files clean and easy to understand. """javascript // Do This import commonjs from '@rollup/plugin-commonjs'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; // Recommended export default defineConfig({ input: 'src/index.ts', output: { file: 'dist/bundle.js', format: 'es', sourcemap: true }, plugins: [ nodeResolve(), commonjs(), typescript() ] }); // Less Preferred (but allowed): Complex logic inline export default { //... very complicated configuration logic } """ *Why:* A well-structured configuration file improves maintainability and reduces the risk of errors. Using "defineConfig" (if using Rollup's TypeScript support) provides type safety and better editor support. * **Environment Variables:** Use environment variables for configuration options that may vary between environments. """javascript // rollup.config.js import replace from '@rollup/plugin-replace'; const production = process.env.NODE_ENV === 'production'; export default { // ... plugins: [ replace({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') }) ], // ... }; """ *Why:* Environment variables allow you to configure Rollup builds without modifying the configuration file, making it easier to deploy to different environments. * **Multiple Configurations:** Export an array of configurations for multiple builds. """javascript // rollup.config.js import { defineConfig } from 'rollup'; import typescript from '@rollup/plugin-typescript'; export default [ defineConfig({ input: 'src/index.ts', output: [ { file: 'dist/bundle.cjs', format: 'cjs' }, { file: 'dist/bundle.js', format: 'es' } ], plugins: [typescript()] }), defineConfig({ input: 'src/cli.ts', output: { file: 'dist/cli.js', format: 'cjs' }, plugins: [typescript()] }) ]; """ *Why:* This is particularly useful for libraries that want to output multiple formats (CJS, ESM, UMD) or for projects that have separate entry points (e.g., a library and a CLI tool). ### 4.4. TypeScript Usage * **Strict Mode:** Enable strict mode in TypeScript (""strict": true" in "tsconfig.json"). """json // tsconfig.json { "compilerOptions": { "strict": true, // ... other options }, "include": ["src/**/*"], "exclude": ["node_modules"] } """ *Why:* Strict mode enables a set of stricter type checking rules, which can help catch errors early and improve code quality. This includes "noImplicitAny", "noImplicitThis", "strictNullChecks", "strictFunctionTypes", and "strictPropertyInitialization". * **Explicit Types:** Use explicit types for function parameters, return values, and variables when the type cannot be inferred. """typescript // Do This function add(a: number, b: number): number { return a + b; } const message: string = 'Hello, world!'; // Avoid: Relying solely on type inference when clarity improves with explicitness. function add(a, b) { // Implied 'any' type – BAD! return a + b; } """ *Why:* Explicit types make the code more readable and help prevent type-related errors. They also improve the developer experience by providing better code completion and error messages in IDEs. * **Interfaces and Types:** Use interfaces and types to define clear data structures. """typescript // Do This interface User { id: number; name: string; email: string; } type Result<T> = { success: true; data: T; } | { success: false; error: string; }; //Example usage function getUser(id: number): Result<User> { // ... implementation } // Don't Do This (using inline type definitions repeatedly) function processUser(user: { id: number; name: string; email: string }) { // ... } """ *Why:* Interfaces and types provide a clear and reusable way to define data structures, improving code organization and maintainability. Using discriminated unions (like "Result<T>") provides type safety for representing different outcomes of a function. * **Null and Undefined Checks:** Use strict null checks ("strictNullChecks") in TypeScript and handle null and undefined values properly. """typescript // Do This function greet(name?: string) { if (name) { console.log("Hello, ${name}!"); } else { console.log('Hello, guest!'); } } // Less Safe (if strictNullChecks is enabled) function greet(name: string) { // Potential error if name is undefined console.log("Hello, ${name}!"); } """ *Why:* TypeScript's "strictNullChecks" feature helps prevent errors caused by unexpected null or undefined values. Always check for these values before using them. ## 5. Documentation * **JSDoc Comments:** Use JSDoc comments to document functions, classes, and variables. """javascript /** * Calculates the sum of two numbers. * * @param {number} a - The first number. * @param {number} b - The second number. * @returns {number} The sum of the two numbers. */ function calculateSum(a, b) { return a + b; } """ *Why:* JSDoc comments provide valuable information about the code and can be used to generate documentation automatically. * **Meaningful Comments:** Write comments that explain the intent and purpose of the code, not just what the code does. """javascript // Do This // Cache the result to improve performance const cachedResult = calculateExpensiveOperation(); // Don't Do This (obvious and unhelpful) // Calculate the result const result = calculateExpensiveOperation(); """ *Why:* Meaningful comments help other developers understand the code and make it possible to maintain it more effectively. ## 6. Testing * **Unit Tests:** Write comprehensive unit tests to ensure that the code works as expected. * **Integration Tests:** Write integration tests to ensure that different parts of the system work together correctly. * **Test Coverage:** Aim for high test coverage to minimize the risk of bugs. * **Descriptive Test Names:** Use descriptive names for tests to make it clear what each test is verifying. ## 7. Security * **Input Validation:** Validate all user input to prevent security vulnerabilities. * **Output Encoding:** Encode output to prevent cross-site scripting (XSS) attacks. * **Secure Dependencies:** Use secure dependencies and keep them up to date. * **Avoid Hardcoded Secrets:** Avoid hardcoding secrets (e.g., API keys, passwords) in the code. Use environment variables or configuration files to store sensitive information. ## 8. Tooling * **ESLint:** Use ESLint to enforce code style and detect potential errors. * **Prettier:** Use Prettier to format the code automatically. * **TypeScript:** Use TypeScript for type checking and to improve code quality. * **Rollup Plugins:** Utilize appropriate Rollup plugins to optimize and transform the code. * **Git:** Utilize Git for version control, following standard branching and commit message conventions. ## 9. Updating this document This document is a living document and should be updated as needed to reflect new best practices and changes in the Rollup ecosystem. All contributions and suggestions are welcome.
# Deployment and DevOps Standards for Rollup This document outlines the Deployment and DevOps standards for Rollup-based projects, focusing on build processes, CI/CD pipelines, and production considerations. These standards aim to ensure maintainable, performant, and secure deployments of Rollup bundles. ## 1. Build Processes and Production Considerations ### 1.1. Configurable Build Scripts **Standard:** Utilize configurable build scripts for different environments (development, staging, production). **Why:** Encapsulating build logic in scripts enables automation and environment-specific optimizations. Configuration allows adjusting output and optimization levels depending on where the build is being deployed. **Do This:** * Use "npm scripts" or similar to define build commands. * Leverage environment variables to modify Rollup's configuration. **Don't Do This:** * Hardcode environment-specific settings directly into your Rollup configuration file. * Manually run Rollup builds without utilizing scripts. **Example:** """json // package.json { "scripts": { "build:dev": "rollup -c rollup.config.js --environment NODE_ENV:development", "build:prod": "rollup -c rollup.config.js --environment NODE_ENV:production" } } """ """javascript // rollup.config.js import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import terser from '@rollup/plugin-terser'; const production = process.env.NODE_ENV === 'production'; export default { input: 'src/main.js', output: { file: 'dist/bundle.js', format: 'iife', sourcemap: true }, plugins: [ nodeResolve(), commonjs(), production && terser() // Only minify in production ] }; """ **Anti-Pattern:** Including sensitive information (API keys, database credentials) in your Rollup configuration or build scripts. Use secure environment variables. ### 1.2. Code Splitting for Optimized Delivery **Standard:** Implement code splitting to reduce initial load times. **Why:** Chunking your code allows browsers to download only the necessary code for a particular page or feature, significantly improving performance, especially for large applications. **Do This:** * Use dynamic "import()" statements for modules that are not needed upfront. * Configure Rollup to create separate chunks for these modules. **Don't Do This:** * Bundle all code into a single, monolithic file. * Neglect to analyze the generated chunks and adjust splitting strategy as needed. **Example:** """javascript // src/main.js async function loadDashboard() { const { renderDashboard } = await import('./dashboard'); renderDashboard(); } loadDashboard(); // src/dashboard.js export function renderDashboard() { console.log('Rendering the dashboard!'); } """ """javascript // rollup.config.js import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; export default { input: 'src/main.js', output: { dir: 'dist', format: 'es', sourcemap: true, chunkFileNames: 'chunks/[name]-[hash].js' // Explicitly name chunks }, plugins: [ nodeResolve(), commonjs() ] }; """ **Explanation:** The dynamic import in "src/main.js" tells Rollup to create a separate chunk for "src/dashboard.js". The "chunkFileNames" output option helps manage the naming of these split chunks. ### 1.3. Asset Management and Optimization **Standard:** Integrate asset management and optimization during the build process. **Why:** Handling assets correctly ensures that images, fonts, and other resources are optimized for performance and delivered efficiently. **Do This:** * Use Rollup plugins like "@rollup/plugin-image" or community plugins for processing images, fonts, and other assets. * Implement techniques like image compression, lazy loading, and resource inlining. **Don't Do This:** * Include large, unoptimized assets directly in your bundle. * Serve assets without appropriate caching headers. **Example:** """javascript // rollup.config.js import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import image from '@rollup/plugin-image'; export default { input: 'src/main.js', output: { file: 'dist/bundle.js', format: 'iife', sourcemap: true }, plugins: [ nodeResolve(), commonjs(), image() // Handles image imports. Configure options within its plugin config if needed. ] }; """ """javascript // src/main.js import logo from './logo.png'; const img = document.createElement('img'); img.src = logo; document.body.appendChild(img); """ **Advanced Asset Handling:** For more advanced asset handling consider using a dedicated asset pipeline tool alongside Rollup, communicating via emitted files. Rollup can then bundle the generated JavaScript files that reference those optimized assets. This approach provides more control over transformations and optimizations. ### 1.4. Sourcemaps for Debugging **Standard:** Generate and properly configure sourcemaps for production debugging. **Why:** Sourcemaps allow you to debug your minified code in the browser using the original source files. This is crucial for diagnosing and fixing production issues. **Do This:** * Enable sourcemap generation in your Rollup configuration ("output.sourcemap: true"). * Ensure your server is configured to serve sourcemaps. * Use a service (like Sentry or Bugsnag) to automatically upload sourcemaps for error tracking. **Don't Do This:** * Deploy code to production without sourcemaps. * Expose sourcemaps publicly if they contain sensitive information. **Example:** """javascript // rollup.config.js export default { input: 'src/main.js', output: { file: 'dist/bundle.js', format: 'iife', sourcemap: true // Enable sourcemap generation } }; """ **Best Practice:** Store sourcemaps in a secure location accessible only to your error tracking service. Update your deployment process to automatically upload the sourcemaps after a successful build and deployment. ## 2. CI/CD Pipelines ### 2.1. Automated Builds and Testing **Standard:** Automate the build and testing process as part of your CI/CD pipeline. **Why:** Automated builds and tests ensure consistent and reliable deployments, reducing the risk of introducing errors into production. **Do This:** * Use a CI/CD platform like Jenkins, GitLab CI, GitHub Actions, CircleCI, or similar. * Configure the pipeline to run tests, linting, and Rollup builds. * Fail the build if any of these steps fail. **Don't Do This:** * Manually build and deploy code to production. * Skip testing or linting in the CI/CD pipeline. **Example (GitHub Actions):** """yaml # .github/workflows/ci.yml name: CI on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '18.x' # Use a supported Node.js version - name: Install dependencies run: npm install - name: Run tests run: npm test - name: Build run: npm run build:prod """ **Explanation:** This workflow automates the installation of dependencies, running tests, and creating a production build using "npm run build:prod" (defined in "package.json"). Any failure in these steps will prevent the workflow from completing, thereby preventing a deployment. ### 2.2. Versioning and Tagging **Standard:** Implement proper versioning and tagging in your CI/CD pipeline. **Why:** Versioning and tagging provide a clear history of releases and allow for easy rollback to previous versions if necessary. **Do This:** * Use semantic versioning (SemVer). * Automatically tag commits with version numbers during the release process. * Store build artifacts with versioned names. **Don't Do This:** * Deploy code without versioning or tagging. * Use arbitrary or inconsistent versioning schemes. **Example (using "npm version" and git tagging):** """bash # In CI/CD pipeline after successful build: npm version patch # or minor/major based on the changes. This increments the version in package.json git add package.json git commit -m "Bump version to $(npm pkg get version)" git tag $(npm pkg get version) git push origin $(npm pkg get version) """ **Important:** Ensure your CI/CD system has the permissions to push tags to your repository. Consider using a dedicated "release" branch to trigger the versioning and tagging process. ### 2.3. Environment-Specific Configuration **Standard:** Manage environment-specific configuration using environment variables or configuration files. **Why:** Environment-specific configuration allows you to deploy the same code to different environments (development, staging, production) without modifying the code itself. **Do This:** * Use environment variables to store sensitive configuration data. * Load environment variables into your application at runtime. * Use ".env" files for development environments. **Don't Do This:** * Hardcode environment-specific settings directly into your code. * Commit sensitive configuration data to your version control system. **Example (using "dotenv" and environment variables):** """javascript // .env (for development) API_URL=http://localhost:3000 """ """javascript // rollup.config.js import dotenv from 'rollup-plugin-dotenv'; export default { // ...other config plugins: [ dotenv() ] } """ """javascript // src/api.js const apiUrl = process.env.API_URL; export async function fetchData() { const response = await fetch(apiUrl + '/data'); return response.json(); } """ **Note:** In production environments, environment variables should be set directly in the deployment environment (e.g., using the cloud provider's configuration settings) and not through ".env" files. ### 2.4. Rollback Strategy **Standard:** Define and test a rollback strategy. **Why:** A rollback strategy allows you to quickly revert to a previous version of your application in case of critical errors or deployment failures. **Do This:** * Keep previous build artifacts available. * Implement a mechanism to easily switch between versions. * Test the rollback process regularly. **Don't Do This:** * Deploy updates without a tested rollback plan. * Rely on manual intervention for rollbacks. **Implementation:** Rollback strategies depend heavily on the specifics of your deployment platform. Consider using feature flags to quickly disable problematic features. Containerization technologies (like Docker) and orchestration systems (like Kubernetes) significantly simplify rollback procedures. ## 3. Monitoring and Alerting ### 3.1. Application Performance Monitoring (APM) **Standard:** Integrate Application Performance Monitoring (APM) tools to monitor the performance of your Rollup-based application. **Why:** APM tools provide insights into the performance of your application, allowing you to identify and resolve performance bottlenecks. **Do This:** * Use tools like New Relic, Datadog, Sentry, or similar. * Track key metrics such as load times, API response times, and error rates. * Set up alerts to notify you when performance metrics exceed predefined thresholds. **Don't Do This:** * Deploy applications without performance monitoring. * Ignore performance alerts. **Implementation:** APM tools often require specific instrumentation within the application code. This might involve wrapping key functions or adding custom event tracking. ### 3.2. Error Tracking and Reporting **Standard:** Implement robust error tracking and reporting to quickly identify and address issues in production. **Why:** Error tracking allows you to proactively identify and fix errors before they impact users. **Do This:** * Use tools like Sentry, Bugsnag, or Rollbar. * Capture detailed error reports, including stack traces and user context. * Set up alerts to notify you of new or recurring errors. **Don't Do This:** * Rely solely on user reports for error detection. * Ignore error reports. **Example (Sentry):** """javascript import * as Sentry from "@sentry/browser"; import { Integrations } from "@sentry/tracing"; Sentry.init({ dsn: "YOUR_SENTRY_DSN", integrations: [new Integrations.BrowserTracing()], // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. // We recommend adjusting this value in production tracesSampleRate: 0.1, release: "my-app@" + process.env.VERSION, // Include version information environment: process.env.NODE_ENV }); try { // Your application code } catch (e) { Sentry.captureException(e); } """ **Best Practice:** Include version information "(process.env.VERSION)" and the environment "(process.env.NODE_ENV)" with your error reports. This greatly aids in diagnosing the root cause of errors, especially in complex deployment environments. Set the "release" value in Sentry's configuration. ### 3.3. Logging **Standard:** Implement comprehensive logging to capture important events and debug issues. **Why:** Logging provides a record of application activity, allowing you to diagnose problems and track user behavior. **Do This:** * Use a logging library like "winston" or "pino". * Log important events, such as user logins, API calls, and errors. * Use different log levels (e.g., debug, info, warn, error) to indicate the severity of the event. * Rotate log files to prevent them from growing too large. * Centralize log storage for easier analysis. **Don't Do This:** * Log sensitive information (e.g., passwords, API keys). * Log excessively, which can impact performance. * Store logs on the same server as your application indefinitely. **Technology Specifics** * **ES Modules and Dynamic Imports:** Rollup is optimized for ES modules and dynamic "import()" statements. When building libraries, prefer ES module outputs, but also consider providing CommonJS or UMD builds for maximum compatibility. * **Tree Shaking:** Rollup's tree shaking algorithm is highly effective. Structure your code to maximize its ability to remove dead code and thus only include what's actually used. Avoid side-effecting code in module scope. * **Plugin Ecosystem:** Leverage the extensive Rollup plugin ecosystem to handle various tasks like TypeScript compilation, CSS processing, asset management, and code minification. * **Configuration:** Rollup configuration files can be complex. Use JavaScript (instead of JSON) for your configuration file allowing for dynamic configurations and better reusability. By adhering to these deployment and DevOps standards, you can ensure that your Rollup-based projects are deployed reliably, performant, and securely. Regularly review and update these standards to align with the latest best practices and Rollup features.
# Testing Methodologies Standards for Rollup This document outlines the recommended testing methodologies for Rollup plugins and configurations. Adhering to these standards ensures code quality, maintainability, and reduces the risk of regressions and vulnerabilities. ## Unit Testing ### Standards * **Do This:** Write focused unit tests that isolate individual functions and modules. * **Why:** Unit tests provide fast feedback on code changes and help pinpoint the source of errors quickly. This approach decreases debug time and increases confidence in the reliability of individual components. * **Don't Do This:** Create overly broad unit tests that test multiple functionalities simultaneously. These tests become brittle, difficult to maintain, and often fail to accurately identify the broken component. ### Implementation * **Frameworks:** Use a testing framework like Jest, Mocha, or Ava, combined with an assertion library like Chai or expect. Jest is generally favored due to its built-in features like mocking and code coverage. * **Mocking:** Use mocking libraries (e.g., Jest's "jest.mock()") to isolate the unit under test from its dependencies. Avoid mocking the internals of Rollup itself, unless absolutely necessary for testing edge cases. Focus on mocking dependencies *used* by your plugin. * **Test Coverage:** Strive for high test coverage (80% or higher). Use tools like Istanbul (integrated into Jest) to measure coverage and identify gaps. Coverage shouldn't be the sole metric, but a good indicator. ### Code Example (Jest) """javascript // src/my-plugin.js import { transform } from './transformer'; // Hypothetical transformer function export default function myPlugin() { return { name: 'my-plugin', transform(code, id) { if (id.endsWith('.special.js')) { return transform(code); } return null; } }; } // src/transformer.js export function transform(code) { // Complex transformation logic here return code.toUpperCase(); // Simple example } // test/my-plugin.test.js import myPlugin from '../src/my-plugin'; import { transform } from '../src/transformer'; // Import the actual transform function jest.mock('../src/transformer', () => ({ // Mock the transformer transform: jest.fn(code => "MOCKED_${code}") })); describe('myPlugin', () => { it('should transform .special.js files', () => { const plugin = myPlugin(); const code = 'some code'; const id = 'file.special.js'; const result = plugin.transform(code, id); expect(transform).toHaveBeenCalledWith(code); // Check mock was invoked expect(result).toBe("MOCKED_${code}"); //Check mock return }); it('should not transform other files', () => { const plugin = myPlugin(); const code = 'some code'; const id = 'file.js'; const result = plugin.transform(code, id); expect(result).toBeNull(); }); }); """ * **Anti-pattern:** Directly depending on the file system or external APIs within a unit test *without mocking*. This introduces external dependencies, making tests slow, unreliable, and harder to reason about. Always mock these dependencies to isolate the unit. ## Integration Testing ### Standards * **Do This:** Verify that different parts of your Rollup plugin work correctly together. Specifically, test the interaction between your plugin, Rollup's internal APIs, and other plugins that might be used in a typical build. * **Why:** Integration tests catch bugs that arise from interactions between modules that individually pass unit tests. This is crucial for Rollup, where plugins frequently modify Rollup's internal state and interact with the module graph. * **Don't Do This:** Neglect integration testing in favor of relying solely on unit tests. This can lead to overlooked issues related to plugin interoperability and Rollup's build process. Also, don't make integration tests *too* broad – keep them focused on specific interactions. ### Implementation * **Rollup API:** Leverage Rollup's programmatic API ("rollup.rollup()", "bundle.generate()") to simulate real-world build scenarios. * **Configuration Files:** Create small, representative "rollup.config.js" files for integration tests. * **Assertions on Output:** Assert on the generated bundle's code, file structure, and emitted assets. * **Plugin Interoperability:** Test your plugin alongside other commonly used plugins (e.g., "@rollup/plugin-commonjs", "@rollup/plugin-node-resolve"). ### Code Example """javascript // test/integration.test.js import { rollup } from 'rollup'; import myPlugin from '../src/my-plugin'; import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import * as fs from 'fs/promises'; describe('Integration Tests', () => { it('should integrate with commonjs and resolve plugins', async () => { const bundle = await rollup({ input: 'test/fixtures/input.js', plugins: [ myPlugin(), commonjs(), resolve({ // Resolve options can be set specifically for testing browser: true // Mock Node context, simulate browser }) ] }); const { output } = await bundle.generate({ format: 'es' }); const generatedCode = output[0].code; expect(generatedCode).toContain('// Generated by my-plugin'); // Check for plugin modification expect(generatedCode).toContain('console.log'); // Validate CommonJS and resolve worked // Optionally write the generated code to a file for debugging failing tests // await fs.writeFile('test/output.js', generatedCode); }, 30000); it('should handle errors gracefully', async () => { // Example config that causes an error in myPlugin const shouldThrow = async () => { await rollup({ // Await the rollup call directly here input: 'test/fixtures/input.js', plugins: [ myPlugin({ shouldFail: true }), // Pass options to simulate error ] }); } await expect(shouldThrow).rejects.toThrowError('Simulated Error'); }); }); """ * **Anti-pattern:** Running integration tests against a *real* production environment or staging server. Integration tests should be self-contained and reproducible, relying only on local files and mocked services or APIs. Relying on external systems introduces volatility and makes debugging nearly impossible. Also avoid complex file system operations within tests unless they are part of the specific functionality you're testing. ## End-to-End (E2E) Testing ### Standards * **Do This:** Simulate real user workflows using a browser environment. Test the *entire* build process, from input files to output. Only necessary for plugins that heavily interact with the browser. * **Why:** E2E tests ensure that the built application functions as expected in a production-like environment. This catches issues arising from complex build configurations, browser-specific behavior, and interactions between different parts of the application. * **Don't Do This:** Use E2E tests as a substitute for unit or integration tests. E2E tests are slower and more complex to set up and maintain, making them unsuited for testing individual components or interactions. ### Implementation * **Frameworks:** Use frameworks like Cypress, Playwright, or Puppeteer. Each framework has a different set of trade-offs in terms of performance, ease of use, and browser support. Playwright is generally preferred for modern projects. * **Sample Application:** Create a small sample application that uses your Rollup plugin and demonstrates typical use cases. * **Build Process:** Integrate the Rollup build process into your E2E test suite. Run Rollup programmatically or via a shell command before executing your browser tests. * **Assertions via Browser:** Use the E2E testing framework's API to interact with the application in the browser, assert on the rendered output, and verify expected behavior. ### Code Example (Playwright) """javascript // playwright.config.js module.exports = { webServer: { command: 'npm run build && npm run serve', // Build and serve your test app port: 3000, timeout: 120 * 1000, // Extend timeout for build reuseExistingServer: !process.env.CI, }, use: { baseURL: 'http://localhost:3000', browserName: 'chromium', }, testMatch: 'test/e2e/*.test.js', }; // test/e2e/my-plugin.test.js const { test, expect } = require('@playwright/test'); test('My Plugin modifies page content', async ({ page }) => { await page.goto('/'); // Add a selector to isolate content myPlugin modifies const title = await page.locator('#test-area'); // Assume test area with id await expect(title).toHaveText("Plugin Applied"); // Expect text based on plugin output }); """ * **Anti-pattern:** Writing overly complex or flaky E2E tests that are difficult to debug. Keep E2E tests focused on verifying critical user flows and minimize dependencies on external services or data. Use "test.describe.configure({ mode: 'serial' })" for E2E tests to ensure they are run sequentially, to avoid race conditions and interference between tests if they share state. ## Additional Considerations * **CI/CD Integration:** Integrate your test suite into your CI/CD pipeline to automatically run tests on every commit. * **Performance Testing:** Use tools like Lighthouse or WebPageTest to measure the impact of your Rollup plugin on the performance of the generated bundle. Especially relevant if your plugin performs complex transformations. * **Security Testing:** Use linters and static analysis tools (e.g., ESLint with security-related rules, SonarQube) to identify potential security vulnerabilities in your code. * **Regression Testing:** Maintain a comprehensive suite of regression tests to catch bugs introduced by new code changes. When fixing a bug, *always* write a test that reproduces the bug to prevent future regressions. * **Snapshot Testing:** Consider snapshot testing for complex UI components or configurations. Use with caution, as snapshots can become brittle and require frequent updates. * **Property-Based Testing (Fuzzing):** Property-based testing (using libraries like fast-check) can generate a wide range of inputs to uncover edge cases and unexpected behavior. * **Documentation:** Always provide comprehensive documentation for your tests, including clear descriptions of the test cases, setup instructions, and expected results. Aim for full transparency for anyone that has to work with the tests. * **Code Review:** Code review is a vital practice. Another developer reviewing your code may suggest improvements to your testing methodology. By following these standards, you can ensure the quality, maintainability, and reliability of your Rollup plugins and configurations. Remember to adapt these standards to your specific project needs and coding style.