# Core Architecture Standards for Vue.js
This document outlines the recommended architectural standards for building Vue.js applications. Adhering to these guidelines promotes maintainability, scalability, testability, and overall code quality. This guide focuses on modern Vue.js (version 3 and above) practices and leverages the latest framework features and ecosystem tools.
## 1. Project Structure and Organization
A well-defined project structure is crucial for navigating and maintaining large Vue.js applications. The following conventions are highly recommended.
### 1.1 Feature-Based Organization
**Standard:** Organize code by feature or domain, rather than by file type (e.g., components, views, services).
**Do This:**
"""
src/
├── components/ # Reusable UI elements
├── features/ # Feature modules
│ ├── authentication/ # Authentication feature
│ │ ├── components/ # Authentication-specific components
│ │ ├── services/ # Authentication services
│ │ ├── views/ # Authentication views
│ │ ├── composables/ # Authentication composables
│ │ ├── router.js # Authentication-specific routes
│ │ └── store.js # Authentication Vuex module/Pinia Store
│ ├── dashboard/ # Dashboard feature
│ │ └── ...
├── services/ # Global services (e.g., API client)
├── composables/ # Reusable composables (global scope)
├── views/ # Top-level views/pages
├── router/ # Vue Router configuration
├── store/ # Vuex/Pinia store configuration
├── App.vue # Root component
└── main.js # Entry point
"""
**Don't Do This:**
"""
src/
├── components/ # All components, regardless of feature
├── views/ # All views, regardless of feature
├── services/ # All services, regardless of feature
└── ...
"""
**Why This Matters:** Feature-based organization improves code discoverability, modularity, and reduces the chances of naming conflicts. It also simplifies feature-specific testing and deployment.
**Example:** Consider an e-commerce application. Instead of having a generic "components" folder, features such as "product catalog," "shopping cart," and "checkout" should be organized into their own dedicated directories under the "features" directory.
### 1.2 Component Directory Structure
**Standard:** For complex components, create a dedicated directory. For simple, self-contained components, they can reside directly in the "components" directory.
**Do This:** (Complex Component)
"""
src/
├── components/
│ ├── ProductCard/ # Directory for a complex component
│ │ ├── ProductCard.vue # Main component file
│ │ ├── ProductCard.scss # Component-specific styles
│ │ ├── ProductCard.spec.js # Component tests
│ │ └── utils.js # Utility functions specific to the component
│ ├── Button.vue # Simple, self-contained component
└── ...
"""
**Don't Do This:**
"""
src/
├── components/
│ ├── ProductCard.vue
│ ├── ProductCard.scss
│ ├── ProductCard.js # Mixing related files at the same level
└── ...
"""
**Why This Matters:** Creating a directory for complex components promotes organization and encapsulation, keeping related files together. Simple components that don't require additional files can be placed directly in the "components" directory for convenience.
### 1.3 Router and Store Configuration
**Standard:** Keep routing and store (Vuex or Pinia) configurations separate and modular.
**Do This:** (Router Configuration)
"""javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AuthRoutes from '../features/authentication/router'; //Import feature-specific routes
import DashboardRoutes from '../features/dashboard/router';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
...AuthRoutes,
...DashboardRoutes,
]
});
export default router;
"""
**Do This:** (Pinia Store - Modular Approach)
"""javascript
// src/store/index.js
import { createPinia } from 'pinia';
import authStore from '../features/authentication/store';
import dashboardStore from '../features/dashboard/store';
const pinia = createPinia();
pinia.use(authStore);
pinia.use(dashboardStore);
export default pinia;
"""
**Don't Do This:** (Router - monolithic)
"""javascript
// src/router/index.js
// Long file with all routes defined here. Difficult to maintain.
"""
**Don't Do This:** (State Management - monolithic)
"""javascript
// src/store/index.js
// Huge state object with all modules defined within a single file
"""
**Why This Matters:** Modularizing routes and stores improves maintainability and scalability. Feature-specific routes and store modules reside within their respective feature directories, contributing to the overall feature-based architecture. Pinia's approach (as shown above) makes modularity much easier.
### 1.4 Asset Management
**Standard:** Organize assets (images, fonts, etc.) in a dedicated "assets" directory. Distinguish between global assets and component-specific assets.
**Do This:**
"""
src/
├── assets/
│ ├── images/ # Global images
│ │ ├── logo.png
│ │ └── ...
│ ├── fonts/ # Global fonts
│ │ ├── OpenSans.woff2
│ │ └── ...
├── components/
│ ├── ProductCard/
│ │ ├── assets/ # Component-specific assets
│ │ │ ├── placeholder.png
│ │ └── ProductCard.vue
└── ...
"""
**Don't Do This:** (Mixing global and component assets)
"""
src/
├── assets/
│ ├── logo.png
│ ├── placeholder.png # Confusing - is this global or component-specific?
└── ...
"""
**Why This Matters:** A clear separation of global and component-specific assets simplifies management and reduces the risk of naming conflicts. Component-specific assets are co-located with the component, improving discoverability.
## 2. Component Architecture
Vue.js excels at component-based architecture. Therefore, defining clear component patterns is crucial.
### 2.1 Single Responsibility Principle (SRP)
**Standard:** Each component should have a single, well-defined responsibility.
**Do This:**
* Create a "ProductCard" component that displays product information.
* Create a separate "AddToCartButton" component for adding the product to the cart.
**Don't Do This:**
* Create a single "ProductCard" component that handles product display, add-to-cart functionality, and wish list management.
**Why This Matters:** SRP promotes code reusability, testability, and reduces complexity. Changes to one area of the application are less likely to impact unrelated components.
### 2.2 Composition API
**Standard:** Utilize the Composition API (with "
"""
**Don't Do This:** (Options API for new components)
"""vue
"""
**Why This Matters:** The Composition API provides a more flexible and organized way to structure component logic, especially when combined with "
"""
**Why This Matters:** Composables promote code reusability and reduce duplication. They encapsulate logic that can be easily shared across multiple components. This enhances maintainability and reduces the risk of introducing inconsistencies.
### 2.4 Prop Validation and Types
**Standard:** Define prop types and validations for all components.
**Do This:**
"""vue
"""
**Don't Do This:** (Omitting prop types and validations)
"""vue
"""
**Why This Matters:** Prop validation and types ensure that components receive the expected data, preventing runtime errors and improving the overall robustness of the application. They also serve as documentation for component usage. The "validator" function is particularly important for complex prop types or specific data constraints. Consider using Typescript for further type safety.
### 2.5 Emitting Custom Events
**Standard:** Use "defineEmits" to declare custom events emitted by components.
**Do This:**
"""vue
"""
**Don't Do This:** (Emitting events without declaration)
"""vue
"""
**Why This Matters:** Declaring emitted events with "defineEmits" provides clarity about the component's communication interface. This improves code readability and maintainability. Declaring emitted events also helps Vue optimize event handling.
### 2.6 Dynamic Components & "is" attribute
**Standard:** Utilize dynamic components with the "is" attribute when needing to switch between components at runtime based on data. Ensure that the data used to determine the component is properly sanitized to prevent potential security vulnerabilities (e.g., XSS).
**Do This:**
"""vue
"""
**Don't Do This:**
"""vue
"""
**Why This Matters:** Dynamic components using the "is" attribute offer a powerful way to render different components based on runtime data. However, it's crucial to sanitize any user-provided input used to determine the component to prevent Cross-Site Scripting (XSS) vulnerabilities. Directly binding user input to the "is" attribute is a major security risk. Whitelist acceptable components.
## 3. State Management (Pinia)
Pinia is now the recommended state management library for Vue.js.
### 3.1 Define Stores with "defineStore"
**Standard:** Use "defineStore" to define stores.
**Do This:**
"""javascript
// src/store/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});
"""
**Don't Do This:** (Defining stores manually)
"""javascript
// Not recommended with Pinia
const counterStore = {
state: () => ({ count: 0 }),
mutations: {
INCREMENT: (state) => state.count++
},
actions: {
increment: ({commit}) => commit('INCREMENT')
},
getters: {
doubleCount: (state) => state.count * 2,
}
}
export default counterStore;
"""
**Why This Matters:** "defineStore" provides a type-safe and structured way to define stores. It handles the complexities of store management, such as reactivity and module registration.
### 3.2 Modular Stores
**Standard:** Break down large stores into smaller, feature-specific stores.
**Do This:** (See example in 1.3)
**Why This Matters:** Modular stores improve maintainability and scalability, reducing the complexity of managing a single, monolithic store. Each feature can have its own dedicated store, making it easier to reason about and test.
### 3.3 Utilize Actions for Mutations
**Standard:** All state modifications should be performed within actions. (Pinia conflates mutations & actions into 'actions')
**Do This:**
"""javascript
// src/store/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++;
},
setCount(newCount) {
this.count = newCount
}
},
});
"""
**Don't Do This:** (Modifying state directly within components)
"""vue
"""
**Why This Matters:** Actions provide a centralized place for managing state mutations. The Pinia store itself manages reactivity. Directly modifying the store from the component bypasses the centralized logging/mutation structure.
## 4. Routing
Vue Router is the official routing library for Vue.js.
### 4.1 Lazy Loading Routes
**Standard:** Use lazy loading for route components to improve initial page load performance.
**Do This:**
"""javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue') // Lazy-loaded
}
]
});
export default router;
"""
**Don't Do This:** (Importing all components upfront)
"""javascript
// src/router/index.js
import AboutView from '../views/AboutView.vue'; // Immediately loaded
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/about',
name: 'about',
component: AboutView
}
]
});
export default router;
"""
**Why This Matters:** Lazy loading defers the loading of route components until they are actually needed. This reduces the initial bundle size and improves the perceived performance of the application.
### 4.2 Route Meta Fields
**Standard:** Use route meta fields to store additional information about a route (e.g., authentication requirements, page title).
**Do This:**
"""javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/profile',
name: 'profile',
component: () => import('../views/ProfileView.vue'),
meta: { requiresAuth: true, title: 'Profile' }
}
]
});
"""
**Why This Matters:** Route meta fields provide a way to associate additional data with a route. This data can be used for various purposes, such as implementing authentication guards, dynamic page titles or storing layout specific information.
## 5. General Coding Standards
### 5.1 Consistent Naming Conventions
**Standard:** Use consistent naming conventions for all files, variables, and functions. Consider PascalCase for component names, camelCase for variables and functions, and kebab-case for file names.
**Do This:**
* Component: "MyComponent.vue"
* Variable: "myVariable"
* Function: "myFunction()"
**Why This Matters:** Consistent naming conventions improve code readability and maintainability. They also help developers quickly understand the purpose of different code elements.
### 5.2 Comments and Documentation
**Standard:** Comment code clearly and document components and composables using JSDoc-style comments.
**Do This:**
"""javascript
/**
* Increments the counter.
*
* @param {number} value The amount to increment by.
*/
const increment = (value) => {
// Increase count by specified value
count.value += value;
};
"""
**Why This Matters:** Comments and documentation improve code understanding and facilitate collaboration. They are essential for maintaining and extending the application over time.
These standards will help create maintainable, scalable, and robust Vue.js applications. Consistent application of these guidelines will improve code quality and reduce the risk of common pitfalls.
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'
# State Management Standards for Vue.js This document outlines the standards for managing application state in Vue.js applications. Consistent state management is crucial for maintainability, scalability, and testability. It covers different approaches to state management, from simple reactive properties to more complex solutions like Vuex and Pinia. This will guide developers to write efficient, predictable, and robust Vue.js applications. ## 1. Core Principles of State Management in Vue.js ### 1.1. Single Source of Truth * **Standard:** Maintain a single source of truth for each piece of data within your application. * **Why:** Having a single source of truth ensures data consistency throughout your application. When any part of the application needs to access or modify a piece of data, it always refers to the same source, preventing inconsistencies and simplifying debugging. * **Do This:** Centralize your application's key data within Vuex stores, Pinia stores, or reactive objects, depending on the complexity of your application. * **Don't Do This:** Avoid spreading the same data across multiple components or local storage without a clear synchronization mechanism, which can lead to data inconsistencies and make debugging harder. """vue // Good: Using Pinia store import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ name: 'John Doe', email: 'john.doe@example.com', }), getters: { displayName: (state) => state.name.toUpperCase(), }, actions: { updateName(newName: string) { this.name = newName; }, }, }); // Component.vue import { useUserStore } from '@/stores/user'; import { storeToRefs } from 'pinia'; import { computed } from 'vue'; export default { setup() { const userStore = useUserStore(); const { name, email } = storeToRefs(userStore); //Destructure with storeToRefs to maintain reactivity const fancyName = computed(() => userStore.displayName); return { name, email, fancyName, updateName: userStore.updateName }; }, template: " <p>Name: {{ name }}</p> <p>Email: {{ email }}</p> <p>Fancy Name: {{fancyName}}</p> <button @click="updateName('Jane Doe')">Update Name</button> ", }; // Bad: Local state duplication export default { data() { return { userName: 'John Doe' // Duplicated state, creates inconsistencies } } } """ ### 1.2. Data Flow * **Standard:** Establish a clear and predictable data flow within your application, making it easier to understand how data changes over time and where those changes originate. * **Why:** A well-defined data flow architecture is essential for maintainability and debugging, especially in large or complex applications. This ensures that changes to the application state are controlled and traceable. * **Do This:** Use unidirectional data flow, where components dispatch actions that commit mutations to the store. This makes it easy to track state changes and understand their impact on the application. * **Don't Do This:** Avoid directly modifying component props from within the component, or introducing two-way data binding without careful consideration, as this can obscure the source of state changes. """vue // Good: Unidirectional Data Flow (Pinia) //store.ts import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), actions: { increment() { this.count++; }, decrement() { this.count--; }, }, }); // CounterComponent.vue import { useCounterStore } from '@/stores/counter'; export default { setup() { const counterStore = useCounterStore(); return { counterStore }; }, template: " <p>Count: {{ counterStore.count }}</p> <button @click="counterStore.increment">+</button> <button @click="counterStore.decrement">-</button> ", }; // Bad: Direct Prop Modification (Anti-Pattern) // ParentComponent.vue export default { data() { return { message: "Hello" } }, template: " <ChildComponent :message="message"/> " } // ChildComponent.vue - directly modifying the message prop. export default { props: ['message'], mounted() { // DON'T DO THIS this.message = "Goodbye" } } """ ### 1.3. Immutability * **Standard:** Treat the state as immutable whenever possible. * **Why:** Immutability simplifies debugging, enables time-travel debugging, and makes it easier to reason about the application's state. Libraries like Vuex and Pinia leverage immutability to optimize rendering and improve the overall performance of the application. * **Do This:** Use immutable data structures or libraries (like Immutable.js) if the performance benefits outweigh the complexity. When mutating state, create a new copy of the object or array instead of modifying it directly. With Pinia this is generally handled for you, but when using "reactive" or similar, this becomes important. * **Don't Do This:** Directly modify objects or arrays in the state. """vue // Good: Mutation with a new array using the spread operator import { defineStore } from 'pinia'; export const useItemsStore = defineStore('items', { state: () => ({ items: ['apple', 'banana'], }), actions: { addItem(item: string) { this.items = [...this.items, item]; // Create new array }, removeItem(index: number) { this.items = this.items.filter((_, i) => i !== index); // Create new array }, }, }); // Bad: Direct array modification (Anti-Pattern) export const useItemsStore = defineStore('items', { state: () => ({ items: ['apple', 'banana'], }), actions: { addItem(item: string) { this.items.push(item); // DO NOT DO THIS - direct mutation }, removeItem(index: number) { this.items.splice(index, 1) // DO NOT DO THIS - direct mutation } }, }); """ ### 1.4. Reactive Properties * **Standard:** Use Vue's reactivity system with "ref" and "reactive" to manage local component state. Use "computed" properties for derived state. * **Why:** Vue's reactivity system provides an efficient way to track changes in component state and automatically update the DOM. "computed" properties ensure that derived data is always up-to-date without manual intervention. * **Do This:** Prefer "ref" for primitive data types (strings, numbers, booleans) and "reactive" for objects. Use "computed" properties to derive values from reactive state. * **Don't Do This:** Directly manipulate the DOM or rely on manual updates when state changes. Do not create reactive properties at runtime outside of the setup function as it can lead to unexpected behavior. """vue // Good: Using ref and reactive with computed import { ref, reactive, computed } from 'vue'; export default { setup() { const count = ref(0); const user = reactive({ firstName: 'John', lastName: 'Doe', }); const fullName = computed(() => "${user.firstName} ${user.lastName}"); const increment = () => { count.value++; }; return { count, user, fullName, increment }; }, template: " <p>Count: {{ count }}</p> <p>Full Name: {{ fullName }}</p> <button @click="increment">Increment</button> ", }; // Bad: Manual DOM updates export default { data() { return { count: 0 } }, methods: { increment() { this.count++ document.getElementById('count').innerText = this.count //DO NOT DO THIS } }, mounted() { document.getElementById('count').innerText = this.count }, template: " <p id="count">Count: {{ count }}</p> <button @click="increment">Increment</button> ", } """ ## 2. Vuex (Legacy) vs. Pinia Note: Vuex is still viable as of Vue 3, but Pinia is now the recommended solution by the Vue team. Migrating Vuex to Pinia is recommended where possible. ### 2.1. When to Use Vuex (Legacy) * **Standard:** Use Vuex for large, complex applications where multiple components and modules need to share and manage a significant amount of state globally. * **Why:** Vuex provides a centralized store with a structured approach for managing the state of your application. It enforces a unidirectional data flow, making state changes predictable and helping you debug complex interactions. Vuex utilizes mutations for commiting state changes. * **Do This:** Utilize Vuex when you need: * Centralized state management in a large app * Time-travel debugging capabilities * Clear action/mutation flow for state modifications * **Don't Do This:** Overuse Vuex for small applications where component props and events or Pinia can handle the state management more efficiently. ### 2.2. When to Use Pinia (Recommended) * **Standard:** Prefer Pinia for most Vue.js applications due to its simpler API, better TypeScript support, and modular design. * **Why:** Pinia offers the benefits of Vuex with less boilerplate, making it easier to learn and use. It's fully typed, lightweight, and integrates seamlessly with Vue 3's composition API. Pinia stores are technically closer to components. * **Do This:** Utilize Pinia when you need: * Simple, intuitive state management * TypeScript support * Modular and composable stores * Direct access to store properties and methods * **Don't Do This:** Avoid mixing Vuex and Pinia in the same application unless there is a specific reason to do so, as this can lead to confusion and maintenance issues. If you must mix, define clear boundaries between the two systems. ### 2.3. Code Comparison: Vuex vs. Pinia """vue // Vuex Example // store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { count: 0, }, mutations: { increment(state) { state.count++; }, }, actions: { increment(context) { context.commit('increment'); }, }, getters: { doubleCount(state) { return state.count * 2; }, }, }); // Component.vue import { mapState, mapActions, mapGetters } from 'vuex'; export default { computed: { ...mapState(['count']), ...mapGetters(['doubleCount']), }, methods: { ...mapActions(['increment']), }, template: " <p>Count: {{ count }}</p> <p>Double Count: {{ doubleCount }}</p> <button @click="increment">Increment</button> ", }; // Pinia Example // stores/counter.ts (or .js) import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0, }), getters: { doubleCount: (state) => state.count * 2, }, actions: { increment() { this.count++; }, }, }); // Component.vue import { useCounterStore } from '@/stores/counter'; import { storeToRefs } from 'pinia'; import { computed } from 'vue'; export default { setup() { const counterStore = useCounterStore(); const { count } = storeToRefs(counterStore); const doubleCount = computed(() => counterStore.doubleCount); return { count, doubleCount, increment: counterStore.increment }; }, template: " <p>Count: {{ count }}</p> <p>Double Count: {{ doubleCount }}</p> <button @click="increment">Increment</button> ", }; """ ## 3. Component Communication ### 3.1. Props and Events * **Standard:** Use props to pass data down from parent components to child components, and events to emit data or trigger actions from child components to parent components. * **Why:** Props and events provide a clear and explicit way to define the interface between components, and are essential for modular and reusable components. * **Do This:** Define props with clear types and validations. Use custom events to communicate actions from child to parent components. Use PascalCase for event names to avoid conflicts with native HTML events (e.g., "updateValue"). * **Don't Do This:** Directly modify props from within the child component. Emit native HTML events unless you have a specific reason to do so. """vue // Good: Using props and custom events // ParentComponent.vue <template> <div> <ChildComponent :message="parentMessage" @update-value="updateParentValue" /> <p>Parent Message: {{ parentMessage }}</p> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; import { ref } from 'vue'; export default { components: { ChildComponent, }, setup() { const parentMessage = ref('Hello from parent'); const updateParentValue = (newValue) => { parentMessage.value = newValue; }; return { parentMessage, updateParentValue, }; }, }; </script> // ChildComponent.vue <template> <div> <p>Child Message: {{ message }}</p> <input type="text" @input="updateValue" /> </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ props: { message: { type: String, required: true, }, }, emits: ['update-value'], // define emitted event. setup(props, { emit }) { const updateValue = (event) => { emit('update-value', event.target.value); }; return { updateValue, }; }, }); </script> <!-- Bad: Two-way binding implementation, generally frowned upon as .sync modifier is deprecated. --> <template> <div> <p>Child Message: {{ message }}</p> <input type="text" :value="message" @input="updateValue" /> </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ props: { message: { type: String, required: true, }, }, emits: ['update:message'], setup(props, { emit }) { const updateValue = (event) => { emit('update:message', event.target.value); //This is the replacement for .sync but is generally not recommended as it's confusing as to the source of truth for values. }; return { updateValue, }; }, }); </script> """ ### 3.2. Provide / Inject * **Standard:** Use "provide" in a parent component to make data available to all its descendant components, regardless of how deeply nested they are. Use "inject" in a descendant component to access the provided data. * **Why:** Provide / inject simplifies passing data through multiple levels of the component tree, avoiding prop drilling and making it easier to share data between components. Using "provide" and "inject" injects the reactive connection. * **Do This:** Use provide / inject for sharing data that is used by many components across the application, such as configuration settings or global services. Provide a symbol as the injection key to avoid naming conflicts. * **Don't Do This:** Overuse provide / inject for simple component communication, as this can make it harder to track the data flow within the application. Don't provide mutable data without carefully considering the implications, as changes to the provided data will affect all injected components. """vue // Good: Provide / Inject with Symbol // ParentComponent.vue import { provide, ref } from 'vue'; const messageSymbol = Symbol('message'); export default { setup() { const message = ref('Hello from parent'); provide(messageSymbol, message); return {}; }, template: " <div> <ChildComponent /> </div> ", }; // ChildComponent.vue import { inject, readonly } from 'vue'; export default { setup() { const messageSymbol = Symbol('message'); const message = inject(messageSymbol); return { message: readonly(message) //Use readonly to prevent mutations in the child }; }, template: " <p>Message: {{ message }}</p> ", }; // Bad: Providing a mutable object directly without readonly import { provide, ref } from 'vue'; export default { setup() { const message = ref('Hello from parent'); provide('message', message); //Using the string instead of a symbol, can conflict. }, template: " <div> <ChildComponent /> </div> ", }; """ ## 4. Async State Management and Data Fetching ### 4.1. Async Actions in Pinia * **Standard:** Use async actions in Pinia to handle asynchronous operations, such as fetching data from an API or performing complex calculations. * **Why:** Async actions provide a structured way to manage asynchronous operations within your state management system, making it easier to handle loading states, errors, and data updates. * **Do This:** Define async actions within your Pinia stores to encapsulate asynchronous logic. Use "try...catch" blocks to handle errors and update the store state accordingly. * **Don't Do This:** Perform asynchronous operations directly within components without using a state management solution, as this can make it harder to track the state of the application and handle errors consistently. """vue // Good: Async Action in Pinia import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ user: null, loading: false, error: null, }), actions: { async fetchUser(id: number) { this.loading = true; this.error = null; try { const response = await fetch("/api/users/${id}"); if (!response.ok) { throw new Error('Failed to fetch user'); } this.user = await response.json(); } catch (error: any) { this.error = error.message; } finally { this.loading = false; } }, }, }); // Component.vue import { useUserStore } from '@/stores/user'; import { storeToRefs } from 'pinia'; export default { setup() { const userStore = useUserStore(); const { user, loading, error } = storeToRefs(userStore); userStore.fetchUser(123); return { user, loading, error }; }, template: " <div v-if="loading">Loading...</div> <div v-if="error">Error: {{ error }}</div> <div v-if="user"> <p>Name: {{ user.name }}</p> <p>Email: {{ user.email }}</p> </div> ", }; // Bad: Fetching User inside of the component without any state tracking. export default { data() { return { user: null, loading: false, error: null } }, mounted() { this.fetchUser(123); }, methods: { async fetchUser(id) { this.loading = true; this.error = null; try { const response = await fetch("/api/users/${id}"); if (!response.ok) { throw new Error('Failed to fetch user'); } this.user = await response.json(); } catch (error) { this.error =error.message; } finally { this.loading = false; } } }, template: " <div v-if="loading">Loading...</div> <div v-if="error">Error: {{ error }}</div> <div v-if="user"> <p>Name: {{ user.name }}</p> <p>Email: {{ user.email }}</p> </div> ", } """ ### 4.2. Suspense for Data Fetching * **Standard:** Utilize the "<Suspense>" component in Vue 3 to handle asynchronous data fetching and display fallback content while data is loading. * **Why:** "<Suspense>" provides a declarative way to handle asynchronous data fetching, making it easier to manage loading states and improve the user experience. Make sure your Vue version is compatible. * **Do This:** Wrap asynchronous components within "<Suspense>" boundaries. Provide a fallback template to display while the component is loading. * **Don't Do This:** Rely on imperative loading state management within components, as this can make the code more complex and harder to maintain. """vue // Good: Using Suspense <template> <Suspense> <template #default> <AsyncComponent /> </template> <template #fallback> <div>Loading...</div> </template> </Suspense> </template> <script> import { defineAsyncComponent } from 'vue'; const AsyncComponent = defineAsyncComponent({ loader: () => import('./components/MyComponent.vue'), delay: 200, timeout: 3000, onError(error, retry, fail) { console.log(error); retry(); //fail() }, }); export default { components: { AsyncComponent, }, }; </script> // Bad: Manual loading state management import MyComponent from './components/MyComponent.vue' export default { components: { MyComponent }, template: " <MyComponent v-if="!loading" /> <p v-else>Loading</p> ", data() { return { loading: true } }, async mounted() { //Simulated example await new Promise(resolve => setTimeout(resolve, 1000)) this.loading = false } } """ ## 5. Testing State Management ### 5.1. Unit Testing Pinia Stores * **Standard:** Write unit tests to verify the behavior of your Pinia stores, including state mutations, actions, and getters. * **Why:** Unit tests ensure that your state management logic is working correctly and prevent regressions when you make changes to the code. * **Do This:** Use testing frameworks like Jest or Mocha, along with testing utilities like "@vue/test-utils", to write unit tests for your Pinia stores. Mock external dependencies, such as API calls, to isolate the store's behavior. * **Don't Do This:** Skip testing your state management logic, as this can lead to bugs and unexpected behavior in your application. """javascript // Good: Unit Testing Pinia Store (Jest) import { createPinia, setActivePinia } from 'pinia'; import { useCounterStore } from '@/stores/counter'; import { beforeEach, describe, expect, it } from 'vitest'; describe('Counter Store', () => { beforeEach(() => { setActivePinia(createPinia()); }); it('should increment the count', () => { const counterStore = useCounterStore(); expect(counterStore.count).toBe(0); counterStore.increment(); expect(counterStore.count).toBe(1); }); it('should decrement the count', () => { const counterStore = useCounterStore(); expect(counterStore.count).toBe(0); counterStore.decrement(); expect(counterStore.count).toBe(-1); }); it('should double the count', () => { const counterStore = useCounterStore(); counterStore.count = 5; expect(counterStore.doubleCount).toBe(10); }); }); // Bad: Not testing """ ### 5.2. End-to-End Testing with State Management * **Standard:** Incorporate state management into your end-to-end tests to verify that the application state is being updated correctly in response to user interactions. * **Why:** End-to-end tests ensure that the entire application, including the state management layer, is working correctly and providing a consistent user experience. * **Do This:** Use end-to-end testing frameworks like Cypress or Playwright to simulate user interactions and verify that the state of the application is being updated as expected. * **Don't Do This:** Only test individual components without considering the overall state of the application, as this can miss important integration issues.
# Security Best Practices Standards for Vue.js This document outlines security best practices for Vue.js development. Following these guidelines helps protect against common vulnerabilities and ensures the creation of secure and robust Vue.js applications. ## 1. General Security Principles ### 1.1 Principle of Least Privilege **Standard:** Grant the minimum necessary privileges to users and components. Avoid excessive permissions. **Why:** Minimizes damage from compromised accounts or components. If a component only needs read access, don't grant it write access. **Do This:** * Carefully evaluate the roles and permissions assigned to users. * Design components to operate with the fewest privileges required. * Use Vue's reactivity to limit data exposure. **Don't Do This:** * Grant administrative privileges to all users. * Expose sensitive data unnecessarily. * Over-scope permissions on backend APIs. **Example:** """vue <template> <div> <p v-if="isEditor"> <button @click="editData">Edit</button> </p> <p>{{ displayData }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const userData = ref({ role: 'viewer', // or 'editor' data: 'Sensitive Data', }); const isEditor = computed(() => { return userData.value.role === 'editor'; }); const displayData = computed(() => { if(isEditor.value) { return userData.value.data; } else { return "You do not have permission to view this data." } }); const editData = () => { // Only execute edit functionality if the user is an editor if (isEditor.value) { console.log('Editing data...'); // In reality, trigger an API request to update the data } }; </script> """ **Anti-Pattern:** Allowing all users to see or modify sensitive data without role-based access control. ### 1.2 Defense in Depth **Standard:** Implement multiple layers of security controls to protect against various attacks. **Why:** If one layer is breached, others will still provide protection. **Do This:** * Combine input validation, output encoding, and content security policies. * Use secure coding practices throughout the application. * Regularly audit security measures. **Don't Do This:** * Rely on a single security measure. * Assume that input validation alone is sufficient. **Example:** Implementing both client-side and server-side validation: """vue // Client-side validation (template) <template> <form @submit.prevent="submitForm"> <input type="text" v-model="userInput" @blur="validateInput"> <span v-if="inputError">{{ inputError }}</span> <button type="submit" :disabled="inputError">Submit</button> </form> </template> // Client-side validation (script) <script setup> import { ref } from 'vue'; const userInput = ref(''); const inputError = ref(''); const validateInput = () => { if (!userInput.value || userInput.value.length < 5) { inputError.value = 'Input must be at least 5 characters.'; } else { inputError.value = ''; } }; const submitForm = async () => { validateInput(); if(!inputError.value) { // Send data to server const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'accept/json' }, body: JSON.stringify({ data: userInput.value }) }) if (response.ok) { console.log('Data submitted successfully'); } else { console.error('Failed to submit data'); } } } </script> // Example server-side validation (Node.js/Express) app.post('/api/submit', (req, res) => { const { data } = req.body; if (!data || data.length < 5) { return res.status(400).json({ error: 'Input must be at least 5 characters.' }); } // Process the data safely here console.log('Received data:', data); res.status(200).json({ message: 'Data received successfully.' }); }); """ **Anti-Pattern:** Relying solely on client-side validation for security. Server-side validation is essential for maintaining data integrity and security. ## 2. Preventing Cross-Site Scripting (XSS) ### 2.1 Output Encoding **Standard:** Encode any user-provided data before rendering it in your components. **Why:** Prevents malicious scripts from being injected into the HTML. **Do This:** * Use Vue.js's built-in template syntax, which automatically escapes HTML entities. * Use the "v-text" directive instead of "v-html" when rendering plain text. * Ensure server-side rendering (SSR) also encodes user input correctly. **Don't Do This:** * Use "v-html" with user-provided data without sanitization. * Directly manipulate the DOM with "innerHTML" and user input. **Example:** """vue <template> <div> <p>{{ unsafeData }}</p> <!-- Encoded by default --> <p v-text="unsafeData"></p> <!-- Explicit encoding --> <!-- <p v-html="unsafeData"></p> Avoid this if unsafeData is user-provided --> </div> </template> <script setup> import { ref } from 'vue'; const unsafeData = ref('<script>alert("XSS");</script>Hello'); </script> """ **Anti-Pattern:** Using "v-html" to render potentially malicious user input without proper sanitization. ### 2.2 Content Security Policy (CSP) **Standard:** Implement a strict CSP to limit the sources of content that the browser is allowed to load. **Why:** Reduces the risk of XSS attacks by preventing the execution of unauthorized scripts. **Do This:** * Configure CSP HTTP headers on the server-side. The most important one is "Content-Security-Policy". * Start with a restrictive policy and gradually relax it as needed. * Monitor CSP violations and adjust policy accordingly. **Don't Do This:** * Use a loose CSP that allows content from any source ("default-src *"). **Example:** Configuring a CSP in your server (e.g., with Node.js and Express): """javascript app.use((req, res, next) => { res.setHeader( 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" ); next(); }); """ **Explanation:** * "default-src 'self'": Only allows content from the same origin. * "script-src 'self' 'unsafe-inline' https://trusted-cdn.com": Allows scripts from the same origin, inline scripts (use sparingly and hash them if needed), and scripts from "https://trusted-cdn.com". "unsafe-inline" is HIGHLY DISCOURAGED. Use nonces or hashes instead. * "style-src 'self' 'unsafe-inline'": Allows styles from the same origin and inline styles. "unsafe-inline" is DISCOURAGED here as well. Use a CSS-in-JS approach to avoid inline styles. * "img-src 'self' data:": Allows images from the same origin and data URIs. Consider using a nonce: """javascript // Server-side (Express example) app.get('/', (req, res) => { const nonce = generateNonce(); // Function to generate a unique nonce res.setHeader('Content-Security-Policy', "script-src 'nonce-${nonce}'"); res.render('index', { nonce }); }); function generateNonce() { return crypto.randomBytes(16).toString('hex'); } // Vue Template <script nonce="<%= nonce %>"> //Your Vue App initialization code. </script> """ **Anti-Pattern:** Using a broad CSP that essentially disables XSS protection. For instance, "Content-Security-Policy: default-src *" allows loading content from any source, negating the benefits of having a CSP. ### 2.3 Sanitizing HTML Input **Standard:** If you *must* use "v-html" with user-provided content, sanitize it using a trusted library like DOMPurify or sanitize-html. **Why:** Removes potentially malicious code from the input before rendering. **Do This:** * Install a sanitization library (e.g., "npm install dompurify"). * Sanitize the input before assigning it to a data property used with "v-html". **Don't Do This:** * Use regular expressions to sanitize HTML. * Trust that users will provide safe input. **Example:** """vue <template> <div v-html="sanitizedData"></div> </template> <script setup> import { ref, onMounted } from 'vue'; import DOMPurify from 'dompurify'; const unsanitizedData = ref('<img src=x onerror=alert("XSS")>Hello'); const sanitizedData = ref(''); onMounted(() => { sanitizedData.value = DOMPurify.sanitize(unsanitizedData.value); }); </script> """ **Anti-Pattern:** Attempting to write your own HTML sanitization logic using regular expressions. It's extremely difficult to cover all attack vectors effectively. Always use a well-maintained and audited library like DOMPurify. ## 3. Preventing Cross-Site Request Forgery (CSRF) ### 3.1 CSRF Tokens **Standard:** Use CSRF tokens to protect against CSRF attacks. **Why:** Ensures that requests originate from your application and not from a malicious site. **Do This:** * Generate a unique CSRF token on the server for each user session. * Include the token in a hidden field in forms or as a header in AJAX requests. * Validate the token on the server before processing the request. **Don't Do This:** * Use the same CSRF token for all users. * Store CSRF tokens in cookies without the "HttpOnly" and "Secure" flags. * Omit CSRF protection for GET requests. **Example:** """vue // Vue Component <template> <form @submit.prevent="submitForm"> <input type="hidden" name="csrf_token" :value="csrfToken"> <input type="text" v-model="data"> <button type="submit">Submit</button> </form> </template> <script setup> import { ref, onMounted } from 'vue'; const data = ref(''); const csrfToken = ref(''); onMounted(async () => { // Fetch CSRF token from the server const response = await fetch('/api/csrf'); const data = await response.json(); csrfToken.value = data.csrfToken; }); const submitForm = async () => { const formData = new FormData(); formData.append('data', data.value); formData.append('csrf_token', csrfToken.value); const response = await fetch('/api/submit', { method: 'POST', body: formData, headers: { 'X-CSRF-Token': csrfToken.value //OR put it in header, this is common } }); // Process response }; </script> // Server-side (Node.js/Express example) const csrf = require('csurf'); const cookieParser = require('cookie-parser'); app.use(cookieParser()); app.use(csrf({ cookie: true })); app.get('/api/csrf', (req, res) => { res.json({ csrfToken: req.csrfToken() }); }); app.post('/api/submit', (req, res) => { // CSRF token is automatically validated by the csrf middleware // Only reached if the token is valid console.log('Data received:', req.body.data); res.status(200).json({ message: 'Data received successfully.' }); }); """ **Anti-Pattern:** Omitting CSRF protection, especially for state-changing operations. This leaves applications vulnerable, especially if the UI prompts users to take actions like initiate bank transfers. ### 3.2 SameSite Cookie Attribute **Standard:** Set the "SameSite" attribute for cookies to "Strict" or "Lax" to prevent CSRF attacks. **Why:** Restricts when the browser sends cookies with cross-site requests. **Do This:** * Configure the "SameSite" attribute when setting cookies on the server. * Consider using "SameSite=Strict" for sensitive cookies. * Use "SameSite=Lax" for cookies that need to be sent with top-level navigation. **Don't Do This:** * Omit the "SameSite" attribute. It defaults to "None" in many browsers, which requires the "Secure" attribute and can be a security risk **Example:** Setting a cookie with "SameSite=Strict" in Node.js: """javascript app.get('/set-cookie', (req, res) => { res.cookie('session_id', '12345', { sameSite: 'strict', secure: true, // Required if SameSite=None httpOnly: true, }); res.send('Cookie set!'); }); """ **Anti-Pattern:** Defaulting to "SameSite=None" without understanding the implications, or omitting the "SameSite" attribute altogether. ## 4. Data Handling and Storage ### 4.1 Secure Storage of Sensitive Data **Standard:** Avoid storing sensitive data in local storage or cookies. If necessary, encrypt the data. **Why:** Local storage and cookies are accessible to JavaScript and can be vulnerable to XSS attacks. **Do This:** * Use secure, server-side storage for sensitive data. * If you must store data client-side, use the Web Crypto API to encrypt it. * Use short-lived tokens instead of storing authentication credentials. **Don't Do This:** * Store passwords, API keys, or other sensitive information in local storage or cookies in plaintext. **Example:** Avoid storing sensitive data. Retrieve it dynamically from authenticated backend endpoints. """vue // DO NOT DO THIS: localStorage.setItem('apiKey', 'VERY_SECRET_API_KEY'); // Insecure! // Instead, use Auth headers and sessions. Example: <script setup> import { ref, onMounted } from 'vue'; const sensitiveData = ref(''); onMounted(async () => { const response = await fetch('/api/sensitive-data', { headers: { 'Authorization': "Bearer ${localStorage.getItem('authToken')}" // Assuming you have an auth token } }); if (response.ok) { const data = await response.json(); sensitiveData.value = data.value; } else { console.error('Failed to fetch sensitive data.'); } }); </script> """ **Anti-Pattern:**Storing non-essential data in local storage, especially without encryption or other mitigation. ### 4.2 Secure API Communication **Standard:** Use HTTPS for all API communication. **Why:** Encrypts data in transit, preventing eavesdropping and man-in-the-middle attacks. **Do This:** * Obtain and install an SSL/TLS certificate for your server. * Configure your server to redirect HTTP traffic to HTTPS. * Always use HTTPS URLs in your Vue.js application when making API requests. **Don't Do This:** * Use HTTP for sensitive API communication. * Ignore browser warnings about invalid or expired SSL certificates. **Example:** """javascript // Insecure (HTTP) fetch('http://api.example.com/data') // Secure (HTTPS) fetch('https://api.example.com/data') """ **Anti-Pattern:** Using HTTP for API communications. ### 4.3 Careful Use of Third-Party Libraries **Standard:** vet third party libraries carefully before including them. **Why:** Third party libraries often have security vulnerabilities. **Do This:** * Vet libraries for known vulnerabilities, licensing and maintenance before including them. * Keep libraries updated to the latest stable versions. * Audit all third Party Libraries regulary to ensure continued compliance. **Don't Do This:** * Automatically include libraries without checking. * Use old and unsupported libraries. **Example:** Use "npm audit" to check your dependencies for vulnerabilities: """bash npm audit """ Update vulnerable packages: """bash npm update """ Replace packages with vulnerabilities that cannot be patched, if necessary: """bash npm uninstall vulnerable-package npm install secure-alternative-package """ **Anti-Pattern:**盲目的に third party libraries をインポートする. ## 5. User Input Validation ### 5.1 Validate all User Inputs **Standard:** Validate all user input, both on the client-side and server-side. **Why:** Prevents injection attacks, data corruption, and unexpected behavior. **Do This:** * Use appropriate data types and formats. * Validate input length, range, and pattern. * Encode or escape special characters. * Use server-side validation to ensure data integrity. **Don't Do This:** * Trust client-side validation alone. * Allow users to input arbitrary data without validation. **Example:** """vue <template> <form @submit.prevent="submitForm"> <input type="text" v-model="userInput" @input="validateInput"> <p v-if="errorMessage">{{ errorMessage }}</p> <button type="submit" :disabled="errorMessage">Submit</button> </form> </template> <script setup> import { ref } from 'vue'; const userInput = ref(''); const errorMessage = ref(''); const validateInput = () => { if (!/^[a-zA-Z0-9]+$/.test(userInput.value)) { errorMessage.value = 'Only alphanumeric characters are allowed.'; } else { errorMessage.value = ''; } }; const submitForm = async () => { if (!errorMessage.value) { // Send data to the server const response = await fetch('/api/submit', { method: 'POST', body: JSON.stringify({ data: userInput.value }), headers: { 'Content-Type': 'application/json' } }); if (response.ok) { console.log('Data submitted successfully'); } else { console.error('Failed to submit data'); } } }; </script> """ **Anti-Pattern:** Failure to validate user input on either the client or server side. ### 5.2 Limiting File Uploads **Standard:** Implement strict controls on file uploads. **Why:** Prevents malicious files from being uploaded and executed on the server. **Do This:** * Validate file extensions and MIME types. * Limit file sizes. * Store uploaded files in a non-executable directory. * Scan uploaded files for malware. * Change the uploaded files names so they are not predictable. **Don't Do This:** * Allow users to upload executable files (e.g., ".exe", ".sh", ".php"). * Store uploaded files in the web root. **Example:** Server-side file upload validation (Node.js example with "multer"): """javascript const multer = require('multer'); const path = require('path'); const crypto = require('crypto'); const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, 'uploads/'); // Store files in 'uploads/' directory }, filename: (req, file, cb) => { crypto.randomBytes(16, (err, raw) => { //Prevents file name predictability cb(null, raw.toString('hex') + path.extname(file.originalname)); }); } }); const fileFilter = (req, file, cb) => { const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif']; if (allowedMimeTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Invalid file type'), false); } }; const upload = multer({ storage: storage, fileFilter: fileFilter, limits: { fileSize: 1024 * 1024 } // Limit file size to 1MB }).single('avatar'); app.post('/api/upload', (req, res) => { upload(req, res, (err) => { if (err) { return res.status(400).json({ error: err.message }); } res.status(200).json({ message: 'File uploaded successfully' }); }); }); """ **Anti-Pattern:** Allowing unrestricted file uploads to a publicly accessible directory. This enables malicious users to potentially upload and execute harmful code. ## 6. Dependency Management ### 6.1 Keep Dependencies Up To Date **Standard:** Regularly update dependencies to patch security vulnerabilities. **Why:** Outdated dependencies may contain known vulnerabilities that can be exploited. **Do This:** * Use "npm update" or "yarn upgrade" to update dependencies. * Use "npm audit" or "yarn audit" to identify vulnerabilities. * Automate dependency updates using tools like Dependabot. * Review dependabot alerts in Github action for any vulnerabilities **Don't Do This:** * Ignore dependency update notifications. **Example:** Using "npm audit" to find and fix vulnerabilities: """bash npm audit fix """ **Anti-Pattern:** Delaying or neglecting dependency updates, thereby allowing known vulnerabilities to persist in the application. ### 6.2 Subresource Integrity (SRI) **Standard:** Use Subresource Integrity (SRI) to ensure that files fetched from CDNs have not been tampered with. **Why:** Protects against compromised CDNs by verifying the integrity of fetched resources. **Do This:** * Generate SRI hashes for your dependencies. * Include the "integrity" attribute in "<script>" and "<link>" tags. **Don't Do This:** * Omit the "integrity" attribute when using CDNs. **Example:** """html <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+wn" crossorigin="anonymous"></script> """ **Anti-Pattern:** Relying on external resources (CDNs) without SRI. ## 7. Security Headers ### 7.1 Implement Security Headers **Standard:** Configure security headers to protect against various attacks. **Why:** Enhances security by providing instructions to the browser about how to handle content. **Do This:** * Set the "X-Frame-Options" header to prevent clickjacking attacks. * Set the "X-Content-Type-Options" header to prevent MIME sniffing. * Set the "Strict-Transport-Security" header to enforce HTTPS. * Use "Content-Security-Policy" as detailed above. * Set "Referrer-Policy" to control the amount of referrer information sent with requests. **Don't Do This:** * Omit security headers. **Example:** Configuring security headers in Node.js (Express): """javascript app.use((req, res, next) => { res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); //... CSP Header from above example res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); }); """ **Anti-Pattern:** Ignoring security headers, thereby missing a basic layer of protection. ## 8. Secure Configuration ### 8.1 Environment Variables **Standard:** Use environment variables to store sensitive configuration data. **Why:** Prevents sensitive information from being hardcoded in the application. **Do This:** * Store API keys, database passwords, and other sensitive information in environment variables. * Use a ".env" file for development and configure environment variables in production. * Never commit ".env" files to version control. **Don't Do This:** * Hardcode sensitive information in your Vue.js code. * Commit sensitive information to a public repository. **Example:** Accessing environment variables in Vue.js (using "process.env"): """javascript const apiKey = process.env.VUE_APP_API_KEY; """ In ".env" file: """text VUE_APP_API_KEY=your_actual_api_key """ **Anti-Pattern:** Storing sensitive data directly in the application code or committing it to version control. ## 9. Error Handling and Logging ### 9.1 Secure Error Handling **Standard:** Handle errors gracefully and avoid exposing sensitive information in error messages. **Why:** Prevents attackers from gaining information about your application. **Do This:** * Log errors on the server-side for debugging purposes. * Display generic error messages to users. * Avoid revealing sensitive information in error messages. **Don't Do This:** * Display stack traces or detailed error messages to users in production. **Example:** """javascript try { // Some potentially failing operation } catch (error) { console.error('An error occurred:', error); // Log the error server-side alert('An unexpected error occurred. Please try again later.'); // Display a user-friendly message } """ **Anti-Pattern:** Unhandled and generic errors, especially those that expose sensitive information. ### 9.2 Secure Logging **Standard:** Implement secure logging practices to prevent sensitive data from being logged. **Why:** Prevents sensitive information from being stored in log files. **Do This:** * Avoid logging sensitive data such as passwords, API keys, and personal information. Mask these if logging is essential for audit purposes. * Secure log files with appropriate permissions. * Regularly review log files for suspicious activity. * Ensure you are in compliance with applicable privacy regulations. **Don't Do This:** * Log sensitive data in plaintext. * Store log files in a publicly accessible directory. ## 10. Vue-Specific Security Considerations ### 10.1 Template Injection **Standard:** Be aware of the risks of template injection, especially when rendering user-provided data in templates. This is usually covered by Output Encoding, but it applies specifically to the way Vue templates render data. **Why:** Malicious users could inject code into templates, leading to Cross-Site Scripting (XSS). **Do This:** * Always encode user-provided data before rendering it in templates unless you NEED HTML to render, and have fully sanitized the HTML as per the XSS instructions in this document. * Use "v-text" for plain text. * Sanitize if you must use "v-html". ### 10.2 Server-Side Rendering (SSR) Security **Standard:** When using SSR, ensure that your server-side code is also protected against vulnerabilities. **Why:** SSR introduces new security considerations, such as the risk of code injection on the server. **Do This:** * Apply the same security principles to your server-side code as you do to your client-side code. * Sanitize user input on the server before rendering it. * Be aware of any third party libraries used on the server, and follow applicable processes for keeping those libraries up to date and secure. ## 11. Testing Security ### 11.1 Implementing regular penetration tests **Standard:** Regular penetration tests should be used to help identify and resolve security threats. **Why:** Regular penetration tests can help to identify a range of security vulnerabilities, including server misconfiguration, out of date software, and OS vulnerabilities. **Do This:** * Implement regular penetration tests. * Ensure that your penetration tests are as realistic as possible. * Ensure that your staff know what to do in response to the penetration tests. * Test after every major release when possible. **Don't Do This:** * Assume that a previous penetration test means that the system is secure. * Ignore the results of your penetration tests. ## 12. Conclusion Following these security best practices will help you build secure and robust Vue.js applications. Regularly review and update your security measures to stay ahead of potential threats. Security is a journey, not a destination.
# Deployment and DevOps Standards for Vue.js This document outlines the standards and best practices for deploying and maintaining Vue.js applications using modern DevOps techniques. It's intended to guide developers and inform AI coding assistants on building robust, scalable, and maintainable Vue.js applications in production environments. ## 1. Build Processes and CI/CD ### 1.1. Standard: Automate Build and Deployment Pipelines **Do This:** * Implement a CI/CD pipeline using tools like GitHub Actions, GitLab CI, Jenkins, CircleCI, or similar. * Automate all build, test, and deployment steps. * Use environment variables to configure the build process for different environments (development, staging, production). **Don't Do This:** * Manually build and deploy the application. * Hardcode environment-specific configurations in the codebase. **Why:** Automation reduces errors, ensures consistency across deployments, significantly reduces deployment time, promotes faster feedback loops and enables continuous delivery. **Example (GitHub Actions):** """yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: - main # Or your main branch jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18.x' # Or your preferred version - name: Install Dependencies run: npm install - name: Run Tests run: npm run test:unit # Or your testing command - name: Build Production run: npm run build - name: Deploy to Production # Replace with your deployment method, e.g., SSH, AWS S3, Netlify CLI run: | echo "Deploying to production..." # Add your deployment commands here # e.g., scp -r dist/* user@server:/var/www/your-app # Or use Netlify CLI: npm install -g netlify-cli && netlify deploy --prod --dir=dist env: PRODUCTION_SERVER_HOST: ${{ secrets.PRODUCTION_SERVER_HOST }} PRODUCTION_SERVER_USER: ${{ secrets.PRODUCTION_SERVER_USER }} PRODUCTION_SERVER_PASSWORD: ${{ secrets.PRODUCTION_SERVER_PASSWORD }} # Store securely as secret! """ **Anti-pattern:** Manually running "npm run build" and copying the "dist" folder to the production server. This is prone to errors and lacks version control and proper testing. ### 1.2. Standard: Use a Version Control System (Git) **Do This:** * Commit all code changes to a Git repository (e.g., GitHub, GitLab, Bitbucket). * Use branching strategies (e.g., Gitflow, GitHub Flow) to manage feature development, bug fixes, and releases. * Use pull requests (or merge requests) to review code before merging it into the main branch. **Don't Do This:** * Commit code directly to the main branch without review. * Ignore important files or directories in ".gitignore" (e.g., "node_modules"). **Why:** Version control ensures that all changes are tracked, auditable and reversible. Branching strategies enable parallel development and maintain stable release versions. Code review improves code quality and knowledge sharing. **Example (.gitignore):** """ node_modules/ dist/ .env .DS_Store """ **Anti-pattern:** Not using version control at all, or committing large binary files to the repository. ### 1.3. Standard: Configure Environment Variables **Do This:** * Use environment variables to store configuration settings that vary between environments (e.g., API endpoints, database credentials, feature flags). * Use a ".env" file for local development and inject environment variables into the build process during CI/CD. * Use libraries like "dotenv" (Node.js based backends) or tools like "cross-env" to manage environment variables. **Don't Do This:** * Hardcode sensitive information (e.g., API keys, passwords) directly in the code. * Commit ".env" files to the repository, especially containing production keys. **Why:** Environment variables allow you to configure your application without modifying the code. **Example (.env):** """ VUE_APP_API_URL=https://api.example.com VUE_APP_FEATURE_FLAG_ENABLED=true """ **Example (vue.config.js or vite.config.js):** """javascript // vue.config.js (for Vue CLI) module.exports = { chainWebpack: config => { config.plugin('define').tap(args => { args[0]['process.env'] = { ...args[0]['process.env'], VUE_APP_API_URL: JSON.stringify(process.env.VUE_APP_API_URL), VUE_APP_FEATURE_FLAG_ENABLED: JSON.stringify(process.env.VUE_APP_FEATURE_FLAG_ENABLED), }; return args; }); }, }; // vite.config.js (for Vite) import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { loadEnv } from 'vite'; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd()); return { plugins: [vue()], define: { 'process.env': env } }; }); """ **Example (Vue Component using env variables):** """vue <template> <div> API URL: {{ apiUrl }} <p v-if="featureFlagEnabled">Feature is Enabled!</p> </div> </template> <script> export default { computed: { apiUrl() { return process.env.VUE_APP_API_URL; }, featureFlagEnabled() { return process.env.VUE_APP_FEATURE_FLAG_ENABLED === 'true'; // Convert to boolean }, }, }; </script> """ **Anti-pattern:** Hardcoding API endpoints or other configuration values directly into the component templates or JavaScript code. ### 1.4. Standard: Apply Build Optimizations **Do This:** * Use webpack (Vue CLI) or Vite to bundle your Vue.js application for production. * Enable minification, code splitting, and tree shaking to reduce the bundle size. * Use lazy loading for components or routes that are not immediately needed. * Optimize images and other assets before deployment. * Generate modern JavaScript bundle targets using "@vue/babel-preset-app" configuration or equivalent for Vite, ensuring browser compatibility while leveraging modern syntax. **Don't Do This:** * Deploy the application without bundling and optimization. * Include unnecessary dependencies in the bundle. * Serve large, unoptimized images. **Why:** Optimization improves page load times, reduces bandwidth consumption, enhances user experience. **Example (vue.config.js - Production Build):** """javascript // vue.config.js module.exports = { productionSourceMap: false, // Disable source maps in production configureWebpack: { optimization: { splitChunks: { chunks: 'all', }, }, }, chainWebpack: config => { config.optimization.minimizer('terser').options({ terserOptions: { compress: { drop_console: true, // Remove console.log statements } } }) } }; """ **Example (Lazy Loading a Component):** """javascript // Router Configuration (Vue Router) import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/lazy', component: () => import('../components/LazyComponent.vue'), // Lazy-loaded component }, // ... other routes ]; const router = createRouter({ history: createWebHistory(), routes, }) export default router; """ **Anti-pattern:** Deploying a development build with large bundle sizes without any optimization, which significantly degrades performance. ### 1.5. Standard: Implement Feature Flags **Do This:** * Use feature flags (also known as feature toggles) to enable or disable features in production. * Manage feature flags using a dedicated feature flag management tool (e.g., LaunchDarkly, ConfigCat) or a custom implementation. * Use environment variables or configuration files to define feature flag values. **Don't Do This:** * Release code with features directly enabled without a way to disable them. * Leave feature flag code in the codebase indefinitely after the feature is fully released. **Why:** Feature flags allow you to test new features in production before releasing them to all users, implement A/B testing, and quickly disable buggy features without deploying new code. **Example (Using a simple feature flag):** """vue <template> <div> <p v-if="showNewFeature">New Feature is enabled!</p> <button @click="performAction">Perform Action</button> </div> </template> <script> export default { data() { return { showNewFeature: process.env.VUE_APP_NEW_FEATURE_ENABLED === 'true', }; }, methods: { performAction() { if (this.showNewFeature) { // Execute new feature logic console.log('New feature logic executed'); } else { // Execute old logic console.log('Old logic executed'); } }, }, }; </script> """ **Anti-pattern:** Rolling out a new, untested feature to all users at once without any means to control the release or quickly disable the feature if issues arise. ## 2. Production Considerations ### 2.1. Standard: Use a CDN for Static Assets **Do This:** * Serve static assets (e.g., JavaScript, CSS, images, fonts) from a Content Delivery Network (CDN). * Configure the build process to upload assets to the CDN. * Use versioned filenames (e.g., "app.12345678.js") to leverage browser caching. **Don't Do This:** * Serve static assets directly from the application server. * Use overly aggressive caching strategies that prevent users from receiving updates. **Why:** CDNs reduce latency, improve page load times, and reduce the load on the application server. **Example (vue.config.js - Public Path and CDN Configuration):** """javascript // vue.config.js module.exports = { publicPath: process.env.NODE_ENV === 'production' ? 'https://cdn.example.com/your-app/' // Replace with your CDN URL : '/', // ... other options }; """ **Example (Uploading to CDN - AWS S3 with AWS CLI):** """bash # In CI/CD script after building the app aws s3 sync dist s3://your-s3-bucket/your-app/ --delete """ **Anti-pattern:** Serving large JavaScript bundles directly from the origin server without leveraging a CDN, leading to slow initial page load times for geographically dispersed users. ### 2.2. Standard: Implement Error Monitoring and Logging **Do This:** * Use an error monitoring service (e.g., Sentry, Rollbar, Bugsnag) to track JavaScript errors in production. * Implement client-side logging to capture user interactions and application state. * Send error reports to the error monitoring service. **Don't Do This:** * Ignore JavaScript errors in production. * Rely solely on server-side logs for debugging client-side issues. **Why:** Error monitoring helps you quickly identify and resolve issues in production, improving application stability. Detailed logs are essential to reproduce errors and find the root causes. **Example (Using Sentry):** """javascript // main.js or app.js import * as Sentry from "@sentry/vue"; import { BrowserTracing } from "@sentry/tracing"; import { createApp } from 'vue'; import App from './App.vue'; const app = createApp(App); Sentry.init({ app, dsn: "YOUR_SENTRY_DSN", integrations: [ new BrowserTracing({ tracePropagationTargets: ["localhost", "your-api.com", /^\/api/], }), ], // Set tracesSampleRate to 1.0 to capture 100% // of transactions for performance monitoring. // We recommend adjusting this value in production tracesSampleRate: 0.2, release: 'your-app@' + process.env.VUE_APP_VERSION, // Optional: add version information for easier tracking after deployment environment: process.env.NODE_ENV, // 'production', 'staging', etc. }); app.mount('#app') """ """vue //Example reporting error in a component <script> import * as Sentry from "@sentry/vue"; export default { methods: { async fetchData() { try { const response = await fetch(this.apiUrl); this.data = await response.json(); } catch (error) { Sentry.captureException(error); console.error("Error fetching data:", error); } }, }, }; </script> """ **Anti-pattern:** Failing to monitor client-side errors which leads to a degraded user experience and prevents proactive problem resolution. ### 2.3. Standard: Implement Performance Monitoring **Do This:** * Use a performance monitoring tool (e.g., New Relic, Datadog, Google PageSpeed Insights) to track key performance metrics (e.g., page load time, first contentful paint, time to interactive). * Set up alerts to notify you of performance regressions. * Regularly review performance metrics and identify areas for improvement using available reports and metrics (TTFB, FCP etc). **Don't Do This:** * Ignore performance issues until users complain. * Rely solely on manual testing to identify performance bottlenecks. **Why:** Performance monitoring helps you identify and resolve performance issues before they impact users, ensuring a smooth and responsive user experience. **Example (Google PageSpeed Insights):** Run PageSpeed Insights on your deployed URL and implement recommendations. **Example (Web Vitals):** Track Core Web Vitals (LCP, FID, CLS) using "web-vitals" library and report. """javascript import { getCLS, getFID, getLCP } from 'web-vitals'; function sendToAnalytics({ name, delta, id }) { // Replace with your analytics endpoint fetch('/analytics', { method: 'POST', body: JSON.stringify({ name, delta, id }), headers: { 'Content-Type': 'application/json', }, }); } getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics); """ **Anti-pattern:** Releasing updates without measuring impact on performance metrics leading to gradual degradation of user experience. ### 2.4. Standard: Implement Health Checks and Monitoring **Do This:** * Implement health checks in your backend application to provide information about its status. * Use a monitoring tool (e.g., Nagios, Prometheus, Grafana) to monitor the health of your application and infrastructure. * Set up alerts to notify you of any issues. **Don't Do This:** * Assume that your application is always healthy. * Rely solely on user reports to identify outages. **Why:** Health checks and monitoring allow you to proactively identify and resolve issues before they cause outages. **Example (Basic Health Check - Node.js Backend):** """javascript // Express.js route for health check app.get('/health', (req, res) => { res.status(200).send('OK'); }); """ **Anti-pattern:** Lack of monitoring means issues are found reactively and resolved slowly, impacting user experience. ### 2.5 Standard: Security Hardening **Do This:** * Implement security best practices as outlined by OWASP. * Use HTTPS for all communication and enforce HSTS headers. * Sanitize user-provided content carefully to prevent cross-site scripting. * Regularly update dependencies and assess known security vulnerabilities. **Don't Do This:** * Expose sensitive data via client-side code. * Store API keys or secrets in the application. **Why:** Following security guidelines helps protect against common attacks reducing risks of data breaches and reputational damage. **Example (Helmet.js for Node.js backend)** """javascript const helmet = require('helmet'); app.use(helmet()); """ **Example (Content Sanitization in Vue component):** """vue <template> <div v-html="sanitizedInput"></div> </template> <script> import DOMPurify from 'dompurify'; export default { data() { return { userInput: '<p>Hello <b>World</b></p><img src="javascript:alert(\'XSS\')">', }; }, computed: { sanitizedInput() { return DOMPurify.sanitize(this.userInput); }, }, }; </script> """ **Anti-pattern:** Allowing unsanitized user input, leading to XSS and related vulnerabilities. ## 3. Modern Approaches and Patterns ### 3.1. Standard: Server-Side Rendering (SSR) and Static Site Generation (SSG) **Do This:** * Consider using SSR (e.g., Nuxt.js) or SSG (e.g., Nuxt.js, VuePress) for improved SEO and initial page load time. * Optimize SSR and SSG builds for production. * Implement proper caching strategies for SSR applications. **Don't Do This:** * Use SSR without considering the increased complexity and server load. * Over-optimize SSG, generating unnecessary static pages. **Why:** SSR improves SEO and provides a faster initial page load. SSG is ideal for content-heavy websites requiring maximum performance at scale. **Example (Nuxt 3 - Server Side Rendering):** Nuxt is designed with SSR in mind, so by default, pages are server-side rendered. Configuration is handled primarily in "nuxt.config.js". """javascript // nuxt.config.js export default defineNuxtConfig({ ssr: true, // Enable Server Side Rendering (default) // ... other configuration }) """ **Example (Nuxt 3 - Static Site Generation):** """javascript // nuxt.config.js export default defineNuxtConfig({ ssr: false, // Disable Server Side Rendering, makes it a SPA, Needs "nuxt generate to created static content // ... other configuration }) """ To generate the static site run: "nuxt generate" **Anti-pattern:** Using a client-side rendered Vue app when SEO is critical. ### 3.2. Standard: Micro Frontend Architecture (if appropriate) **Do This:** * Consider using a micro frontend architecture for large, complex applications. * Choose an appropriate micro frontend strategy (e.g., build-time integration, run-time integration). * Use a component library or design system to ensure consistency across micro frontends. **Don't Do This:** * Introduce micro frontends for small or simple applications. * Create excessive inter-dependencies between micro frontends. **Why:** Micro frontends enable independent development and deployment of different parts of the application, improving team autonomy and velocity. **Example (Basic Concept):** Each team is responsible for its own Vue application or component that can be deployed and updated independently. These can then be combined using various strategies. This is a complex topic; researching different strategies is crucial. **Anti-pattern:** Implementing micro frontends prematurely or without clear organizational benefits. ### 3.3. Standard: Containerization (Docker) **Do This:** * Containerize your Vue.js app using Docker. * Use multi-stage builds for smaller images. * Use Docker Compose for development environments. **Don't Do This:** * Store data in containers. * Expose unnecessary ports. **Why:** Containerization simplifies deployment, ensures consistency across environments, isolates applications improving security and resource management. **Example (Dockerfile):** """dockerfile # Stage 1: Build the application FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build # Stage 2: Serve the application with Nginx FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] """ **Anti-pattern:** Deploying applications directly to servers without containerization leads to environmental inconsistencies and potential dependency conflicts.
# Performance Optimization Standards for Vue.js This document outlines coding standards and best practices specifically focused on performance optimization in Vue.js applications. Adhering to these guidelines will improve application speed, responsiveness, and resource utilization. It is designed to guide developers and be used as context for AI coding assistants. ## 1. General Principles ### 1.1. Prioritize Perceived Performance * **Do This:** Focus on optimizing the user experience. Load essential content quickly and use techniques like lazy loading and placeholders to improve perceived load times. * **Don't Do This:** Obsess over micro-optimizations that have minimal impact on the user’s experience. **Why:** Users care more about how quickly the application *feels* than the raw numbers. A slightly slower but smoother experience is often preferable to a technically faster but janky one. ### 1.2. Measure, Then Optimize * **Do This:** Use browser developer tools (Lighthouse, Performance tab) and Vue Devtools to identify performance bottlenecks before attempting to optimize. Use real-world data and user flows to guide your optimization efforts. * **Don't Do This:** Guess at what's slow and start refactoring without data. **Why:** Optimization efforts are wasted if they target areas that aren't genuinely impacting performance. Data-driven decisions are key. ### 1.3. Progressive Optimization * **Do This:** Start with high-level architectural improvements and then drill down to component-level optimizations. * **Don't Do This:** Get bogged down in optimizing individual lines of code before addressing fundamental architectural issues. **Why:** Architectural choices often have the most significant impact on performance. Optimize these first, and lower-level optimizations become more effective. ## 2. Architectural Considerations ### 2.1. Code Splitting * **Do This:** Use Vue Router's lazy loading capabilities and Webpack's dynamic imports to split your application into smaller chunks. This reduces the initial download size and improves startup time. """javascript // Vue Router example with lazy loading const Foo = () => import('./Foo.vue') const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] }) """ """javascript // Component-level lazy loading using dynamic import import { defineAsyncComponent } from 'vue' const AsyncExample = defineAsyncComponent(() => import('./components/Example.vue') ) export default { components: { AsyncExample } } """ * **Don't Do This:** Bundle the entire application into a single large JavaScript file. **Why:** Reduces initial load time by only downloading the code required for the current view. Improves Time to Interactive (TTI). ### 2.2. Server-Side Rendering (SSR) or Static Site Generation (SSG) * **Do This:** Consider SSR (with Nuxt.js) or SSG (with Nuxt.js or VitePress) for content-heavy or SEO-critical applications. """bash // Example using Nuxt.js npx create-nuxt-app my-nuxt-app """ * **Don't Do This:** Rely solely on client-side rendering for applications where SEO or initial load time is paramount. **Why:** SSR improves initial load time and SEO by rendering the page on the server. SSG pre-renders pages at build time, resulting in even faster load times. ### 2.3. Pre-rendering * **Do This:** Explore pre-rendering specific routes or components if SSR or SSG are not feasible. This allows you to generate static HTML for specific parts of your application. """javascript //Example with vite-plugin-prerender // Vite config import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { VitePWA } from "vite-plugin-pwa"; import Prerender from 'vite-plugin-prerender'; import { resolve } from 'path' export default defineConfig({ plugins: [ vue(), Prerender({ staticDir: resolve(__dirname, 'dist'), routes: [ '/', '/about', '/contact' // Add other routes here ], }), VitePWA() ], }) """ * **Don't Do This:** Assume client-side rendering is always the best option, especially for static content. **Why:** Pre-rendering optimizes initial load time for specific routes/components. ### 2.4. Efficient Routing * **Do This:** Structure your routes to minimize the number of components loaded on initial page load. Lazy-load less frequently accessed routes. """javascript //Example (Vue Router): const routes = [ { path: '/', component: Home }, { path: '/about', component: () => import('./components/About.vue') }, // Lazy-loaded ]; """ * **Don't Do This:** Load all routes and components upfront, even if they are not immediately needed. **Why:** Reduces initial load time and memory usage by loading routes on demand. ### 2.5. Tree-Shaking * **Do This:** Use ES module syntax ("import" and "export") to allow Webpack or Vite to effectively tree-shake unused code. """javascript // Correct: import { ref, computed } from 'vue'; // Incorrect (imports the entire Vue library): import Vue from 'vue'; // Avoid """ * **Don't Do This:** Use CommonJS ("require") syntax when possible. **Why:** Tree-shaking removes dead code, reducing the size of the final bundle. ## 3. Component-Level Optimization ### 3.1. Functional Components * **Do This:** Use functional components for simple, stateless components that only render UI based on props. They are faster to render than stateful components because they don't have a lifecycle. """vue <template functional> <div>{{ props.message }}</div> </template> <script> export default { props: ['message'] } </script> """ Note: in Vue 3, functional components should still use the "<template>" tag and are defined like regular components. The "functional" attribute is no longer necessary. """vue <template> <div>{{ message }}</div> </template> <script setup> defineProps({ message: { type: String, required: true } }) </script> """ * **Don't Do This:** Use stateful components when a functional component would suffice. **Why:** Functional components have a smaller memory footprint and faster rendering performance. ### 3.2. "v-once" Directive * **Do This:** Use the "v-once" directive for components or elements that only need to be rendered once. This prevents Vue from re-rendering them on subsequent updates. """vue <template> <div v-once> This content will only be rendered once. </div> </template> """ * **Don't Do This:** Omit "v-once" when rendering static content that never changes. **Why:** Prevents unnecessary re-renders, especially for complex static content. ### 3.3. "v-memo" Directive (Vue 3) * **Do This:** Use the "v-memo" directive when you have a component that only needs to re-render when certain dependencies change. This is more powerful than "v-once" and allows for selective updates. """vue <template> <div v-memo="[item.id, item.name]"> {{ item.name }} - {{ item.description }} </div> </template> <script setup> defineProps({ item: { type: Object, required: true } }) </script> """ * **Don't Do This:** Overuse "v-memo". The cost of dependency comparison can outweigh the benefits for simple components. Only use it when the component is expensive to re-render and the dependencies are well-defined. Also, if using "v-for", make sure "key" binding is performed on the outmost element of the list rendering. **Why:** Prevents unnecessary re-renders based on specified dependencies. Improves performance for complex component updates. ### 3.4. Computed Properties vs. Methods * **Do This:** Use computed properties for calculations that depend on reactive data. Computed properties are cached, so they only re-evaluate when their dependencies change. """javascript import { ref, computed } from 'vue'; const message = ref('Hello'); const reversedMessage = computed(() => message.value.split('').reverse().join('')); """ * **Don't Do This:** Use methods for calculations that rarely change or depend on reactive data. Methods are executed on every render. Use methods *only* when you need side effects or when the calculation is very simple. **Why:** Computed properties are cached, improving performance for frequently accessed, derived values. ### 3.5. Minimize Watchers * **Do This:** Use computed properties or event listeners to react to data changes whenever possible. * **Don't Do This:** Use watchers excessively. Watchers can trigger unnecessary computations and re-renders, especially if they are deeply nested. If you need to watch a deep property, use the "immediate: true" and "deep: true" options very sparingly, and only after careful consideration. **Why:** Watchers can be expensive. They should be used judiciously. ### 3.6. Optimize "v-for" Loops * **Do This:** Always use the "key" attribute when rendering lists with "v-for". The "key" attribute helps Vue track nodes efficiently during updates. The key should be unique and stable. Also use "v-for" with "<template>" tag instead of wrapping it around the list element. In this case you can add "v-if" conditional rendering to template element, reducing the number of rendered nodes. """vue <template> <ul> <template v-for="item in items" :key="item.id"> <li v-if="item.isVisible">{{ item.name }}</li> </template> </ul> </template> """ * **Don't Do This:** Omit the "key" attribute or use an index as the key. Using an index can lead to rendering issues and performance problems when the list changes. Also, avoid using "v-if" against the same element as the "v-for" directive. In some cases this could lead to performance problems. **Why:** The "key" attribute allows Vue to efficiently update and reorder list items. Index as a key anti-pattern recreates DOM elements unnecessarily. ### 3.7. Debouncing and Throttling * **Do This:** Use debouncing or throttling to limit the frequency of expensive operations, such as API calls or DOM updates, in response to rapid user input. """javascript import { debounce } from 'lodash-es'; // Or a similar utility export default { mounted() { this.expensiveOperation = debounce(this.expensiveOperation, 300); }, methods: { onInput(event) { this.expensiveOperation(event.target.value); }, expensiveOperation(value) { // Perform expensive operation here console.log('Performing expensive operation with:', value); } } } """ * **Don't Do This:** Execute expensive operations directly in response to every user input event. **Why:** Debouncing and throttling reduce the number of times expensive operations are executed, improving performance and responsiveness. ### 3.8. Virtualized Lists * **Do This:** Use a virtualized list component (e.g., "vue-virtual-scroller", "vue-virtual-list") when rendering large lists. Virtualized lists only render the visible items, significantly improving performance. """vue <template> <RecycleScroller class="scroller" :items="items" :item-size="24" key-field="id" > <template v-slot="{ item }"> {{ item.name }} </template> </RecycleScroller> </template> <script setup> import { RecycleScroller } from '@vueuse/vue-virtual-scroller' import '@vueuse/vue-virtual-scroller/dist/vue-virtual-scroller.css' const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: "Item ${i}" })) </script> <style scoped> .scroller { height: 300px; } </style> """ * **Don't Do This:** Render all list items at once, especially for large datasets. **Why:** Virtualized lists dramatically reduce the number of DOM nodes, improving performance for long lists. ### 3.9. Image Optimization * **Do This:** Optimize images by compressing them, using appropriate formats (WebP), and lazy loading them with "<img loading="lazy">". Consider using a dedicated image optimization service (e.g., Cloudinary, Imgix). """html <img src="optimized-image.webp" alt="Description" loading="lazy"> """ * **Don't Do This:** Use large, unoptimized images. **Why:** Image optimization reduces load times and bandwidth consumption. ### 3.10. Efficient Data Structures * **Do This:** Use appropriate data structures (e.g., Maps, Sets) for lookups and operations that require efficient performance. """javascript const myMap = new Map(); myMap.set('key1', 'value1'); console.log(myMap.get('key1')); // Efficient lookup """ * **Don't Do This:** Rely solely on arrays for all data storage and manipulation, especially when performance is critical. **Why:** Using the right data structure improves algorithmic efficiency. ### 3.11. Async Components * **DoThis:** Use async components when the component doesn't need to load right away. This helps break up initial bundles and improve performance. """javascript import { defineAsyncComponent } from 'vue' const MyComponent = defineAsyncComponent(() => { return new Promise((resolve) => { // Simulate a delay to mimic async loading setTimeout(() => { resolve({ template: '<div>I am an async component!</div>' }) }, 1000) }) }) """ * **Don't DoThis:** Import all the components eagerly when component may not be used during the page load. ### 3.12 Reduce Reactive Data * **Do This**: Avoid using "ref" or "reactive" on data that doesn't need to be reactive. For example, configuration data or static content. """javascript // Example of data that doesn't need to be reactive const config = { apiUrl: 'https://example.com/api', theme: 'dark' }; """ * **Don't Do This**: Use "ref" or "reactive" on everything by default. This can lead to unnecessary overhead as Vue will be tracking changes on this data needlessly. """javascript // Avoid making static configuration reactive const config = reactive({ apiUrl: 'https://example.com/api', theme: 'dark' }); """ **Why**: Each "ref" and "reactive" property is managed by Vue's reactivity system, which adds overhead. Reducing the number of reactive properties reduces this overhead and improves performance. ## 4. Data Fetching Optimization ### 4.1. Minimize API Requests * **Do This:** Batch multiple API requests into a single request whenever possible. Use GraphQL or other techniques to request only the data you need. * **Don't Do This:** Make multiple small API requests when a single request could retrieve the same data. **Why:** Reducing the number of API requests reduces network overhead and improves response times. ### 4.2. Caching * **Do This:** Implement caching strategies for API responses using browser storage (localStorage, sessionStorage, IndexedDB) or a dedicated caching library (e.g., "lru-cache"). Use HTTP caching headers on the server to leverage browser caching. """javascript // Example using localStorage for caching const cachedData = localStorage.getItem('apiData'); if (cachedData) { // Use cached data this.data = JSON.parse(cachedData); } else { // Fetch data from API fetch('/api/data') .then(response => response.json()) .then(data => { this.data = data; localStorage.setItem('apiData', JSON.stringify(data)); // Cache the data }); } """ * **Don't Do This:** Repeatedly fetch the same data from the API without caching. **Why:** Caching reduces network traffic and improves performance by serving data from local storage. ### 4.3. Pagination * **Do This:** Implement pagination for large datasets to avoid loading and rendering all data at once. * **Don't Do This:** Load and render entire datasets without pagination. **Why:** Pagination reduces the amount of data transferred and rendered, improving performance for large datasets. ### 4.4. Prefetching * **Do This:** Use prefetching techniques to load data or resources before they are needed. This can be done using "<link rel="prefetch">" or programmatically using JavaScript. * **Don't Do This:** Wait until the user navigates to a page or interacts with a component to load necessary data. **Why:** Prefetching improves perceived performance by loading resources in the background. ## 5. Build Optimization ### 5.1. Production Mode * **Do This:** Always build your application in production mode using the appropriate command (e.g., "vue-cli-service build --mode production", "vite build"). * **Don't Do This:** Deploy the development build to production. **Why:** Production builds are optimized for performance, including minification, tree-shaking, and dead code elimination. ### 5.2. Minification and Compression * **Do This:** Ensure that your build process automatically minifies JavaScript, CSS, and HTML files. Enable gzip or Brotli compression on your web server. * **Don't Do This:** Deploy unminified and uncompressed code to production. **Why:** Minification and compression reduce the size of files, improving load times. ### 5.3. Content Delivery Network (CDN) * **Do This:** Host static assets (JavaScript, CSS, images) on a CDN to improve load times for users in different geographic locations. * **Don't Do This:** Serve static assets from your application server, especially for high-traffic applications. **Why:** CDNs distribute content across multiple servers, reducing latency and improving load times for global users. ## 6. General JavaScript Performance Considerations ### 6.1. Avoid Memory Leaks * **Do This:** Carefully manage event listeners and timers, and ensure that they are properly cleaned up when components are destroyed. Use the "beforeDestroy" lifecycle hook to remove event listeners. Use the "onBeforeUnmount" hook in Vue 3. """vue <template> <div></div> </template> <script> export default { mounted() { window.addEventListener('resize', this.handleResize); }, beforeDestroy() { // Vue 2 window.removeEventListener('resize', this.handleResize); }, methods: { handleResize() { // Handle resize event } } } </script> """ """vue <template> <div></div> </template> <script setup> import { onBeforeUnmount } from 'vue'; let resizeHandler = () => { // handle resize event } window.addEventListener('resize', resizeHandler); onBeforeUnmount(() => { window.removeEventListener('resize', resizeHandler); }); </script> """ * **Don't Do This:** Forget to clean up event listeners and timers, leading to memory leaks and performance degradation. **Why:** Memory leaks can cause the application to consume more memory over time, leading to performance issues and crashes. ### 6.2. Optimize Loops * **Do This:** Optimize loops by minimizing DOM manipulations inside the loop and caching values that are used repeatedly. """javascript // Correct: const arrayLength = myArray.length; // Cache the length for (let i = 0; i < arrayLength; i++) { // ... } // Optimized loop with minimal DOM manipulations let innerHTML = ''; for (let i = 0; i < array.length; i++) { innerHTML += '<div>' + array[i] + '</div>'; } myElement.innerHTML = innerHTML; // Single DOM manipulation """ * **Don't Do This:** Perform frequent DOM manipulations inside loops, leading to performance bottlenecks. **Why:** Frequent DOM manipulations can be expensive. Minimize them by batching updates. ## 7. Reactive Performance Vue 3 is designed to greatly improve reactive performance using proxies. Still some rules apply to even modern versions of Vue 3: ### 7.1. Avoid Deeply Nested Reactive Objects * **Do This**: Try to keep reactivity as shallow as reasonably possible. Use reactive structures for direct properties that actively change within a component's lifecycle. * **Don't Do This**: Wrap complete complex hierarchies of static, relatively unchanging objects within "reactive" or "ref" without analyzing their change patterns. This comes with useless tracking costs. ### 7.2. Use "shallowRef" and "shallowReactive" Especially useful when dealing with large external libraries or objects. * **Do This**: Use "shallowRef" and "shallowReactive", when you know that you only need to track changes on the top level properties. This will reduce the overhead related to deep reactivity tracking across all the properties. It is mostly useful when you deal with large objects that have a lot of primitive data, but you're not interested to deeply watch and re-render nested properties changes. """javascript import { shallowReactive } from 'vue' const state = shallowReactive({ nested: { count: 0 } }) // mutating the property in place WILL trigger updates state.nested.count++ // replacing the property WILL NOT trigger updates state.nested = { count: 1 } """ * **Don't Do This**: Default to 'reactive' without understanding the nature of data updates within your component. ### 7.3 De-Reference "ref"s Properly * **Do This**: Keep in mind a "ref" contains a ".value". Make sure you are dereferencing "ref"s inside of templates or computed properties or "setup()". * **Don't Do This**: Directly pass around an un-referenced "ref"; it leads to confusion and potential mis-handling downstream. ## 8. Tooling and Monitoring ### 8.1. Vue Devtools * **Do This:** Use Vue Devtools to profile component rendering performance and identify bottlenecks. * **Don't Do This:** Neglect to use Vue Devtools for performance analysis. **Why:** Vue Devtools provides valuable insights into component rendering and data flow. ### 8.2. Browser Developer Tools * **Do This:** Use browser developer tools (Lighthouse, Performance tab) to measure overall application performance and identify areas for improvement. * **Don't Do This:** Rely solely on Vue Devtools for performance analysis. **Why:** Browser developer tools provide a holistic view of application performance, including network requests, rendering, and JavaScript execution. ### 8.3. Performance Monitoring Tools * **Do This:** Integrate performance monitoring tools (e.g., Sentry, New Relic) to track real-world performance metrics and identify performance issues in production. * **Don't Do This:** Ignore performance issues in production until users complain. **Why:** Performance monitoring tools provide visibility into real-world application performance, allowing you to proactively identify and address performance issues. By following these performance optimization standards, you can build Vue.js applications that are fast, responsive, and efficient. Remember to prioritize perceived performance, measure before optimizing, and progressively optimize from the architecture down to the component level. Regularly monitor your application's performance in production and adapt your optimization strategies as needed.
# Testing Methodologies Standards for Vue.js This document outlines the testing methodologies standards for Vue.js applications. It provides guidelines for unit, integration, and end-to-end testing, with a focus on best practices for maintainability, performance, and security. All examples will reflect modern Vue.js syntax and patterns with a focus on Vue 3 and its ecosystem. ## 1. General Testing Principles ### 1.1 Write Testable Code **Do This:** Design components with testability in mind. This means keeping components small, focused, and loosely coupled. Use dependency injection where appropriate to mock dependencies during testing. **Don't Do This:** Create large, monolithic components that are difficult to isolate and test. Avoid tight coupling between components, as this makes it harder to mock dependencies. **Why:** Testable code is easier to understand, maintain, and refactor. It also leads to more comprehensive and reliable tests. **Example:** """vue // Good: Testable component with dependency injection <template> <div>{{ message }}</div> </template> <script> import { inject, ref, onMounted } from 'vue'; export default { setup() { const apiService = inject('apiService'); const message = ref(''); onMounted(async () => { message.value = await apiService.fetchMessage(); }); return { message }; } }; </script> // Bad: Hard to test component with tight coupling <template> <div>{{ message }}</div> </template> <script> import { ref, onMounted } from 'vue'; import apiService from './apiService'; // Tight coupling export default { setup() { const message = ref(''); onMounted(async () => { message.value = await apiService.fetchMessage(); }); return { message }; } }; </script> """ ### 1.2 Test-Driven Development (TDD) **Do This:** Write tests before writing the code. Write a failing test first, then implement the functionality to make the test pass. **Don't Do This:** Write tests after the code is written. This often leads to tests that are less comprehensive and may miss edge cases. **Why:** TDD helps to ensure that code meets requirements and encourages design that is testable from the outset. ### 1.3 Aim for High Test Coverage **Do This:** Strive for a high level of test coverage (e.g., 80% or higher). Use code coverage tools to identify areas of code that are not covered by tests. **Don't Do This:** Assume that code is well-tested simply because you have some tests in place. Neglect code coverage analysis. **Why:** High test coverage reduces the risk of bugs and improves the overall quality of the software. ### 1.4 Write Clear and Concise Tests **Do This:** Write tests in a clear, concise, and readable manner. Use descriptive names for tests and assertions. Avoid complex logic within tests. **Don't Do This:** Write tests that are difficult to understand or maintain. Use vague or ambiguous test names. **Why:** Clear tests are easier to maintain and debug. They also serve as documentation for the code they are testing. ### 1.5 Automate Tests **Do This:** Integrate tests into the development workflow using tools like CI/CD pipelines which automatically runs test suites. **Don't Do This:** Manually run tests. This is time-consuming and error-prone. **Why:** Automated tests ensure that tests are run consistently and frequently, reducing the risk of regressions. ## 2. Unit Testing ### 2.1 Component Isolation **Do This:** Unit test components in isolation, mocking out dependencies, the "$emit" function and external services. This is typically done with libraries like Jest and Vue Testing Library. **Don't Do This:** Unit test components in combination when a unit test suffices. This often leads to brittle tests that are more prone to failure. **Why:** Isolation helps to focus tests on specific components, making them more reliable and easier to maintain. **Example:** """javascript // Unit test for a component using Vue Testing Library and Jest import { render, screen, fireEvent } from '@testing-library/vue'; import MyButton from './MyButton.vue'; describe('MyButton', () => { it('renders the button with the correct text', () => { render(MyButton, { props: { label: 'Click me' } }); expect(screen.getByText('Click me')).toBeInTheDocument(); }); it('emits an event when clicked', async () => { const { emitted } = render(MyButton, { props: { label: 'Click me' } }); const button = screen.getByText('Click me'); await fireEvent.click(button); expect(emitted().click).toBeTruthy(); }); }); """ ### 2.2 Testing Props and Events **Do This:** Verify that components render correctly based on the props they receive. Ensure that events which emit are done so with appropriate payloads. **Don't Do This:** Neglect testing how different prop values affect rendering or if events are missing required data. **Why:** Props and events are the primary way components communicate. Accurate testing of these interfaces is crucial. **Example:** """javascript // Testing props and emitted events import { render, screen, fireEvent } from '@testing-library/vue'; import MyComponent from './MyComponent.vue'; describe('MyComponent', () => { it('renders with the correct title based on the title prop', () => { render(MyComponent, { props: { title: 'Hello World' } }); expect(screen.getByText('Hello World')).toBeInTheDocument(); }); it('emits a "custom-event" with the correct payload when clicked', async () => { const { emitted } = render(MyComponent); const button = screen.getByRole('button'); await fireEvent.click(button); expect(emitted()['custom-event'][0][0]).toEqual({ message: 'Button clicked' }); // Verifies the event and payload }); }); """ ### 2.3 Testing Computed Properties **Do This:** Test computed properties to make sure they computed correctly on component data. **Don't Do This:** Directly test the component data if the outcome is through a computed property. **Why:** Computed properties are central to reactivity and ensuring they function correctly is important for reliable state management. **Example:** """javascript // Testing a component with a computed property import { render, screen } from '@testing-library/vue'; import MyComponent from './MyComponent.vue'; describe('MyComponent', () => { it('displays the full name correctly when first and last name props are provided', () => { render(MyComponent, { props: { firstName: 'John', lastName: 'Doe', }, }); expect(screen.getByText('Full Name: John Doe')).toBeInTheDocument(); }); it('displays a default message when either first or last name is missing', () => { render(MyComponent, { props: { firstName: 'John', lastName: '', }, }); expect(screen.getByText('Full Name: N/A')).toBeInTheDocument(); }); }); """ ### 2.4 Mocking External Dependencies **Do This:** Use mocking libraries (e.g., Jest mocks) to isolate the component and control the behavior of external dependencies like API requests or external libraries. The "vi" library is rising in popularity. **Don't Do This:** Rely on real API endpoints or external services during unit testing. **Why:** Mocking external dependencies allows you to test the component's logic without being affected by external factors. **Example:** """javascript // Mocking an API call using Jest mocks import { render, screen, waitFor } from '@testing-library/vue'; import MyComponent from './MyComponent.vue'; import apiService from './apiService'; jest.mock('./apiService'); describe('MyComponent', () => { it('fetches and displays data correctly', async () => { // Mock the API response apiService.fetchData.mockResolvedValue({ id: 1, name: 'Test Data' }); render(MyComponent); // Wait for the data to load and be displayed await waitFor(() => { expect(screen.getByText('Name: Test Data')).toBeInTheDocument(); }); expect(apiService.fetchData).toHaveBeenCalledTimes(1); }); it('handles errors correctly', async () => { // Mock the API to reject with an error apiService.fetchData.mockRejectedValue(new Error('API Error')); render(MyComponent); // Wait for the error message to be displayed await waitFor(() => { expect(screen.getByText('Error: API Error')).toBeInTheDocument(); }); }); }); """ ### 2.5 Testing Vuex/Pinia Actions and Getters (If Applicable) **Do This:** Unit test Vuex/Pinia actions and getters in isolation to ensure they perform as expected. **Don't Do This:** Neglect to test Vuex/Pinia actions and getters. **Why:** Vuex/Pinia actions and getters are responsible for managing the application state. Testing them ensures the accuracy of state management is maintained. """javascript // Pinia action and getter testing example using Jest import { createPinia, setActivePinia } from 'pinia'; import { useMyStore } from './store'; // Assuming store.js exports useMyStore describe('MyStore', () => { beforeEach(() => { // creates a fresh pinia and make it active so it's automatically picked // up by any useStore() call without having to pass it to it: // "useStore(pinia)" setActivePinia(createPinia()); }); it('should update the count when increment is called', () => { const store = useMyStore(); store.increment(); expect(store.count).toBe(1); }); it('should decrement the count when decrement is called', () => { const store = useMyStore(); store.decrement(); expect(store.count).toBe(-1); }); it('should return the correct evenOrOdd value', () => { const store = useMyStore(); // Initial count value is 0 expect(store.evenOrOdd).toBe('even'); store.increment(); // Count becomes 1 expect(store.evenOrOdd).toBe('odd'); }); }); """ ## 3. Integration Testing ### 3.1 Component Interactions **Do This:** Integration test how components interact with each other in a realistic scenario by rendering multiple components together. **Don't Do This:** Assume components work together correctly based solely on unit tests. Avoid testing how components respond to different user interactions. **Why:** Component interactions reveal bugs that may not be apparent during unit testing. **Example:** """javascript // Integration testing of two components import { render, screen, fireEvent } from '@testing-library/vue'; import ParentComponent from './ParentComponent.vue'; import ChildComponent from './ChildComponent.vue'; describe('ParentComponent', () => { it('updates the message in ParentComponent when button in ChildComponent is clicked', async () => { render(ParentComponent); const button = screen.getByText('Click me'); // Confirm the initial value of messageInParent expect(screen.getByText('Message from parent: Initial Message')).toBeInTheDocument(); await fireEvent.click(button); // Confirm that the message in ParentComponent has been updated expect(screen.getByText('Message from parent: Message from child')).toBeInTheDocument(); }); }); """ ### 3.2 Vue Router Navigation **Do This:** Test navigation guards and how routes transitions work in your application using "vue-router". Mock "$router" calls or use navigation APIs directly. **Don't Do This:** Neglect to ensure route parameters are passed correctly and components are rendered post-navigation. **Why:** Navigation is crucial in most SPAs for usability. This ensures core user flows are not broken by navigation issues. **Example:** """javascript // Integration testing with Vue Router import { createMemoryHistory, createRouter } from 'vue-router'; import { render, screen, fireEvent, waitFor } from '@testing-library/vue'; import MyComponent from './MyComponent.vue'; const routes = [ { path: '/', component: { template: '<div>Home</div>' } }, { path: '/about', component: { template: '<div>About</div>' } }, ]; const router = createRouter({ history: createMemoryHistory(), routes, }); describe('MyComponent with Router', () => { it('navigates to the "/about" route when the about link is clicked', async () => { router.push('/'); // Start at the home route render(MyComponent, { global: { plugins: [router], }, }); // Get the router link for /about const aboutLink = screen.getByRole('link', { name: 'About' }); // Fire the click event await fireEvent.click(aboutLink); // Wait for the navigation to complete before checking the DOM await waitFor(() => { expect(router.currentRoute.value.path).toBe('/about'); expect(screen.getByText('About')).toBeInTheDocument(); }); }); }); """ ### 3.3 API Calls **Do This:** Test if components correctly fetch and process data from external APIs using tools like "axios-mock-adapter". **Don't Do This:** Call real APIs during testing. This can slow down the testing process and may lead to inconsistent results. **Why:** Correct data handling is critical for application functionality. Validating API use enhances reliability. **Example:** """javascript // Integration testing with a mocked Axios API import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { render, screen, waitFor } from '@testing-library/vue'; import MyComponent from './MyComponent.vue'; const mock = new MockAdapter(axios); describe('MyComponent with API', () => { afterEach(() => { mock.reset(); // Reset the mock adapter after each test }); it('fetches and displays data correctly from a mock API', async () => { // Define the API URL and the mock response data const apiUrl = '/api/data'; const mockData = { id: 1, name: 'Mocked Data' }; // Set up the mock to intercept the API call and return mock data mock.onGet(apiUrl).reply(200, mockData); render(MyComponent); // Wait for the data to load and be displayed await waitFor(() => { expect(screen.getByText('Name: Mocked Data')).toBeInTheDocument(); }); }); it('handles errors gracefully when the API call fails', async () => { const apiUrl = '/api/data'; mock.onGet(apiUrl).reply(500, { message: 'Server Error' }); render(MyComponent); await waitFor(() => { expect(screen.getByText('Error: Request failed with status code 500')).toBeInTheDocument(); }); }); }); """ ### 3.4 Vuex/Pinia Store Interactions (If Applicable) **Do This:** Test how components interact with the Vuex/Pinia store including interactions with state, mutations and actions. **Don't Do This:** Neglect testing how components respond to state changes or how actions are dispatched in reaction to user actions. **Why:** Store validation ensures consistent and correct behaviour around store dispatches. **Example:** """javascript // Testing component interactions with Pinia store import { createPinia, setActivePinia } from 'pinia'; import { render, screen, fireEvent } from '@testing-library/vue'; import MyComponent from './MyComponent.vue'; import { useMyStore } from './store'; // Assuming store.js exports useMyStore describe('MyComponent with Pinia Store', () => { beforeEach(() => { // creates a fresh pinia and make it active so it's automatically picked // up by any useStore() call without having to pass it to it: // "useStore(pinia)" setActivePinia(createPinia()); }); it('displays the count from the store and dispatches an action when clicked', async () => { render(MyComponent); const store = useMyStore(); // Initial Count expect(screen.getByText('Count: 0')).toBeInTheDocument(); // Increment count when Increment Button is clicked const incrementButton = screen.getByText('Increment'); await fireEvent.click(incrementButton); expect(screen.getByText('Count: 1')).toBeInTheDocument(); // Decrement count when Decrement Button is clicked const decrementButton = screen.getByText('Decrement'); await fireEvent.click(decrementButton); expect(screen.getByText('Count: 0')).toBeInTheDocument(); // Ensure action was dispatched the right number of times expect(store.increment).toHaveBeenCalledTimes(1); expect(store.decrement).toHaveBeenCalledTimes(1); }); }); """ ## 4. End-to-End (E2E) Testing ### 4.1 Realistic User Flows **Do This:** Emulate real user flows using tools like Cypress or Playwright. Login, navigate, interact with forms, and more. **Don't Do This:** Write E2E tests that are too narrow or do not mirror real user interactions. Avoid testing basic UI elements without a complete scenario. **Why:** E2E confirms the application works as expected from a user's perspective, covering multiple layers of the application. **Example (Cypress):** """javascript // End-to-end test using Cypress describe('User Login Flow', () => { it('allows a user to log in and view the dashboard', () => { cy.visit('/login'); cy.get('input[name=email]').type('test@example.com'); cy.get('input[name=password]').type('password123'); cy.get('button[type=submit]').click(); cy.url().should('include', '/dashboard'); cy.contains('Welcome to the Dashboard').should('be.visible'); }); it('displays an error message for invalid login credentials', () => { cy.visit('/login'); cy.get('input[name=email]').type('invalid@example.com'); cy.get('input[name=password]').type('wrongpassword'); cy.get('button[type=submit]').click(); cy.contains('Invalid credentials').should('be.visible'); }); }); """ ### 4.2 Cross-Browser Testing **Do This:** Run E2E tests across multiple browsers (Chrome, Firefox, Safari, Edge) and ensure consistent application behaviour. **Don't Do This:** Test only in the primary development browser. **Why:** Cross-browser testing uncovers rendering and compatibility issues that may exist only on specific browsers. ### 4.3 Testing API Integrations **Do This:** Verify UI components correctly interact with backend services and handle real-world data scenarios. **Don't Do This:** Skip testing API integrations within E2E tests. **Why:** Full API interaction scenarios demonstrate the entire application stack works harmoniously. ### 4.4 Accessibility Testing **Do This:** Check that the application is accessible to users with disabilities. Use tools like axe-core integrated in Cypress/Playwright. **Don't Do This:** Neglect accessibility checks in E2E tests. **Why:** To guarantee your app is inclusive and works for all users. ## 5. Tools and Libraries * **Unit Testing:** Jest, Vue Test Utils, Vue Testing Library, Vitest * **Integration Testing:** Vue Test Utils, Vue Testing Library, Cypress Component Testing, Vitest * **End-to-End Testing:** Cypress, Playwright * **Mocking:** Jest Mocks, Mocked.js, Axios Mock Adapter, Vi * **Assertion Libraries:** Jest Expect, Chai, Vitest expect * **Code Coverage:** Istanbul, c8 This document provides a solid foundation for establishing testing standards in Vue.js projects. Following these standards diligently will lead to more robust, reliable, and maintainable code.