# 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 (
{children}
);
};
export const useTheme = () => useContext(ThemeContext);
// Component.jsx
import React from 'react';
import { useTheme } from './Context';
const MyComponent = () => {
const { theme, toggleTheme } = useTheme();
return (
Current Theme: {theme}
Toggle Theme
);
};
export default MyComponent;
"""
**Example (Vue 3 with Provide/Inject):**
"""vue
// App.vue - Providing the state
// MyComponent.vue - Injecting the state
"""
### 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 {bears} around here ...
}
function BearIncreaseButton() {
const increasePopulation = useStore((state) => state.increasePopulation)
return one up
}
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 => {
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
"""
## 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
"""
### 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.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Core Architecture Standards for Vite This document outlines the core architectural standards and best practices for developing with Vite. It serves as a guide for developers and provides context for AI coding assistants to generate consistent, maintainable, performant, and secure Vite code. ## 1. Fundamental Architecture Patterns Vite's architecture leverages modern JavaScript and web development best practices, emphasizing speed, simplicity, and developer experience. ### 1.1. Module Federation Vite has first class support for Module Federation. It's critical to understand the implications of exposing or consuming modules. **Do This:** * Use Module Federation to build composable applications where independent teams own different parts of the UI. * Version your exposed modules so breaking changes do not impact consuming applications. * Implement proper error handling mechanisms when dealing with remotely loaded modules. **Don't Do This:** * Don't overuse Module Federation. Weigh the benefits against the added complexity. * Don't expose internal implementation details as modules. * Avoid circular dependencies between federated modules. **Why:** Module Federation facilitates code reuse and independent deployments, promoting scalable and maintainable applications. """javascript // vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import federation from "@originjs/vite-plugin-federation"; //expose component export default defineConfig({ plugins: [ vue(), federation({ name: 'remote-app', filename: 'remoteEntry.js', exposes: { './RemoteButton': './src/components/RemoteButton.vue', }, shared: ['vue'] }) ], build: { target: 'esnext' } }) """ """javascript // consuming application vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import federation from "@originjs/vite-plugin-federation"; export default defineConfig({ plugins: [ vue(), federation({ name: 'host-app', remotes: { remote_app: "http://localhost:4173/assets/remoteEntry.js", // adjust path if needed }, shared: ['vue'] }) ], build: { target: 'esnext' } }) """ ### 1.2. Component-Based Architecture Vite projects often utilize component-based architectures, particularly when using frameworks like Vue or React. **Do This:** * Break down UIs into reusable, independent components. * Follow the Single Responsibility Principle (SRP) for each component. * Use props to pass data and events to communicate between components. **Don't Do This:** * Avoid creating monolithic components with too much logic. * Don't tightly couple components to specific data sources. * Don't mutate props directly within a component. Use "emit" in Vue, or state management solutions. **Why:** Component-based architecture improves code organization, reusability, and testability. """vue // MyComponent.vue <template> <div> <h1>{{ title }}</h1> <p>{{ description }}</p> <button @click="handleClick">Click me</button> </div> </template> <script setup> import { defineProps, defineEmits } from 'vue'; const props = defineProps({ title: { type: String, required: true }, description: { type: String, default: '' } }); const emit = defineEmits(['update']); const handleClick = () => { emit('update', 'New value'); }; </script> """ ### 1.3. State Management Choose a preferred state management solution and apply it consistently across your project. **Do This:** * Select state management based on the complexity of your application. Pinia and Vuex are common choices with Vue. Redux or Zustand integrate well with React. * Centralize application state. * Use mutations or actions to update state predictably. **Don't Do This:** * Overuse global state management for simple component communication. Props and events often suffice. * Directly modify state without using defined actions/mutations. **Why:** Consistent state management makes applications predictable and reduces debugging time. """javascript // Pinia store example (store.js) import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), actions: { increment() { this.count++; }, decrement() { this.count--; } }, getters: { doubleCount: (state) => state.count * 2 } }); """ ### 1.4 API Layer Isolate API interactions. This promotes loose coupling and enables easier testing and maintenance. **Do This:** * Create dedicated API service modules. * Handle all API calls, data transformation, and error handling within this layer. * Abstract away specific API implementations. **Don't Do This:** * Directly making API calls within components * Mixing API logic with unrelated code * Exposing API keys or secrets directly in the client-side code. Use environment variables. **Why:** An API layer makes API calls easier to manage, and easier to swap out. """javascript // apiService.js import axios from 'axios'; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api'; export const getPosts = async () => { try { const response = await axios.get("${API_BASE_URL}/posts"); return response.data; } catch (error) { console.error('Error fetching posts:', error); throw error; } }; export const createPost = async (postData) => { try { const response = await axios.post("${API_BASE_URL}/posts", postData); return response.data; } catch (error) { console.error('Error creating post:', error); throw error; } }; """ ## 2. Project Structure and Organization Principles A well-defined project structure is crucial for maintainability, scalability, and team collaboration. ### 2.1. Standard Directory Structure Follow a consistent directory structure across all Vite projects. A recommended structure is: """ vite-project/ ├── public/ # Static assets ├── src/ # Source code │ ├── assets/ # Images, fonts, etc. │ ├── components/ # Reusable UI components │ ├── composables/ # Vue composables │ ├── layouts/ # Application layouts │ ├── pages/ # Page components (if using a router) │ ├── styles/ # Global styles and themes │ ├── utils/ # Utility functions │ ├── App.vue # Root component │ ├── main.js # Entry point │ └── router.js # Router configuration ├── .env # Environment variables ├── vite.config.js # Vite configuration ├── package.json # Project dependencies └── README.md # Project documentation """ **Do This:** * Adhere to the proposed structure or establish a project-specific standard. * Keep components self-contained within the "components" directory. * Group related files within meaningful subdirectories. **Don't Do This:** * Store files in arbitrary locations without a clear organizational principle. * Mix different types of files within the same directory. * Create overly deep or complex directory structures. **Why:** A consistent structure simplifies navigation, enhances discoverability, and promotes code reuse, leading to better collaboration and maintainability. ### 2.2. Naming Conventions Establish clear naming conventions for files, directories, components, and variables. **Do This:** * Use descriptive and meaningful names. * Follow consistent naming patterns (e.g., PascalCase for components, camelCase for variables). * Use a consistent suffix for component files (e.g., ".vue" for Vue components, ".jsx" for React components). * Use kebab-case for directories and file names, especially when components are accessed through HTML. **Don't Do This:** * Use ambiguous or cryptic names. * Mix different naming conventions within the same project. * Use reserved keywords as names. **Why:** Consistent naming improves code readability, reduces confusion, and makes it easier to understand the purpose of each file and variable. **Example:** """ components/ ├── MyButton.vue // PascalCase for component filename ├── user-profile/ // kebab-case for directories │ └── UserProfile.vue // PascalCase for component filename """ """javascript // MyButton.vue <script setup> const buttonText = 'Click me'; // camelCase for variables const handleClick = () => { // camelCase for function names console.log('Button clicked'); }; </script> """ ### 2.3. Modular Design Break down code into small, independent, and reusable modules. **Do This:** * Create dedicated modules for specific functionalities. * Export only necessary functions and variables from each module. * Use ES module syntax ("import" and "export") to manage dependencies. **Don't Do This:** * Create large, monolithic modules with multiple responsibilities. * Export unnecessary variables or functions. * Rely on global variables for inter-module communication. **Why:** Modular design promotes code reuse, simplifies maintenance, and makes it easier to test and debug individual components. """javascript // utils/formatDate.js export function formatDate(date) { const options = { year: 'numeric', month: 'long', day: 'numeric' }; return new Date(date).toLocaleDateString(undefined, options); } // components/MyComponent.vue import { formatDate } from '../utils/formatDate.js'; <template> <p>Published: {{ formattedDate }}</p> </template> <script setup> import { ref, onMounted } from 'vue'; import { getPosts } from '../apiService.js'; const formattedDate = ref(''); onMounted(async () => { const posts = await getPosts(); formattedDate.value = formatDate(posts[0].createdAt); }); </script> """ ### 2.4. Environment Variables Use environment variables to manage configuration settings that vary between environments (development, staging, production). Vite handles environment variables differently than other bundlers. **Do This:** * Store sensitive settings in environment variables instead of hardcoding them. * Use the ".env" file for development environment variables. * Prefix environment variables with "VITE_" to expose them to client-side code. * Use "import.meta.env" to access environment variables in your code. **Don't Do This:** * Commit ".env" files to version control. Add them to ".gitignore". Use ".env.example" to track the *names* of the environment variables. * Store sensitive information directly in your code. * Expose sensitive environment variables to the client-side code. **Why:** Environment variables improve security, simplify configuration management, and allow you to deploy the same code to different environments without modification. """ // .env VITE_API_BASE_URL=https://api.example.com VITE_APP_TITLE=My Awesome App """ """javascript // vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], define: { 'process.env': {} //required if you're migrating a web app to vite } }) """ """javascript // components/MyComponent.vue <template> <h1>{{ appTitle }}</h1> </template> <script setup> const appTitle = import.meta.env.VITE_APP_TITLE; </script> """ ## 3. Vite Ecosystem & Plugins Leverage the Vite ecosystem of plugins and integrations to extend its functionality. ### 3.1. Plugin Usage Use plugins to extend Vite's capabilities, such as adding support for different file formats, optimizing assets, or integrating with other tools. **Do This:** * Use official or well-maintained community plugins. * Configure plugins properly according to their documentation. * Understand the specific tasks performed by each plugin. **Don't Do This:** * Add unnecessary plugins that duplicate functionality. * Use outdated or abandoned plugins. * Ignore plugin configuration options. **Why:** Plugins provide a modular way to extend Vite's functionality and integrate with other tools. **Example:** """javascript // vite.config.js import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import eslintPlugin from 'vite-plugin-eslint'; export default defineConfig({ plugins: [ vue(), eslintPlugin() ] }); """ ### 3.2. HMR (Hot Module Replacement) Take full advantage of Vite's HMR feature for rapid development. **Do This:** * Ensure that your components and modules are designed to support HMR. * Use state management solutions that are compatible with HMR. * Use Vite's built-in HMR API to handle custom HMR logic. * When using Vue, ensure components are correctly re-rendered when changes occur. **Don't Do This:** * Rely on full page reloads during development. * Ignore HMR errors. **Why:** HMR dramatically improves developer productivity by allowing you to see changes in the browser instantly without losing application state. ### 3.3. Code Splitting Utilize Vite's built-in code splitting to optimize the loading performance of your application. **Do This:** * Use dynamic imports ("import()") to load modules on demand. * Group related modules into separate chunks. * Analyze the bundle size and identify opportunities for code splitting. * Leverage Vite's automatic code splitting for routes and components. **Don't Do This:** * Load all modules upfront in a single large bundle. * Create overly granular code splits that result in too many small files. **Why:** Code splitting reduces the initial load time of your application by only loading the code that is needed for the current page or component. This significantly improves the user experience, especially on slow network connections. ## 4. Version Control and Collaboration ### 4.1. Git Workflow Adopt a standardized Git workflow for team collaboration. **Do This:** * Use feature branches for developing new features or fixing bugs. * Create pull requests for code review. * Write clear and concise commit messages. * Use Git tags to mark releases. **Don't Do This:** * Commit directly to the main branch without code review. * Write vague or uninformative commit messages. * Ignore code review feedback. **Why:** Git workflows facilitate collaboration, track changes, and ensure code quality. ### 4.2. Code Review Implement a code review process to catch errors, enforce standards, and share knowledge. **Do This:** * Review code changes thoroughly. * Provide constructive feedback. * Focus on code quality, readability, and maintainability. * Use automated code analysis tools to identify potential issues. **Don't Do This:** * Skip code reviews. * Provide vague or unhelpful feedback. * Ignore code analysis warnings. **Why:** Code review improves code quality, prevents errors, and promotes knowledge sharing. ### 4.3. Documentation Create clear and concise documentation for your Vite projects. **Do This:** * Write a README.md file that describes the project, how to set it up, and how to use it. * Document complex components and modules. * Use inline comments to explain tricky or non-obvious code. * Keep documentation up-to-date. **Don't Do This:** * Skip documentation entirely. * Write vague or incomplete documentation. * Let documentation become outdated. **Why:** Documentation makes it easier for others (and yourself) to understand, use, and maintain your code. ## 5. Performance Optimization Techniques ### 5.1. Lazy Loading Use lazy loading for components, images, and other assets that are not immediately visible on the screen. **Do This:** * Use dynamic imports ("import()") to load components on demand. * Use the "loading="lazy"" attribute for images. * Use Intersection Observer API for more advanced lazy loading scenarios. **Don't Do This:** * Load all assets upfront, even if they are not immediately needed. * Use lazy loading for critical above-the-fold content. **Why:** Lazy loading improves the initial load time of your application by only loading assets as they become visible on the screen. ### 5.2. Image Optimization Optimize images to reduce their file size without sacrificing quality. **Do This:** * Use appropriate image formats (e.g., WebP, AVIF). * Compress images using tools like ImageOptim or TinyPNG. * Resize images to the appropriate dimensions. * Use responsive images ("<picture>" element or "srcset" attribute). **Don't Do This:** * Use excessively large images. * Use inappropriate image formats (e.g., PNG for photographs). * Ignore image optimization techniques. **Why:** Optimized images reduce the page size and improve loading performance. ### 5.3. Minification and Compression Minify and compress JavaScript, CSS, and HTML files to reduce their file size. Vite handles this automatically in production mode. **Do This:** * Ensure that your Vite configuration is set up to minify and compress assets. * Use tools like Terser or esbuild for JavaScript minification. * Use gzip or Brotli compression on your server. **Don't Do This:** * Deploy unminified or uncompressed assets to production. * Disable minification or compression. **Why:** Minification and compression reduce the page size and improve loading performance. ## 6. Security Best Practices ### 6.1. Dependency Management Keep dependencies up to date to patch security vulnerabilities. **Do This:** * Regularly update dependencies using "npm update" or "yarn upgrade". * Use a tool like Snyk or Dependabot to monitor dependencies for vulnerabilities. * Remove unused dependencies. **Don't Do This:** * Use outdated dependencies. * Ingore security alerts. **Why:** Keeping dependencies up to date mitigates known security vulnerabilities. ### 6.2. Input Validation Validate user input to prevent injection attacks. **Do This:** * Validate all user input on both the client-side and server-side. * Use appropriate validation techniques to prevent SQL injection, XSS, and other attacks. * Encode or sanitize user input before displaying it on the page. **Don't Do This:** * Trust user input without validation. * Display raw user input on the page. **Why:** Input validation prevents malicious code from being injected into your application. ### 6.3. Content Security Policy (CSP) Use CSP to restrict the sources of content that your application is allowed to load. **Do This:** * Configure CSP headers on your server. * Specify the allowed sources for scripts, styles, images, and other resources. * Use a stricter CSP policy in production than in development. **Don't Do This:** * Disable CSP entirely. * Use a overly permissive CSP policy. **Why:** CSP mitigates the risk of XSS attacks.
# Testing Methodologies Standards for Vite This document outlines the recommended testing methodologies and best practices for Vite projects to ensure code quality, maintainability, and reliability. These standards are designed for developers and AI coding assistants alike. ## Unit Testing Unit testing focuses on testing individual components or functions in isolation. In a Vite environment, this often involves testing Vue components, React components, JavaScript modules, or TypeScript classes. ### Standards * **Do This:** Use a dedicated unit testing framework like Jest, Vitest (Vite-native), or Mocha. Vitest is highly recommended due to its tight integration with Vite. * **Don't Do This:** Rely on manual browser testing or "console.log" statements for verifying functionality. * **Why:** Automated unit tests provide rapid feedback, prevent regressions, and serve as living documentation of the code's intended behavior. ### Best Practices * **Test-Driven Development (TDD):** Consider writing tests *before* implementing the code. This helps clarify requirements and ensures testability. * **Code Coverage:** Aim for high (but not necessarily 100%) code coverage. Use coverage reports to identify untested areas of the codebase. * **Mocking:** Use mocks or stubs to isolate the unit under test from its dependencies. Use libraries like "vi" (Vitest's built-in mocking) or "sinon" can be used for this. * **Assertions:** Write clear and meaningful assertions that accurately describe the expected behavior. * **Atomic Tests:** Each test should focus on verifying only ONE specific thing. This simplifies debugging and maintenance. ### Vite-Specific Considerations * **Vitest:** Leverage Vitest, which is designed to work seamlessly with Vite. It offers fast feedback loops and excellent TypeScript support. * **Module Resolution:** Vite's module resolution handles ES modules and other formats correctly. Ensure test configurations mimic Vite's resolution. * **Configuration:** Use "vite.config.ts" to configure Vitest's behavior like test environment, globals, and coverage. ### Code Examples **Example using Vitest for a Vue Component** """typescript // src/components/Greeting.vue <template> <h1>Hello, {{ name }}!</h1> </template> <script setup lang="ts"> import { defineProps } from 'vue'; defineProps({ name: { type: String, required: true, }, }); </script> """ """typescript // src/components/Greeting.spec.ts import { mount } from '@vue/test-utils'; import Greeting from './Greeting.vue'; import { describe, it, expect } from 'vitest'; describe('Greeting.vue', () => { it('renders the greeting with the provided name', () => { const wrapper = mount(Greeting, { props: { name: 'World', }, }); expect(wrapper.text()).toContain('Hello, World!'); }); it('renders the greeting with a different name', () => { const wrapper = mount(Greeting, { props: { name: 'Vitest', }, }); expect(wrapper.text()).toContain('Hello, Vitest!'); }); }); """ **Example using Vitest for a JavaScript Module** """typescript // src/utils/math.ts export function add(a: number, b: number): number { return a + b; } export function subtract(a: number, b: number): number { return a - b; } """ """typescript // src/utils/math.spec.ts import { add, subtract } from './math'; import { describe, it, expect } from 'vitest'; describe('math.ts', () => { it('adds two numbers correctly', () => { expect(add(2, 3)).toBe(5); expect(add(-1, 1)).toBe(0); }); it('subtracts two numbers correctly', () => { expect(subtract(5, 2)).toBe(3); expect(subtract(0, 0)).toBe(0); }); }); """ **Vitest Configuration (vite.config.ts)** """typescript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'jsdom', // or 'node' if you're testing server-side code globals: true, // Allows using expect, describe, it without importing them coverage: { reporter: ['text', 'json', 'html'], }, }, }) """ ### Anti-Patterns * **Testing Implementation Details:** Tests should focus on the component's public interface and behavior, not its internal implementation. * **Creating Brittle Tests:** Avoid tests that are overly sensitive to minor code changes. * **Ignoring Edge Cases:** Ensure tests cover all possible input values, including edge cases and error conditions. * **Over-Mocking:** Mocking EVERYTHING can lead to tests that are detached from reality. Mock only dependencies that are truly necessary. ## Integration Testing Integration testing verifies the interactions between different parts of the application. In a Vite project, this might involve testing the interaction between components, modules, or services. ### Standards * **Do This:** Use integration tests to verify that components work together correctly. * **Don't Do This:** Rely solely on unit tests, as they don't catch integration issues. * **Why:** Integration tests ensure that the application functions as a cohesive whole, not just a collection of isolated units. ### Best Practices * **End-to-End Coverage:** Consider designing integration tests to exercise critical user flows. * **Real Dependencies:** Minimize mocking in integration tests. Where possible, use real dependencies (databases, APIs) in a testing environment. * **Test Data:** Use realistic test data that reflects the data the application will process in production. * **Database Testing:** Use database migrations and seeding to ensure a consistent database state for each test run. ### Vite-Specific Considerations * **Mocking External APIs:** Use tools like "nock" or "msw (Mock Service Worker)" to mock external API calls in integration tests without making real network requests. * **Component Composition:** Thoroughly test the interaction between different Vue or React components to ensure data flows correctly and events are handled properly. * **Asynchronous Operations:** Vite often involves asynchronous operations (API calls, animations). Use "async/await" and proper error handling in integration tests. * **State Management Integration:** Test state management solutions like Vuex or Pinia to ensure state changes are reflected correctly in the UI. ### Code Examples **Example using Vitest and Mock Service Worker (msw) for API integration** """typescript // src/api/todos.ts import axios from 'axios'; const API_URL = 'https://jsonplaceholder.typicode.com'; export async function fetchTodos() { const response = await axios.get("${API_URL}/todos"); return response.data; } """ """typescript // src/api/todos.spec.ts import { fetchTodos } from './todos'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'; const mockTodos = [ { userId: 1, id: 1, title: 'delectus aut autem', completed: false }, { userId: 1, id: 2, title: 'quis ut nam facilis et officia qui', completed: false }, ]; const server = setupServer( rest.get('https://jsonplaceholder.typicode.com/todos', (req, res, ctx) => { return res(ctx.status(200), ctx.json(mockTodos)); }) ); describe('todos API', () => { beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); it('fetches todos correctly', async () => { const todos = await fetchTodos(); expect(todos).toEqual(mockTodos); }); it('handles errors correctly when the API fails', async () => { server.use( rest.get('https://jsonplaceholder.typicode.com/todos', (req, res, ctx) => { return res(ctx.status(500)); }) ); await expect(fetchTodos()).rejects.toThrowError(); }); }); """ **Example Integration Test for Vue Component Interaction** """typescript // ParentComponent.vue <template> <div> <ChildComponent :message="parentMessage" @message-changed="updateMessage" /> <p>Parent Message: {{ parentMessage }}</p> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const parentMessage = ref('Hello from Parent'); const updateMessage = (newMessage: string) => { parentMessage.value = newMessage; }; </script> """ """typescript // ChildComponent.vue <template> <div> <p>Child Message: {{ message }}</p> <button @click="emitMessage">Change Message</button> </div> </template> <script setup lang="ts"> import { defineProps, defineEmits } from 'vue'; defineProps({ message: { type: String, required: true, }, }); const emit = defineEmits(['message-changed']); const emitMessage = () => { emit('message-changed', 'Message from Child'); }; </script> """ """typescript // ParentComponent.spec.ts import { mount } from '@vue/test-utils'; import ParentComponent from './ParentComponent.vue'; import { describe, it, expect } from 'vitest'; describe('ParentComponent.vue', () => { it('updates the parent message when the child emits an event', async () => { const wrapper = mount(ParentComponent); await wrapper.find('button').trigger('click'); expect(wrapper.find('p').text()).toContain('Parent Message: Message from Child'); }); }); """ ### Anti-Patterns * **Flaky Tests:** Integration tests that sometimes pass and sometimes fail are a major problem. Ensure tests are reliable and repeatable using proper setup and teardown. * **Overlapping Tests:** Ensure each integration test focuses on a specific interaction. Avoid tests that try to cover too much simultaneously. * **Ignoring Error Handling:** Integration tests are an excellent place to verify error handling behavior, such as API request failures. ## End-to-End Testing End-to-end (E2E) testing simulates real user interactions with the application in a browser environment. This verifies the entire application stack, from the frontend to the backend and database. ### Standards * **Do This:** Use an E2E testing framework such as Cypress, Playwright, or Selenium. * **Don't Do This:** Neglect E2E testing, as it's the only way to guarantee the application works correctly from a user's perspective. * **Why:** E2E tests catch issues that unit and integration tests might miss, such as problems with routing, UI rendering, or third-party integrations. ### Best Practices * **Real Browsers:** Run E2E tests in real browsers (Chrome, Firefox, Safari) to ensure compatibility. * **CI/CD Integration:** Integrate E2E tests into your CI/CD pipeline to automatically run tests on every commit. Playwright and Cypress are great for this. * **Test Environments:** Use a dedicated test environment that closely resembles the production environment. * **Data Setup:** Seed the database with test data before running E2E tests. * **Clear Assertions:** Write assertions that verify the expected state of the application after each interaction. * **Page Object Model (POM):** Utilize the Page Object Model design pattern to abstract away the details of the UI and make tests more maintainable. ### Vite-Specific Considerations * **Development Server:** Ensure the Vite development server is running before starting E2E tests, or use tools that manage the server lifecycle. Playwright and Cypress can start/stop the dev server as part of their testing process. * **Build Artifacts:** For production-like testing, build the Vite application and serve the static assets from a local server. * **Routing:** Test all critical routes in the application to ensure navigation works correctly. ### Code Examples **Example using Playwright for E2E Testing** """typescript // playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', // Replace with your Vite dev server URL trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, ], webServer: { command: 'npm run dev', // Your Vite development server command url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); """ """typescript // tests/example.spec.ts import { test, expect } from '@playwright/test'; test('homepage has title and links to intro page', async ({ page }) => { await page.goto('/'); // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Vite App/); // create a locator const getStarted = page.getByRole('link', { name: 'Learn More' }); // Expect an attribute "to be strictly equal" to the value. await expect(getStarted).toHaveAttribute('href', '/intro'); // Click the get started link. await getStarted.click(); // Expect the URL to contain intro. await expect(page).toHaveURL(/.*intro/); }); """ **Example using Cypress for E2E Testing** """javascript // cypress.config.js const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:5173', // Your Vite development server URL setupNodeEvents(on, config) { // implement node event listeners here }, specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', }, component: { devServer: { framework: 'vue', bundler: 'vite', }, }, }); """ """javascript // cypress/e2e/spec.cy.js describe('My First Test', () => { it('Visits the Kitchen Sink', () => { cy.visit('/') // Visits the baseURL cy.contains('Learn More').click() // Should be on a new URL which includes '/intro' cy.url().should('include', '/intro') // Get an input, type into it and verify that the value has been updated cy.get('input') .type('fake@email.com') .should('have.value', 'fake@email.com') }) }) """ ### Anti-Patterns * **Slow Tests:** E2E tests can be slow to run. Optimize tests by minimizing unnecessary steps and using parallel execution. * **Unreliable Tests:** Strive to make tests as reliable as possible by handling asynchronous operations correctly and waiting for elements to be visible before interacting with them. * **Hardcoded Values:** Avoid hardcoding values in tests. Use configuration variables or generate test data dynamically. * **Lack of Isolation:** Ensure that tests are isolated from each other to prevent one test from affecting the results of another. Clear browser state (cookies, local storage) between tests. ## Key Considerations within a CI/CD Pipeline * **Automated Execution:** Integrate testing into your CI/CD pipline, so every push, merge, or tag triggers a test run. * **Reporting:** Generate test reports after each run and make them easily accessible to the development team. Tools like JUnit report XML can be integrated into CI/CD pipelines to track build success and failure metrics. * **Parallelization:** Run tests in parallel to reduce the overall test execution time. All modern testing frameworks support parallel test execution. * **Environment Configuration:** Ensure that the test environment is properly configured with all necessary dependencies and configurations. This may involve setting environment variables, creating database schemas, or deploying application dependencies. * **Artifact Storage:** Store test artifacts (logs, reports, screenshots) for later analysis and debugging. Services like S3 and Azure Blob Storage can be used to store these artifacts. * **Notifications:** Send notifications (email, Slack, etc.) to the development team when tests fail. This guide establishes comprehensive testing standards to ensure high code quality and reliability in Vite projects. By adhering to these guidelines, development teams can produce robust, maintainable applications suitable for production environments.
# Component Design Standards for Vite This document outlines best practices for designing reusable, maintainable, and performant components within a Vite project. It focuses specifically on component design principles relevant to Vite's ecosystem, leveraging its features for optimal development workflows. ## 1. Component Architecture & Organization ### 1.1. Standard: Single Responsibility Principle (SRP) * **Do This:** Design components to have a single, well-defined purpose. A component should handle one specific task or display a single piece of information. * **Don't Do This:** Create "god components" that handle multiple responsibilities, making them difficult to understand, test, and reuse. **Why:** SRP improves code clarity, reduces complexity, and simplifies testing. Changes to one part of a component are less likely to impact other parts when the component adheres to SRP. **Example:** Splitting a complex form component into smaller, specialized components: """vue // Bad: Form component handling everything <template> <form @submit.prevent="handleSubmit"> <label for="name">Name:</label> <input type="text" id="name" v-model="name"> <label for="email">Email:</label> <input type="email" id="email" v-model="email"> <textarea v-model="message"></textarea> <button type="submit">Submit</button> </form> </template> <script setup> import { ref } from 'vue'; const name = ref(''); const email = ref(''); const message = ref(''); const handleSubmit = () => { // Handle form submission logic here }; </script> """ """vue // Good: Separated components // NameInput.vue <template> <div> <label for="name">Name:</label> <input type="text" id="name" v-model="modelValue" @input="$emit('update:modelValue', $event.target.value)"> </div> </template> <script setup> defineProps({ modelValue: { type: String, required: true } }); defineEmits(['update:modelValue']); </script> // EmailInput.vue <template> <div> <label for="email">Email:</label> <input type="email" id="email" v-model="modelValue" @input="$emit('update:modelValue', $event.target.value)"> </div> </template> <script setup> defineProps({ modelValue: { type: String, required: true } }); defineEmits(['update:modelValue']); </script> // MessageTextarea.vue <template> <div> <label for="message">Message:</label> <textarea id="message" v-model="modelValue" @input="$emit('update:modelValue', $event.target.value)"></textarea> </div> </template> <script setup> defineProps({ modelValue: { type: String, required: true } }); defineEmits(['update:modelValue']); </script> // Parent Form.vue <template> <form @submit.prevent="handleSubmit"> <NameInput v-model="name" /> <EmailInput v-model="email" /> <MessageTextarea v-model="message" /> <button type="submit">Submit</button> </form> </template> <script setup> import { ref } from 'vue'; import NameInput from './NameInput.vue'; import EmailInput from './EmailInput.vue'; import MessageTextarea from './MessageTextarea.vue'; const name = ref(''); const email = ref(''); const message = ref(''); const handleSubmit = () => { // Handle form submission logic here console.log(name.value, email.value, message.value); }; </script> """ ### 1.2. Standard: Component Composition over Inheritance * **Do This:** Favor composing components together to create more complex UI elements rather than relying on inheritance. * **Don't Do This:** Create deep inheritance hierarchies, which can lead to tight coupling and make components difficult to understand and modify. **Why:** Composition offers more flexibility and avoids the problems associated with inheritance, such as the fragile base class problem. Vue's composable component system lends itself naturally to composition. **Example:** Creating a custom button with different styles: """vue // ButtonBase.vue (Base button component) <template> <button class="base-button" @click="$emit('click')"> <slot /> </button> </template> <script setup> defineEmits(['click']); </script> <style scoped> .base-button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; } </style> // PrimaryButton.vue (Composing ButtonBase with specific styles) <template> <ButtonBase @click="$emit('click')"> <slot /> </ButtonBase> </template> <script setup> import ButtonBase from './ButtonBase.vue'; defineEmits(['click']); </script> <style scoped> .base-button { background-color: #007bff; color: white; } </style> // Usage <template> <PrimaryButton @click="handleClick">Click Me</PrimaryButton> </template> <script setup> import PrimaryButton from './PrimaryButton.vue'; const handleClick = () => { alert('Button clicked!'); }; </script> """ ### 1.3. Standard: Use SFC (Single-File Components) * **Do This:** Utilize Vue's Single-File Component (SFC) format (".vue" files) for organizing component logic, template, and styling. * **Don't Do This:** Mix HTML, JavaScript, and CSS in separate files, especially for non-trivial components. **Why:** SFCs provide excellent structure, scopability, and maintainability. Vite's built-in support for SFCs provides hot module replacement (HMR) and efficient compilation. SFCs are crucial for optimal DX in Vite projects. **Example:** """vue // MyComponent.vue <template> <div> <h1>{{ title }}</h1> <p>{{ message }}</p> </div> </template> <script setup> import { ref } from 'vue'; const title = ref('Hello, Vite!'); const message = ref('This is a Single-File Component.'); </script> <style scoped> h1 { color: #3498db; } </style> """ ### 1.4. Standard: Clearly Defined Component API * **Do This:** Define a clear and concise API for each component using "props", "emits", and "slots". Leverage TypeScript to enforce strong typing and improve development-time error detection. Utilize "defineProps" and "defineEmits" if not using Typescript for clarity and documentation. * **Don't Do This:** Rely on implicit data sharing or unpredictable side effects, making it difficult to understand how a component interacts with its environment. **Why:** A well-defined API makes components easier to use, test, and reason about. Strong typing with TypeScript significantly reduces runtime errors and facilitates code maintenance, even with JavaScript projects using "defineProps". **Example:** """vue // MyComponent.vue (with TypeScript) <template> <div> <p>Name: {{ name }}</p> <button @click="$emit('update:name', 'New Name')">Update Name</button> </div> </template> <script setup lang="ts"> import { defineProps, defineEmits } from 'vue'; interface Props { name: string; } const props = defineProps<Props>(); const emit = defineEmits(['update:name']); </script> // Another way without Typescript <script setup> defineProps({ name: { type: String, required: true, } }) defineEmits(['update:name']) </script> """ ### 1.5. Standard: Directory Structure * **Do This:** Establish a consistent and meaningful directory structure for your components. A common pattern is to group related components together in dedicated directories. * **Don't Do This:** Scatter components randomly across the project, making it difficult to locate and manage them. **Why:** A well-organized directory structure contributes to project maintainability and scalability. It makes it easier for developers to navigate the codebase and understand the relationships between components. **Example:** """ src/ ├── components/ │ ├── Button/ │ │ ├── Button.vue │ │ ├── Button.stories.js (for Storybook integration) │ │ └── index.js (for convenient imports) │ ├── Input/ │ │ ├── Input.vue │ │ └── ... │ ├── Card/ │ │ ├── Card.vue │ │ └── ... │ └── AppHeader.vue """ ## 2. Component Implementation Details ### 2.1. Standard: Use "setup" Script Syntax * **Do This:** Embrace the "<script setup>" syntax in your SFCs. This syntax provides a cleaner and more concise way to define component logic without manually returning reactive properties. * **Don't Do This:** Use the traditional "export default { ... }" syntax unless there's a compelling reason to do so. **Why:** "<script setup>" offers improved type inference, better performance (due to automatic template analysis), and reduced boilerplate code. It is the recommended approach for modern Vue development with Vite. **Example:** """vue // Good: <script setup> <template> <div> <p>{{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script setup> import { ref } from 'vue'; const count = ref(0); const increment = () => { count.value++; }; </script> // Bad: traditional syntax <template> <div> <p>{{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const count = ref(0); const increment = () => { count.value++; }; return { count, increment } } } </script> """ ### 2.2. Standard: Controlled Components * **Do This:** Treat form inputs (and other interactive elements) as controlled components, using "v-model" or explicitly binding values and handling input events. * **Don't Do This:** Rely on uncontrolled components where the DOM manages the state directly. **Why:** Controlled components provide better control over data flow and enable features like validation and formatting. Using "v-model" with custom components requires using "defineProps" and "defineEmits". **Example:** """vue // Good: Controlled Component <template> <input type="text" :value="inputValue" @input="updateValue"> </template> <script setup> import { defineProps, defineEmits } from 'vue'; const props = defineProps({ inputValue: String, }); const emit = defineEmits(['updateValue']); const updateValue = (event) => { emit('updateValue', event.target.value); }; </script> // Bad: Uncontrolled Component (avoid this) <template> <input type="text" ref="myInput"> </template> <script setup> import { ref, onMounted} from 'vue'; const myInput = ref(null); onMounted(() => { // Directly accessing DOM node, avoid this practice. console.log(muInput.value.value); }) </script> """ ### 2.3. Standard: Use Slots Effectively * **Do This:** Utilize slots to create flexible and customizable components. Use named slots for more complex component structures. Use scoped slots to pass data back to the parent component. * **Don't Do This:** Hardcode content or rely on props when slots can provide a more adaptable solution. **Why:** Slots allow parent components to inject custom content into specific parts of a child component, increasing reusability. **Example:** """vue // Card.vue <template> <div class="card"> <header class="card-header"> <slot name="header"></slot> </header> <div class="card-body"> <slot></slot> <!-- Default slot --> </div> <footer class="card-footer"> <slot name="footer"></slot> </footer> </div> </template> // Usage <template> <Card> <template #header> <h2>Card Title</h2> </template> <p>Card content here.</p> <template #footer> <button @click="handleAction">Action</button> </template> </Card> </template> <script setup> import Card from './Card.vue'; const handleAction = () => { alert('Action performed!'); }; </script> """ ### 2.4. Standard: Dynamic Imports for Performance * **Do This:** Use dynamic imports ("import()") for components (especially lazy-loaded components or components used infrequently) to improve initial page load time. Especially useful for large components or those including computationally expensive logic. * **Don't Do This:** Import all components eagerly, which can increase the initial bundle size and negatively affect performance. **Why:** Dynamic imports split the code into smaller chunks, which are loaded only when needed. This reduces the initial download size and improves the user experience. Vite's built-in support for dynamic imports makes this easy to implement. **Example:** """vue <template> <Suspense> <template #default> <MyComponent /> </template> <template #fallback> <div>Loading...</div> </template> </Suspense> </template> <script setup> import { defineAsyncComponent } from 'vue'; const MyComponent = defineAsyncComponent(() => import('./MyComponent.vue')); </script> """ ### 2.5. Standard: Component Names * **Do This:** Use PascalCase for component names (e.g., "MyButton", "UserProfile"). This makes it easy to distinguish components from native HTML elements in templates. Consistent naming conventions help with readability and maintainability. * **Don't Do This:** Use kebab-case or camelCase for component names. **Why:** Consistent naming improves readability and helps developers quickly identify components. ## 3. Component Styling ### 3.1. Standard: Scoped Styles * **Do This:** Use scoped styles ("<style scoped>") in SFCs whenever possible to prevent style conflicts and ensure that styles apply only to the specific component. * **Don't Do This:** Rely excessively on global styles, which can lead to unintended side effects and make it difficult to maintain the application's styling. **Why:** Scoped styles encapsulate the component's styling, making it more predictable and maintainable. **Example:** """vue <template> <div class="my-component"> <p>This is my component.</p> </div> </template> <style scoped> .my-component { border: 1px solid black; padding: 10px; } p { color: blue; } </style> """ ### 3.2. Standard: CSS Preprocessors (Sass, Less, Stylus) * **Do This:** Consider using a CSS preprocessor like Sass, Less, or Stylus for more advanced styling features (variables, mixins, nesting). Configure the preprocessor in your "vite.config.js" file. * **Don't Do This:** Use inline styles excessively, as they reduce maintainability and make it difficult to manage styles consistently. **Why:** CSS preprocessors enhance the styling workflow and improve code organization. Vite provides excellent integration for CSS preprocessors. **Example:** """vue // vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], css: { preprocessorOptions: { scss: { // example : additionalData: "@import "@/styles/variables.scss";" // for avoid define @import in each file. }, }, }, }) // MyComponent.vue <template> <div class="my-component"> <p>Styled with Sass!</p> </div> </template> <style scoped lang="scss"> .my-component { $primary-color: #e74c3c; border: 1px solid $primary-color; padding: 10px; p { color: $primary-color; } } </style> """ ### 3.3. Standard: CSS Modules * **Do This:** Use CSS Modules for component-level styling to avoid naming collisions and ensure that styles are scoped to the component. * **Don't Do This:** Use generic class names that can conflict with styles in other parts of the application. **Why:** CSS Modules automatically generate unique class names, preventing naming collisions and simplifying CSS management. **Example:** """vue // MyComponent.vue <template> <div :class="$style.myComponent"> <p :class="$style.text">Styled with CSS Modules!</p> </div> </template> <script setup> import styles from './MyComponent.module.css'; </script> <style module src="./MyComponent.module.css"></style> // MyComponent.module.css .myComponent { border: 1px solid green; padding: 10px; } .text { color: green; } """ ## 4. Component Testing ### 4.1. Standard: Unit Tests * **Do This:** Write unit tests for your components to verify that they behave as expected in isolation. Use a testing framework like Vitest (created by the Vue/Vite team) along with Vue Test Utils for component testing. * **Don't Do This:** Skip unit testing, which can lead to undetected bugs and make it difficult to refactor components. **Why:** Unit tests provide confidence in the correctness of your components and make it easier to make changes without introducing regressions. **Example:** """javascript // MyComponent.spec.js import { mount } from '@vue/test-utils'; import MyComponent from './MyComponent.vue'; import { describe, it, expect } from 'vitest'; describe('MyComponent', () => { it('renders the correct message', () => { const wrapper = mount(MyComponent, { props: { message: 'Hello, Test!', }, }); expect(wrapper.text()).toContain('Hello, Test!'); }); }); """ ### 4.2. Standard: Component Stories (Storybook) * **Do This:** Create component stories using Storybook to showcase the different states and variations of your components. * **Don't Do This:** Rely solely on manual component inspection, which can be time-consuming and prone to errors. **Why:** Storybook provides a dedicated environment for developing and testing components in isolation. It improves component discoverability and facilitates collaboration with designers and other developers. **Example:** """javascript // Button.stories.js import Button from './Button.vue'; export default { title: 'Components/Button', component: Button, }; const Template = (args) => ({ components: { Button }, setup() { return { args }; }, template: '<Button v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { label: 'Primary Button', primary: true, }; export const Secondary = Template.bind({}); Secondary.args = { label: 'Secondary Button', }; """ ## 5. Security Considerations ### 5.1. Standard: Sanitize User Input * **Do This:** Always sanitize user input to prevent cross-site scripting (XSS) vulnerabilities. Use Vue's built-in HTML escaping or a dedicated sanitization library. * **Don't Do This:** Directly render unsanitized user input, which can allow attackers to inject malicious code into your application. **Why:** XSS vulnerabilities can compromise user data and application security. **Example:** """vue <template> <div> <p v-html="sanitizedMessage"></p> </div> </template> <script setup> import { ref, computed } from 'vue'; const userInput = ref('<script>alert("XSS");</script>Hello!'); const sanitizedMessage = computed(() => { // Basic HTML escaping (more robust solutions should be used in production) return userInput.value.replace(/</g, '<').replace(/>/g, '>'); }); </script> """ ### 5.2. Standard: Avoid Direct DOM Manipulation * **Do This:** Minimize direct DOM manipulation and rely on Vue's data binding and component lifecycle hooks to update the UI. * **Don't Do This:** Use "document.querySelector" or other DOM APIs to directly modify the DOM, as this can bypass Vue's reactivity system and introduce security vulnerabilities. **Why:** Direct DOM manipulation can lead to inconsistencies and make it difficult to track changes to the UI.
# 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.