# Core Architecture Standards for Rollup
This document outlines the core architectural standards for developing Rollup plugins, core functionalities, and related tools. Adhering to these standards ensures maintainability, performance, security, and consistency across the project. It targets both human developers and AI coding assistants.
## 1. Fundamental Architectural Patterns
### 1.1 Modular Design
**Standard:** Implement modular design principles to promote code reusability, testability, and maintainability. Rollup's core and plugins should be composed of independent, well-defined modules with minimal dependencies.
**Why:**
* **Maintainability:** Easier to understand, modify, and debug individual modules.
* **Testability:** Modules can be tested in isolation, leading to more robust and reliable code.
* **Reusability:** Modules can be reused across different parts of the codebase or in other projects.
**Do This:**
* Break down large functionalities into smaller, focused modules.
* Use dependency injection to decouple modules.
* Ensure each module has a clear and well-defined responsibility.
**Don't Do This:**
* Create monolithic components with tightly coupled dependencies.
* Overload modules with multiple responsibilities.
**Example:**
"""javascript
// src/moduleA.js
export function doSomething(input) {
// ... complex logic
return result;
}
// src/moduleB.js
import { doSomething } from './moduleA';
export function useModuleA(data) {
const processedData = doSomething(data);
// ... further processing
return finalResult;
}
"""
### 1.2 Event-Driven Architecture
**Standard:** Leverage Rollup's plugin hooks (e.g., "buildStart", "resolveId", "transform", "generateBundle") to create an event-driven architecture for extending Rollup's functionality.
**Why:**
* **Extensibility:** Plugins can tap into Rollup's build process without modifying the core.
* **Decoupling:** Plugins operate independently, reducing the risk of conflicts or unexpected side effects.
* **Flexibility:** Allows developers to customize Rollup's behavior to suit specific project needs.
**Do This:**
* Use appropriate plugin hooks to intercept and modify Rollup's build process.
* Ensure event handlers are efficient and avoid blocking the main thread.
* Provide clear and consistent plugin options for customization.
**Don't Do This:**
* Directly modify Rollup's core code to add new features.
* Create plugin hooks that conflict with existing ones.
**Example:**
"""javascript
// rollup-plugin-example.js
export default function examplePlugin(options = {}) {
return {
name: 'example-plugin',
transform(code, id) {
if (options.applyTo && !options.applyTo.test(id)) {
return null;
}
const transformedCode = code.replace(/foo/g, 'bar');
return {
code: transformedCode,
map: null // If you create a sourcemap, return it here
};
},
generateBundle(options, bundle, isWrite) {
// Access and manipulate the generated bundle
for (const fileName in bundle) {
const chunk = bundle[fileName];
if (chunk.type === 'chunk') {
chunk.code = chunk.code.replace(/console\.log/g, '//console.log'); //Example: Remove console.log statements
}
}
}
};
}
"""
### 1.3 Data Flow and Immutability
**Standard:** Maintain a clear and predictable data flow throughout the build process. Emphasize immutability to prevent unexpected state changes and simplify debugging.
**Why:**
* **Predictability:** Easier to reason about the behavior of the code.
* **Debuggability:** Immutability simplifies tracking down errors.
* **Performance (potentially):** Although immutability can introduce overhead, it can also enable optimizations (e.g., memoization, structural sharing).
**Do This:**
* Use immutable data structures where appropriate (e.g., "Object.freeze", libraries like Immutable.js or Immer if needed for complex scenarios - but only when justified by performance bottlenecks).
* Avoid modifying input parameters directly within functions.
* Return new objects/arrays instead of mutating existing ones.
**Don't Do This:**
* Rely on mutable state to track changes across different phases of the build.
* Modify input parameters directly within functions without creating copies.
**Example:**
"""javascript
function processData(data) {
// Create a new object instead of modifying the original
const newData = { ...data, processed: true };
return newData;
}
const originalData = { value: 'hello' };
const processedData = processData(originalData);
console.log(originalData); // { value: 'hello' } Original remains unchanged
console.log(processedData); // { value: 'hello', processed: true } New object with modification
"""
## 2. Project Structure and Organization
### 2.1 Directory Structure
**Standard:** Follow a consistent and well-defined directory structure for all Rollup projects, plugins, and related tools.
**Example:**
"""
rollup-project/
├── src/ # Source code
│ ├── core/ # Core modules
│ │ ├── index.js # Entry point for core module
│ │ └── utils.js # Utility functions
│ ├── plugins/ # Plugin-related modules
│ │ ├── pluginA.js # Plugin A implementation
│ │ └── pluginB.js # Plugin B implementation
│ └── index.js # Main entry point for the project
├── test/ # Unit Tests
│ ├── core/ # Core module tests
│ │ └── utils.test.js # Tests for utils.js
│ ├── plugins/
│ │ └── pluginA.test.js # Tests for pluginA.js
├── dist/ # Output files (generated by Rollup)
├── rollup.config.js # Rollup configuration file
├── package.json # Project dependencies and metadata
└── README.md # Project documentation
"""
**Why:**
* **Discoverability:** Easily locate specific files and functionalities.
* **Maintainability:** Consistent structure simplifies navigation and understanding of the project.
* **Scalability:** Well-organized structure facilitates adding new features and modules.
**Do This:**
* Separate source code, tests, and build artifacts into dedicated directories.
* Use descriptive names for directories and files.
* Organize modules within "src" based on their functionality.
**Don't Do This:**
* Mix source code, tests, and build artifacts in the same directory.
* Use cryptic or ambiguous names for directories and files.
* Create a deeply nested or overly complex directory structure.
### 2.2 Naming Conventions
**Standard:** Use clear, descriptive, and consistent naming conventions for all variables, functions, classes, and files. Follow a consistent naming style (e.g., camelCase for variables and functions, PascalCase for classes).
**Why:**
* **Readability:** Easy to understand the purpose and functionality of code elements.
* **Maintainability:** Consistent naming simplifies code modification and debugging.
* **Collaboration:** Improves communication and understanding among developers.
**Do This:**
* Use descriptive names that accurately reflect the purpose of the code element.
* Follow the camelCase convention for variables and functions (e.g., "myVariable", "calculateSum").
* Follow the PascalCase convention for classes and components (e.g., "MyComponent", "DataProcessor").
* Use UPPER_SNAKE_CASE for constants (e.g., "MAX_VALUE", "DEFAULT_SETTINGS").
**Don't Do This:**
* Use single-character or ambiguous names (e.g., "x", "y", "data").
* Violate naming conventions (e.g., use PascalCase for variables).
* Use inconsistent naming throughout the codebase.
**Example:**
"""javascript
// Variable names
const userAge = 30; // Good: Descriptive and camelCase
const a = 30; // Bad: Ambiguous
// Function names
function calculateTotal(price, quantity) { // Good: Descriptive and camelCase
// ...
}
// Class names
class UserProfile { // Good: PascalCase
// ...
}
"""
### 2.3 Module Exports
**Standard:** Use named exports for better code organization and tree-shaking capabilities unless a module has a clear and singular primary function.
**Why:**
* **Tree-shaking:** Enables Rollup to remove unused code during the build process.
* **Readability:** Clearer identification of exported functions and variables.
* **Maintainability:** Easier to refactor and update code without breaking dependencies.
**Do This:**
* Use named exports for most modules.
* Use a default export only when a module has a single, primary function or component.
**Don't Do This:**
* Rely solely on default exports, especially for modules with multiple functions.
* Mix named and default exports inconsistently.
**Example:**
"""javascript
// src/utils.js (Named exports)
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// src/MyComponent.js (Default export, assuming it's the main component)
import React from 'react';
function MyComponent() {
// ...
return (
My Component
);
}
export default MyComponent;
"""
## 3. Coding Style and Best Practices
### 3.1 ECMAScript Standards
**Standard:** Adhere to the latest ECMAScript standards (ES2023 and beyond) for writing modern, efficient, and readable JavaScript code.
**Why:**
* **Modernity:** Leveraging the latest language features improves code quality and performance.
* **Compatibility:** Ensures compatibility with modern browsers and environments.
* **Readability:** Modern syntax often results in more concise and expressive code.
**Do This:**
* Use "const" and "let" for variable declarations instead of "var".
* Use arrow functions for concise function definitions.
* Use template literals for string interpolation.
* Use destructuring for extracting values from objects and arrays.
* Utilize modern features of the JS language.
**Don't Do This:**
* Use "var" for variable declarations.
* Use traditional function expressions when arrow functions are more appropriate.
* Use string concatenation instead of template literals.
* Avoid destructuring when it can improve code readability.
**Example:**
"""javascript
// Variable declaration
const name = 'John';
let age = 30;
// Arrow function
const multiply = (x, y) => x * y;
// Template literal
const message = "Hello, ${name}! You are ${age} years old.";
// Destructuring
const user = { firstName: 'John', lastName: 'Doe' };
const { firstName, lastName } = user;
console.log(firstName, lastName); // John Doe
"""
### 3.2 Asynchronous Programming
**Standard:** Use "async/await" for handling asynchronous operations to improve code readability and error handling. Avoid deeply nested callbacks (callback hell).
**Why:**
* **Readability:** "async/await" makes asynchronous code look and behave like synchronous code.
* **Error Handling:** "try/catch" blocks can be used to handle errors in asynchronous operations.
* **Maintainability:** Easier to reason about and debug asynchronous code.
**Do This:**
* Use "async" keyword to define asynchronous functions.
* Use "await" keyword to wait for the completion of asynchronous operations.
* Use "try/catch" blocks to handle errors gracefully.
**Don't Do This:**
* Rely on deeply nested callbacks for handling asynchronous operations.
* Ignore errors in asynchronous operations.
**Example:**
"""javascript
async function fetchData() {
try {
const response = await fetch('https://example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw error; // Re-throw the error to be handled by the caller
}
}
async function processData() {
try {
const data = await fetchData();
// ... process data ...
} catch(error) {
//Error already logged in FetchData, maybe do something more.
}
}
"""
### 3.3 Error Handling
**Standard:** Implement robust error handling mechanisms to prevent unexpected crashes and provide informative error messages.
**Why:**
* **Stability:** Ensures the application continues to function even when errors occur.
* **Debuggability:** Provides valuable information for diagnosing and resolving errors.
* **User Experience:** Prevents the application from crashing and provides user-friendly error messages.
**Do This:**
* Use "try/catch" blocks to handle exceptions.
* Log errors with descriptive messages, including relevant context.
* Provide meaningful error messages to the user when appropriate (without exposing sensitive information).
* Consider using error tracking tools for monitoring and analyzing errors in production.
**Don't Do This:**
* Ignore exceptions or swallow errors silently.
* Expose sensitive information in error messages.
* Rely on generic error messages that provide no useful information.
**Example:**
"""javascript
try {
// ... code that may throw an error ...
if (x === 0) {
throw new Error('Division by zero is not allowed.');
}
const result = 10 / x;
return result;
} catch (error) {
console.error('An error occurred:', error.message); // Log the error with context
// Perhaps throw a custom error for caller to handle or retry.
throw new CustomError("Division failed")
}
"""
### 3.4 Comments and Documentation
**Standard:** Write clear, concise, and up-to-date comments and documentation to explain the purpose, functionality, and usage of code elements. Use JSDoc-style comments for documenting functions, classes, and modules.
**Why:**
* **Readability:** Comments and documentation make it easier to understand the code.
* **Maintainability:** Helps developers quickly grasp the functionality of code elements.
* **Collaboration:** Facilitates communication and knowledge sharing among developers.
**Do This:**
* Write comments to explain complex logic or non-obvious code.
* Use JSDoc-style comments to document functions, classes, and modules.
* Keep comments and documentation up-to-date with the latest code changes.
* Add documentation to public facing portions of the software.
**Don't Do This:**
* Write redundant comments that simply repeat what the code already says.
* Leave outdated or inaccurate comments.
* Neglect to document important code elements.
**Example:**
"""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 add(a, b) {
// This function adds two numbers together
return a + b;
}
"""
## 4. Performance Optimization
### 4.1 Code Splitting
**Standard:** Utilize Rollup's code splitting capabilities to create smaller bundles and improve initial load times.
**Why:**
* **Improved Load Times:** Smaller bundles download and parse faster, resulting in a better user experience.
* **Caching:** Browsers can cache individual chunks, so only the changed code needs to be re-downloaded.
* **Reduced Bandwidth Consumption:** Users only download the code they need.
**Do This:**
* Use dynamic imports ("import()") to create split points in your code.
* Configure Rollup to generate multiple output chunks.
**Don't Do This:**
* Bundle the entire application into a single large chunk.
* Over-split the code into too many small chunks, which can lead to increased request overhead.
**Example:**
"""javascript
// Dynamically import a module
async function loadModule() {
const module = await import('./myModule');
module.default();
}
loadModule();
"""
### 4.2 Tree-Shaking
**Standard:** Take advantage of Rollup's tree-shaking feature to eliminate unused code and reduce bundle size.
**Why:**
* **Smaller Bundles:** Removes dead code, resulting in smaller and more efficient bundles.
* **Improved Performance:** Reduces the amount of code that needs to be downloaded and parsed.
**Do This:**
* Use ES modules (named exports) to enable tree-shaking.
* Avoid side effects in your code.
* Use "sideEffects: false" in your "package.json" to indicate that your code has no side effects (if applicable).
**Don't Do This:**
* Rely on CommonJS modules, which are not as effectively tree-shakable.
* Introduce side effects that prevent Rollup from removing unused code.
**Example:**
"""javascript
// package.json
{
"name": "my-module",
"version": "1.0.0",
"sideEffects": false // Indicate no side effects
}
"""
### 4.3 Minimization and Compression
**Standard:** Use a minifier (e.g., Terser) to reduce the size of the generated code. Enable Gzip or Brotli compression on the server to further reduce the file sizes transmitted to the browser.
**Why:**
* **Smaller File Sizes:** Minimization removes whitespace and shortens variable names, reducing file sizes.
* **Improved Load Times:** Smaller files download and parse faster.
* **Reduced Bandwidth Consumption:** Users download less data.
**Do This:**
* Configure Rollup to use a minifier plugin (e.g., "@rollup/plugin-terser").
* Enable Gzip or Brotli compression on the server.
**Don't Do This:**
* Deploy unminified or uncompressed code to production.
**Example:**
"""javascript
// rollup.config.js
import terser from '@rollup/plugin-terser';
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife'
},
plugins: [
terser() // Minify the code
]
};
"""
## 5. Security Best Practices
### 5.1 Dependency Management
**Standard:** Use a dependency management tool (e.g., npm, yarn, pnpm) to manage project dependencies. Regularly update dependencies to patch security vulnerabilities.
**Why:**
* **Security:** Outdated dependencies may contain security vulnerabilities.
* **Stability:** Dependency management ensures consistent versions across different environments.
* **Reproducibility:** Makes it easier to recreate the project environment.
**Do This:**
* Use "npm install", "yarn install", or "pnpm install" to install dependencies.
* Use "npm audit", "yarn audit", or "pnpm audit" to identify security vulnerabilities.
* Regularly update dependencies to the latest versions.
* Use "package-lock.json" or "yarn.lock" to lock dependency versions.
**Don't Do This:**
* Manually download and install dependencies.
* Ignore security vulnerabilities reported by the dependency management tool.
* Use outdated or unmaintained dependencies.
### 5.2 Input Validation
**Standard:** Validate all external inputs to prevent injection attacks and other security vulnerabilities.
**Why:**
* **Security:** Prevents attackers from injecting malicious code or data into the application.
* **Stability:** Ensures that the application behaves predictably even with invalid inputs.
**Do This:**
* Validate all user inputs, including form data, query parameters, and API requests.
* Sanitize inputs to remove potentially harmful characters or code.
* Use appropriate validation libraries or functions for specific data types.
**Don't Do This:**
* Trust external inputs without validation.
* Store or process sensitive data without proper sanitization.
### 5.3 Secure Configuration
**Standard:** Store sensitive configuration data (e.g., API keys, database passwords) securely using environment variables or dedicated configuration management tools. Avoid hardcoding sensitive data in the codebase.
**Why:**
* **Security:** Prevents sensitive data from being exposed in the codebase or version control system.
* **Flexibility:** Allows you to easily change configuration settings without modifying the code.
* **Environment Isolation:** Enables you to use different configuration settings for different environments (e.g., development, testing, production).
**Do This:**
* Store sensitive configuration data in environment variables.
* Use a configuration management tool (e.g., "dotenv", "config") to manage configuration settings.
* Avoid hardcoding sensitive data in the codebase.
**Don't Do This:**
* Store sensitive data in plain text files or in the codebase.
* Commit sensitive data to version control.
* Expose sensitive data in client-side code.
**Example:**
"""javascript
// .env file
API_KEY=your_secret_api_key
DATABASE_URL=your_database_connection_string
// src/config.js
import dotenv from 'dotenv';
dotenv.config();
const apiKey = process.env.API_KEY; // Access the API key from the environment variable
const databaseUrl = process.env.DATABASE_URL;
export { apiKey, databaseUrl };
"""
These standards aim to improve the quality, consistency, and security of Rollup projects and provide a solid foundation for both human developers and AI coding assistants. Regular reviews and updates of these standards should be conducted to keep pace with advancements in the Rollup ecosystem and evolving security threats.
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.