# Code Style and Conventions Standards for Vite
This document outlines the coding style and conventions to follow when developing with Vite. Adhering to these standards will improve code readability, maintainability, and consistency across projects. These guidelines are designed to be compatible with AI coding assistants such as GitHub Copilot and Cursor.
## 1. General Principles
### 1.1. Consistency
* **Do This:** Maintain a consistent style throughout the codebase. Use tools like Prettier and ESLint to enforce these standards automatically.
* **Don't Do This:** Mix different coding styles within the same project, as this reduces readability and increases cognitive load.
* **Why:** Consistency is key to making code easier to understand and maintain. A predictable style allows developers to quickly grasp the structure and intent of the code.
### 1.2. Readability
* **Do This:** Write code that is easy to read and understand. Use meaningful variable names, clear comments, and proper indentation.
* **Don't Do This:** Write overly complex or convoluted code that is difficult to decipher.
* **Why:** Readability is crucial for collaboration and long-term maintainability. Code should be self-explanatory and easy for others (and your future self) to understand.
### 1.3. Simplicity
* **Do This:** Keep code simple and focused. Break down complex tasks into smaller, manageable functions or modules.
* **Don't Do This:** Write large, monolithic functions or modules that are hard to test and maintain.
* **Why:** Simpler code is easier to understand, test, and debug. It reduces the risk of introducing bugs and makes it easier to refactor when necessary.
## 2. Formatting
### 2.1. Indentation
* **Do This:** Use 2 spaces for indentation.
* **Don't Do This:** Use tabs or a combination of tabs and spaces.
* **Why:** Two spaces provide a good balance between readability and code density.
"""javascript
// Correct indentation
function myFunction() {
console.log('Hello, world!');
}
// Incorrect indentation
function myFunction() {
console.log('Hello, world!');
}
"""
### 2.2. Line Length
* **Do This:** Limit lines to a maximum of 120 characters.
* **Don't Do This:** Exceed the recommended line length.
* **Why:** Shorter lines are easier to read and prevent horizontal scrolling.
### 2.3. Whitespace
* **Do This:** Use whitespace to improve readability, including:
* A blank line after each function or method.
* Spaces around operators (=, +, -, *, /, etc.).
* Spaces after commas in lists.
* **Don't Do This:** Omit whitespace or use excessive whitespace.
* **Why:** Proper whitespace enhances readability by visually separating code elements.
"""javascript
// Correct usage of whitespace
function add(a, b) {
return a + b;
}
const result = add(1, 2);
console.log(result);
// Incorrect usage of whitespace
function add(a,b){
return a+b;
}
const result=add(1,2);
console.log( result );
"""
### 2.4. Semicolons
* **Do This:** Always use semicolons at the end of statements.
* **Don't Do This:** Rely on automatic semicolon insertion (ASI).
* **Why:** Using semicolons explicitly avoids unexpected behavior due to ASI and improves code predictability.
"""javascript
// Correct usage of semicolons
const message = 'Hello, world!';
console.log(message);
// Incorrect usage (relying on ASI)
const message = 'Hello, world!'
console.log(message)
"""
### 2.5. Trailing Commas
* **Do This:** Use trailing commas in object literals, arrays, and function parameters.
* **Don't Do This:** Omit trailing commas.
* **Why:** Trailing commas simplify adding, removing, or reordering elements. They also lead to cleaner diffs in version control.
"""javascript
// Correct usage of trailing commas
const person = {
firstName: 'John',
lastName: 'Doe',
};
const numbers = [
1,
2,
3,
];
function greet(
name,
greeting,
) {
console.log("${greeting}, ${name}!");
}
// Incorrect usage
const person = {
firstName: 'John',
lastName: 'Doe'
};
"""
## 3. Naming Conventions
### 3.1. Variables
* **Do This:** Use descriptive, camelCase names.
* **Don't Do This:** Use single-letter names or cryptic abbreviations.
* **Why:** Clear variable names make code easier to understand.
"""javascript
// Correct variable names
const userFirstName = 'John';
const numberOfItems = 10;
// Incorrect variable names
const fn = 'John';
const noi = 10;
"""
### 3.2. Functions
* **Do This:** Use descriptive, camelCase names that clearly indicate the function's purpose.
* **Don't Do This:** Use ambiguous or overly short names.
* **Why:** Explicit function names improve code readability and maintainability.
"""javascript
// Correct function names
function calculateTotalAmount(price, quantity) {
return price * quantity;
}
// Incorrect function names
function calc(p, q) {
return p * q;
}
"""
### 3.3. Constants
* **Do This:** Use uppercase, snake_case names for constants.
* **Don't Do This:** Use lowercase or camelCase names for constants.
* **Why:** Uppercase names clearly indicate that the variable is a constant and should not be modified.
"""javascript
// Correct constant names
const MAX_USERS = 100;
const API_URL = 'https://example.com/api';
// Incorrect constant names
const maxUsers = 100;
const apiUrl = 'https://example.com/api';
"""
### 3.4. Components (Vue/React with Vite)
* **Do This:** Use PascalCase names for components. File names should match the component name.
* **Don't Do This:** Use kebab-case or camelCase names.
* **Why:** PascalCase is the standard convention for components in both Vue and React, enhancing consistency.
"""jsx
// Correct component name and file name (e.g., MyComponent.jsx)
function MyComponent() {
return My Component;
}
export default MyComponent;
// Incorrect component name
function myComponent() {
return My Component;
}
"""
### 3.5. File and Directory Structure
* **Do This:** Structure your project logically, separating concerns into different directories (e.g., "components", "utils", "api"). Use descriptive file names following the component or module naming conventions.
* **Don't Do This:** Dump all files into a single directory or use cryptic file names.
* **Why:** A well-organized file structure makes it easier to navigate and maintain the project.
"""
/src
├── components/
│ ├── MyComponent.jsx
│ ├── AnotherComponent.jsx
├── utils/
│ ├── api.js
│ ├── helpers.js
├── App.jsx
├── main.jsx
"""
## 4. Vite-Specific Conventions
### 4.1. Environment Variables
* **Do This:** Use ".env" files for environment variables and access them via "import.meta.env".
* **Don't Do This:** Hardcode sensitive information directly into the code.
* **Why:** Environment variables allow you to configure your application for different environments (e.g., development, production) without modifying the code.
"""javascript
// .env file
VITE_API_URL=https://api.example.com
// In your component or module
const apiUrl = import.meta.env.VITE_API_URL;
console.log(apiUrl);
"""
### 4.2. Asset Imports
* **Do This:** Import assets (images, fonts, etc.) directly using their relative paths. Vite handles these imports efficiently.
* **Don't Do This:** Use absolute paths or manual asset management.
* **Why:** Vite automatically optimizes and bundles imported assets, providing better performance and simplified asset management.
"""javascript
// Correct asset import
import logo from './assets/logo.svg';
function MyComponent() {
return ;
}
export default MyComponent;
"""
### 4.3. Dynamic Imports
* **Do This:** Use dynamic imports ("import()") for code splitting, especially for large components or modules that are not immediately needed.
* **Don't Do This:** Load all code upfront, which can slow down initial page load.
* **Why:** Dynamic imports allow you to load code on demand, improving performance by reducing the initial bundle size.
"""javascript
// Dynamic import
async function loadComponent() {
const { default: MyComponent } = await import('./components/MyComponent');
// Use MyComponent
}
loadComponent();
"""
### 4.4. Vite Plugins
* **Do This:** Utilize Vite plugins for common tasks like transforming code, optimizing assets, and integrating with other tools. Configure plugins in "vite.config.js".
* **Don't Do This:** Implement complex build processes manually when a plugin can handle it.
* **Why:** Vite plugins extend Vite's functionality and streamline the build process.
"""javascript
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
});
"""
### 4.5. Development Server
* **Do This:** Leverage Vite's built-in development server for fast hot module replacement (HMR) during development. Take advantage of features like proxying API requests.
* **Don't Do This:** Use a separate development server that doesn't integrate with Vite's build pipeline.
* **Why:** Vite's development server provides a superior development experience with fast updates and seamless integration.
"""javascript
// vite.config.js (proxy example)
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
"""
### 4.6. Build Configuration
* **Do This:** Configure the build process in "vite.config.js" to optimize the output for production. Use features like code minification, tree shaking, and asset inlining.
* **Don't Do This:** Use default build settings without considering production requirements.
* **Why:** Proper build configuration is essential for optimizing performance and reducing the size of the production bundle. Consider using the "defineConfig" helper for type safety.
"""javascript
// vite.config.js
import { defineConfig } from 'vite';
import { terser } from 'rollup-plugin-terser';
export default defineConfig({
build: {
minify: 'terser', // applies minification
rollupOptions: {
plugins: [terser()], // configures minification with terser
},
},
});
"""
### 4.7. Server-Side Rendering (SSR)
* **Do This:** If using SSR, structure your code to support both server and client environments. Use "process.server" (or equivalent with Vite SSR plugins) to conditionally execute server-specific code. Configure Vite for SSR builds.
* **Don't Do This:** Write code that only works on one environment.
* **Why:** Supporting both environments is crucial for SSR applications.
"""javascript
// Example of conditional code execution
if (import.meta.env.SSR) {
// Server-side code
console.log('Running on the server');
} else {
// Client-side code
console.log('Running on the client');
}
"""
### 4.8 Lazy Loading Images
* **Do This:** Implement Lazy Loading for images and other media to improve page load performance.
* **Don't Do This:** Load all assets upfront, especially for content below the fold.
* **Why:** Lazy loading improves initial load time by only loading visible assets.
"""html
"""
### 4.9 Minimize Third Party Libraries
* **Do This:** Evaluate and minimize usage of third party libraries, choosing the smallest and most efficient libraries when necessary.
* **Don't Do This:** Add numerous large libraries without considering their impact on performance.
* **Why:** Reduces bundle size and dependencies, leading to faster load times and improved security.
## 5. Stylistic Consistency
### 5.1. Quotes
* **Do This:** Use single quotes ("'") for strings, unless you need to embed variables.
* **Don't Do This:** Use double quotes (""") unnecessarily.
* **Why:** Single quotes are more common in JavaScript and can be slightly more efficient.
"""javascript
// Correct usage of single quotes
const message = 'Hello, world!';
// Use template literals for variable embedding
const name = 'John';
const greeting = "Hello, ${name}!";
"""
### 5.2. Object Literals
* **Do This:** Use concise syntax for object literals.
* **Don't Do This:** Use verbose syntax unnecessarily.
* **Why:** Concise syntax makes code cleaner and easier to read.
"""javascript
// Correct concise syntax
const person = {
firstName: 'John',
lastName: 'Doe',
};
// Incorrect verbose syntax
const person = {
firstName: person.firstName,
lastName: person.lastName
};
"""
### 5.3. Arrow Functions
* **Do This:** Use arrow functions ("=>") for concise, simple functions, especially when using higher-order functions like "map" and "filter".
* **Don't Do This:** Use arrow functions for complex functions with multiple statements; use traditional function declarations for those.
* **Why:** Arrow functions provide a more compact syntax and automatically bind "this" to the surrounding context, making them ideal for simple callbacks and expressions.
"""javascript
// Correct usage of arrow functions
const numbers = [1, 2, 3];
const squaredNumbers = numbers.map(number => number * number);
// Use traditional function declaration for complex functions
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
"""
### 5.4. Ternary Operators
* **Do This:** Use ternary operators ("condition ? value1 : value2") for simple conditional assignments or expressions.
* **Don't Do This:** Use ternary operators for complex logic or multiple nested conditions.
* **Why:** Ternary operators provide a concise way to express simple conditional logic.
"""javascript
// Correct usage of ternary operator
const age = 20;
const isAdult = age >= 18 ? true : false;
// Avoid for complex logic
const result = (a > b) ? (a > c ? a : c) : (b > c ? b : c); // Hard to read!
"""
## 6. Error Handling
### 6.1. Try-Catch Blocks
* **Do This:** Use "try-catch" blocks to handle potential errors in asynchronous operations, like API calls or dynamic imports.
* **Don't Do This:** Ignore potential errors or rely solely on unhandled rejections.
* **Why:** Proper error handling prevents unexpected crashes and provides a better user experience.
"""javascript
// Correct error handling
async function fetchData() {
try {
const response = await fetch('https://example.com/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
// Handle the error (e.g., display an error message to the user)
}
}
fetchData();
"""
### 6.2. Error Logging
* **Do This:** Log errors with sufficient context to help with debugging. Include relevant information like the component name, function name, and error message.
* **Don't Do This:** Log errors without any context, making it difficult to troubleshoot.
* **Why:** Detailed error logs are essential for diagnosing and fixing issues in production.
## 7. Comments and Documentation
### 7.1. Code Comments
* **Do This:** Write clear and concise comments to explain complex logic, non-obvious code, or important decisions.
* **Don't Do This:** Comment obvious code or write comments that are outdated or misleading.
* **Why:** Comments should provide additional context and help developers understand the "why" behind the code.
"""javascript
// This function calculates the total price of items in the cart
function calculateTotalPrice(cartItems) {
// ...
}
"""
### 7.2. JSDoc
* **Do This:** Use JSDoc syntax to document functions, classes, and modules.
* **Don't Do This:** Omit documentation for important parts of the codebase.
* **Why:** JSDoc-style comments enable tools to generate API documentation and provide better code completion and type checking.
"""javascript
/**
* Greets a person with a given name.
*
* @param {string} name - The name of the person to greet.
* @returns {string} A greeting message.
*/
function greet(name) {
return "Hello, ${name}!";
}
"""
## 8. Security
### 8.1 Input Validation
* **Do This:** Validate all user inputs on both the client and server-side and sanitize potentially malicious data.
* **Don't Do This:** Trust the client-side data without validation.
* **Why:** Prevents XSS attacks, SQL injection, and other security vulnerabilities.
### 8.2 Avoid Hardcoding Sensitive Data
* **Do This:** Use environment variables to store any keys or tokens that should not be exposed.
* **Don't Do This:** Hard code API keys, passwords, or other sensitive information in the application code, risking exposure.
* **Why:** Keeps private credentials from being exposed in the code repository.
### 8.3 Dependency Management
* **Do This:** Regularly review and update project dependencies to patch security vulnerabilities as soon as they are released. Use tools like "npm audit" or "yarn audit".
* **Don't Do This:** Use outdated dependencies without security patches.
* **Why:** Prevents usage of compromised packages that may introduce security risks.
## 9. Continuous Integration/Continuous Deployment (CI/CD)
### 9.1. Automated Linting and Formatting
* **Do This:** Integrate linting and formatting tools (e.g., ESLint, Prettier) into your CI/CD pipeline to automatically enforce coding standards on every commit.
* **Don't Do This:** Rely solely on manual code reviews to catch style issues.
* **Why:** Automated linting and formatting ensures consistent code style across the entire team, reduces the burden on code reviewers, and minimizes the risk of style-related bugs.
### 9.2. Automated Testing
* **Do This:** Set up automated unit, integration, and end-to-end tests in your CI/CD pipeline to catch regressions and ensure code quality.
* **Don't Do This:** Skip automated testing or rely solely on manual testing.
* **Why:** Automated testing provides confidence in the codebase, accelerates development, and reduces the risk of introducing bugs into production.
## 10. Deprecated Features and Anti-Patterns in Vite
### 10.1. Legacy "require()" statements
* **Avoid:** Using Node.js style "require()" statements in favor of modern ESM "import" statements.
* **Why:** "import" statements are more efficient for tree shaking and produce smaller bundles. Also, mixing module systems can increase complexity.
"""javascript
// Anti-pattern (CommonJS)
const fs = require('fs');
// Preferred ESM syntax
import fs from 'fs';
"""
### 10.2 Global Scope Pollution
* **Avoid:** Adding variables and functions directly to the global scope (e.g., "window" in browsers).
* **Why:** Can lead to unexpected conflicts if another script declares a variable with the same name.
"""javascript
// Anti-pattern: Global scope pollution
window.appVersion = '1.2.3'; // Avoid this
// Preferred: Export in a module or use a dedicated object
const appConfig = {
version: '1.2.3',
};
export default appConfig;
"""
### Example Vite Configuration (vite.config.js):
This is a comprehensive "vite.config.js" example incorporating many of the best practices mentioned above.
"""javascript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { terser } from 'rollup-plugin-terser';
import { resolve } from 'path';
const API_TARGET = process.env.API_TARGET || 'http://localhost:8000';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'), // Alias for easy imports
},
},
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version), // Expose version
},
server: {
hmr: true, // Enable hot module replacement
proxy: {
'/api': {
target: API_TARGET,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
build: {
minify: 'terser', // Enable minification
sourcemap: true, // Generate sourcemaps for debugging
rollupOptions: {
plugins: [terser()],
output: {
chunkFileNames: 'js/[name]-[hash].js', // Chunk file naming
entryFileNames: 'js/[name]-[hash].js', // Entry file naming
assetFileNames: '[ext]/[name]-[hash].[ext]', // Asset file naming
},
manualChunks(id) { // Vendor splitting
if (id.includes('node_modules')) {
return id
.toString()
.split('node_modules/')[1]
.split('/')[0]
.toString();
}
},
},
//Target latest browsers - updated for performance
target: 'esnext', // Target modern browser for smaller bundles
// Disable brotliSize reporting if you do not use Brotli
brotliSize: false,
manifest: true, // Generate manifest file
},
optimizeDeps: {
esbuildOptions: {
// Node.js polyfills for older browsers may be required
// Define global used in your project
define: {
global: 'globalThis'
},
// Enable/disable browser specific transform
target: "esnext",
}
}
});
"""
This document provides a comprehensive guide to coding style and conventions for Vite projects and is designed to be integrated with AI coding assistance tools. Continuously reviewing and adapting these standards as Vite evolves is crucial to maintaining a high-quality codebase.
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'
# Core Architecture Standards for Vite This document outlines the core architectural standards and best practices for developing with Vite. It serves as a guide for developers and provides context for AI coding assistants to generate consistent, maintainable, performant, and secure Vite code. ## 1. Fundamental Architecture Patterns Vite's architecture leverages modern JavaScript and web development best practices, emphasizing speed, simplicity, and developer experience. ### 1.1. Module Federation Vite has first class support for Module Federation. It's critical to understand the implications of exposing or consuming modules. **Do This:** * Use Module Federation to build composable applications where independent teams own different parts of the UI. * Version your exposed modules so breaking changes do not impact consuming applications. * Implement proper error handling mechanisms when dealing with remotely loaded modules. **Don't Do This:** * Don't overuse Module Federation. Weigh the benefits against the added complexity. * Don't expose internal implementation details as modules. * Avoid circular dependencies between federated modules. **Why:** Module Federation facilitates code reuse and independent deployments, promoting scalable and maintainable applications. """javascript // vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import federation from "@originjs/vite-plugin-federation"; //expose component export default defineConfig({ plugins: [ vue(), federation({ name: 'remote-app', filename: 'remoteEntry.js', exposes: { './RemoteButton': './src/components/RemoteButton.vue', }, shared: ['vue'] }) ], build: { target: 'esnext' } }) """ """javascript // consuming application vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import federation from "@originjs/vite-plugin-federation"; export default defineConfig({ plugins: [ vue(), federation({ name: 'host-app', remotes: { remote_app: "http://localhost:4173/assets/remoteEntry.js", // adjust path if needed }, shared: ['vue'] }) ], build: { target: 'esnext' } }) """ ### 1.2. Component-Based Architecture Vite projects often utilize component-based architectures, particularly when using frameworks like Vue or React. **Do This:** * Break down UIs into reusable, independent components. * Follow the Single Responsibility Principle (SRP) for each component. * Use props to pass data and events to communicate between components. **Don't Do This:** * Avoid creating monolithic components with too much logic. * Don't tightly couple components to specific data sources. * Don't mutate props directly within a component. Use "emit" in Vue, or state management solutions. **Why:** Component-based architecture improves code organization, reusability, and testability. """vue // MyComponent.vue <template> <div> <h1>{{ title }}</h1> <p>{{ description }}</p> <button @click="handleClick">Click me</button> </div> </template> <script setup> import { defineProps, defineEmits } from 'vue'; const props = defineProps({ title: { type: String, required: true }, description: { type: String, default: '' } }); const emit = defineEmits(['update']); const handleClick = () => { emit('update', 'New value'); }; </script> """ ### 1.3. State Management Choose a preferred state management solution and apply it consistently across your project. **Do This:** * Select state management based on the complexity of your application. Pinia and Vuex are common choices with Vue. Redux or Zustand integrate well with React. * Centralize application state. * Use mutations or actions to update state predictably. **Don't Do This:** * Overuse global state management for simple component communication. Props and events often suffice. * Directly modify state without using defined actions/mutations. **Why:** Consistent state management makes applications predictable and reduces debugging time. """javascript // Pinia store example (store.js) import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), actions: { increment() { this.count++; }, decrement() { this.count--; } }, getters: { doubleCount: (state) => state.count * 2 } }); """ ### 1.4 API Layer Isolate API interactions. This promotes loose coupling and enables easier testing and maintenance. **Do This:** * Create dedicated API service modules. * Handle all API calls, data transformation, and error handling within this layer. * Abstract away specific API implementations. **Don't Do This:** * Directly making API calls within components * Mixing API logic with unrelated code * Exposing API keys or secrets directly in the client-side code. Use environment variables. **Why:** An API layer makes API calls easier to manage, and easier to swap out. """javascript // apiService.js import axios from 'axios'; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api'; export const getPosts = async () => { try { const response = await axios.get("${API_BASE_URL}/posts"); return response.data; } catch (error) { console.error('Error fetching posts:', error); throw error; } }; export const createPost = async (postData) => { try { const response = await axios.post("${API_BASE_URL}/posts", postData); return response.data; } catch (error) { console.error('Error creating post:', error); throw error; } }; """ ## 2. Project Structure and Organization Principles A well-defined project structure is crucial for maintainability, scalability, and team collaboration. ### 2.1. Standard Directory Structure Follow a consistent directory structure across all Vite projects. A recommended structure is: """ vite-project/ ├── public/ # Static assets ├── src/ # Source code │ ├── assets/ # Images, fonts, etc. │ ├── components/ # Reusable UI components │ ├── composables/ # Vue composables │ ├── layouts/ # Application layouts │ ├── pages/ # Page components (if using a router) │ ├── styles/ # Global styles and themes │ ├── utils/ # Utility functions │ ├── App.vue # Root component │ ├── main.js # Entry point │ └── router.js # Router configuration ├── .env # Environment variables ├── vite.config.js # Vite configuration ├── package.json # Project dependencies └── README.md # Project documentation """ **Do This:** * Adhere to the proposed structure or establish a project-specific standard. * Keep components self-contained within the "components" directory. * Group related files within meaningful subdirectories. **Don't Do This:** * Store files in arbitrary locations without a clear organizational principle. * Mix different types of files within the same directory. * Create overly deep or complex directory structures. **Why:** A consistent structure simplifies navigation, enhances discoverability, and promotes code reuse, leading to better collaboration and maintainability. ### 2.2. Naming Conventions Establish clear naming conventions for files, directories, components, and variables. **Do This:** * Use descriptive and meaningful names. * Follow consistent naming patterns (e.g., PascalCase for components, camelCase for variables). * Use a consistent suffix for component files (e.g., ".vue" for Vue components, ".jsx" for React components). * Use kebab-case for directories and file names, especially when components are accessed through HTML. **Don't Do This:** * Use ambiguous or cryptic names. * Mix different naming conventions within the same project. * Use reserved keywords as names. **Why:** Consistent naming improves code readability, reduces confusion, and makes it easier to understand the purpose of each file and variable. **Example:** """ components/ ├── MyButton.vue // PascalCase for component filename ├── user-profile/ // kebab-case for directories │ └── UserProfile.vue // PascalCase for component filename """ """javascript // MyButton.vue <script setup> const buttonText = 'Click me'; // camelCase for variables const handleClick = () => { // camelCase for function names console.log('Button clicked'); }; </script> """ ### 2.3. Modular Design Break down code into small, independent, and reusable modules. **Do This:** * Create dedicated modules for specific functionalities. * Export only necessary functions and variables from each module. * Use ES module syntax ("import" and "export") to manage dependencies. **Don't Do This:** * Create large, monolithic modules with multiple responsibilities. * Export unnecessary variables or functions. * Rely on global variables for inter-module communication. **Why:** Modular design promotes code reuse, simplifies maintenance, and makes it easier to test and debug individual components. """javascript // utils/formatDate.js export function formatDate(date) { const options = { year: 'numeric', month: 'long', day: 'numeric' }; return new Date(date).toLocaleDateString(undefined, options); } // components/MyComponent.vue import { formatDate } from '../utils/formatDate.js'; <template> <p>Published: {{ formattedDate }}</p> </template> <script setup> import { ref, onMounted } from 'vue'; import { getPosts } from '../apiService.js'; const formattedDate = ref(''); onMounted(async () => { const posts = await getPosts(); formattedDate.value = formatDate(posts[0].createdAt); }); </script> """ ### 2.4. Environment Variables Use environment variables to manage configuration settings that vary between environments (development, staging, production). Vite handles environment variables differently than other bundlers. **Do This:** * Store sensitive settings in environment variables instead of hardcoding them. * Use the ".env" file for development environment variables. * Prefix environment variables with "VITE_" to expose them to client-side code. * Use "import.meta.env" to access environment variables in your code. **Don't Do This:** * Commit ".env" files to version control. Add them to ".gitignore". Use ".env.example" to track the *names* of the environment variables. * Store sensitive information directly in your code. * Expose sensitive environment variables to the client-side code. **Why:** Environment variables improve security, simplify configuration management, and allow you to deploy the same code to different environments without modification. """ // .env VITE_API_BASE_URL=https://api.example.com VITE_APP_TITLE=My Awesome App """ """javascript // vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], define: { 'process.env': {} //required if you're migrating a web app to vite } }) """ """javascript // components/MyComponent.vue <template> <h1>{{ appTitle }}</h1> </template> <script setup> const appTitle = import.meta.env.VITE_APP_TITLE; </script> """ ## 3. Vite Ecosystem & Plugins Leverage the Vite ecosystem of plugins and integrations to extend its functionality. ### 3.1. Plugin Usage Use plugins to extend Vite's capabilities, such as adding support for different file formats, optimizing assets, or integrating with other tools. **Do This:** * Use official or well-maintained community plugins. * Configure plugins properly according to their documentation. * Understand the specific tasks performed by each plugin. **Don't Do This:** * Add unnecessary plugins that duplicate functionality. * Use outdated or abandoned plugins. * Ignore plugin configuration options. **Why:** Plugins provide a modular way to extend Vite's functionality and integrate with other tools. **Example:** """javascript // vite.config.js import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import eslintPlugin from 'vite-plugin-eslint'; export default defineConfig({ plugins: [ vue(), eslintPlugin() ] }); """ ### 3.2. HMR (Hot Module Replacement) Take full advantage of Vite's HMR feature for rapid development. **Do This:** * Ensure that your components and modules are designed to support HMR. * Use state management solutions that are compatible with HMR. * Use Vite's built-in HMR API to handle custom HMR logic. * When using Vue, ensure components are correctly re-rendered when changes occur. **Don't Do This:** * Rely on full page reloads during development. * Ignore HMR errors. **Why:** HMR dramatically improves developer productivity by allowing you to see changes in the browser instantly without losing application state. ### 3.3. Code Splitting Utilize Vite's built-in code splitting to optimize the loading performance of your application. **Do This:** * Use dynamic imports ("import()") to load modules on demand. * Group related modules into separate chunks. * Analyze the bundle size and identify opportunities for code splitting. * Leverage Vite's automatic code splitting for routes and components. **Don't Do This:** * Load all modules upfront in a single large bundle. * Create overly granular code splits that result in too many small files. **Why:** Code splitting reduces the initial load time of your application by only loading the code that is needed for the current page or component. This significantly improves the user experience, especially on slow network connections. ## 4. Version Control and Collaboration ### 4.1. Git Workflow Adopt a standardized Git workflow for team collaboration. **Do This:** * Use feature branches for developing new features or fixing bugs. * Create pull requests for code review. * Write clear and concise commit messages. * Use Git tags to mark releases. **Don't Do This:** * Commit directly to the main branch without code review. * Write vague or uninformative commit messages. * Ignore code review feedback. **Why:** Git workflows facilitate collaboration, track changes, and ensure code quality. ### 4.2. Code Review Implement a code review process to catch errors, enforce standards, and share knowledge. **Do This:** * Review code changes thoroughly. * Provide constructive feedback. * Focus on code quality, readability, and maintainability. * Use automated code analysis tools to identify potential issues. **Don't Do This:** * Skip code reviews. * Provide vague or unhelpful feedback. * Ignore code analysis warnings. **Why:** Code review improves code quality, prevents errors, and promotes knowledge sharing. ### 4.3. Documentation Create clear and concise documentation for your Vite projects. **Do This:** * Write a README.md file that describes the project, how to set it up, and how to use it. * Document complex components and modules. * Use inline comments to explain tricky or non-obvious code. * Keep documentation up-to-date. **Don't Do This:** * Skip documentation entirely. * Write vague or incomplete documentation. * Let documentation become outdated. **Why:** Documentation makes it easier for others (and yourself) to understand, use, and maintain your code. ## 5. Performance Optimization Techniques ### 5.1. Lazy Loading Use lazy loading for components, images, and other assets that are not immediately visible on the screen. **Do This:** * Use dynamic imports ("import()") to load components on demand. * Use the "loading="lazy"" attribute for images. * Use Intersection Observer API for more advanced lazy loading scenarios. **Don't Do This:** * Load all assets upfront, even if they are not immediately needed. * Use lazy loading for critical above-the-fold content. **Why:** Lazy loading improves the initial load time of your application by only loading assets as they become visible on the screen. ### 5.2. Image Optimization Optimize images to reduce their file size without sacrificing quality. **Do This:** * Use appropriate image formats (e.g., WebP, AVIF). * Compress images using tools like ImageOptim or TinyPNG. * Resize images to the appropriate dimensions. * Use responsive images ("<picture>" element or "srcset" attribute). **Don't Do This:** * Use excessively large images. * Use inappropriate image formats (e.g., PNG for photographs). * Ignore image optimization techniques. **Why:** Optimized images reduce the page size and improve loading performance. ### 5.3. Minification and Compression Minify and compress JavaScript, CSS, and HTML files to reduce their file size. Vite handles this automatically in production mode. **Do This:** * Ensure that your Vite configuration is set up to minify and compress assets. * Use tools like Terser or esbuild for JavaScript minification. * Use gzip or Brotli compression on your server. **Don't Do This:** * Deploy unminified or uncompressed assets to production. * Disable minification or compression. **Why:** Minification and compression reduce the page size and improve loading performance. ## 6. Security Best Practices ### 6.1. Dependency Management Keep dependencies up to date to patch security vulnerabilities. **Do This:** * Regularly update dependencies using "npm update" or "yarn upgrade". * Use a tool like Snyk or Dependabot to monitor dependencies for vulnerabilities. * Remove unused dependencies. **Don't Do This:** * Use outdated dependencies. * Ingore security alerts. **Why:** Keeping dependencies up to date mitigates known security vulnerabilities. ### 6.2. Input Validation Validate user input to prevent injection attacks. **Do This:** * Validate all user input on both the client-side and server-side. * Use appropriate validation techniques to prevent SQL injection, XSS, and other attacks. * Encode or sanitize user input before displaying it on the page. **Don't Do This:** * Trust user input without validation. * Display raw user input on the page. **Why:** Input validation prevents malicious code from being injected into your application. ### 6.3. Content Security Policy (CSP) Use CSP to restrict the sources of content that your application is allowed to load. **Do This:** * Configure CSP headers on your server. * Specify the allowed sources for scripts, styles, images, and other resources. * Use a stricter CSP policy in production than in development. **Don't Do This:** * Disable CSP entirely. * Use a overly permissive CSP policy. **Why:** CSP mitigates the risk of XSS attacks.
# Testing Methodologies Standards for Vite This document outlines the recommended testing methodologies and best practices for Vite projects to ensure code quality, maintainability, and reliability. These standards are designed for developers and AI coding assistants alike. ## Unit Testing Unit testing focuses on testing individual components or functions in isolation. In a Vite environment, this often involves testing Vue components, React components, JavaScript modules, or TypeScript classes. ### Standards * **Do This:** Use a dedicated unit testing framework like Jest, Vitest (Vite-native), or Mocha. Vitest is highly recommended due to its tight integration with Vite. * **Don't Do This:** Rely on manual browser testing or "console.log" statements for verifying functionality. * **Why:** Automated unit tests provide rapid feedback, prevent regressions, and serve as living documentation of the code's intended behavior. ### Best Practices * **Test-Driven Development (TDD):** Consider writing tests *before* implementing the code. This helps clarify requirements and ensures testability. * **Code Coverage:** Aim for high (but not necessarily 100%) code coverage. Use coverage reports to identify untested areas of the codebase. * **Mocking:** Use mocks or stubs to isolate the unit under test from its dependencies. Use libraries like "vi" (Vitest's built-in mocking) or "sinon" can be used for this. * **Assertions:** Write clear and meaningful assertions that accurately describe the expected behavior. * **Atomic Tests:** Each test should focus on verifying only ONE specific thing. This simplifies debugging and maintenance. ### Vite-Specific Considerations * **Vitest:** Leverage Vitest, which is designed to work seamlessly with Vite. It offers fast feedback loops and excellent TypeScript support. * **Module Resolution:** Vite's module resolution handles ES modules and other formats correctly. Ensure test configurations mimic Vite's resolution. * **Configuration:** Use "vite.config.ts" to configure Vitest's behavior like test environment, globals, and coverage. ### Code Examples **Example using Vitest for a Vue Component** """typescript // src/components/Greeting.vue <template> <h1>Hello, {{ name }}!</h1> </template> <script setup lang="ts"> import { defineProps } from 'vue'; defineProps({ name: { type: String, required: true, }, }); </script> """ """typescript // src/components/Greeting.spec.ts import { mount } from '@vue/test-utils'; import Greeting from './Greeting.vue'; import { describe, it, expect } from 'vitest'; describe('Greeting.vue', () => { it('renders the greeting with the provided name', () => { const wrapper = mount(Greeting, { props: { name: 'World', }, }); expect(wrapper.text()).toContain('Hello, World!'); }); it('renders the greeting with a different name', () => { const wrapper = mount(Greeting, { props: { name: 'Vitest', }, }); expect(wrapper.text()).toContain('Hello, Vitest!'); }); }); """ **Example using Vitest for a JavaScript Module** """typescript // src/utils/math.ts export function add(a: number, b: number): number { return a + b; } export function subtract(a: number, b: number): number { return a - b; } """ """typescript // src/utils/math.spec.ts import { add, subtract } from './math'; import { describe, it, expect } from 'vitest'; describe('math.ts', () => { it('adds two numbers correctly', () => { expect(add(2, 3)).toBe(5); expect(add(-1, 1)).toBe(0); }); it('subtracts two numbers correctly', () => { expect(subtract(5, 2)).toBe(3); expect(subtract(0, 0)).toBe(0); }); }); """ **Vitest Configuration (vite.config.ts)** """typescript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'jsdom', // or 'node' if you're testing server-side code globals: true, // Allows using expect, describe, it without importing them coverage: { reporter: ['text', 'json', 'html'], }, }, }) """ ### Anti-Patterns * **Testing Implementation Details:** Tests should focus on the component's public interface and behavior, not its internal implementation. * **Creating Brittle Tests:** Avoid tests that are overly sensitive to minor code changes. * **Ignoring Edge Cases:** Ensure tests cover all possible input values, including edge cases and error conditions. * **Over-Mocking:** Mocking EVERYTHING can lead to tests that are detached from reality. Mock only dependencies that are truly necessary. ## Integration Testing Integration testing verifies the interactions between different parts of the application. In a Vite project, this might involve testing the interaction between components, modules, or services. ### Standards * **Do This:** Use integration tests to verify that components work together correctly. * **Don't Do This:** Rely solely on unit tests, as they don't catch integration issues. * **Why:** Integration tests ensure that the application functions as a cohesive whole, not just a collection of isolated units. ### Best Practices * **End-to-End Coverage:** Consider designing integration tests to exercise critical user flows. * **Real Dependencies:** Minimize mocking in integration tests. Where possible, use real dependencies (databases, APIs) in a testing environment. * **Test Data:** Use realistic test data that reflects the data the application will process in production. * **Database Testing:** Use database migrations and seeding to ensure a consistent database state for each test run. ### Vite-Specific Considerations * **Mocking External APIs:** Use tools like "nock" or "msw (Mock Service Worker)" to mock external API calls in integration tests without making real network requests. * **Component Composition:** Thoroughly test the interaction between different Vue or React components to ensure data flows correctly and events are handled properly. * **Asynchronous Operations:** Vite often involves asynchronous operations (API calls, animations). Use "async/await" and proper error handling in integration tests. * **State Management Integration:** Test state management solutions like Vuex or Pinia to ensure state changes are reflected correctly in the UI. ### Code Examples **Example using Vitest and Mock Service Worker (msw) for API integration** """typescript // src/api/todos.ts import axios from 'axios'; const API_URL = 'https://jsonplaceholder.typicode.com'; export async function fetchTodos() { const response = await axios.get("${API_URL}/todos"); return response.data; } """ """typescript // src/api/todos.spec.ts import { fetchTodos } from './todos'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'; const mockTodos = [ { userId: 1, id: 1, title: 'delectus aut autem', completed: false }, { userId: 1, id: 2, title: 'quis ut nam facilis et officia qui', completed: false }, ]; const server = setupServer( rest.get('https://jsonplaceholder.typicode.com/todos', (req, res, ctx) => { return res(ctx.status(200), ctx.json(mockTodos)); }) ); describe('todos API', () => { beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); it('fetches todos correctly', async () => { const todos = await fetchTodos(); expect(todos).toEqual(mockTodos); }); it('handles errors correctly when the API fails', async () => { server.use( rest.get('https://jsonplaceholder.typicode.com/todos', (req, res, ctx) => { return res(ctx.status(500)); }) ); await expect(fetchTodos()).rejects.toThrowError(); }); }); """ **Example Integration Test for Vue Component Interaction** """typescript // ParentComponent.vue <template> <div> <ChildComponent :message="parentMessage" @message-changed="updateMessage" /> <p>Parent Message: {{ parentMessage }}</p> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const parentMessage = ref('Hello from Parent'); const updateMessage = (newMessage: string) => { parentMessage.value = newMessage; }; </script> """ """typescript // ChildComponent.vue <template> <div> <p>Child Message: {{ message }}</p> <button @click="emitMessage">Change Message</button> </div> </template> <script setup lang="ts"> import { defineProps, defineEmits } from 'vue'; defineProps({ message: { type: String, required: true, }, }); const emit = defineEmits(['message-changed']); const emitMessage = () => { emit('message-changed', 'Message from Child'); }; </script> """ """typescript // ParentComponent.spec.ts import { mount } from '@vue/test-utils'; import ParentComponent from './ParentComponent.vue'; import { describe, it, expect } from 'vitest'; describe('ParentComponent.vue', () => { it('updates the parent message when the child emits an event', async () => { const wrapper = mount(ParentComponent); await wrapper.find('button').trigger('click'); expect(wrapper.find('p').text()).toContain('Parent Message: Message from Child'); }); }); """ ### Anti-Patterns * **Flaky Tests:** Integration tests that sometimes pass and sometimes fail are a major problem. Ensure tests are reliable and repeatable using proper setup and teardown. * **Overlapping Tests:** Ensure each integration test focuses on a specific interaction. Avoid tests that try to cover too much simultaneously. * **Ignoring Error Handling:** Integration tests are an excellent place to verify error handling behavior, such as API request failures. ## End-to-End Testing End-to-end (E2E) testing simulates real user interactions with the application in a browser environment. This verifies the entire application stack, from the frontend to the backend and database. ### Standards * **Do This:** Use an E2E testing framework such as Cypress, Playwright, or Selenium. * **Don't Do This:** Neglect E2E testing, as it's the only way to guarantee the application works correctly from a user's perspective. * **Why:** E2E tests catch issues that unit and integration tests might miss, such as problems with routing, UI rendering, or third-party integrations. ### Best Practices * **Real Browsers:** Run E2E tests in real browsers (Chrome, Firefox, Safari) to ensure compatibility. * **CI/CD Integration:** Integrate E2E tests into your CI/CD pipeline to automatically run tests on every commit. Playwright and Cypress are great for this. * **Test Environments:** Use a dedicated test environment that closely resembles the production environment. * **Data Setup:** Seed the database with test data before running E2E tests. * **Clear Assertions:** Write assertions that verify the expected state of the application after each interaction. * **Page Object Model (POM):** Utilize the Page Object Model design pattern to abstract away the details of the UI and make tests more maintainable. ### Vite-Specific Considerations * **Development Server:** Ensure the Vite development server is running before starting E2E tests, or use tools that manage the server lifecycle. Playwright and Cypress can start/stop the dev server as part of their testing process. * **Build Artifacts:** For production-like testing, build the Vite application and serve the static assets from a local server. * **Routing:** Test all critical routes in the application to ensure navigation works correctly. ### Code Examples **Example using Playwright for E2E Testing** """typescript // playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', // Replace with your Vite dev server URL trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, ], webServer: { command: 'npm run dev', // Your Vite development server command url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); """ """typescript // tests/example.spec.ts import { test, expect } from '@playwright/test'; test('homepage has title and links to intro page', async ({ page }) => { await page.goto('/'); // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Vite App/); // create a locator const getStarted = page.getByRole('link', { name: 'Learn More' }); // Expect an attribute "to be strictly equal" to the value. await expect(getStarted).toHaveAttribute('href', '/intro'); // Click the get started link. await getStarted.click(); // Expect the URL to contain intro. await expect(page).toHaveURL(/.*intro/); }); """ **Example using Cypress for E2E Testing** """javascript // cypress.config.js const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:5173', // Your Vite development server URL setupNodeEvents(on, config) { // implement node event listeners here }, specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', }, component: { devServer: { framework: 'vue', bundler: 'vite', }, }, }); """ """javascript // cypress/e2e/spec.cy.js describe('My First Test', () => { it('Visits the Kitchen Sink', () => { cy.visit('/') // Visits the baseURL cy.contains('Learn More').click() // Should be on a new URL which includes '/intro' cy.url().should('include', '/intro') // Get an input, type into it and verify that the value has been updated cy.get('input') .type('fake@email.com') .should('have.value', 'fake@email.com') }) }) """ ### Anti-Patterns * **Slow Tests:** E2E tests can be slow to run. Optimize tests by minimizing unnecessary steps and using parallel execution. * **Unreliable Tests:** Strive to make tests as reliable as possible by handling asynchronous operations correctly and waiting for elements to be visible before interacting with them. * **Hardcoded Values:** Avoid hardcoding values in tests. Use configuration variables or generate test data dynamically. * **Lack of Isolation:** Ensure that tests are isolated from each other to prevent one test from affecting the results of another. Clear browser state (cookies, local storage) between tests. ## Key Considerations within a CI/CD Pipeline * **Automated Execution:** Integrate testing into your CI/CD pipline, so every push, merge, or tag triggers a test run. * **Reporting:** Generate test reports after each run and make them easily accessible to the development team. Tools like JUnit report XML can be integrated into CI/CD pipelines to track build success and failure metrics. * **Parallelization:** Run tests in parallel to reduce the overall test execution time. All modern testing frameworks support parallel test execution. * **Environment Configuration:** Ensure that the test environment is properly configured with all necessary dependencies and configurations. This may involve setting environment variables, creating database schemas, or deploying application dependencies. * **Artifact Storage:** Store test artifacts (logs, reports, screenshots) for later analysis and debugging. Services like S3 and Azure Blob Storage can be used to store these artifacts. * **Notifications:** Send notifications (email, Slack, etc.) to the development team when tests fail. This guide establishes comprehensive testing standards to ensure high code quality and reliability in Vite projects. By adhering to these guidelines, development teams can produce robust, maintainable applications suitable for production environments.
# Component Design Standards for Vite This document outlines best practices for designing reusable, maintainable, and performant components within a Vite project. It focuses specifically on component design principles relevant to Vite's ecosystem, leveraging its features for optimal development workflows. ## 1. Component Architecture & Organization ### 1.1. Standard: Single Responsibility Principle (SRP) * **Do This:** Design components to have a single, well-defined purpose. A component should handle one specific task or display a single piece of information. * **Don't Do This:** Create "god components" that handle multiple responsibilities, making them difficult to understand, test, and reuse. **Why:** SRP improves code clarity, reduces complexity, and simplifies testing. Changes to one part of a component are less likely to impact other parts when the component adheres to SRP. **Example:** Splitting a complex form component into smaller, specialized components: """vue // Bad: Form component handling everything <template> <form @submit.prevent="handleSubmit"> <label for="name">Name:</label> <input type="text" id="name" v-model="name"> <label for="email">Email:</label> <input type="email" id="email" v-model="email"> <textarea v-model="message"></textarea> <button type="submit">Submit</button> </form> </template> <script setup> import { ref } from 'vue'; const name = ref(''); const email = ref(''); const message = ref(''); const handleSubmit = () => { // Handle form submission logic here }; </script> """ """vue // Good: Separated components // NameInput.vue <template> <div> <label for="name">Name:</label> <input type="text" id="name" v-model="modelValue" @input="$emit('update:modelValue', $event.target.value)"> </div> </template> <script setup> defineProps({ modelValue: { type: String, required: true } }); defineEmits(['update:modelValue']); </script> // EmailInput.vue <template> <div> <label for="email">Email:</label> <input type="email" id="email" v-model="modelValue" @input="$emit('update:modelValue', $event.target.value)"> </div> </template> <script setup> defineProps({ modelValue: { type: String, required: true } }); defineEmits(['update:modelValue']); </script> // MessageTextarea.vue <template> <div> <label for="message">Message:</label> <textarea id="message" v-model="modelValue" @input="$emit('update:modelValue', $event.target.value)"></textarea> </div> </template> <script setup> defineProps({ modelValue: { type: String, required: true } }); defineEmits(['update:modelValue']); </script> // Parent Form.vue <template> <form @submit.prevent="handleSubmit"> <NameInput v-model="name" /> <EmailInput v-model="email" /> <MessageTextarea v-model="message" /> <button type="submit">Submit</button> </form> </template> <script setup> import { ref } from 'vue'; import NameInput from './NameInput.vue'; import EmailInput from './EmailInput.vue'; import MessageTextarea from './MessageTextarea.vue'; const name = ref(''); const email = ref(''); const message = ref(''); const handleSubmit = () => { // Handle form submission logic here console.log(name.value, email.value, message.value); }; </script> """ ### 1.2. Standard: Component Composition over Inheritance * **Do This:** Favor composing components together to create more complex UI elements rather than relying on inheritance. * **Don't Do This:** Create deep inheritance hierarchies, which can lead to tight coupling and make components difficult to understand and modify. **Why:** Composition offers more flexibility and avoids the problems associated with inheritance, such as the fragile base class problem. Vue's composable component system lends itself naturally to composition. **Example:** Creating a custom button with different styles: """vue // ButtonBase.vue (Base button component) <template> <button class="base-button" @click="$emit('click')"> <slot /> </button> </template> <script setup> defineEmits(['click']); </script> <style scoped> .base-button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; } </style> // PrimaryButton.vue (Composing ButtonBase with specific styles) <template> <ButtonBase @click="$emit('click')"> <slot /> </ButtonBase> </template> <script setup> import ButtonBase from './ButtonBase.vue'; defineEmits(['click']); </script> <style scoped> .base-button { background-color: #007bff; color: white; } </style> // Usage <template> <PrimaryButton @click="handleClick">Click Me</PrimaryButton> </template> <script setup> import PrimaryButton from './PrimaryButton.vue'; const handleClick = () => { alert('Button clicked!'); }; </script> """ ### 1.3. Standard: Use SFC (Single-File Components) * **Do This:** Utilize Vue's Single-File Component (SFC) format (".vue" files) for organizing component logic, template, and styling. * **Don't Do This:** Mix HTML, JavaScript, and CSS in separate files, especially for non-trivial components. **Why:** SFCs provide excellent structure, scopability, and maintainability. Vite's built-in support for SFCs provides hot module replacement (HMR) and efficient compilation. SFCs are crucial for optimal DX in Vite projects. **Example:** """vue // MyComponent.vue <template> <div> <h1>{{ title }}</h1> <p>{{ message }}</p> </div> </template> <script setup> import { ref } from 'vue'; const title = ref('Hello, Vite!'); const message = ref('This is a Single-File Component.'); </script> <style scoped> h1 { color: #3498db; } </style> """ ### 1.4. Standard: Clearly Defined Component API * **Do This:** Define a clear and concise API for each component using "props", "emits", and "slots". Leverage TypeScript to enforce strong typing and improve development-time error detection. Utilize "defineProps" and "defineEmits" if not using Typescript for clarity and documentation. * **Don't Do This:** Rely on implicit data sharing or unpredictable side effects, making it difficult to understand how a component interacts with its environment. **Why:** A well-defined API makes components easier to use, test, and reason about. Strong typing with TypeScript significantly reduces runtime errors and facilitates code maintenance, even with JavaScript projects using "defineProps". **Example:** """vue // MyComponent.vue (with TypeScript) <template> <div> <p>Name: {{ name }}</p> <button @click="$emit('update:name', 'New Name')">Update Name</button> </div> </template> <script setup lang="ts"> import { defineProps, defineEmits } from 'vue'; interface Props { name: string; } const props = defineProps<Props>(); const emit = defineEmits(['update:name']); </script> // Another way without Typescript <script setup> defineProps({ name: { type: String, required: true, } }) defineEmits(['update:name']) </script> """ ### 1.5. Standard: Directory Structure * **Do This:** Establish a consistent and meaningful directory structure for your components. A common pattern is to group related components together in dedicated directories. * **Don't Do This:** Scatter components randomly across the project, making it difficult to locate and manage them. **Why:** A well-organized directory structure contributes to project maintainability and scalability. It makes it easier for developers to navigate the codebase and understand the relationships between components. **Example:** """ src/ ├── components/ │ ├── Button/ │ │ ├── Button.vue │ │ ├── Button.stories.js (for Storybook integration) │ │ └── index.js (for convenient imports) │ ├── Input/ │ │ ├── Input.vue │ │ └── ... │ ├── Card/ │ │ ├── Card.vue │ │ └── ... │ └── AppHeader.vue """ ## 2. Component Implementation Details ### 2.1. Standard: Use "setup" Script Syntax * **Do This:** Embrace the "<script setup>" syntax in your SFCs. This syntax provides a cleaner and more concise way to define component logic without manually returning reactive properties. * **Don't Do This:** Use the traditional "export default { ... }" syntax unless there's a compelling reason to do so. **Why:** "<script setup>" offers improved type inference, better performance (due to automatic template analysis), and reduced boilerplate code. It is the recommended approach for modern Vue development with Vite. **Example:** """vue // Good: <script setup> <template> <div> <p>{{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script setup> import { ref } from 'vue'; const count = ref(0); const increment = () => { count.value++; }; </script> // Bad: traditional syntax <template> <div> <p>{{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const count = ref(0); const increment = () => { count.value++; }; return { count, increment } } } </script> """ ### 2.2. Standard: Controlled Components * **Do This:** Treat form inputs (and other interactive elements) as controlled components, using "v-model" or explicitly binding values and handling input events. * **Don't Do This:** Rely on uncontrolled components where the DOM manages the state directly. **Why:** Controlled components provide better control over data flow and enable features like validation and formatting. Using "v-model" with custom components requires using "defineProps" and "defineEmits". **Example:** """vue // Good: Controlled Component <template> <input type="text" :value="inputValue" @input="updateValue"> </template> <script setup> import { defineProps, defineEmits } from 'vue'; const props = defineProps({ inputValue: String, }); const emit = defineEmits(['updateValue']); const updateValue = (event) => { emit('updateValue', event.target.value); }; </script> // Bad: Uncontrolled Component (avoid this) <template> <input type="text" ref="myInput"> </template> <script setup> import { ref, onMounted} from 'vue'; const myInput = ref(null); onMounted(() => { // Directly accessing DOM node, avoid this practice. console.log(muInput.value.value); }) </script> """ ### 2.3. Standard: Use Slots Effectively * **Do This:** Utilize slots to create flexible and customizable components. Use named slots for more complex component structures. Use scoped slots to pass data back to the parent component. * **Don't Do This:** Hardcode content or rely on props when slots can provide a more adaptable solution. **Why:** Slots allow parent components to inject custom content into specific parts of a child component, increasing reusability. **Example:** """vue // Card.vue <template> <div class="card"> <header class="card-header"> <slot name="header"></slot> </header> <div class="card-body"> <slot></slot> <!-- Default slot --> </div> <footer class="card-footer"> <slot name="footer"></slot> </footer> </div> </template> // Usage <template> <Card> <template #header> <h2>Card Title</h2> </template> <p>Card content here.</p> <template #footer> <button @click="handleAction">Action</button> </template> </Card> </template> <script setup> import Card from './Card.vue'; const handleAction = () => { alert('Action performed!'); }; </script> """ ### 2.4. Standard: Dynamic Imports for Performance * **Do This:** Use dynamic imports ("import()") for components (especially lazy-loaded components or components used infrequently) to improve initial page load time. Especially useful for large components or those including computationally expensive logic. * **Don't Do This:** Import all components eagerly, which can increase the initial bundle size and negatively affect performance. **Why:** Dynamic imports split the code into smaller chunks, which are loaded only when needed. This reduces the initial download size and improves the user experience. Vite's built-in support for dynamic imports makes this easy to implement. **Example:** """vue <template> <Suspense> <template #default> <MyComponent /> </template> <template #fallback> <div>Loading...</div> </template> </Suspense> </template> <script setup> import { defineAsyncComponent } from 'vue'; const MyComponent = defineAsyncComponent(() => import('./MyComponent.vue')); </script> """ ### 2.5. Standard: Component Names * **Do This:** Use PascalCase for component names (e.g., "MyButton", "UserProfile"). This makes it easy to distinguish components from native HTML elements in templates. Consistent naming conventions help with readability and maintainability. * **Don't Do This:** Use kebab-case or camelCase for component names. **Why:** Consistent naming improves readability and helps developers quickly identify components. ## 3. Component Styling ### 3.1. Standard: Scoped Styles * **Do This:** Use scoped styles ("<style scoped>") in SFCs whenever possible to prevent style conflicts and ensure that styles apply only to the specific component. * **Don't Do This:** Rely excessively on global styles, which can lead to unintended side effects and make it difficult to maintain the application's styling. **Why:** Scoped styles encapsulate the component's styling, making it more predictable and maintainable. **Example:** """vue <template> <div class="my-component"> <p>This is my component.</p> </div> </template> <style scoped> .my-component { border: 1px solid black; padding: 10px; } p { color: blue; } </style> """ ### 3.2. Standard: CSS Preprocessors (Sass, Less, Stylus) * **Do This:** Consider using a CSS preprocessor like Sass, Less, or Stylus for more advanced styling features (variables, mixins, nesting). Configure the preprocessor in your "vite.config.js" file. * **Don't Do This:** Use inline styles excessively, as they reduce maintainability and make it difficult to manage styles consistently. **Why:** CSS preprocessors enhance the styling workflow and improve code organization. Vite provides excellent integration for CSS preprocessors. **Example:** """vue // vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], css: { preprocessorOptions: { scss: { // example : additionalData: "@import "@/styles/variables.scss";" // for avoid define @import in each file. }, }, }, }) // MyComponent.vue <template> <div class="my-component"> <p>Styled with Sass!</p> </div> </template> <style scoped lang="scss"> .my-component { $primary-color: #e74c3c; border: 1px solid $primary-color; padding: 10px; p { color: $primary-color; } } </style> """ ### 3.3. Standard: CSS Modules * **Do This:** Use CSS Modules for component-level styling to avoid naming collisions and ensure that styles are scoped to the component. * **Don't Do This:** Use generic class names that can conflict with styles in other parts of the application. **Why:** CSS Modules automatically generate unique class names, preventing naming collisions and simplifying CSS management. **Example:** """vue // MyComponent.vue <template> <div :class="$style.myComponent"> <p :class="$style.text">Styled with CSS Modules!</p> </div> </template> <script setup> import styles from './MyComponent.module.css'; </script> <style module src="./MyComponent.module.css"></style> // MyComponent.module.css .myComponent { border: 1px solid green; padding: 10px; } .text { color: green; } """ ## 4. Component Testing ### 4.1. Standard: Unit Tests * **Do This:** Write unit tests for your components to verify that they behave as expected in isolation. Use a testing framework like Vitest (created by the Vue/Vite team) along with Vue Test Utils for component testing. * **Don't Do This:** Skip unit testing, which can lead to undetected bugs and make it difficult to refactor components. **Why:** Unit tests provide confidence in the correctness of your components and make it easier to make changes without introducing regressions. **Example:** """javascript // MyComponent.spec.js import { mount } from '@vue/test-utils'; import MyComponent from './MyComponent.vue'; import { describe, it, expect } from 'vitest'; describe('MyComponent', () => { it('renders the correct message', () => { const wrapper = mount(MyComponent, { props: { message: 'Hello, Test!', }, }); expect(wrapper.text()).toContain('Hello, Test!'); }); }); """ ### 4.2. Standard: Component Stories (Storybook) * **Do This:** Create component stories using Storybook to showcase the different states and variations of your components. * **Don't Do This:** Rely solely on manual component inspection, which can be time-consuming and prone to errors. **Why:** Storybook provides a dedicated environment for developing and testing components in isolation. It improves component discoverability and facilitates collaboration with designers and other developers. **Example:** """javascript // Button.stories.js import Button from './Button.vue'; export default { title: 'Components/Button', component: Button, }; const Template = (args) => ({ components: { Button }, setup() { return { args }; }, template: '<Button v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { label: 'Primary Button', primary: true, }; export const Secondary = Template.bind({}); Secondary.args = { label: 'Secondary Button', }; """ ## 5. Security Considerations ### 5.1. Standard: Sanitize User Input * **Do This:** Always sanitize user input to prevent cross-site scripting (XSS) vulnerabilities. Use Vue's built-in HTML escaping or a dedicated sanitization library. * **Don't Do This:** Directly render unsanitized user input, which can allow attackers to inject malicious code into your application. **Why:** XSS vulnerabilities can compromise user data and application security. **Example:** """vue <template> <div> <p v-html="sanitizedMessage"></p> </div> </template> <script setup> import { ref, computed } from 'vue'; const userInput = ref('<script>alert("XSS");</script>Hello!'); const sanitizedMessage = computed(() => { // Basic HTML escaping (more robust solutions should be used in production) return userInput.value.replace(/</g, '<').replace(/>/g, '>'); }); </script> """ ### 5.2. Standard: Avoid Direct DOM Manipulation * **Do This:** Minimize direct DOM manipulation and rely on Vue's data binding and component lifecycle hooks to update the UI. * **Don't Do This:** Use "document.querySelector" or other DOM APIs to directly modify the DOM, as this can bypass Vue's reactivity system and introduce security vulnerabilities. **Why:** Direct DOM manipulation can lead to inconsistencies and make it difficult to track changes to the UI.
# State Management Standards for Vite This document outlines the recommended coding standards for state management in Vite projects. Adhering to these standards improves code maintainability, enhances performance, and promotes predictable application behavior. ## 1. Architectural Overview: Choosing a State Management Solution Selecting the right state management solution is crucial for the scalability and maintainability of your Vite application. Consider the complexity of your application before deciding on a technology. ### 1.1. Standard: Evaluate Application Complexity * **Do This:** Assess the size, complexity, and data flow of your application early in the development process. * **Don't Do This:** Automatically adopt a complex state management solution for simple applications or components. **Why:** Over-engineering adds unnecessary complexity and overhead. Simple applications may benefit from simpler approaches. ### 1.2. Standard: Consider Lightweight Options First * **Do This:** For smaller applications, use "useState" and "useReducer" from React (or equivalents in Vue/Svelte) for local component state and simple global state management with Context API/Provide/Inject patterns. * **Don't Do This:** Immediately jump to Redux/Vuex/Pinia for trivial state needs. **Why:** React's built-in hooks and Context API provide a functional and efficient approach for managing state in smaller applications without the boilerplate associated with larger libraries. **Example (React with Context API):** """jsx // Context.js import React, { createContext, useState, useContext } from 'react'; const ThemeContext = createContext(); export const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; const value = { theme, toggleTheme }; return ( <ThemeContext.Provider value={value}> {children} </ThemeContext.Provider> ); }; export const useTheme = () => useContext(ThemeContext); // Component.jsx import React from 'react'; import { useTheme } from './Context'; const MyComponent = () => { const { theme, toggleTheme } = useTheme(); return ( <div className={"App ${theme}"}> <h1>Current Theme: {theme}</h1> <button onClick={toggleTheme}>Toggle Theme</button> </div> ); }; export default MyComponent; """ **Example (Vue 3 with Provide/Inject):** """vue // App.vue - Providing the state <template> <MyComponent /> </template> <script setup> import { provide, ref } from 'vue'; import MyComponent from './MyComponent.vue'; const theme = ref('light'); const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light'; }; provide('theme', theme); provide('toggleTheme', toggleTheme); </script> // MyComponent.vue - Injecting the state <template> <div :class="'App ' + theme"> <h1>Current Theme: {{ theme }}</h1> <button @click="toggleTheme">Toggle Theme</button> </div> </template> <script setup> import { inject } from 'vue'; const theme = inject('theme'); const toggleTheme = inject('toggleTheme'); </script> """ ### 1.3. Standard: When to Use State Management Libraries * **Do This:** Utilize libraries like Zustand, Jotai, Recoil, or Pinia when your application reaches a scale where managing global state with Context/Provide/Inject becomes cumbersome, or you need advanced features like time-travel debugging, middleware, or centralized state mutations. * **Don't Do This:** Introduce a complex state management library without a clear understanding of its benefits and tradeoffs. **Why:** State management libraries provide structured approaches to managing global application state, improving testability, and simplifying debugging. ### 1.4. Standard: Choose a Reactive State Management Solution * **Do This:** Prioritize reactive state management libraries that automatically update components when the state changes (e.g., Pinia for Vue, Zustand or Valtio for frameworks that need a more explicit reactivity model outside of a framework e.g. Vanilla JS, Svelte, React if you use a non-reactive state model). * **Don't Do This:** Manually trigger updates or rely on manual data synchronization. **Why:** Reactivity simplifies component logic, reduces boilerplate code, and ensures that the UI accurately reflects the application's state. ## 2. State Management Implementation Standards ### 2.1. Standard: Single Source of Truth * **Do This:** Ensure that each piece of data has a single, authoritative source within your state management system. * **Don't Do This:** Duplicate or derive data in multiple places without proper synchronization. **Why:** A single source of truth prevents inconsistencies and simplifies debugging by making it clear where data originates. **Example (Zustand):** """javascript import { create } from 'zustand' const useStore = create((set) => ({ bears: 0, increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }), })) function BearCounter() { const bears = useStore((state) => state.bears) return <h1>{bears} around here ...</h1> } function BearIncreaseButton() { const increasePopulation = useStore((state) => state.increasePopulation) return <button onClick={increasePopulation}>one up</button> } useStore.subscribe(console.log) """ ### 2.2. Standard: Immutability * **Do This:** Treat state as immutable, meaning that you should create a new copy of the state whenever you need to update it. Avoid direct modifications to existing state objects. * **Don't Do This:** Mutate state directly, as this can lead to unpredictable component behavior and difficult-to-debug issues. **Why:** Immutability facilitates efficient change detection, simplifies debugging with features like time-travel debugging, and improves performance by avoiding unnecessary re-renders. **Example (Pinia):** When using Pinia, state mutations are already handled reactively. You should still adopt immutability best practices when dealing with complex state structures, particularly within actions. """typescript import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => ({ count: 0, nestedObject: { value: 'initial' } }), actions: { increment() { this.count++ }, // Correct way to update nested objects immutably updateNestedValue(newValue: string) { // Option 1: Using the $patch method this.$patch((state) => { state.nestedObject = { ...state.nestedObject, value: newValue }; }); // Option 2: Replacing the entire nested object this.nestedObject = { value: newValue }; } }, }) """ ### 2.3. Standard: Explicit State Mutations * **Do This:** Use dedicated functions or actions to modify the state. Centralize these modifications in a predictable location. * **Don't Do This:** Directly modify state within components or spread state updates across multiple unrelated components. **Why:** Centralized state modifications make it easier to track changes, debug issues, and implement complex state transitions. **Example (Pinia with Actions):** """typescript import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ userData: null, isLoading: false, error: null, }), actions: { async fetchUserData(userId: string) { this.isLoading = true; try { const response = await fetch("/api/users/${userId}"); //Assumes API is properly configured with CORS this.userData = await response.json(); } catch (error) { this.error = error; } finally { this.isLoading = false; } }, }, }); """ ### 2.4. Standard: Selectors for Derived Data * **Do This:** Use selectors (computed properties in Vue/Pinia, selector functions in Redux/Recoil/Zustand) to derive data from the state. Cache and memoize selector results to avoid unnecessary computations on every render. * **Don't Do This:** Perform complex data transformations directly in components or recalculate derived data repeatedly. **Why:** Selectors improve performance by preventing redundant computations and encapsulate the logic for deriving data from the state. **Example (Pinia with Getters):** """typescript import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => ({ count: 0, }), getters: { doubleCount: (state) => state.count * 2, // Use this type if you use TypeScript doubleCountPlusOne(): number { return this.doubleCount + 1 }, }, actions: { increment() { this.count++ }, }, }) """ ### 2.5. Standard: Asynchronous Actions with Caution * **Do This:** Handle asynchronous operations (e.g., API calls) within actions. Use "async/await" or Promises appropriately. Implement error handling and loading states to provide feedback to the user. * **Don't Do This:** Perform asynchronous operations directly in components without managing loading states or handling errors. Mutate loading states directly in components. **Why:** Isolating asynchronous operations in actions improves testability and provides a centralized way to manage side effects. Managed loading states/errors enhances UX. """typescript import { defineStore } from 'pinia'; export const useDataStore = defineStore('data', { state: () => ({ data: null, loading: false, error: null, }), actions: { async fetchData() { this.loading = true; this.error = null; // Reset error state at the beginning try { const response = await fetch('/api/data'); // Assumes API properly configured for CORS and available if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } this.data = await response.json(); } catch (e: any) { this.error = e.message; } finally { this.loading = false; } }, }, }); """ ### 2.6. Standard: Centralized API interaction * **Do This:** Abstract all API requests to dedicated service/APIModules. * **Don't Do This:** Perform your API request inside Vue components or Pinia actions. **Why:** Improves maintainability, reusability and avoids complexity in your Pinia stores. """typescript // api/todos.ts import { Todo } from '@/types' const BASE_URL = 'https://jsonplaceholder.typicode.com' const getTodos = async (): Promise<Todo[]> => { try { const response = await fetch("${BASE_URL}/todos") if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}") } return await response.json() as Todo[] } catch (error) { console.error('Failed to fetch todos:', error) throw error // re-throw the error so the caller can handle it } } export { getTodos } """ ### 2.7 Standard: Modular State Management * **Do This:** Break down large stores into smaller, manageable modules. Group related state, actions, and getters into separate store files. * **Don't Do This:** Create a single, monolithic store that contains all of your application's state. **Why:** Improves code organization, maintainability, especially in larger applications. **Example (Pinia modular stores):** """typescript // stores/user.ts import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ name: 'John Doe', email: 'john.doe@example.com' }), getters: { profile: (state) => "${state.name} (${state.email})" } }) // stores/settings.ts import { defineStore } from 'pinia' export const useSettingsStore = defineStore('settings', { state: () => ({ theme: 'light', notificationsEnabled: true }), actions: { toggleTheme() { this.theme = this.theme === 'light' ? 'dark' : 'light' } } }) // Component.vue <script setup> import { useUserStore, useSettingsStore } from '@/stores' const userStore = useUserStore() // you can pass the entire store to the composable const settingsStore = useSettingsStore() </script> """ ## 3. Vite-Specific Considerations ### 3.1. Standard: Environment Variables in State * **Do This:** Access environment variables through "import.meta.env" instead of directly referencing "process.env". Define a clear schema for the environment variables used by your application. * **Don't Do This:** Hardcode sensitive information or directly expose "process.env" to the client-side code. **Why:** Vite exposes environment variables through "import.meta.env", which is specifically designed for browser environments. Direct "process.env" access will fail in the browser. **Snippet:** """javascript // Accessing an environment variable const apiUrl = import.meta.env.VITE_API_URL; // Ensure VITE_API_URL is prefixed with VITE_ """ ### 3.2. Standard: Lazy Loading Modules with State * **Do This:** Use dynamic imports ("import()") with state modules to reduce initial bundle size and improve loading performance, in concert with component-level lazy loading. * **Don't Do This:** Load all state modules upfront, as this can negatively impact the initial load time of your application. **Why:** Dynamic imports allow you to load state modules on demand, reducing the initial bundle size and improving performance. This is especially helpful for feature-rich applications. **Example:** """javascript // Load the user store only when needed async function loadUserStore() { const { useUserStore } = await import('./stores/user'); const userStore = useUserStore(); // ... use the store } """ ### 3.3. Standard: Minimizing Re-renders with "shallowRef" * **Do This:** Leveraging "shallowRef" and "shallowReactive" in Vue when fine-grained reactivity is not needed, particularly for large immutable data structures. * **Don't Do This:** Blindly using "ref" or "reactive" for all state, which can lead to unnecessary re-renders and performance bottlenecks. * **Why:** By default, Vue's reactivity system deeply observes all properties of an object, which can be overkill for data that doesn't change frequently. "shallowRef" and "shallowReactive" only track changes at the top level, providing a performance boost by avoiding unnecessary re-renders. **Example:** """vue <script setup> import { shallowRef, onMounted } from 'vue'; import { getLargeDataSet } from './api'; // Simulate fetching a large dataset const dataSet = shallowRef(null); const loadData = async () => { dataSet.value = await getLargeDataSet(); }; onMounted(loadData); </script> <template> <div v-if="dataSet"> <!-- Render the dataSet. Changes *within* the object will NOT trigger re-renders, only replacing the entire object will trigger an update. --> <p>Data loaded</p> </div> <div v-else>Loading...</div> </template> """ ### 3.4 Standard: Use Vite's HMR * **Do This:** Take advantage of Vite's Hot Module Replacement (HMR) to preserve state between code changes during development. * **Don't Do This:** Refresh entire application manually on even minor code edits. **Why:** HMR significantly speeds up the development process by allowing you to see changes instantly without losing the current application state. In state management this means preserving the current state within Pinia/Zustand/etc, so you don't have to manually reset or re-navigate. ## 4. Security Considerations ### 4.1. Standard: Avoid Storing Sensitive Information in Client-Side State * **Do This:** Store only non-sensitive data in client-side state. Handle sensitive information (e.g., user passwords, API keys) on the server-side or use secure storage mechanisms. * **Don't Do This:** Store sensitive information directly in the global state, where it can be accessed by malicious actors. **Why:** Client-side state is inherently exposed to the user. Storing sensitive information in the frontend poses a significant security risk. ### 4.2. Standard: Sanitize Data Retrieved form the Backend * **Do This:** Sanitize and validate any data received from the backend before storing it in the state. This reduces potential XSS (Cross-Site Scripting) risks. * **Don't Do This:** Directly store unsanitized data in the state, which can expose your application to security vulnerabilities. **Why:** Sanitizing data before storing in state reduces the risk of stored cross-site scripting ## 5. Performance Optimization ### 5.1. Standard: Minimize State Size * **Do This:** Store only the data needed for the current view or component. Avoid storing large, unnecessary data structures in the global state. * **Don't Do This:** Store excessive amounts of data in the global state, which can increase memory usage and slow down performance. **Why:** Reducing the state size improves performance by minimizing memory usage and reducing the amount of data that needs to be processed on every state change. ### 5.2. Standard: Optimize Data Structures * **Do This:** Use efficient data structures (e.g., Maps, Sets) for storing and accessing data in the state. Consider using Immutable.js for immutable data structures. * **Don't Do This:** Rely on inefficient data structures (e.g., arrays for lookups) when performance is critical. **Why:** Efficient data structures can significantly improve performance, especially when dealing with large datasets. ## 6. Common Anti-Patterns ### 6.1. Anti-Pattern: Prop Drilling * **Avoid:** Passing props through multiple layers of components to reach a deeply nested component that needs the data. * **Instead:** Use state management libraries (e.g., Pinia) or Context API/Provide/Inject to make the data available directly to the component that needs it. ### 6.2. Anti-Pattern: Mutating State Directly * **Avoid:** Directly modifying objects or arrays stored in the state. * **Instead:** Create new copies of the data using the spread operator or other immutable update techniques. ### 6.3. Anti-Pattern: Over-reliance on Global State * **Avoid:** Storing everything in the global state. Many components will do perfectly well with "useState". * **Instead:** Carefully choose which state truly is global to the application. ### 6.4. Anti-pattern: Tight Coupling to Specific State Management Library * **Avoid** Design code that is heavily dependent on the specifics(classes / functions) of a particular library. * **Instead** Abstract usage behind interfaces or custom hooks (as appropriate to your framework). This reduces your risk when it's time to upgrade that dependency or swap to a different solution. ## Conclusion These state management standards provide a solid foundation for building maintainable, performant, and secure Vite applications. By following these guidelines, development teams can ensure code consistency and improve the overall quality of their projects.
# Performance Optimization Standards for Vite This document outlines coding standards specifically for performance optimization in Vite projects. These standards aim to improve application speed, responsiveness, and resource usage, leveraging Vite's unique features and capabilities. This guide incorporates best practices based on the latest Vite version. ## 1. Bundling and Code Splitting Strategies ### 1.1. Dynamic Imports and Route-Based Chunking **Standard:** Use dynamic imports ("import()") for lazy-loading components and route-based chunking to split the application into smaller, more manageable bundles. **Why:** Reduces initial load time by only loading the code required for the current view. Route-based chunking minimizes redundant code across routes. **Do This:** """javascript // src/router/index.js import { createRouter, createWebHistory } from 'vue-router'; const routes = [ { path: '/', name: 'Home', component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue') }, { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ]; const router = createRouter({ history: createWebHistory(), routes }); export default router; """ **Don't Do This:** """javascript // Avoid importing all components upfront import Home from '../views/Home.vue'; // Avoid this for larger components. const routes = [ { path: '/', name: 'Home', component: Home } ]; """ **Anti-Pattern:** Importing large components eagerly within main application files drastically increases initial bundle size. **Technology Specific:** Vite automatically leverages Rollup's code splitting capabilities when you use dynamic imports. "/* webpackChunkName: "home" */" is a magic comment Vite uses to name the generated chunk. ### 1.2. Vendor Chunking Optimization **Standard:** Ensure your "vite.config.js" optimizes vendor chunking to reduce duplication of dependencies. Use "manualChunks" to manually control chunk creation when needed. **Why:** Improves caching and reduces overall bundle sizes. **Do This:** """javascript // vite.config.js import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], build: { rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { // Create a vendor chunk for all node_modules dependencies return 'vendor'; } } } } } }); """ **Don't Do This:** """javascript // Avoid letting Rollup determine chunking without guidance for large projects. export default defineConfig({ plugins: [vue()] // No manualChunks configuration - Can lead to suboptimal chunking. }); """ **Anti-Pattern:** Letting Rollup handle chunking automatically *can* work in some scenarios, BUT in larger projects, this often leads to inefficient chunk creation, where the dependency code gets duplicated across chunks. Using "manualChunks" strategy with carefully crafted rules gives you the *control* you need for *optimal* performance in larger projects. Common rules are to put all "node_modules" in one "vendor" chunk, which should not change very often. **Technology Specific:** Vite's "build.rollupOptions.output.manualChunks" configuration allows fine-grained control over how Rollup splits your code into chunks. ### 1.3. Minimizing Dependencies (Tree Shaking) **Standard:** Use ES modules and take advantage of tree-shaking to eliminate unused code from your dependencies. **Why:** Tree-shaking reduces the bundle size by excluding dead code, leading to faster load times. **Do This:** """javascript // my-utils.js export function usefulFunction() { console.log('This function is used.'); } export function unusedFunction() { console.log('This function is never called.'); } """ """javascript // main.js import { usefulFunction } from './my-utils.js'; usefulFunction(); // Only 'usefulFunction' will be included in the bundle. """ **Don't Do This:** """javascript // Avoid importing the entire module if you only need one function. import * as utils from './my-utils.js'; // Avoid this, may include unused code. utils.usefulFunction(); """ **Anti-Pattern:** Importing the entire module using "import * as" prevents tree-shaking, as the bundler cannot determine which parts of the module are actually used. **Technology Specific:** Vite relies on Rollup, which performs tree-shaking automatically when using ES modules. Ensure your dependencies also provide ES module versions for optimal tree-shaking. ## 2. Asset Optimization ### 2.1. Image Optimization **Standard:** Optimize images by compressing them without losing significant quality, using appropriate formats (WebP), and serving them at the correct size. **Why:** Smaller images load faster and consume less bandwidth, improving the user experience. **Do This:** * Use tools like ImageOptim, TinyPNG, or ShortPixel to compress images losslessy or lossly as appropriate. * Use the "<picture>" element to provide multiple image formats and sizes: """html <picture> <source srcset="img/my-image.webp" type="image/webp"> <img src="img/my-image.jpg" alt="My Image"> </picture> """ **Don't Do This:** * Uploading unoptimized, high-resolution images directly to your project. **Anti-Pattern:** Serving large, unoptimized images is a common performance bottleneck, especially on mobile devices. **Technology Specific:** Consider using Vite plugins like "vite-plugin-imagemin" to automate image optimization during the build process. Vite's "public" directory allows you to serve static assets directly. ### 2.2. Font Optimization **Standard:** Use web fonts sparingly, load them asynchronously, and use "font-display: swap" to prevent blocking rendering. **Why:** Web fonts can significantly impact page load time if not managed correctly. **Do This:** """css /* Load font asynchronously */ @font-face { font-family: 'MyFont'; src: url('/fonts/MyFont.woff2') format('woff2'); font-display: swap; /* Use swap to prevent blocking */ } body { font-family: 'MyFont', sans-serif; } """ **Don't Do This:** """css /* Avoid blocking rendering */ @font-face { font-family: 'MyFont'; src: url('/fonts/MyFont.woff2') format('woff2'); /* Avoid: font-display: block; */ } """ **Anti-Pattern:** Using "font-display: block" can cause a flash of invisible text (FOIT) while the font is loading which is very poor UX. "font-display: swap" mitigates this by displaying fallback text until the font is loaded and then replaces it. **Technology Specific:** Vite automatically optimizes asset URLs in CSS and JavaScript. Consider using font subsetting tools to reduce font file sizes further. ### 2.3. SVG Optimization **Standard:** Optimize SVGs by removing unnecessary metadata and attributes. Consider using SVGs inline (when small) or as symbols in a sprite. **Why:** Optimized SVGs are smaller and render faster than unoptimized ones. **Do This:** * Use tools like SVGO to optimize SVGs: """bash #Example Command Line Usage svgo my-icon.svg """ * Use inline SVGs or SVG sprites for small icons: """html <!-- Inline SVG --> <svg width="24" height="24" viewBox="0 0 24 24"> <path fill="currentColor" d="..."></path> </svg> <!-- SVG Sprite --> <svg> <use xlink:href="#my-icon"></use> </svg> """ **Don't Do This:** * Using raster images (PNG, JPEG) when SVGs would provide better quality and smaller file sizes. **Anti-Pattern:** Using complex SVGs that are not properly optimized slows down rendering. **Technology Specific:** Vite's asset handling treats SVGs as modules, allowing you to import them directly into your JavaScript code. Can be used with Vue Single File Components very effectively. ## 3. Component Optimization (Vue Specific) ### 3.1. Virtualization for Large Lists **Standard:** Use virtualization (e.g., "vue-virtual-scroller", "vue-virtual-list") for rendering very long lists to only render elements visible in the viewport. **Why:** Virtualization significantly reduces the number of DOM elements, improving rendering performance. **Do This:** """vue <template> <RecycleScroller class="scroller" :items="items" :item-size="30"> <template v-slot="{ item }"> <div class="item">{{ item.text }}</div> </template> </RecycleScroller> </template> <script setup> import { RecycleScroller } from 'vue-virtual-scroller'; import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'; import { ref } from 'vue'; const items = ref(Array.from({ length: 10000 }, (_, i) => ({ id: i, text: "Item ${i}" }))); </script> <style> .scroller { height: 300px; overflow: auto; } .item { height: 30px; line-height: 30px; border-bottom: 1px solid #eee; } </style> """ **Don't Do This:** """vue <!-- Avoid rendering large lists directly --> <template> <div v-for="item in items" :key="item.id" class="item">{{ item.text }}</div> </template> """ **Anti-Pattern:** Rendering thousands of elements with "v-for" directly in the DOM can lead to severe performance issues, making the UI unresponsive. **Technology Specific:** Vue's reactivity system makes it efficient to update virtualized lists as the user scrolls. ### 3.2. Memoization and Computed Properties **Standard:** Use "computed" properties and "memoization" (e.g., "useMemo" or "computed" with dependency tracking) to avoid unnecessary re-renders in Vue components. **Why:** Computed properties are cached, preventing expensive calculations from being re-executed unless their dependencies change. **Do This:** """vue <template> <p>Result: {{ expensiveCalculation }}</p> </template> <script setup> import { ref, computed } from 'vue'; const input = ref(''); const expensiveCalculation = computed(() => { //Perform expensive calculation on input change console.log("Expensive calc running!"); //Only runs when input changes let result = input.value.split('').reverse().join(''); return result; }); </script> """ **Don't Do This:** """vue <template> <p>Result: {{ performExpensiveCalculation() }}</p> </template> <script setup> import { ref } from 'vue'; const input = ref(''); const performExpensiveCalculation = () => { //This function will re-run every render (bad for perf) console.log("Expensive calc running!"); let result = input.value.split('').reverse().join(''); return result; }; </script> """ **Anti-Pattern:** Directly executing expensive functions in the template will cause them to re-run every time the component re-renders, leading to performance issues. **Technology Specific:** Vue's "computed" properties offer a built-in memoization mechanism. You can use dependency tracking for granular control over when computed properties are re-evaluated. ### 3.3. "v-once" Directive for Static Content **Standard:** Use the "v-once" directive for parts of your Vue templates that contain static content that will never change. **Why:** "v-once" tells Vue to render the element/component only once and then skip future updates. **Do This:** """vue <template> <div v-once> <h1>Static Title</h1> <p>This content will never change.</p> </div> </template> """ **Don't Do This:** """vue <template> <div> <h1>Dynamic Title : {{ dynamicTitle }}</h1> <p>This content changes.</p> </div> </template> """ **Anti-Pattern:** Using "v-once" on dynamic content will prevent it from updating correctly which is obviously wrong. Only apply it to purely static sections of your template. **Technology Specific:** Vue's rendering engine will skip the diffing and patching for elements/components marked with "v-once". ### 3.4. Properly Keyed v-for Loops **Standard:** Always use a unique and stable "key" attribute when using "v-for", especially when the list is subject to mutations like adding, removing, or reordering items. **Why:** Keys help Vue track the identity of each node, allowing it to efficiently update the DOM when the list changes. Using the index as a key can lead to problems with re-rendering the items in unusual orders. **Do This:** """vue <template> <ul> <li v-for="item in items" :key="item.id">{{ item.text }}</li> </ul> </template> <script setup> import { ref } from 'vue'; const items = ref([ { id: 'a', text: 'Item A' }, { id: 'b', text: 'Item B' }, ]); </script> """ **Don't Do This:** """vue <template> <ul> <li v-for="(item, index) in items" :key="index">{{ item.text }}</li> </ul> </template> """ **Anti-Pattern:** Using the index as the "key" is an anti-pattern is because Vue may incorrectly reuse existing DOM elements for different list items if the order changes - This can lead to incorrect rendering and lost component state. Using a unique ID for key is critical in maintaining accurate performance. **Technology Specific:** Vue's virtual DOM uses the "key" attribute to optimize DOM updates. If keys are missing or incorrect, Vue may have to re-render entire list instead of just updating necessary elements. ## 4. Network Optimization ### 4.1. Caching Strategies (Browser and CDN) **Standard:** Implement aggressive browser caching using appropriate "Cache-Control" headers and leverage CDNs for static assets. **Why:** Caching reduces the number of requests to the server, improving load times, reducing bandwidth usage, and improving scalability. **Do This:** * Configure your server or CDN to set appropriate "Cache-Control" headers: """ Cache-Control: public, max-age=31536000 // 1 year for immutable assets Cache-Control: no-cache, must-revalidate //For assets that change often """ * Use a CDN like Cloudflare, AWS CloudFront, or Fastly to serve static assets. **Don't Do This:** * Setting short cache expiry times or disabling caching altogether for static assets. **Anti-Pattern:** Failing to leverage browser and CDN caching increases server load and slows down the application for returning users. **Technology Specific:** Vite's build process generates hashed filenames for assets, enabling long-term caching. Modern CDNs can intelligently cache and deliver content based on the user's location. ### 4.2. Resource Hints (Preload, Prefetch) **Standard:** Use resource hints like "<link rel="preload">" and "<link rel="prefetch">" to prioritize loading of critical assets and fetch resources that will be needed later. **Why:** Resource hints improve perceived performance by loading critical resources early and speeding up future navigation. **Do This:** """html <head> <link rel="preload" href="/fonts/MyFont.woff2" as="font" type="font/woff2" crossorigin> <link rel="prefetch" href="/components/MyComponent.vue" as="script"> </head> """ **Don't Do This:** * Overusing "preload" or "prefetch" can lead to unnecessary requests and negatively impact performance. Only use them strategically for specific resources. **Anti-Pattern:** Not using resource hints for critical resources causes delays in rendering and navigation. **Technology Specific:** Vite's plugin ecosystem may offer plugins that automatically inject resource hints based on your application's dependency graph. ### 4.3. Gzip or Brotli Compression **Standard:** Enable Gzip or Brotli compression on your web server to reduce the size of text-based assets (HTML, CSS, JavaScript). **Why:** Compression significantly reduces the size of responses, leading to faster download times and reduced bandwidth consumption. Brotli offers better compression ratios than Gzip. **Do This:** * Configure your web server (e.g., Nginx, Apache, Node.js) to enable Gzip or Brotli compression. * Example Nginx Configuration: """nginx gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/rss+xml application/atom+xml image/svg+xml; """ * Also consider setting the "Content-Encoding" HTTP header properly to let clients know that the resources are compressed. **Don't Do This:** * Serving uncompressed text-based assets. * Not configuring your server to correctly notify clients that they have been compressed. **Anti-Pattern:** Sending large uncompressed text files over the network is a significant waste of bandwidth and slows down page load times. **Technology Specific:** Vite's build process can be configured to generate pre-compressed versions of your assets (e.g., ".gz", ".br") for your server to serve directly. ## 5. Runtime Optimization ### 5.1. Debouncing and Throttling **Standard:** Implement debouncing and throttling techniques to limit the rate at which event handlers are executed, improving performance in scenarios like search input and window resizing. **Why:** Prevents excessive function calls that can degrade performance and responsiveness. **Do This:** """javascript // Debouncing Example function debounce(func, delay) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), delay); }; } window.addEventListener('resize', debounce(() => { // Perform expensive operation after resizing is complete console.log('Resized!'); }, 250)); //Throttling example function throttle (func, limit) { let inThrottle return function() { const args = arguments const context = this if (!inThrottle) { func.apply(context, args) inThrottle = true setTimeout(() => inThrottle = false, limit) } } } window.addEventListener('scroll', throttle(()=>{ console.log("Scrolling - throttled.") }, 500)); """ **Don't Do This:** * Executing expensive operations directly within event handlers without any rate limiting mechanisms. **Anti-Pattern:** Excessive function calls on event handlers can overwhelm the browser's resources and cause performance issues. **Technology Specific:** Libraries like Lodash provide utility functions for debouncing and throttling. Vue's reactivity system can be used to efficiently manage debounced or throttled values. ### 5.2. Web Workers for Background Tasks **Standard:** Offload CPU-intensive tasks to Web Workers to prevent blocking the main thread and maintain UI responsiveness. **Why:** Web Workers run in a separate thread, allowing you to perform calculations or data processing in the background without freezing the UI. **Do This:** """javascript // Create a Web Worker const worker = new Worker(new URL('./my-worker.js', import.meta.url)); worker.postMessage({ data: 'some data' }); worker.onmessage = (event) => { console.log('Received from worker:', event.data); }; // my-worker.js self.onmessage = (event) => { const data = event.data; // Perform heavy computation const result = someHeavyComputation(data); self.postMessage(result); }; """ Remember to use a build tool like Vite to properly bundle and serve the worker file. **Don't Do This:** *Performing long-running or computationally intensive tasks directly on the main thread.* **Anti-Pattern:** Blocking the main thread with CPU-bound tasks results in a janky and unresponsive UI. **Technology Specific:** Vite supports Web Workers out of the box. You can import Web Worker scripts using "new Worker(new URL('./my-worker.js', import.meta.url))". Ensure that your Web Worker code adheres to the same coding standards as your main application code. By adhering to these standards, development teams can create high-performance Vite applications that provide a smooth and responsive user experience.