# Security Best Practices Standards for Vite
This document outlines security best practices for developing Vite applications. Following these standards will help protect against common vulnerabilities and ensure the development of secure, maintainable, and performant applications.
## 1. General Security Principles
### 1.1 Input Validation and Sanitization
**Standard:** Always validate and sanitize user inputs to prevent script injection and data manipulation.
**Why:** Untrusted data from users can lead to cross-site scripting (XSS), SQL injection (if backend integration occurs), and other vulnerabilities.
**Do This:**
* Use built-in browser APIs like "encodeURIComponent" or libraries like DOMPurify for sanitization, especially when rendering user-provided content.
* Implement server-side validation for critical operations, even if client-side validation is in place.
* Use strict data typing and regular expressions for validating input formats.
**Don't Do This:**
* Directly render user inputs without sanitizing them.
* Rely solely on client-side validation, as it can be bypassed.
**Code Example:** Sanitizing user input with DOMPurify
"""javascript
import DOMPurify from 'dompurify';
function renderUserInput(userInput) {
const cleanInput = DOMPurify.sanitize(userInput);
document.getElementById('output').innerHTML = cleanInput;
}
// Usage
const maliciousInput = '';
renderUserInput(maliciousInput); // Renders the image tag safely, preventing execution of the alert.
"""
### 1.2 Output Encoding
**Standard:** Encode output properly to prevent vulnerabilities when displaying data.
**Why:** Encoding ensures that data is displayed in its intended format and not interpreted as code.
**Do This:**
* Use appropriate encoding strategies based on the context (e.g., HTML encoding, URL encoding).
* For HTML contexts, prefer using templating engines that automatically handle encoding, such as Vue.js or React, with their built-in escaping mechanisms.
**Don't Do This:**
* Output data directly without encoding, especially in HTML contexts.
**Code Example:** Using Vue.js to auto-escape output
"""vue
"""
### 1.3 Cross-Origin Resource Sharing (CORS)
**Standard:** Configure CORS policies correctly to control which origins can access your resources.
**Why:** Incorrect CORS configurations can allow unauthorized access to sensitive data or APIs.
**Do This:**
* Specify allowed origins explicitly instead of using a wildcard (*), especially in production.
* Implement proper authentication and authorization mechanisms for sensitive endpoints.
* Consider using different CORS policies for different environments (e.g., more restrictive policies in production).
**Don't Do This:**
* Use a wildcard (*) for "Access-Control-Allow-Origin" in production environments.
**Example Configuration in Vite (using a middleware)**
In "vite.config.js":
"""javascript
import { defineConfig } from 'vite';
import myPlugin from './plugins/my-plugin'; // Import the plugin
export default defineConfig({
server: {
middleware: (app, server) => {
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://your-allowed-origin.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
}
},
plugins: [myPlugin()], // Use the imported plugin
});
"""
Create a "plugins/my-plugin.js" file:
"""javascript
// Example Vite plugin
export default function myPlugin() {
return {
name: 'my-plugin',
// Additional plugin configurations or code, if required
};
}
"""
This configuration sets explicit headers, enhancing security compared to wildcard configurations. Adapt the headers and origin configuration if necessary.
## 2. Vite-Specific Security Considerations
### 2.1 Dependency Management
**Standard:** Regularly audit and update dependencies to patch known vulnerabilities.
**Why:** Vulnerabilities in dependencies can be exploited to compromise the application.
**Do This:**
* Use "npm audit" or "yarn audit" to identify and fix vulnerabilities in dependencies.
* Automate dependency updates using tools like Dependabot or Renovate Bot.
* Keep "devDependencies" separate from "dependencies" to minimize the attack surface in production.
* Use tools like "npm-check-updates" to upgrade dependencies interactively.
**Don't Do This:**
* Ignore audit warnings about vulnerable dependencies
* Use outdated or unmaintained packages
**Code Example:** Updating dependencies
"""bash
npm audit fix # Automatically fix vulnerabilities detectable by npm
npm update # Update dependencies to their latest versions according to package.json
"""
### 2.2 Environment Variables
**Standard:** Securely manage and expose environment variables to the client-side code.
**Why:** Environment variables can contain sensitive information (API keys, database credentials). Exposing them directly risks leakage.
**Do This:**
* Use the "import.meta.env" in Vite to access environment variables safely. Vite automatically exposes variables prefixed with "VITE_".
* Avoid embedding sensitive information directly in client-side code.
* Use ".env" files for local development, but never commit them to version control.
* Prefer setting environment variables in the deployment environment.
**Don't Do This:**
* Expose sensitive environment variables directly to the client without proper scoping or filtering.
* Commit ".env" files to Git repositories.
**Code Example:** Accessing environment variables in Vite
"""javascript
// .env
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My Awesome App
// Component.vue
"""
### 2.3 Server Configuration
**Standard:** Configure the Vite development server and production server to prevent unauthorized access and information leakage.
**Why:** Weak server configurations can create entry points for attackers.
**Do This:**
* In production, use a dedicated web server (Nginx, Apache) to serve the Vite build output, rather than directly exposing the Vite dev server.
* Configure security headers (CSP, HSTS, X-Frame-Options) to protect against common attacks.
* Disable directory listing.
* Ensure proper MIME type settings.
**Don't Do This:**
* Expose the development server directly to the internet in production.
* Use default server configurations without reviewing and hardening them.
**Code Example:** Setting security headers (example with Nginx)
"""nginx
server {
listen 443 ssl;
server_name your-vite-app.com;
ssl_certificate /path/to/your/certificate.pem;
ssl_certificate_key /path/to/your/private.key;
root /path/to/your/vite/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted-scripts.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com;";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Permissions-Policy "geolocation=(), microphone=()";
}
}
"""
### 2.4 Code Splitting and Lazy Loading
**Standard:** Use code splitting and lazy loading strategically to minimize the initial payload and reduce the attack surface.
**Why:** Smaller initial payloads reduce the amount of code that needs to be parsed and executed, potentially mitigating vulnerabilities. Lazy loading defers the loading of non-critical resources, reducing initial attack vectors.
**Do This:**
* Use Vite's built-in support for dynamic imports ("import()") for lazy loading modules.
* Analyze the application's dependencies and create logical code splits.
* Utilize tools such as webpack-bundle-analyzer (or equivalent in the Vite ecosystem) to visualize bundle sizes and identify optimization opportunities.
**Don't Do This:**
* Load entire application code at once, especially if parts are rarely used.
* Neglect analyzing dependencies for unnecessary code.
**Code Example:** Lazy loading a component in Vue.js
"""vue
"""
### 2.5 Template and Component Security
**Standard:** Securely handle templates and components to avoid injection vulnerabilities.
**Why:** Templates and components can be susceptible to injection if user-provided data is not properly handled.
**Do This:**
* Use templating engines (Vue.js, React) with automatic escaping of HTML entities by default.
* Sanitize user-provided data before rendering it, even if auto-escaping is enabled.
* Avoid using "v-html" or "dangerouslySetInnerHTML" unless strictly necessary and only with sanitized data.
* Implement server-side rendering (SSR) with caution and ensure SSR environments are properly secured.
**Don't Do This:**
* Directly inject unsanitized user input into templates.
* Overlook the security implications of using "v-html" or "dangerouslySetInnerHTML".
**Code Example:** Sanitizing data before using "v-html"
"""vue
"""
### 2.6 Third-Party Libraries
**Standard:** Evaluate and monitor third-party libraries and plugins for security risks.
**Why:** Third-party code can introduce vulnerabilities or malicious code into the application.
**Do This:**
* Research and vet third-party libraries before including them in the project.
* Use tools like "snyk", "whitesource" or "retire.js" to identify known vulnerabilities in third-party dependencies.
* Regularly update third-party libraries to patch security issues.
* Implement Subresource Integrity (SRI) for third-party resources loaded from CDNs.
* Consider using a Software Composition Analysis (SCA) tool to automate dependency risk assessment.
**Don't Do This:**
* Blindly trust all third-party libraries without proper evaluation.
* Ignore security warnings related to third-party dependencies.
**Code Example:** Implementing SRI
"""html
"""
### 2.7 Secure File Handling
**Standard:** Implement secure file handling practices for uploads and downloads.
**Why:** Improper file handling can lead to vulnerabilities such as remote code execution or local file inclusion.
**Do This:**
* Validate file types, sizes, and content before processing uploads.
* Store uploaded files in a secure location outside the webroot.
* Use unique and unpredictable filenames.
* Sanitize filenames to prevent path traversal attacks.
* Set appropriate Content-Disposition headers for downloads.
* Implement access controls to restrict access to uploaded files.
**Don't Do This:**
* Store uploaded files directly in the webroot without restrictions.
* Use original filenames without sanitization.
* Rely solely on client-side validation for file uploads.
**Code Example:** Basic server-side file upload validation (Node.js)
"""javascript
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/'); // Store outside webroot
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); //Generate unique names, preserve extension
}
});
const fileFilter = (req, file, cb) => {
// Allowed ext
const filetypes = /jpeg|jpg|png|gif/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
const mimetype = filetypes.test(file.mimetype);
if(mimetype && extname){
return cb(null,true);
} else {
cb('Error: Images Only!');
}
}
const upload = multer({
storage: storage,
limits: { fileSize: 1000000 }, // 1MB limit
fileFilter: fileFilter
}).single('myImage'); // myImage = input field name for the file
app.post('/upload', (req, res) => {
upload(req, res, (err) => {
if (err) {
return res.status(400).json({ message: err });
}
if (!req.file) {
return res.status(400).json({ message: 'No file uploaded' });
}
res.json({ message: 'File uploaded successfully', filename: req.file.filename });
});
});
app.listen(3000, () => console.log('Server started'));
"""
### 2.8 Regular Security Audits
**Standard:** Regularly perform security audits and penetration testing to identify and address potential vulnerabilities proactively.
**Why:** Ongoing security assessments help uncover newly discovered vulnerabilities and ensure that security measures are effective.
**Do This:**
* Schedule periodic security code reviews.
* Engage external security experts to perform penetration testing.
* Maintain an incident response plan.
* Use automated security scanning tools.
**Don't Do This:**
* Neglect security audits and assume the application is secure.
* Wait until an incident occurs to address security concerns.
## 3. Authentication and Authorization
### 3.1 Secure Authentication
**Standard:** Implement secure authentication mechanisms to verify the identity of users.
**Why:** Strong authentication is crucial to prevent unauthorized access to sensitive resources.
**Do This:**
* Use a robust authentication library (e.g., Passport.js, Auth0)
* Use multi-factor authentication (MFA) whenever possible.
* Implement strong password policies (length, complexity, rotation).
* Hash passwords securely using bcrypt or Argon2.
* Protect against brute-force attacks using rate limiting and account lockout mechanisms.
* Use secure session management practices (e.g., using HttpOnly and Secure cookies).
**Don't Do This:**
* Store passwords in plain text.
* Use weak or default credentials.
* Reuse passwords across different applications.
* Disable account lockout policies.
**Code Example:** Password Hashing with bcrypt
"""javascript
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
return hashedPassword;
}
async function comparePassword(password, hashedPassword) {
const match = await bcrypt.compare(password, hashedPassword);
return match;
}
// Example Usage
async function registerUser(username, password) {
const hashedPassword = await hashPassword(password)
//store username and hashedPassword in database...
}
async function loginUser(username, password) {
//fetch user from database based on matching username;
const match = await comparePassword(password, user.hashedPassword)
return match;
}
"""
### 3.2 Robust Authorization
**Standard:** Implement granular authorization controls to define what authenticated users can access.
**Why:** Authorization ensures that users only have access to the resources they are permitted to use.
**Do This:**
* Apply the principle of least privilege (POLP): grant users only the minimum necessary permissions.
* Use role-based access control (RBAC) to manage permissions.
* Implement access control lists (ACLs) for fine-grained control.
* Validate user permissions on every request to protected resources.
**Don't Do This:**
* Grant excessive permissions to users.
* Rely solely on client-side authorization.
* Bypass authorization checks.
**Code Example:** Basic RBAC using Middleware (Node.js)
"""javascript
function authorize(role) {
return (req, res, next) => {
if (req.user && req.user.role === role) {
return next();
}
res.status(403).json({ message: 'Unauthorized' });
};
}
// Example Route
app.get('/admin', authenticate, authorize('admin'), (req, res) => {
res.json({ message: 'Admin Resource' });
});
"""
## 4. Logging and Monitoring
### 4.1 Comprehensive Logging
**Standard:** Implement detailed logging to track user activity, system events, and errors.
**Why:** Logging helps to identify security incidents, troubleshoot issues, and monitor application health.
**Do This:**
* Log all authentication attempts, authorization failures, and other security-related events.
* Include relevant context in logs.
* Rotate log files regularly.
* Secure log files to prevent unauthorized access or tampering.
* Use structured logging formats (e.g., JSON) for improved readability and analysis.
**Don't Do This:**
* Log sensitive information (e.g., passwords, API keys)
* Disable logging in production.
* Store log files in a publicly accessible location.
### 4.2 Real-Time Monitoring
**Standard:** Implement real-time monitoring and alerting to detect and respond to security incidents promptly.
**Why:** Monitoring provides visibility into application behavior and allows for rapid response to threats.
**Do This:**
* Monitor application logs for suspicious activity.
* Set up alerts for critical security events.
* Use intrusion detection systems (IDS) and intrusion prevention systems (IPS).
* Integrate monitoring tools with security information and event management (SIEM) systems.
**Don't Do This:**
* Ignore monitoring alerts.
* Lack visibility into the application's runtime behavior.
## 5. Secure Coding Patterns
### 5.1 Principle of Least Privilege
**Standard:** Apply the principle of least privilege (POLP) whenever possible.
**Why:** POLP minimizes the potential damage from security breaches by limiting the scope of access.
**Do This:**
* Grant users and applications only the minimum necessary permissions.
* Use secure coding practices to prevent privilege escalation.
* Isolate components and services to limit the impact of vulnerabilities.
### 5.2 Secure Random Number Generation
**Standard:** Use cryptographically secure random number generators for security-sensitive operations.
**Why:** Predictable random numbers can be exploited to bypass security measures.
**Do This:**
* Use "crypto.getRandomValues()" in browsers.
* Use a library like "crypto" in Node.js.
* Avoid using "Math.random()" for security-sensitive operations.
**Code Example** Javascript
"""javascript
function generateRandomToken(length) {
const array = new Uint32Array(length); //array of unsigned 32 bit integers
window.crypto.getRandomValues(array); //Populate with secure random integer
return Array.from(array, dec => dec.toString(16).padStart(2, '0')).join(''); //Convert Integer Array to Hex String
}
const token = generateRandomToken(32); // Generates a random hex string of length 64
console.log(token);
"""
### 5.3 Preventing Clickjacking
**Standard:** Protect against clickjacking attacks by using the "X-Frame-Options" header.
**Why:** Clickjacking allows an attacker to trick users into performing unintended actions.
**Do This:**
* Set the "X-Frame-Options" header to "DENY" or "SAMEORIGIN".
**Code Example:** Server Configuration (Nginx)
"""nginx
add_header X-Frame-Options "SAMEORIGIN";
"""
## 6. Vite Plugin Security
### 6.1 Plugin Vetting
**Standard:** Carefully vet any Vite plugins you add to your project.
**Why:** Vite plugins, like any third-party dependency, can introduce security vulnerabilities.
**Do This:**
* Check the plugin's author reputation, update frequency, and community feedback.
* Scan the source code for any suspicious or malicious code.
* Use plugins from trusted sources with a strong track record.
**Don't Do This:**
* Install plugins blindly without any due diligence.
* Use plugins that are no longer maintained or have known security issues.
### 6.2 Plugin Permissions
**Standard** When developing your own Vite plugins: Request only the necessary permissions.
**Why**: Malicious plugins could exploit permissions they don't need.
**Do This**:
* Follow the principle of least privilege when requesting access to file system, network or any other resource.
* Document clearly why each permission is needed.
This concludes the security best practices standards document for Vite. By following these guidelines, developers can create safer, more robust applications that protect against common security threats. Always stay updated with the latest security advisories and best practices to maintain a secure development environment.
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.