# 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.
# Tooling and Ecosystem Standards for Vite This document outlines coding standards and best practices for the Vite ecosystem, focusing on tooling and related libraries/extensions. These standards promote maintainability, performance, security, and a consistent developer experience. ## 1. Recommended IDEs and Extensions ### 1.1. Standard * **Do This:** Use VS Code as the primary IDE due to its rich ecosystem of extensions and excellent support for JavaScript, TypeScript, and front-end development. * **Don't Do This:** Rely solely on basic text editors without IDE-level features. ### 1.2. Essential VS Code Extensions * **Do This:** Install the following extensions to enhance your Vite development workflow: * **ESLint:** For consistent code style and early error detection. * **Prettier:** For automatic code formatting. * **Volar:** (Recommended for Vue 3 projects) or **Vue Language Features (VLS)** (for older Vue versions). Provides enhanced syntax highlighting, auto-completion, and type checking specifically tailored for Vue.js components within Vite. * **TypeScript Vue Plugin (Volar)**: Improves TypeScript support within Vue SFCs (Single-File Components). * **EditorConfig for VS Code:** Ensures consistent coding styles across different IDEs and team members by reading ".editorconfig" files. * **npm Intellisense:** Autocompletes npm modules in "import" statements. * **Path Intellisense:** Autocompletes file paths. * **JavaScript (ES6) code snippets:** Provides helpful code snippets for common JavaScript/ES6 patterns. * **Vite Plugin for VS Code:** (if available and actively maintained) Directly integrates Vite commands and features within VS Code, simplifying development tasks. * **Why:** These extensions provide real-time linting, formatting, and code completion, leading to fewer errors, increased productivity, and a consistent codebase. * **Anti-Pattern:** Neglecting linting or formatting tools leads to inconsistent styling, making the code harder to read and maintain. ### 1.3 Configuration * **Do This:** Configure ESLint, Prettier, and EditorConfig at the project level to enforce consistent coding styles across the team. Use ".eslintrc.js", ".prettierrc.js", and ".editorconfig" files at the project root. * **Example ".eslintrc.js":** """javascript module.exports = { root: true, env: { node: true, browser: true, es2021: true, }, extends: [ 'eslint:recommended', 'plugin:vue/vue3-essential', '@vue/eslint-config-typescript/recommended', 'prettier', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the LAST extension in the array. ], parserOptions: { ecmaVersion: 2020, }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'vue/multi-word-component-names': 'off', // Often not practical for smaller components. Adjust as needed. }, overrides: [ { files: ['*.vue'], parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', sourceType: 'module', ecmaFeatures: { jsx: true, }, }, }, ], }; """ * **Example ".prettierrc.js":** """javascript module.exports = { semi: false, singleQuote: true, trailingComma: 'es5', printWidth: 100, tabWidth: 2, }; """ * **Example ".editorconfig":** """ini root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false """ * **Why:** Project-level configuration ensures that all team members adhere to the same coding standards. This simplifies code reviews and reduces merge conflicts caused by style differences. Using "@vue/eslint-config-typescript/recommended" provides sensible defaults for Typscript-based Vue projects, integrating well with Vite projects using Vue and Typescript. ## 2. Dependency Management ### 2.1. Using "npm" or "yarn" or "pnpm" * **Do This:** Use "npm","yarn", or "pnpm" as your package manager. "pnpm" is often preferred for its efficient disk space usage and speed advantages due to its use of symlinked "node_modules". * **Don't Do This:** Manually download and manage dependencies or rely on outdated package managers. ### 2.2. Version Control * **Do This:** Use "npm ci", "yarn install --frozen-lockfile", or "pnpm install --frozen-lockfile" in CI/CD environments to ensure deterministic builds based on the "package-lock.json", "yarn.lock", or "pnpm-lock.yaml" file. * **Why:** "npm ci", "yarn install --frozen-lockfile", and "pnpm install --frozen-lockfile" prevent unexpected dependency updates from breaking builds. ### 2.3. Dependency Updates * **Do This:** Regularly update dependencies to patch security vulnerabilities and access new features. Use tools like Dependabot to automate dependency updates and receive security alerts. * **Don't Do This:** Ignore dependency updates for extended periods. ### 2.4. Peer Dependencies * **Do This:** Carefully manage peer dependencies to avoid conflicts and ensure compatibility between your packages and their dependencies. Pay close attention to warnings about unmet peer dependencies during installation. * **Why:** Mismatched peer dependencies can lead to runtime errors and unexpected behavior. ### 2.5. Dev Dependencies * **Do This:** Clearly distinguish between production dependencies (listed under "dependencies") and development dependencies (listed under "devDependencies"). Only include packages needed at runtime in the "dependencies" section. * **Why:** Including unnecessary development dependencies in production bundles increases bundle size. ## 3. Vite Plugins ### 3.1. Standard * **Do This:** Leverage Vite plugins to extend Vite's functionality and integrate with other tools. * **Don't Do This:** Reinvent the wheel by implementing functionality that is already available in existing plugins. ### 3.2. Key Plugins * **Do This:** Consider using the following plugins for common tasks: * "@vitejs/plugin-vue": Official Vue 3 support. Crucial for Vue-based projects. * "@vitejs/plugin-vue-jsx": Official Vue JSX support. * "vite-plugin-html": For transforming "index.html" during build (e.g., injecting environment variables). * "vite-plugin-compression": Compresses assets (e.g., gzip, brotli) for improved performance. * "vite-plugin-imagemin": Optimizes images during the build process. * "vite-plugin-pwa": Generates a Progressive Web App manifest. * "vite-tsconfig-paths": Allows importing modules using paths defined in "tsconfig.json". Essential for avoiding relative path hell. * "rollup-plugin-visualizer": Visualizes the size and composition of your bundles for optimization purposes. * "unplugin-auto-import": Automatically imports APIs from Vue, React, or other libraries. Reduces boilerplate. * "unplugin-vue-components": Auto-imports Vue components. Reduces boilerplate. * "vite-plugin-inspect ": Inspects the intermediate state of Vite's transforms. Helpful for debugging plugin issues. * **Why:** Plugins enhance Vite's capabilities and streamline common development tasks. ### 3.3. Plugin Configuration * **Do This:** Configure plugins correctly in "vite.config.js" or "vite.config.ts". Provide necessary options and ensure plugins are enabled in the appropriate environments (development vs. production). * **Example:** """typescript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import viteCompression from 'vite-plugin-compression' import { resolve } from 'path' export default defineConfig({ plugins: [ vue(), viteCompression({ algorithm: 'gzip', ext: '.gz', deleteOriginFile: false, // Keep the original files }) ], resolve: { alias: { '@': resolve(__dirname, 'src'), // For absolute imports }, }, build: { rollupOptions: { output: { manualChunks(id) { // Code splitting if (id.includes('node_modules')) { return id.toString().split('node_modules/')[1].split('/')[0].toString(); } } } } } }) """ * **Why:** Incorrect plugin configuration can lead to unexpected behavior or build errors. The "resolve.alias" configuration significantly improves code readability by enabling absolute imports using the "@" symbol as an alias for the "src" directory. This avoids deeply nested relative paths ("../../../components/MyComponent.vue") and makes refactoring easier. Implementing "rollupOptions.output.manualChunks" enables code splitting for improved performance. ### 3.4. Custom Plugins * **Do This:** Create custom plugins when you need to implement functionality that is not available in existing plugins. Follow the Vite Plugin API documentation. * **Don't Do This:** Eject from Vite's build process and reinvent the entire build pipeline. * **Example:** """typescript // vite-plugin-example.ts import { Plugin } from 'vite'; export function vitePluginExample(): Plugin { return { name: 'vite-plugin-example', transform(code, id) { if (id.endsWith('.vue')) { // Modify Vue component code return code.replace('This is a placeholder', 'This is a Vite plugin!'); } return code; }, }; } """ """typescript // vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { vitePluginExample } from './vite-plugin-example'; export default defineConfig({ plugins: [vue(), vitePluginExample()], }); """ * **Why:** Custom plugins provide a flexible way to extend Vite's functionality and adapt it to your specific needs. Using the "transform" hook illustrated above enables modification of the code during the build process, in this case modifying Vue component content. ## 4. Debugging and Profiling ### 4.1. Standard * **Do This:** Utilize browser developer tools and Vite's built-in features for debugging and profiling your application. * **Don't Do This:** Resort to "console.log" statements as the primary debugging method for complex issues. ### 4.2. Debugging Techniques * **Do This:** Use the "debugger" statement to set breakpoints in your code. Inspect variables and step through the execution flow using the browser's debugger. * **Do This:** Use source maps to debug your code in its original form, even after it has been transformed and bundled. * **Use editor integration:** VS Code's debugger is now capable of debugging Vite applications directly. Configure launch configurations in ".vscode/launch.json" for seamless debugging. * **Example ".vscode/launch.json":** """json { "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:5173", // Or whatever port Vite is using "webRoot": "${workspaceFolder}/src" } ] } """ ### 4.3. Profiling Techniques * **Do This:** Use the browser's performance profiling tools to identify performance bottlenecks in your application. Analyze CPU usage, memory allocation, and rendering performance. * **Do This:** Use the "rollup-plugin-visualizer" to analyze bundle sizes and identify large dependencies. Implement code splitting to reduce initial load times. * **Do This:** Analyze the network tab to identify slow-loading resources. Optimize images and other assets to reduce file sizes. * **Why:** Profiling helps identify and address performance bottlenecks, improving the overall user experience. ### 4.4. Vite Devtools * **Do This:** Investigate and utilize any available Vite Devtools extensions for your browser. These tools can provide insights into Vite's internal state and simplify debugging. ## 5. Continuous Integration and Deployment (CI/CD) ### 5.1. Standard * **Do This:** Automate the build, test, and deployment process using a CI/CD pipeline. * **Don't Do This:** Manually build and deploy your application. ### 5.2. Popular CI/CD Services * **Do This:** Consider using the following CI/CD services: * GitHub Actions * GitLab CI * CircleCI * Travis CI ### 5.3. CI/CD Configuration * **Do This:** Configure your CI/CD pipeline to perform the following steps: 1. **Checkout code:** Clone the repository. 2. **Install dependencies:** Use "npm ci", "yarn install --frozen-lockfile", or "pnpm install --frozen-lockfile" to install dependencies from the lockfile. 3. **Run tests:** Execute unit tests, integration tests, and end-to-end tests. 4. **Build application:** Run "vite build" to build the application. 5. **Deploy application:** Deploy the built assets to your hosting provider (e.g., Netlify, Vercel, AWS S3). * **Example GitHub Actions Workflow (".github/workflows/deploy.yml"):** """yaml name: Deploy to Netlify on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 # Or your preferred Node version - name: Install dependencies run: npm ci # or yarn install --frozen-lockfile or pnpm install --frozen-lockfile - name: Run tests run: npm test # Or your test command - name: Build run: npm run build # Or yarn build or pnpm build - name: Deploy to Netlify uses: netlify/actions/deploy@v1 with: publishDir: './dist' # Your build output directory netlify_auth_token: ${{ secrets.NETLIFY_AUTH_TOKEN }} netlify_site_id: ${{ secrets.NETLIFY_SITE_ID }} """ * **Why:** CI/CD pipelines automate the build, test, and deployment process, reducing the risk of human error and ensuring consistent deployments. ### 5.4. Environment Variables * **Do This:** Use environment variables to configure your application in different environments (development, staging, production). Access environment variables using "import.meta.env" in Vite. * **Don't Do This:** Hardcode sensitive information (e.g., API keys, database passwords) in your code. * **Example:** """typescript // vite.config.ts import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') return { plugins: [vue()], define: { __APP_ENV__: JSON.stringify(env.APP_ENV), }, } }) """ """vue <template> <div> {{ appEnv }} </div> </template> <script setup> import { ref } from 'vue'; const appEnv = ref(import.meta.env.VITE_APP_ENV); </script> """ ### 5.5. Staging Environments * **Do This:** Implement a staging environment that mirrors your production environment as closely as possible. Deploy code to the staging environment before deploying to production. * **Why:** Staging environments allow you to test changes in a realistic environment and catch potential issues before they affect your users. ## 6. Collaboration and Communication ### 6.1. Standard * **Do This:** Use collaborative tools and effective communication strategies to ensure that team members are aligned and informed about project progress and decisions. * **Don't Do This:** Work in isolation without communicating with your team. ### 6.2. Communication Tools * **Do This:** Use tools like Slack, Microsoft Teams, or Discord for real-time communication. Use project management tools like Jira, Trello, or Asana to track tasks and manage workflows. * **Why:** Effective communication tools facilitate collaboration and ensure that everyone is on the same page. ### 6.3. Code Reviews * **Do This:** Conduct thorough code reviews to identify potential issues and ensure code quality. Use pull requests to manage code changes and facilitate code reviews. * **Why:** Code reviews help catch errors, improve code quality, and promote knowledge sharing. ### 6.4. Documentation * **Do This:** Document your code and project architecture. Use tools like Storybook or Styleguidist to document UI components. * **Why:** Documentation makes it easier for team members to understand and maintain the codebase. By adhering to these coding standards, your team can build maintainable, performant, and secure Vite applications efficiently. These standards will help ensure consistent code quality across the entire project.
# 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.
# API Integration Standards for Vite This document outlines the coding standards and best practices for integrating APIs into Vite applications. These standards are designed to promote maintainability, performance, security, and a consistent development experience. ## 1. Architectural Considerations ### 1.1. Separation of Concerns **Standard:** Separate API interaction logic from component logic. **Do This:** Create dedicated modules or services for handling API requests. **Don't Do This:** Directly embed API calls within Vue components or other UI components. **Why:** Separation of concerns improves code reusability, testability, and maintainability. Makes UI components cleaner and simpler. **Example:** """typescript // src/services/api.ts import axios from 'axios'; const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, // Use Vite's environment variables headers: { 'Content-Type': 'application/json', }, }); export const getPosts = async () => { try { const response = await apiClient.get('/posts'); return response.data; } catch (error) { console.error("Failed to fetch posts:", error); throw error; // Re-throw to allow component-level error handling } }; export const createPost = async (postData: any) => { try { const response = await apiClient.post('/posts', postData); return response.data; } catch (error) { console.error("Failed to create post:", error); throw error; } }; """ """vue <!-- src/components/PostList.vue --> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import { getPosts } from '@/services/api'; // Import API service const posts = ref([]); const loading = ref(true); const error = ref(null); onMounted(async () => { try { posts.value = await getPosts(); } catch (err: any) { error.value = err.message || 'Failed to load posts.'; } finally { loading.value = false; } }); </script> <template> <div v-if="loading">Loading posts...</div> <div v-if="error">Error: {{ error }}</div> <ul v-else> <li v-for="post in posts" :key="post.id">{{ post.title }}</li> </ul> </template> """ ### 1.2. Environment Variables **Standard:** Utilize Vite's environment variable system for API base URLs and sensitive configuration settings. **Do This:** Access API base URLs through "import.meta.env.VITE_API_BASE_URL". **Don't Do This:** Hardcode URLs or sensitive API keys directly in the source code. **Why:** Improves security by preventing accidental exposure of secrets and allows easy configuration for different environments (development, staging, production). Vite's environment variable system is designed for frontend applications. **Example:** ".env": """ VITE_API_BASE_URL=https://api.example.com VITE_API_KEY=supersecretapikey """ "src/services/api.ts": """typescript import axios from 'axios'; const apiKey = import.meta.env.VITE_API_KEY; //Access Key const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey }, }); export default apiClient; """ ### 1.3. Data Transformation **Standard:** Transform API responses into a format suitable for the application's needs within the API service. **Do This:** Map, filter, or reshape data within "api.ts" before it reaches components. **Don't Do This:** Perform complex data transformations directly inside Vue components. **Why:** Keeps components focused on presentation and simplifies data handling. **Example:** """typescript // src/services/api.ts import apiClient from './apiClient'; interface RawPost { id: string; title: string; content: string; authorId: string; publishedAt: string; } interface TransformedPost { id: string; title: string; content: string; author: string; publishedDate: Date; } export const getPosts = async (): Promise<TransformedPost[]> => { try { const response = await apiClient.get<RawPost[]>('/posts'); //transformation logic. return response.data.map(post => ({ id: post.id, title: post.title, content: post.content, author: getAuthorName(post.authorId), // Assuming a function to get author's name publishedDate: new Date(post.publishedAt) })); } catch (error) { console.error("Failed to fetch posts:", error); throw error; } }; """ ## 2. Implementation Details ### 2.1. HTTP Client Libraries **Standard:** Use a modern HTTP client library like "axios" or "ky". **Do This:** Install "axios" or "ky" via "npm install axios" or "npm install ky". Configure with a base URL. **Don't Do This:** Use "fetch" directly without error handling, or older libraries less actively maintained. **Why:** Modern libraries provide features such as automatic JSON parsing, interceptors, request cancellation, and better error handling. **Example (axios):** """typescript // src/services/apiClient.ts import axios from 'axios'; const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000, // Timeout after 10 seconds headers: { 'Content-Type': 'application/json', }, }); // Request interceptor (optional) apiClient.interceptors.request.use( (config) => { // Add authentication token here if needed const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = "Bearer ${token}"; } return config; }, (error) => { console.error('Request error:', error); return Promise.reject(error); } ); // Response interceptor (optional) apiClient.interceptors.response.use( (response) => { return response; }, (error) => { console.error('Response error:', error); return Promise.reject(error); } ); export default apiClient; """ ### 2.2. Error Handling **Standard:** Implement comprehensive error handling at both the service and component levels. **Do This:** Use "try...catch" blocks in API service functions. Display user-friendly error messages in components and consider central error logging. **Don't Do This:** Ignore errors or display generic error messages without context. **Why:** Proper error handling improves user experience, helps debug issues, and prevents application crashes. **Example (service level):** """typescript // src/services/api.ts import apiClient from './apiClient'; export const fetchData = async () => { try { const response = await apiClient.get('/data'); return response.data; } catch (error: any) { console.error("API Error:", error.message); // Optionally, log the error to a central logging service. throw new Error('Failed to fetch data from the API.'); // Custom error message } }; """ **Example (component level):** """vue <!-- src/components/DataDisplay.vue --> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import { fetchData } from '@/services/api'; const data = ref(null); const error = ref(''); const loading = ref(true); onMounted(async () => { try { data.value = await fetchData(); } catch (err: any) { error.value = err.message || 'An unexpected error occurred.'; } finally { loading.value = false; } }); </script> <template> <div v-if="loading">Loading...</div> <div v-else-if="error">{{ error }}</div> <div v-else> <!-- Display your data here --> {{ data }} </div> </template> """ ### 2.3. Data Validation **Standard:** Validate both request and response data to ensure data integrity. **Do This:** Use libraries like "zod" or "yup" to define schemas and validate data before sending requests and after receiving responses. **Don't Do This:** Assume data from the API is always in the correct format. **Why:** Prevents unexpected errors and ensures data consistency throughout the application. **Example (using "zod"):** Install "zod": "npm install zod" """typescript // src/services/api.ts import apiClient from './apiClient'; import { z } from 'zod'; const postSchema = z.object({ id: z.string().uuid(), title: z.string().min(5).max(100), content: z.string(), createdAt: z.string().datetime(), }); type Post = z.infer<typeof postSchema>; export const getPost = async (id: string): Promise<Post> => { try { const response = await apiClient.get("/posts/${id}"); const parsedData = postSchema.parse(response.data); // Validate response return parsedData; } catch (error: any) { console.error("Failed to fetch post:", error); if (error instanceof z.ZodError) { console.error("Validation Error: ", error.errors); throw new Error("Invalid post data received from the API."); } throw error; } }; """ ### 2.4. Caching **Standard:** implement client-side caching for frequently accessed API data to improve performance. **Do This:** Utilize techniques like "localStorage", "sessionStorage", or dedicated caching libraries like "vue-query" (Tanstack Query) or "swr" (Stale-While-Revalidate). Consider using "Cache-Control" headers on the server side for HTTP caching. **Don't Do This:** Fetch the same data repeatedly without caching. **Why:** Reduces network requests, improves load times, offers a better user experience, and reduces load on the backend server. **Example (using "sessionStorage")** """typescript // src/services/api.ts import apiClient from './apiClient'; export const getCachedData = async () => { const cachedData = sessionStorage.getItem('apiData'); if (cachedData) { return JSON.parse(cachedData); } try { const response = await apiClient.get('/data'); sessionStorage.setItem('apiData', JSON.stringify(response.data)); return response.data; } catch (error) { console.error("Failed to fetch data:", error); throw error; } }; """ **Example ("vue-query")**: Install Dependencies: "npm install @tanstack/vue-query" """vue // src/components/UserList.vue <script setup lang="ts"> import { useQuery } from '@tanstack/vue-query'; import { getUsers } from '@/services/api'; const { isLoading, isError, data, error } = useQuery({ queryKey: ['users'], queryFn: getUsers, }); </script> <template> <div v-if="isLoading">Loading...</div> <div v-else-if="isError">Error: {{ error.message }}</div> <ul v-else> <li v-for="user in data" :key="user.id">{{ user.name }}</li> </ul> </template> """ ### 2.5. Authentication and Authorization **Standard:** Implement secure authentication and authorization mechanisms to protect API endpoints. **Do This:** Use industry-standard protocols like OAuth 2.0 or JWT for authentication. Store tokens securely (e.g., using "httpOnly" cookies or the browser's "Credential Management API"). Always validate user roles and permissions on the server-side. **Don't Do This:** Store sensitive credentials in plain text in local storage or cookies without proper protection. Implement authorization solely on the client-side. **Why:** Protects sensitive data and prevents unauthorized access to API resources. **Example (using JWT and a secure cookie - Backend responsibility, but influences frontend)** *Backend (Node.js with Express, simplified)* """javascript // Example. In reality, you'd use a full-fledged auth library like Passport app.post('/login', async (req, res) => { // ... Authentication logic ... const user = { id: 123, username: 'testuser' }; const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: '1h' }); res.cookie('authToken', token, { httpOnly: true, // Prevents client-side JavaScript access secure: process.env.NODE_ENV === 'production', // Only send over HTTPS in production sameSite: 'strict', // Protect against CSRF attacks }); res.json({ message: 'Login successful' }); }); app.post('/logout', (req, res) => { res.clearCookie('authToken'); res.json({ message: 'Logged out' }); }); // Example middleware function authenticateToken(req, res, next) { const token = req.cookies.authToken; if (!token) return res.sendStatus(401); // Unauthorized jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) return res.sendStatus(403); // Forbidden req.user = user; next(); }); } app.get('/protected', authenticateToken, (req, res) => { res.json({ message: 'Protected route accessed by user ' + req.user.username}); }); """ *Frontend (Vite app)* """typescript // src/services/api.ts import apiClient from './apiClient'; import { ref } from 'vue'; const isAuthenticated = ref(false); // Check if the user is initially authenticated, perhaps on app load const checkAuthStatus = async () => { try { // A request to a protected endpoint to check authentication await apiClient.get('/protected'); isAuthenticated.value = true; } catch (error: any) { isAuthenticated.value = false; console.error("Not Authenticated"); } }; checkAuthStatus(); export { isAuthenticated }; """ **Important Considerations:** * **Refresh Tokens:** Implement refresh tokens for long-lived sessions. * **Token Revocation:** Allow users to revoke tokens (e.g., on logout). * **CORS:** Configure CORS (Cross-Origin Resource Sharing) to restrict which origins can access the API. * **Rate Limiting:** Implement rate limiting to prevent abuse of the API. ### 2.6. Optimistic Updates **Standard:** Implement optimistic updates where appropriate for a better user experience. **Do This:** Update the UI immediately as if the API call was successful, and revert the update if the call fails. This makes the application feel more responsive. **Don't Do This:** Wait for the API call to complete before updating the UI, which can lead to a slower and less responsive user experience. **Why:** It provides a smoother and faster user experience, by making the UI feel more responsive. **Example (Optimistic Update)** """vue // src/components/TaskList.vue <script setup lang="ts"> import { ref } from 'vue'; import { updateTask } from '@/services/api'; const props = defineProps({ task: { type: Object, required: true } }) const completing = ref(false) const completeTask = async () => { const originalCompletionStatus = props.task.completed; props.task.completed = !originalCompletionStatus // Optimistically update the UI completing.value = true; try { await updateTask(props.task.id, { completed: props.task.completed }); } catch (error) { console.error("Failed to update task:", error); props.task.completed = originalCompletionStatus; // Revert on error } finally { completing.value = false; } }; </script> <template> <li> <input type="checkbox" :checked="task.completed" @change="completeTask" :disabled="completing" /> {{ task.name }} </li> </template> """ ## 3. Vite Specific Considerations ### 3.1 "import.meta.env" Usage **Standard:** Leverage "import.meta.env" for accessing environment variables in Vite. **Do This:** Ensure variables are prefixed with VITE_ to be exposed to the client-side code in Vite. **Don't Do This:** Directly use "process.env" variables without proper Vite configuration, since they might not be available in the browser. **Why:** Vite automatically handles replacement during the build process, making it easy to manage different configurations for various deployment environments. **Example:** In ".env" file: """ VITE_API_BASE_URL=https://api.example.com """ In "src/services/api.ts": """typescript const baseURL = import.meta.env.VITE_API_BASE_URL; """ ### 3.2 Proxy Configuration **Standard:** Use Vite's "server.proxy" option for proxying API requests during development. **Why:** This avoids CORS issues when the frontend and backend are running on different ports during development. Vite handles proxying efficiently. **Example:** In "vite.config.ts" or "vite.config.js": """typescript import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:3000', // Backend server address changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, }, }, }); """ This configuration proxies all requests to "/api/*" to "http://localhost:3000/*". ### 3.3. Build Optimization **Standard:** Ensure API interaction code is optimized for production builds by using code splitting and tree shaking. **Do This:** Keep API services modular and import only necessary functions in components. **Don't Do This:** Import entire API service modules when only specific functions are needed. **Why:** Improves initial load time and reduces the overall bundle size of the application. Vite's module system enables effective tree shaking. **Example:** Instead of: """typescript // src/components/MyComponent.vue import * as api from '@/services/api'; // Avoid this api.getData(); """ Do this: """typescript // src/components/MyComponent.vue import { getData } from '@/services/api'; // Prefer this getData(); """ ## 4. Common Anti-Patterns * **Global State Mismanagement:** Avoid using global state (e.g., Vuex) excessively for API data caching. Opt for component-level state management, or dedicated caching solutions like "vue-query". * **Tight Coupling:** Prevent tight coupling between UI components and API endpoint details, by abstracting API calls to services. * **Ignoring Cancellation:** In scenarios that need it (e.g. search bars), don't forget to handle cancellation of ongoing API requests when the user makes changes, to avoid race conditions. ## 5. Security Best Practices * **Input Sanitization:** Sanitize user inputs before sending them to the API to prevent injection attacks. * **Output Encoding:** Encode data received from the API before rendering it in the UI to prevent cross-site scripting (XSS) attacks. Libraries like DOMPurify can help. * **HTTPS:** Always use HTTPS to encrypt communication between the client and the API server. * **Regular Updates:** Keep all dependencies, including the HTTP client library and Vite itself, up to date to patch any security vulnerabilities. By adhering to these standards, you can ensure that your Vite applications have robust, maintainable, and secure API integrations. Remember to continuously review and update these standards as the Vite ecosystem evolves.