# 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 = {
success: true;
data: T;
} | {
success: false;
error: string;
};
//Example usage
function getUser(id: number): Result {
// ... 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") 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.
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'
# Performance Optimization Standards for Rollup This document outlines coding standards focused specifically on performance optimization when using Rollup. Adhering to these guidelines will help improve application speed, responsiveness, and resource usage. We will focus on modern approaches and patterns relevant to the latest versions of Rollup. ## General Principles * **Minimize Bundle Size:** Smaller bundles download and parse faster, leading to quicker application startup times. * **Optimize Dependency Graph:** Efficient module resolution and tree-shaking are crucial for removing dead code and minimizing dependencies. * **Leverage Code Splitting:** Divide your application into smaller, on-demand chunks to reduce initial load time. * **Use Efficient Plugins & Configurations:** Choose plugins and configurations that are optimized for performance. * **Profile and Analyze:** Regularly profile your builds to identify performance bottlenecks and areas for improvement. ## 1. Tree-Shaking and Dead Code Elimination ### 1.1 Strict Mode and ES Modules **Standard:** Always use strict mode (""use strict";") and ES modules ("import"/"export") for optimal tree-shaking. **Why:** Rollup relies on static analysis enabled by ES modules to determine which parts of your code are used and which can be safely removed. Strict mode helps prevent unintended side effects that can hinder accurate analysis. **Do This:** """javascript // my-module.js "use strict"; export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; } """ """javascript // main.js import { add } from './my-module.js'; console.log(add(5, 3)); // 8 """ **Don't Do This:** (Common Anti-Pattern: CommonJS) """javascript // my-module.js (Avoid CommonJS) module.exports = { add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; } }; """ **Explanation:** Using CommonJS ("module.exports" and "require") makes it very difficult for Rollup to figure out which exports are *actually* used, potentially preventing it from eliminating dead code. ES modules provide static structure which enables precise tree-shaking. ### 1.2 Side Effects Configuration **Standard:** Explicitly declare side effects in your "package.json". **Why:** By default, Rollup assumes that any module might have side effects (e.g., modifying global state). Declaring modules as side-effect-free allows Rollup to safely remove entire modules or parts of modules if they aren't directly used, further reducing the bundle size. **Do This:** (Example in "package.json") """json { "name": "my-library", "version": "1.0.0", "sideEffects": false, "main":"dist/index.cjs", "module": "dist/index.mjs", "exports": { ".": { "import": "./dist/index.mjs", "require": "./dist/index.cjs" } }, "files": [ "dist" ], "devDependencies": { "rollup": "^4.0.0" }, "scripts": { "build": "rollup -c" } } """ """javascript // rollup.config.js import { terser } from 'rollup-plugin-terser'; export default { input: 'src/index.js', output: [ { file: 'dist/index.mjs', format: 'es', sourcemap: true }, { file: 'dist/index.cjs', format: 'cjs', sourcemap: true } ], plugins: [ terser() // Minify the output with Terser ] }; """ If only specific files in your package have side effects, list them specifically: """json { "sideEffects": [ "./src/styles.css", "./src/global-init.js" ] } """ **Don't Do This:** Omit the "sideEffects" property, especially for libraries. This unnecessarily increases bundle sizes. **Explanation:** The "sideEffects" property tells Rollup whether a module execution causes any observable change outside of its scope. When set to "false", Rollup can safely remove the module if none of its exports are used. It provides critical hints to optimization tools. ### 1.3 Ensure Pure Functions **Standard:** Write functions that are "pure," meaning they produce the same output for the same input and have no side effects. **Why:** Pure functions are much easier for Rollup (and Terser via plugin) to optimize because their results can be memoized or inlined, and calls to unused pure functions can be safely removed, as they don't cause any unexpected behavior. **Do This:** """javascript // Pure function export function calculateArea(width, height) { return width * height; } """ **Don't Do This:** """javascript // Impure function (modifies external state) let totalArea = 0; export function addToTotalArea(width, height) { totalArea += width * height; return totalArea; } """ **Explanation:** Impure functions (like "addToTotalArea") are difficult to optimize because their behavior depends on external state and can have side effects. Rollup may be forced to include them even if their return value isn't directly used. ## 2. Code Splitting ### 2.1 Dynamic Imports **Standard:** Use dynamic imports ("import()") to split your code into smaller chunks loaded on demand. **Why:** Dynamic imports allow you to load modules asynchronously only when needed, which significantly reduces the initial load time of your application. **Do This:** """javascript // main.js async function loadComponent() { const { default: MyComponent } = await import('./my-component.js'); const component = new MyComponent(); document.body.appendChild(component.render()); } document.getElementById('load-button').addEventListener('click', loadComponent); """ """javascript // my-component.js export default class MyComponent { render() { const element = document.createElement('div'); element.textContent = 'This is my component!'; return element; } } """ **Don't Do This:** Load all modules upfront, even those only needed under specific conditions. This defeats the purpose of code splitting. **Explanation:** When Rollup encounters a dynamic "import()", it creates a separate chunk for the imported module. This chunk is only loaded when the "import()" statement is executed. Helps user experience by loading parts needed immediately and fetching the rest in the background. ### 2.2 Manual Chunks **Standard:** Use the "manualChunks" option in your Rollup configuration to define custom chunks. **Why:** The "manualChunks" option provides fine-grained control over how your code is split into chunks, allowing you to optimize for specific use cases (e.g., vendor libraries, common utility functions). **Do This:** (Example in "rollup.config.js") """javascript import { terser } from 'rollup-plugin-terser'; export default { input: 'src/index.js', output: { dir: 'dist', format: 'es', // Ensure ES Modules output sourcemap: true, chunkFileNames: 'chunks/[name]-[hash].js', manualChunks: { vendor: ['lodash', 'moment'], // create a vendor chunk utils : ['./src/utils/helper.js'] }, }, plugins: [ terser() ] }; """ """javascript // main.js import _ from 'lodash'; import moment from 'moment'; import { helperFunction } from './utils/helper.js'; console.log(_.chunk([1, 2, 3, 4, 5], 2)); console.log(moment().format('MMMM Do YYYY, h:mm:ss a')); console.log(helperFunction()); """ **Don't Do This:** Rely solely on Rollup's default chunking behavior. Manual chunking can dramatically improve performance in many situations by avoiding unnecessary duplication of code. **Explanation:** The "manualChunks" configuration option uses a function or an object to define custom chunks. The example consolidates "lodash" and "moment" into a "vendor" chunk, which can be cached separately by the browser and reused across multiple pages. ### 2.3 Entry Points for Separate Pages **Standard:** Create distinct entry points for different pages or sections of your application to facilitate code splitting. **Why:** This ensures that only the code required for a specific page is loaded initially, improving page load times and reducing resource consumption. **Do This:** """ src/ index.js (Entry point for the main app) about.js (Entry point for the about page) contact.js (Entry point for the contact page) components/ (Shared components) """ """javascript // rollup.config.js export default { input: { index: 'src/index.js', about: 'src/about.js', contact: 'src/contact.js' }, output: { dir: 'dist', format: 'es', chunkFileNames: 'chunks/[name]-[hash].js', // Important for clarity } }; """ **Explanation:** Each input file ("index.js", "about.js", "contact.js") becomes a separate entry point, which directs Rollup to generate dedicated chunks for main app, about page, and contact page. ## 3. Plugin Optimization ### 3.1 Minimize Plugin Usage **Standard:** Use only the plugins that are strictly necessary for your build process and evaluate their performance impact. **Why:** Each plugin adds overhead to the build process. Unnecessary or inefficient plugins can significantly slow down your builds. **Do This:** Analyze plugin impact and remove any unused plugins. Benchmark your builds with and without specific plugins. If you find a plugin causing performance issues, investigate alternatives or create a custom solution. **Don't Do This:** Add plugins without thoroughly understanding their impact on build performance. **Explanation:** Plugin performance varies. Some plugins might perform computationally intensive tasks, like code transformations or file system operations. Only use the essential ones to keep build times down. ### 3.2 Optimize Plugin Configuration **Standard:** Configure your plugins to be as efficient as possible. **Why:** Many plugins offer configuration options that can significantly affect their performance. By carefully adjusting these options, you can optimize plugin behavior for your specific needs. **Do This:** (Example using "rollup-plugin-terser" and "rollup-plugin-esbuild") """javascript // rollup.config.js import { terser } from 'rollup-plugin-terser'; import esbuild from 'rollup-plugin-esbuild'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es', sourcemap: true }, plugins: [ esbuild({ minify: process.env.NODE_ENV === 'production', // Only minify in production target: 'es2020', // Specify target environment jsxFactory: 'React.createElement', // Configure JSX if needed jsxFragment: 'React.Fragment', }), terser({ compress: { //Optimized settings for maximum compression while maintaining runtime compatibility passes: 3, //Increased passes for better compression results unsafe: true, //Enables potentially unsafe transformations unsafe_comps: true, //Enables unsafe comparisons/optimizations }, mangle: true, //Enable name mangling }) ] }; """ **Don't Do This:** Use default plugin configurations without considering their performance implications. **Explanation:** The "terser" plugin's compression options can be tweaked to control the level of code minification, with tradeoffs between build time and output size. Similarly, "rollup-plugin-esbuild" provides excellent performance out of the box, but can be further tuned based on your app's specific JSX or ES version requirements. Setting the "minify" option appropriately ensures terser does not run in development mode. Consider using environment variables to switch between production and development settings. ### 3.3 Consider Alternative Plugins **Standard:** Explore alternative plugins that provide similar functionality with better performance. **Why:** The Rollup ecosystem is constantly evolving, and new plugins emerge that may offer significant performance improvements over existing ones. **Do This:** Evaluate new plugins based on benchmarks comparing their speed and output size against existing plugins on your project. **Example:** Consider using "rollup-plugin-esbuild" or swc instead of Babel for faster transpilation. Esbuild and SWC are written in Go and Rust, respectively, making them significantly faster at transpilation and minification than Babel, which is written in JavaScript. ## 4. Output Format ### 4.1 ES Modules Format **Standard:** Prefer the ES module format ("format: 'es'") for modern browsers and bundlers. **Why:** ES modules are the standard format for modern JavaScript development and offer the best tree-shaking and code-splitting capabilities. **Do This:** """javascript // rollup.config.js export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es', sourcemap: true } }; """ **Don't Do This:** Use legacy formats like UMD or CommonJS unless you need to support older environments with no ES module support. **Explanation:** While UMD and CommonJS have their uses, ES modules provide a structured way for module resolution and optimization that is well-suited for modern web development workflows. Outputting multiple formats can increase build time and complexity when it's not necessary. ### 4.2 Optimize Sourcemaps **Standard:** Use sourcemaps strategically. **Why:** Sourcemaps are essential for debugging, but generating them can add overhead to the build process and increase the size of output files. **Do This:** * Enable sourcemaps in development for easier debugging. * Consider disabling or using inline sourcemaps in production to reduce file size. * Test the performance differences to ensure you can debug efficiently. **Example:** """javascript // rollup.config.js export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es', sourcemap: process.env.NODE_ENV === 'development' ? 'inline' : false } }; """ **Explanation:** Using "'inline'" sourcemaps embeds the sourcemap directly in the JavaScript file, reducing the number of requests required by the browser, but increasing the file size; disable sourcemaps entirely in production if minimizing file size is the top priority, accepting potential debugging difficulties. ### 4.3 Minification and Compression **Standard:** Minify your code in production environments to reduce bundle size. **Why:** Minification removes whitespace, comments, and other unnecessary characters from your code, which can significantly reduce the size of your JavaScript files. **Do This:** * Use a minification plugin like "rollup-plugin-terser". * Configure the minification plugin to use aggressive compression settings in production. * Use Brotli or Gzip Compression on your server """javascript // rollup.config.js import { terser } from 'rollup-plugin-terser'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es', sourcemap: true }, plugins: [ terser({ compress: { drop_console: true // Remove console.log statements } }) ] }; """ **Explanation:** The "terser" plugin can be configured to remove "console.log" statements, minify variable names, and perform other optimizations to reduce the size of your code. Employing Brotli (if available) or Gzip compression on your web server further reduces the file size of served assets. ## 5. Asynchronous Operations and Lazy Loading ### 5.1 Prioritize Asynchronous Operations **Standard:** Favor asynchronous operations (e.g., "async/await", "Promise.all") to avoid blocking the main thread. **Why:** Synchronous operations can block the main thread, causing the user interface to freeze. Asynchronous operations allow the browser to perform other tasks while waiting for the operation to complete, improving responsiveness. **Do This:** """javascript async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); return data; } """ **Don't Do This:** Perform long-running synchronous operations on the main thread. **Explanation:** Using "async/await" makes asynchronous code easier to read and understand. The "fetch" API is inherently asynchronous, preventing the main thread from blocking. ### 5.2 Lazy Loading of Resources **Standard:** Lazy load images, videos, and other resources that are not initially visible on the page. **Why:** Lazy loading prevents the browser from downloading resources that are not immediately needed, reducing initial load time and bandwidth consumption. **Do This:** * Use the "loading="lazy"" attribute on "<img>" elements. * Use a JavaScript library like "lozad.js" for more advanced lazy loading techniques. """html <img src="my-image.jpg" loading="lazy" alt="My Image"> """ **Explanation:** The "loading="lazy"" attribute tells the browser to only load the image when it is about to enter the viewport. ## 6. Profiling and Analysis ### 6.1 Analyze Bundle Contents **Standard:** Use tools like "rollup-plugin-visualizer" to analyze the contents of your bundles. **Why:** Bundle analysis tools provide insights into the size and composition of your bundles, helping you identify large dependencies and potential areas for optimization. **Do This:** """javascript // rollup.config.js import { visualizer } from 'rollup-plugin-visualizer'; export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'es', sourcemap: true }, plugins: [ visualizer({ template: 'treemap', // Use a treemap visualization open: true, // Automatically open the visualization in the browser filename: 'dist/stats.html' // Output file name }) ] }; """ **Explanation:** The "rollup-plugin-visualizer" generates an interactive treemap visualization that shows the size of each module in your bundle. You can identify large dependencies and consider alternatives or code splitting strategies. ### 6.2 Measure Performance **Standard:** Use browser developer tools and performance monitoring tools (e.g., Lighthouse, WebPageTest) to measure the performance of your application. **Why:** Profiling your application in real-world conditions is crucial for identifying performance bottlenecks and validating the effectiveness of your optimization efforts.
# 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 ( <div>My Component</div> ); } 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.
# 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.
# State Management Standards for Rollup This document outlines the coding standards for state management within Rollup projects. It aims to provide a comprehensive guide for developers to write maintainable, performant, and secure code when handling application state in Rollup modules and plugins. It focuses on modern JavaScript and Rollup conventions, using up-to-date examples and best practices. This guide will help teams ensure consistent architecture, data flow, and reactivity across their Rollup ecosystem. ## 1. Principles of State Management in Rollup State management in Rollup differs from traditional frontend frameworks like React or Vue. Rollup is a *module bundler*, meaning it *combines* your code and *doesn't* directly manage runtime application state. However, the modules Rollup bundles *do*, and so how your modules are structured to manage their internal state is important for overall application design. We need to consider: * **Module Scoping:** Rollup encourages modularity. Modules should manage their own state, minimizing global state. * **Data Flow:** Understand how data flows through your application components and how this affects the bundling process. * **Side Effects**: Rollup enables side effects within modules, so handle them with care. ### 1.1. Standard: Prefer Module-Level Scoping * **Do This:** Encapsulate state within individual modules to promote reusability and prevent naming conflicts. * **Don't Do This:** Rely heavily on global variables or singletons for state, as these can lead to unintended side effects and make debugging difficult. **Why?** Module-level scope reduces the risk of variable collisions and improves code maintainability by encapsulating logic and state. Global variables make code harder to reason about because any part of the application can modify them unexpectedly. """javascript // Good: module using internal state let counter = 0; export function increment() { counter++; return counter; } export function decrement() { counter--; return counter; } """ """javascript // Bad: using a global variable window.globalCounter = 0; // Avoid! export function increment() { window.globalCounter++; return window.globalCounter; } """ ### 1.2. Standard: Be Explicit About Data Flow * **Do This:** Clearly define how data enters and exits your modules using function parameters and return values of the imported functions. * **Don't Do This:** Depend on implicit or magical state updates, which can make the application logic difficult to follow. **Why?** Explicit data flow increases code predictability, making it easier to trace and debug data-related issues. Predictable data flow is crucial for maintainability. """javascript // Good: Explicit data flow export function updateState(state, changes) { return { ...state, ...changes }; } // Usage: import { updateState } from './state-management'; let currentState = { name: 'Initial', value: 0 }; const newState = updateState(currentState, { value: 1 }); console.log(newState); // { name: 'Initial', value: 1 } """ """javascript // Bad: Implicit state update let internalState = { name: 'Initial', value: 0 }; export function updateValue(newValue) { internalState.value = newValue; // Avoid direct mutation! } """ ### 1.3. Standard: Control Side Effects * **Do This:** Isolate side effects (such as modifying external state) to specific functions and modules. * **Don't Do This:** Allow side effects to occur randomly throughout your code, as this makes debugging and testing far more difficult. **Why?** Controlling side effects helps in debugging and testing. By isolating side effects, you can quickly identify and fix issues when unexpected behavior occurs. Testing becomes more reliable and predictable. """javascript // Good: Controlled side effect within a module import { log } from './logger'; export function processData(data) { // Perform some calculations const result = data * 2; // Log the result (side effect) log("Processed data: ${result}"); return result; } """ """javascript // Bad: Random side effect - using console.log calls directly export function processData(data) { const result = data * 2; console.log("Processing data: ${result}"); // Side effect interspersed. Avoid! return result; } """ ## 2. Implementing State Management Patterns The specific approach to state management depends heavily on the complexity of your application and how your individual modules will interact. Rollup itself doesn't enforce a specific pattern, so you choose the one that best fits your needs. ### 2.1. Standard: Plain JavaScript Objects * **Do This:** Use standard JavaScript objects to store and manage simple state within modules. This approach works well for small to medium-sized applications. Focus on immutability where feasible. * **Don't Do This:** Overcomplicate with state management libraries if basic objects suffice. **Why?** Using plain JavaScript objects reduces dependencies and keeps the code lightweight. It is perfect for scenarios where external libraries would just add unnecessary overhead. """javascript // Example: Managing state with JavaScript objects (immutably) let initialState = { count: 0, name: 'Example' }; function reducer(state, action) { switch (action.type) { case 'INCREMENT': return { ...state, count: state.count + 1 }; case 'DECREMENT': return { ...state, count: state.count - 1 }; case 'UPDATE_NAME': return { ...state, name: action.payload }; default: return state; } } // Usage let currentState = initialState; currentState = reducer(currentState, { type: 'INCREMENT' }); currentState = reducer(currentState, { type: 'UPDATE_NAME', payload: 'New Name' }); console.log(currentState); // { count: 1, name: 'New Name' } """ ### 2.2. Standard: Event Emitters * **Do This:** Implement an event emitter pattern to create reactive state using custom events. This is useful for decoupling modules and handling asynchronous updates. * **Don't Do This:** Overuse event emitters for synchronous state updates; consider direct function calls for simpler cases. **Why?** Event emitters allow different modules to react to state changes without direct dependencies, promoting looser coupling. This decoupling makes code easier to modify and extend. """javascript // Example: Event emitter implementation import { EventEmitter } from 'events'; class Store extends EventEmitter { constructor(initialState) { super(); this.state = initialState; } getState() { return this.state; } update(newState) { this.state = { ...this.state, ...newState }; this.emit('stateChanged', this.state); } } const store = new Store({ count: 0 }); // Subscribe to state changes store.on('stateChanged', (newState) => { console.log('State changed:', newState); }); // Update the state store.update({ count: 1 }); """ ### 2.3. Standard: RxJS Observables * **Do This:** Use RxJS Observables for managing complex asynchronous state and data streams. * **Don't Do This:** Implement complex custom solutions when RxJS can handle the scenarios more efficiently. This is particularly useful for real-time data processing. **Why?** RxJS provides a powerful and flexible way to handle asynchronous data streams. Observables simplify complex logic with operators for filtering, mapping, and combining data, making your code more reactive and efficient. """javascript // Example: RxJS implementation import { BehaviorSubject } from 'rxjs'; class Store { constructor(initialState) { this.state$ = new BehaviorSubject(initialState); } getState() { return this.state$.getValue(); } update(newState) { this.state$.next({ ...this.state$.getValue(), ...newState }); } subscribe(callback) { return this.state$.subscribe(callback); } } const store = new Store({ count: 0 }); // Subscribe to state changes const subscription = store.subscribe((newState) => { console.log('RxJS State changed:', newState); }); // Update the state store.update({ count: 1 }); // Unsubscribe when not needed. subscription.unsubscribe(); """ ## 3. State Management Within Rollup Plugins Rollup plugins can maintain their internal state, which can be helpful for caching or managing plugin-specific settings. ### 3.1. Standard: Encapsulate Plugin State * **Do This:** Use closures or classes to encapsulate the plugin's state. * **Don't Do This:** Rely on global variables to store plugin state, which could clash with other plugins or application code. **Why?** Encapsulation ensures that the plugin's internal state does not interfere with other parts of the application or other plugins, preventing unexpected side effects. """javascript // Example: Plugin state using closures function myPlugin() { let internalState = {}; // Encapsulated state return { name: 'my-plugin', transform(code, id) { // Access and/or modify the internal state internalState[id] = code.length; // Example of side effect (logging plugin activity) console.log("File ${id} transformed. Code length: ${internalState[id]}"); return code; }, buildEnd() { console.log('Plugin build completed. Processed file sizes:', internalState); } }; } export default myPlugin; """ ### 3.2. Standard: Use Plugin Context * **Do This:** Leverage the "this" context within plugin lifecycle hooks to share state and methods. Rollup plugins that use ES Module syntax bind "this" to a plugin context object. * **Don't Do This:** Directly modify the configuration object, as it could lead to unexpected behavior. **Why?** The plugin context facilitates sharing data and utility functions between different hooks within the plugin, promoting a consistent and maintainable structure. """javascript // Example: Sharing state using plugin context function myPlugin() { return { name: 'my-plugin', options(options) { this.sharedState = { count: 0 }; return options; }, transform(code, id) { this.sharedState.count++; console.log("Transforming ${id}. Count: ${this.sharedState.count}"); return code; }, buildEnd() { console.log("Total files transformed: ${this.sharedState.count}"); } }; } export default myPlugin; """ ### 3.3. Standard: Be Mindful of Persistent State * **Do This:** Ensure that any persistent state within the plugin (e.g., cached data) is properly managed and does not lead to memory leaks. * **Don't Do This:** Accumulate state indefinitely without clearing or updating it, especially when dealing with large datasets. Always clean up resources appropriately. **Why?** Properly managing persistent state inside a plugin prevents memory leaks and ensures that the plugin behaves efficiently, especially in long-running or watch-mode builds. """javascript // Example: Caching data within a Rollup plugin import { createHash } from 'crypto'; function cachingPlugin() { const cache = new Map(); return { name: 'caching-plugin', transform(code, id) { const hash = createHash('sha256').update(code).digest('hex'); if (cache.has(id) && cache.get(id).hash === hash) { console.log("[cache] Returning cached version of ${id}"); return cache.get(id).code; } // Process the code (in this example, just converting to uppercase) const transformedCode = code.toUpperCase(); // Store the transformed code in the cache cache.set(id, { code: transformedCode, hash: hash }); console.log("[cache] Caching transformed version of ${id}"); return transformedCode; } }; } export default cachingPlugin; """ ## 4. Asynchronous State Management Rollup plugins often perform asynchronous operations, such as reading files or making network requests. Proper state management is crucial to handle these operations correctly. ### 4.1. Standard: Use Async/Await * **Do This:** Utilize "async" and "await" to manage asynchronous state updates in a readable and maintainable way. * **Don't Do This:** Rely on callbacks or promises without proper error handling, which can lead to difficult-to-debug issues. **Why?** "async/await" makes asynchronous code look and behave a bit more like synchronous code, which improves readability. It also provides easier error handling with "try/catch" blocks. """javascript // Example: Asynchronous plugin using async/await import { readFile } from 'fs/promises'; function asyncPlugin() { let state = { filesRead: 0 }; return { name: 'async-plugin', async load(id) { try { const content = await readFile(id, 'utf-8'); state.filesRead++; console.log("[async plugin] Read file ${id}. Total files read: ${state.filesRead}"); return content; } catch (error) { this.error("Failed to read file ${id}: ${error.message}"); return null; } } }; } export default asyncPlugin; """ ### 4.2. Standard: Handle Errors Robustly * **Do This:** Implement error-handling mechanisms (e.g., "try/catch" blocks) to catch and manage exceptions that may occur during asynchronous operations. Specifically using the plugin context's "this.warn" and "this.error" methods. * **Don't Do This:** Ignore possible errors, as this can cause your plugin to fail silently or produce unexpected results. **Why?** Robust error handling prevents the plugin from crashing or producing incorrect output, improving the overall stability and reliability of the build process. """javascript // Example: Robust error handling in an async plugin import { readFile } from 'fs/promises'; function errorHandlingPlugin() { return { name: 'error-handling-plugin', async load(id) { try { const content = await readFile(id, 'utf-8'); return content; } catch (error) { this.error("[error-handling-plugin] Failed to read file ${id}: ${error.message}"); return null; // Important to return null to halt processing of this file. } } }; } export default errorHandlingPlugin; """ ## 5. Security Considerations When managing state, especially in plugins that handle user-provided data, security is paramount. ### 5.1. Standard: Validate and Sanitize Data * **Do This:** Validate and sanitize all external data to prevent common security vulnerabilities such as cross-site scripting (XSS) or injection attacks. * **Don't Do This:** Directly use external data without proper validation, as this can expose your plugin and application to security risks. **Why?** Validating and sanitizing data ensures that only safe and expected data is processed, mitigating potential security threats and ensuring the integrity of the build process. """javascript // Example: Data validation and sanitization import { createHash } from 'crypto'; function securePlugin() { return { name: 'secure-plugin', transform(code, id) { // Validate the file ID if (!isValidFileId(id)) { this.warn("[secure-plugin] Invalid file ID: ${id}"); return null; } // Sanitize the code content const sanitizedCode = sanitize(code); // Generate a hash of the sanitized code const hash = createHash('sha256').update(sanitizedCode).digest('hex'); console.log("[secure-plugin] Processed and secured file ${id}, hash: ${hash}"); return sanitizedCode; } }; function isValidFileId(id) { // Implement your validation logic here return typeof id === 'string' && id.length > 0; } function sanitize(code) { // Implement your code sanitization logic here (e.g., escaping HTML entities) return code.replace(/</g, '<').replace(/>/g, '>'); } } export default securePlugin; """ ### 5.2. Standard: Avoid Storing Sensitive Information * **Do This:** Avoid storing sensitive information (e.g., API keys, passwords) directly in the plugin's state. If you must store such data, encrypt it and manage access carefully. Consider environment variables. * **Don't Do This:** Hardcode sensitive information or store it in plain text within the plugin's codebase or state. **Why?** Preventing the storage of sensitive information minimizes the risk of data breaches and security compromises. Use secure configuration management practices, such as environment variables or secure credential stores. ### 5.3. Standard: Use Secure Dependencies * **Do This:** Regularly update your dependencies to patch security vulnerabilities. Scan your dependencies for known security issues using tools like "npm audit" or "yarn audit". * **Don't Do This:** Use outdated or unmaintained dependencies, as they may contain known security vulnerabilities that can be exploited. **Why?** Keeping dependencies up to date and actively scanning for vulnerabilities reduces the risk of introducing security flaws into your plugin and application. Regularly audit and update dependencies to maintain a secure environment. ## 6. Conclusion Adhering to these coding standards ensures clean, maintainable, and secure code for state management in Rollup. By embracing modularity, explicit data flow, and robust error handling, developers can build plugins and applications that are manageable and reliable.
# 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.