# Tooling and Ecosystem Standards for Vue.js
This document outlines the standards for tooling and ecosystem usage in Vue.js projects. It's designed to guide developers in selecting and utilizing the most effective tools and libraries to build maintainable, performant, and secure Vue.js applications.
## 1. Project Setup and Build Tools
### 1.1. Recommended Tooling
* **Do This:** Use Vue CLI or Vite for project scaffolding. These tools provide opinionated setups with best practices baked in, significantly reducing configuration overhead.
* "Vue CLI": While historically popular, prefer Vite for new projects due to faster development and build times. Still suitable for maintaining existing projects.
* "Vite": The recommended choice for new Vue.js projects. It offers significantly faster cold start and build times due to its ES module-based development server. It also streamlines the build process using Rollup.
* **Don't Do This:** Avoid manually configuring Webpack from scratch unless highly customized configurations are absolutely necessary. Starting from a barebones configuration is time-consuming and introduces a higher risk of misconfiguration.
* **Why:** Using Vue CLI or Vite ensures a consistent project structure, simplifies dependency management, and improves developer experience.
### 1.2. Project Structure
* **Do This:** Maintain a consistent and logical project structure. For example:
"""
my-vue-project/
├── src/
│ ├── assets/ # Static assets (images, fonts, etc.)
│ ├── components/ # Reusable Vue components
│ ├── views/ # Page-level components (routes)
│ ├── App.vue # Root component
│ ├── router/ # Vue Router configuration
│ │ └── index.js
│ ├── store/ # Vuex/Pinia store (state management)
│ │ └── index.js
│ ├── plugins/ # Vue Plugins
│ │ └── index.js
│ ├── services/ # API Services
│ │ └── api.js
│ └── main.js # Entry point
├── public/ # Static assets served as-is
│ └── index.html
├── .env # Environment variables
├── vite.config.js # Vite configuration
├── package.json # Dependencies and scripts
└── README.md
"""
* **Don't Do This:** Scatter files randomly throughout the project. This leads to poor organization and difficulty in finding specific code.
* **Why:** A clear project structure makes it easy to navigate, understand, and maintain the codebase.
### 1.3. Configuration
* **Do This:** Utilize environment variables for configuration, especially for sensitive information or settings that vary between environments (development, staging, production).
"""javascript
// .env
VITE_API_BASE_URL=https://api.example.com
VITE_APP_NAME=MyAwesomeApp
// In a Vue component
const apiUrl = import.meta.env.VITE_API_BASE_URL;
const appName = import.meta.env.VITE_APP_NAME;
console.log(apiUrl, appName);
"""
* **Don't Do This:** Hardcode configuration values directly into the source code. This makes it difficult to change settings without modifying the code and potentially introducing errors.
* **Why:** Environment variables improve security and flexibility by separating configuration from code.
### 1.4 Script Aliases
* **Do This:** Define and use script aliases within "package.json".
"""json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx src/",
"format": "prettier --write src/"
}
}
"""
* **Don't Do This:** Use long, complicated commands directly in the terminal.
* **Why:** Aliases allow for easy execution of complex commands improving consistency across the team.
## 2. Dependency Management
### 2.1. Package Manager
* **Do This:** Use npm, yarn, or pnpm for dependency management. "pnpm" is generally preferred for its space efficiency and speed through the use of hard links.
* **Don't Do This:** Manually download and include library files directly.
* **Why:** Package managers automate dependency installation, versioning, and updates, ensuring a consistent and reproducible development environment.
### 2.2. Versioning
* **Do This:** Use semantic versioning (SemVer) for your own packages and specify version ranges in "package.json" using appropriate prefixes ("^" for compatible updates, "~" for bug fixes). Consider tools like "renovatebot" or "dependabot" for automated dependency updates.
"""json
{
"dependencies": {
"vue": "^3.0.0",
"axios": "~1.0.0"
},
"devDependencies": {
"eslint": "^8.0.0"
}
}
"""
* **Don't Do This:** Pin dependencies to exact versions unless there is a specific reason to do so. Avoid using "*" for versions, as this can lead to unpredictable behavior.
* **Why:** Versioning ensures compatibility and allows for controlled updates to dependencies.
### 2.3. Avoiding Dependency Bloat
* **Do This:** Carefully evaluate all dependencies before adding them to the project. Consider if the functionality can be implemented without adding a new library. Use tools like "webpack-bundle-analyzer" or "rollup-plugin-visualizer" to identify large dependencies. Tree-shake unused code.
* **Don't Do This:** Add dependencies without understanding their impact on the project's bundle size and performance.
* **Why:** Reducing unnecessary dependencies improves the application's loading time and overall performance.
## 3. State Management
### 3.1. Recommended Libraries
* **Do This:** Use Pinia for state management in Vue 3 projects. Pinia offers a simpler API and better TypeScript support compared to Vuex and is now the officially recommended state management solution. Vuex is an acceptable alternative in existing projects, especially larger, more complex state management needs.
* **Don't Do This:** Use Vuex for new Vue 3 projects unless there's a compelling reason to do so (e.g., integration with existing Vuex-based codebase). Rely on "this.$root.$data" or simple prop drilling for complex state management needs.
* **Why:** Pinia simplifies state management and provides a more intuitive API, especially for smaller to medium-sized applications.
### 3.2. Pinia Usage
* **Do This:** Define stores in separate files and use actions to mutate the state. Consider using plugins for cross-cutting concerns and composables to share logic between components and stores.
"""javascript
// 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++
},
},
})
// Component
import { useCounterStore } from '@/store/counter'
import { storeToRefs } from 'pinia'
export default {
setup() {
const counter = useCounterStore()
const { count, doubleCount } = storeToRefs(counter) // Destructure state with storeToRefs for reactivity
return {
count,
doubleCount,
increment: counter.increment,
}
},
}
"""
* **Don't Do This:** Mutate the state directly within components. This makes it harder to track state changes and debug issues. Avoid mixing state logic within components when global state makes more sense.
* **Why:** Using actions and getters enforces a clear separation of concerns and enhances maintainability. "storeToRefs" mantains the reactivity when destructuring the store.
## 4. Routing
### 4.1. Recommended Library
* **Do This:** Use Vue Router for handling application routing.
* **Don't Do This:** Implement your own routing solution or rely on browser's default navigation, especially in SPAs.
* **Why:** Vue Router provides a declarative and flexible way to manage client-side routing in Vue.js applications.
### 4.2. Router Configuration
* **Do This:** Define routes in a separate file and use named routes for navigation. Use lazy-loaded components for route definitions where ever possible to improve the initial load time.
"""javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/HomeView.vue'), // Lazy-loaded component
},
{
path: '/about',
name: 'About',
component: () => import('../views/AboutView.vue'), // Lazy-loaded component
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL), // using modern history mode
routes,
})
export default router
"""
"""vue
About
"""
* **Don't Do This:** Hardcode paths directly in components when using "router-link" or "router.push". Forget to include "process.env.BASE_URL" in history mode to properly resolve paths when deploying to a subdirectory.
* **Why:** Named routes improve code readability and make it easier to refactor routes without breaking navigation. Lazy loading improves initial page load performance.
### 4.3. Navigation Guards
* **Do This:** Use navigation guards (e.g., "beforeEach", "beforeRouteEnter") to protect routes and handle authentication.
"""javascript
// router/index.js
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
"""
* **Don't Do This:** Implement authentication logic directly within components. Neglect to handle edge cases or provide proper error handling in navigation guards.
* **Why:** Navigation guards ensure that only authorized users can access certain routes and allow for centralized authentication handling.
## 5. API Communication
### 5.1. Recommended Library
* **Do This:** Use Axios or "fetch" API for making HTTP requests. Axios is preferred for its features like automatic JSON transformation, request cancellation, and interceptors. "fetch" api is acceptable where you prefer being dependency free.
* **Don't Do This:** Use jQuery's "$.ajax" or other outdated HTTP libraries.
* **Why:** Axios and "fetch" offers a modern and feature-rich API for handling HTTP requests.
### 5.2. API Service
* **Do This:** Create an API service module to encapsulate API requests. This promotes reusability and simplifies maintenance.
"""javascript
// services/api.js
import axios from 'axios'
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
})
export default {
getPosts() {
return apiClient.get('/posts')
},
createPost(data) {
return apiClient.post('/posts', data)
},
}
// In a Vue component
import api from '@/services/api'
export default {
mounted() {
api.getPosts()
.then(response => {
this.posts = response.data
})
.catch(error => {
console.error(error)
})
},
}
"""
* **Don't Do This:** Make API requests directly within components without any abstraction. Repeat API requests in components.
* **Why:** An API service encapsulates API logic, making it easier to maintain, test, and reuse API calls throughout the application.
### 5.3. Error Handling
* **Do This:** Implement proper error handling for API requests. Use "try...catch" blocks or Axios interceptors to catch errors and display user-friendly messages.
"""javascript
// services/api.js
apiClient.interceptors.response.use(
(response) => response,
(error) => {
// Log the error
console.error("API Error:", error);
// Optionally, display a global error message using Pinia or a custom event
// For example, dispatch an action to your notification store:
// useNotificationStore().showError(error.message);
// Reject the promise to propagate the error to the component that made the request
return Promise.reject(error);
}
);
"""
* **Don't Do This:** Ignore errors or rely on default error messages.
* **Why:** Proper error handling improves the user experience and provides valuable insights into application issues.
## 6. Component Libraries and UI Frameworks
### 6.1. Recommended Libraries
* **Do This:** Consider using a component library like Vuetify, Element Plus, or Ant Design Vue for building consistent and visually appealing UIs. Evaluate each library based on project requirements, design preferences, and community support. For simple UI elements, building custom components remains a viable and performant option.
* **Don't Do This:** Use multiple component libraries in the same project without a clear justification. This leads to CSS conflicts and increased bundle size. Use a component library when it is not needed.
* **Why:** Component libraries provide pre-built, reusable components that accelerate development and ensure a consistent look and feel.
### 6.2. Customization
* **Do This:** Customize component library styles using CSS variables or themes to match the application's branding. Respect the intended use of components library and understand the implications of overriding styles.
* **Don't Do This:** Modify the internal styles of component libraries directly (e.g., by directly targeting CSS classes). Use "!important" excessively.
* **Why:** CSS variables and themes allow for consistent customization without breaking the component library's structure.
## 7. Testing
### 7.1. Recommended Libraries
* **Do This:** Use Jest, Vitest, or Mocha with Vue Test Utils for unit testing Vue components. Vitest is recommended for projects using Vite for a seamless integration. Cypress or Playwright for end-to-end (E2E) testing.
* **Don't Do This:** Skip testing altogether or rely solely on manual testing.
* **Why:** Testing ensures the quality and reliability of the application.
### 7.2. Unit Testing
* **Do This:** Write unit tests for components, composables, and utility functions. Use test-driven development (TDD) to guide development. Aim for high test coverage.
* **Don't Do This:** Write brittle tests that are tightly coupled to implementation details. Test implementation specific functionality instead of the output.
* **Why:** Unit tests isolate components and modules, making it easier to identify and fix bugs.
### 7.3. E2E Testing
* **Do This:** Write E2E tests to verify the application's functionality from a user's perspective. Focus on testing critical user flows.
* **Don't Do This:** Neglect E2E testing or rely solely on unit tests. Write E2E tests that are too granular or test implementation details.
* **Why:** E2E tests ensure that the application works as expected in a real-world environment.
## 8. Linting and Formatting
### 8.1. Recommended Libraries
* **Do This:** Use ESLint with the "eslint-plugin-vue" plugin for linting Vue.js code. Use Prettier for code formatting. Integrate linting and formatting into the development workflow (e.g., using Git hooks or CI/CD pipelines).
* **Don't Do This:** Ignore linting warnings or rely on manual code formatting.
* **Why:** Linting and formatting tools enforce consistent code style and help identify potential errors.
### 8.2. Configuration
* **Do This:** Configure ESLint and Prettier to match the project's coding style. Use a shareable configuration (e.g., "eslint-config-standard", "eslint-config-airbnb") as a starting point.
* **Don't Do This:** Override linting rules without a clear justification. Use conflicting linting and formatting rules.
* **Why:** Consistent configuration ensures a uniform code style across the project.
### 8.3. Git Hooks
* **Do This:** Use Husky and lint-staged to automatically run linters and formatters before committing code.
"""json
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,vue}": [
"eslint --fix",
"prettier --write",
"git add"
]
}
}
"""
* **Don't Do This:** Skip Git hooks or allow commits with linting errors.
* **Why:** Git hooks prevent developers from committing code that violates the project's coding style.
## 9. Internationalization (i18n)
### 9.1. Recommended Libraries
* **Do This:** Use vue-i18n for internationalization. For Vue 3, use "vue-i18n@v9".
* **Don't Do This:** Hardcode text directly in components or implement your own i18n solution.
* **Why:** "vue-i18n" provides a comprehensive solution for managing translations in Vue.js applications.
### 9.2. Usage
* **Do This:** Store translations in separate files and use the "$t" function or "" component to access translations.
"""javascript
// i18n/index.js
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {
en: {
message: {
hello: 'hello world',
},
},
fr: {
message: {
hello: 'bonjour monde',
},
},
},
})
export default i18n
// In Vue component
"""
* **Don't Do This:** Duplicate translations or use inconsistent translation keys.
* **Why:** Centralized translation management ensures consistency and simplifies updates.
## 10. Accessibility (a11y)
### 10.1. Standards
* **Do This:** Follow accessibility guidelines (e.g., WCAG) to ensure that the application is usable by people with disabilities. Use semantic HTML elements, provide alternative text for images, and ensure sufficient color contrast. Use tools like "vue-axe" for automated accessibility audits.
* **Don't Do This:** Ignore accessibility or rely solely on visual design.
* **Why:** Accessibility improves the user experience for everyone and ensures compliance with legal requirements.
### 10.2. Implementation
* **Do This:** Use appropriate ARIA attributes to enhance accessibility for complex components. Test the application with screen readers.
"""vue
Menu
"""
* **Don't Do This:** Use ARIA attributes incorrectly or rely solely on ARIA attributes without using semantic HTML.
* **Why:** ARIA attributes provide additional information to assistive technologies, making the application more accessible.
## Conclusion
Adhering to these tooling and ecosystem standards will help developers build robust, maintainable, performant, and accessible Vue.js applications. Remember that these are guidelines, and specific project requirements may necessitate deviations. However, any deviations should be carefully considered and documented.
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'
# API Integration Standards for Vue.js This document outlines the coding standards for API integration in Vue.js applications. Adhering to these standards will improve the maintainability, performance, security, and overall quality of your Vue.js projects. These standards are informed by the latest Vue.js best practices and are designed to work seamlessly with modern tooling and workflows. ## 1. Architectural Patterns for API Integration ### 1.1. Centralized API Service Layer **Standard:** Implement a centralized API service layer comprised of dedicated modules or classes. These modules encapsulate the logic for making API requests, handling responses, and managing errors. Avoid direct API calls within components. **Why:** * **Maintainability:** Decouples components from API details, allowing for easier changes to API endpoints or data structures without affecting UI code. * **Testability:** Facilitates unit testing of API interaction logic independent of Vue components. * **Reusability:** Promotes reuse of API logic across multiple components. * **Centralized Error Handling**: Allows better error management and reduces code duplication. **Do This:** Create dedicated API service using composables or classes. **Don't Do This:** Embed API calls directly within Vue components. **Code Example (Composable approach):** """javascript // src/composables/useApi.js import { ref } from 'vue'; import axios from 'axios'; const useApi = (baseUrl) => { const data = ref(null); const loading = ref(false); const error = ref(null); const fetchData = async (endpoint, options = {}) => { loading.value = true; error.value = null; try { const response = await axios({ url: "${baseUrl}/${endpoint}", method: 'GET', // Default method ...options, }); data.value = response.data; return response.data } catch (err) { error.value = err; console.error('API Error:', err); // Consider a global error handling mechanism here throw err; // Re-throw to allow component-level handling. } finally { loading.value = false; } }; return { data, loading, error, fetchData }; }; export default useApi; // Example usage in a component // src/components/MyComponent.vue <template> <div v-if="loading">Loading...</div> <div v-if="error">Error: {{ error.message }}</div> <div v-if="posts"> <ul> <li v-for="post in posts" :key="post.id">{{ post.title }}</li> </ul> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import useApi from '@/composables/useApi'; const { data: posts, loading, error, fetchData } = useApi('https://jsonplaceholder.typicode.com'); onMounted(() => { fetchData('/posts'); }); </script> """ **Code Example (Class-based approach):** """javascript // src/services/PostService.js import axios from 'axios'; class PostService { constructor(baseURL) { this.baseURL = baseURL; this.axiosInstance = axios.create({ baseURL: this.baseURL, }); } async getPosts() { try { const response = await this.axiosInstance.get('/posts'); return response.data; } catch (error) { console.error('Error fetching posts:', error); throw error; // Re-throw to let components handle errors. } } async getPost(id) { try { const response = await this.axiosInstance.get("/posts/${id}"); return response.data; } catch (error) { console.error("Error fetching post with ID ${id}:", error); throw error; } } // Add other methods for POST, PUT, DELETE, etc. } export default PostService; // usage in a component // src/components/MyComponent.vue <template> <div v-if="loading">Loading...</div> <div v-if="error">Error: {{ error.message }}</div> <div v-if="posts"> <ul> <li v-for="post in posts" :key="post.id">{{ post.title }}</li> </ul> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import PostService from '@/services/PostService'; const postService = new PostService('https://jsonplaceholder.typicode.com'); const posts = ref([]); const loading = ref(true); const error = ref(null); onMounted(async () => { try { posts.value = await postService.getPosts(); } catch (err) { error.value = err; } finally { loading.value = false; } }); </script> """ ### 1.2. State Management Integration **Standard:** Integrate API data with Vuex (or Pinia) for global state management, providing a single source of truth for data fetched from the backend. **Why:** * **Data Consistency:** Ensures that data is consistent across multiple components. * **Simplified Data Sharing:** Allows components to easily access and modify API data without prop drilling or complex event handling. * **Centralized State Mutations:** Makes it easier to track changes to the application state and debug issues. * **Improved Performance:** Optimizes re-renders by leveraging Vuex's reactivity system. **Do This:** Utilize Vuex (or Pinia) to store and manage API data that is shared across multiple components. **Don't Do This:** Rely solely on component-local state for data that needs to be accessed or modified by other components. **Code Example (using Pinia):** """javascript // src/stores/posts.js import { defineStore } from 'pinia'; import axios from 'axios'; export const usePostsStore = defineStore('posts', { state: () => ({ posts: [], loading: false, error: null, }), getters: { getPostById: (state) => (id) => state.posts.find((post) => post.id === id), }, actions: { async fetchPosts() { this.loading = true; this.error = null; try { const response = await axios.get('https://jsonplaceholder.typicode.com/posts'); this.posts = response.data; } catch (err) { this.error = err; } finally { this.loading = false; } }, addPost(post) { this.posts.push(post) } }, }); // src/components/PostsList.vue <template> <div v-if="postsStore.loading">Loading...</div> <div v-if="postsStore.error">Error: {{ postsStore.error.message }}</div> <div v-if="postsStore.posts.length"> <ul> <li v-for="post in postsStore.posts" :key="post.id">{{ post.title }}</li> </ul> </div> </template> <script setup> import { onMounted } from 'vue'; import { usePostsStore } from '@/stores/posts'; const postsStore = usePostsStore(); onMounted(() => { if (postsStore.posts.length === 0) { postsStore.fetchPosts(); } }); </script> """ ### 1.3. Data Transformation Layer **Standard:** Implement a data transformation layer to adapt API responses to the specific needs of your Vue components. This could involve renaming fields, restructuring data, or calculating derived values. **Why:** * **Decoupling:** Shields the UI from changes in the API data structure. * **Data Consistency:** Ensures that the data used by components is always in the expected format. * **Performance:** Reduces the amount of data processing required in components. **Do This:** Create data transformation functions or classes to map API data to the format expected by your components. **Don't Do This:** Directly use raw API responses in your components without any transformation. **Code Example:** """javascript // src/utils/dataTransformations.js export const transformPostData = (apiPost) => { return { id: apiPost.id, title: apiPost.title, content: apiPost.body, // Renamed field authorName: 'Unknown', // Default value //... additional transformations }; }; // usage in a composable/service/component: import { transformPostData } from '@/utils/dataTransformations'; // Example within the useApi composable: const useApi = (baseUrl) => { // ... other code from previous example const fetchData = async (endpoint, options = {}) => { // ... const response = await axios({ url: "${baseUrl}/${endpoint}", method: 'GET', // Default method ...options, }); data.value = response.data.map(transformPostData); // Transformation applied here. return response.data } } """ ## 2. Implementation Best Practices ### 2.1. Using "async/await" **Standard:** Use "async/await" syntax for asynchronous API calls to improve code readability and reduce callback nesting. **Why:** * **Readability:** Makes asynchronous code easier to read and understand. * **Error Handling:** Simplifies error handling with "try/catch" blocks. * **Debugging:** Facilitates easier debugging of asynchronous code. **Do This:** Use "async/await" for making API calls and handling responses. **Don't Do This:** Rely on ".then()" and ".catch()" for asynchronous operations unless absolutely necessary. **Code Example:** """javascript // Correct using async/await. const fetchData = async () => { try { const response = await axios.get('/api/data'); this.data = response.data; } catch (error) { console.error('Error fetching data:', error); } }; // Anti-pattern: avoids then/catch where async/await is appropriate const fetchDataBad = () => { axios.get('/api/data') .then(response => { this.data = response.data; }) .catch(error => { console.error('Error fetching data:', error); }); }; """ ### 2.2. Error Handling **Standard:** Implement robust error handling for API calls, including displaying user-friendly error messages and logging errors for debugging. Use try/catch blocks in "async" functions or ".catch()" blocks for promises. Avoid generic error messages; provide context. **Why:** * **User Experience:** Provides informative feedback to users when API calls fail. * **Debugging:** Helps identify and fix issues in the application. * **Resilience:** Prevents the application from crashing due to API errors. * **Security**: Prevents revealing sensitive information in error messages. **Do This:** Wrap API calls in "try...catch" blocks, log errors, and display appropriate messages to the user. **Don't Do This:** Ignore errors or display generic error messages. **Code Example:** """javascript const fetchData = async () => { try { const response = await axios.get('/api/data'); this.data = response.data; } catch (error) { console.error('Error fetching data:', error); // Display a user-friendly error message. this.errorMessage = 'Failed to load data. Please try again later.'; if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error("Data:", error.response.data); console.error("Status:", error.response.status); console.error("Headers:", error.response.headers); if (error.response.status === 404) { this.errorMessage = 'The requested resource was not found.'; } else if (error.response.status === 500) { this.errorMessage = 'A server error occurred. Please inform support.'; } } else if (error.request) { // The request was made but no response was received console.error("Request:", error.request); this.errorMessage = 'Could not connect to the server. Please check your internet connection.'; } else { // Something happened in setting up the request that triggered an Error console.error('Error', error.message); this.errorMessage = 'An unexpected error occurred.'; } } }; """ ### 2.3. Request Cancellation **Standard:** Implement request cancellation for long-running API calls, especially in scenarios where the user might navigate away from the page or trigger another request before the previous one completes. **Why:** * **Performance:** Prevents unnecessary resource consumption on the client and server. * **User Experience:** Avoids displaying stale or irrelevant data to the user. * **Data Integrity:** Prevents race conditions and potential data corruption. **Do This:** Use "axios.CancelToken" (or equivalent functionality in other HTTP clients) to cancel pending requests. **Don't Do This:** Allow long-running requests to continue executing in the background after they are no longer needed. **Code Example:** """javascript import axios from 'axios'; import { ref, onBeforeUnmount } from 'vue'; export default { setup() { const data = ref(null); const loading = ref(false); const error = ref(null); const cancelTokenSource = axios.CancelToken.source(); const fetchData = async () => { loading.value = true; error.value = null; try { const response = await axios.get('/api/data', { cancelToken: cancelTokenSource.token, }); data.value = response.data; } catch (err) { if (axios.isCancel(err)) { console.log('Request cancelled:', err.message); } else { error.value = err; } } finally { loading.value = false; } }; onBeforeUnmount(() => { cancelTokenSource.cancel('Component unmounted.'); // Cancel the request }); return { data, loading, error, fetchData }; }, }; """ ### 2.4. Data Caching **Standard:** Implement client-side caching for frequently accessed API data that does not change frequently. **Why:** * **Performance:** Reduces the number of API requests, improving application performance. * **User Experience:** Provides a faster and more responsive user interface. * **Offline Support:** Enables the application to function partially or fully offline. * **Cost Savings:** Reduces bandwidth consumption. **Do This:** Use browser storage (e.g., "localStorage", "sessionStorage", "IndexedDB"), Vuex persistence plugins, or dedicated caching libraries to store API data. **Don't Do This:** Cache sensitive data without proper encryption or store data indefinitely without invalidation. **Code Example (using localStorage):** """javascript import axios from 'axios'; const CACHE_KEY = 'api_data'; const CACHE_EXPIRY = 60 * 60 * 1000; // 1 hour const fetchData = async () => { const cachedData = localStorage.getItem(CACHE_KEY); const cachedTime = localStorage.getItem("${CACHE_KEY}_timestamp"); if (cachedData && cachedTime && (Date.now() - parseInt(cachedTime)) < CACHE_EXPIRY) { console.log('Using cached data'); return JSON.parse(cachedData); } try { const response = await axios.get('/api/data'); const data = response.data; localStorage.setItem(CACHE_KEY, JSON.stringify(data)); localStorage.setItem("${CACHE_KEY}_timestamp", Date.now().toString()); //store timestamp as string return data; } catch (error) { console.error('Error fetching data:', error); throw error; } }; """ ### 2.5. Request/Response Interceptors **Standard:** Utilize request and response interceptors provided by "axios" or similar libraries to add common headers, handle authentication, log requests, or transform responses globally. Use sparingly and carefully. **Why:** * **Code Reusability:** Avoids code duplication by centralizing common logic. * **Maintainability:** Simplifies global configuration changes. * **Security:** Enables automatic injection of authentication tokens. **Do This:** Use interceptors to implement cross-cutting concerns related to API requests and responses. **Don't Do This:** Overuse interceptors for component-specific logic or perform complex operations that impact performance. **Code Example:** """javascript // src/utils/axiosConfig.js import axios from 'axios'; // Request interceptor axios.interceptors.request.use( (config) => { // Add authentication token const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = "Bearer ${token}"; } // Log the request console.log('Request:', config.method, config.url); return config; }, (error) => { console.error('Request Error:', error); return Promise.reject(error); } ); // Response interceptor axios.interceptors.response.use( (response) => { // Log the response console.log('Response:', response.status, response.data); return response; }, (error) => { console.error('Response Error:', error); // Handle unauthorized errors globally if (error.response && error.response.status === 401) { // Redirect to login page window.location.href = '/login'; } return Promise.reject(error); } ); export default axios; // Usage: Simply import the configured axios instance import axios from '@/utils/axiosConfig'; const fetchData = async () => { try { const response = await axios.get('/api/protected-data'); // ... } catch(error) { // ... } } """ ## 3. Security Considerations ### 3.1. Sensitive Data Handling **Standard:** Never store sensitive information (e.g., passwords, API keys) directly in the Vue.js codebase or client-side storage (localStorage, sessionStorage). Manage API keys securely using environment variables or a secure configuration management system. **Why:** * **Security:** Prevents unauthorized access to sensitive data. **Do This:** Use environment variables, backend services, or secure configuration management systems to store and manage sensitive data. **Don't Do This:** Store sensitive data directly in the codebase or client-side storage. ### 3.2. Cross-Origin Resource Sharing (CORS) **Standard:** Ensure that the backend API is properly configured with CORS to allow requests from the Vue.js application's origin. **Why:** * **Security:** Prevents unauthorized access to the API from other origins. **Do This:** Configure the backend API to allow requests from the Vue.js application's origin. **Don't Do This:** Disable CORS without understanding the security implications. The backend team should handle CORS configuration, not the frontend. ### 3.3. Input Validation and Sanitization **Standard:** Validate and sanitize all user inputs on both the client-side (Vue.js) and server-side to prevent injection attacks (e.g., XSS, SQL injection). **Why:** * **Security:** Prevents malicious code from being injected into the application. **Do This:** Use appropriate validation and sanitization techniques to protect against injection attacks. **Don't Do This:** Trust user input without validation or sanitization. ### 3.4. HTTPS **Standard:** Use HTTPS for all communication between the Vue.js application and the backend API. **Why:** * **Security:** Encrypts data in transit, preventing eavesdropping and man-in-the-middle attacks. **Do This:** Configure both the Vue.js application and the backend API to use HTTPS. **Don't Do This:** Use HTTP for sensitive data or production environments. ## 4. Tooling and Libraries ### 4.1. Axios **Standard:** Use "axios" as the primary HTTP client for making API requests in Vue.js applications unless there's a very good reason to use something else. It offers request cancellation, interceptors, and automatic JSON parsing. **Why:** * Widely used and well-supported. * Provides a consistent and intuitive API. * Supports request cancellation, interceptors, and automatic JSON parsing. **Do This:** Use axios for making API requests. **Don't Do This:** Re-invent the wheel by using the browser's "fetch" API directly for complex scenarios handled well by Axios. ### 4.2. Vue Devtools **Standard:** Use Vue Devtools browser extension for inspecting API requests and responses, component state, and Vuex (or Pinia) mutations. **Why:** * **Debugging:** Provides valuable insights into the application's behavior. * **Performance Optimization:** Helps identify performance bottlenecks. **Do This:** Install and use Vue Devtools for debugging and performance analysis. ## 5. GraphQL Considerations ### 5.1 Apollo Client **Standard:** Use Apollo Client, or similar, to integrate GraphQL APIs. **Why:** * Official Vue integration. * Caching, and state management. **Do This:** Use the Apollo Client to manage the client-side integration. ### 5.2 Query Design **Standard:** Design GraphQL queries to only fetch what is needed. Avoid over-fetching. **Why:** * To reduce payload size. * To improve performance. **Do This:** Fetch only the required data. ## 6. Testing API Integrations ### 6.1 Unit Testing **Standard:** Write unit tests for API service modules using tools like Jest or Vitest, mocking the actual HTTP requests to isolate the logic. **Why:** * Ensure that API interaction logic is working correctly. * Catch regressions early in the development process. **Do This:** Create comprehensive unit tests covering all API service methods. """javascript // Example unit test for PostService.js using Jest and mock-axios import PostService from '@/services/PostService'; import axios from 'axios'; jest.mock('axios'); // mock axios describe('PostService', () => { let postService; beforeEach(() => { postService = new PostService('https://example.com/api'); axios.get.mockClear(); // clear mock calls before each test }); it('fetches posts successfully', async () => { const mockPosts = [{ id: 1, title: 'Test Post' }]; axios.get.mockResolvedValue({ data: mockPosts }); const posts = await postService.getPosts(); expect(axios.get).toHaveBeenCalledWith('/posts'); expect(posts).toEqual(mockPosts); }); it('handles errors when fetching posts', async () => { const mockError = new Error('Failed to fetch posts'); axios.get.mockRejectedValue(mockError); await expect(postService.getPosts()).rejects.toThrow(mockError); expect(axios.get).toHaveBeenCalledWith('/posts'); }); }); """ ### 6.2 End-to-End (E2E) Testing **Standard:** Use end-to-end testing frameworks (e.g., Cypress, Playwright) to verify that the API integrations work correctly from the user's perspective, simulating real user interactions and validating the displayed data. **Why:** * Ensure that the entire application flow, including API interactions, is working as expected. * Identify integration issues that might not be caught by unit tests. **Do This:** Implement E2E tests that cover the critical API-driven features of the application. These standards provide a comprehensive guide for API integration in Vue.js applications. Adhering to these guidelines will result in more maintainable, performant, and secure applications. Remember, these are guidelines, and thoughtful deviations can be made when justified by specific project requirements.
# Component Design Standards for Vue.js This document outlines the coding standards specifically focused on *Component Design* for Vue.js applications. Following these guidelines will help ensure that your Vue.js components are reusable, maintainable, performant, and secure. These recommendations are based on current best practices in the Vue.js ecosystem and reference the latest version of Vue.js (currently Vue 3). ## 1. Component Architecture and Organization ### 1.1 Single Responsibility Principle (SRP) **Standard:** Each component should have a single, well-defined responsibility. **Why:** SRP increases readability, testability, and reusability. A component focused on a single task is easier to understand, test in isolation, and reuse in different parts of the application. **Do This:** Break down complex features into smaller, single-purpose components. Use composition API to manage complex logic within these components. **Don't Do This:** Create "God components" that handle multiple unrelated tasks. This leads to tightly coupled code that's difficult to maintain and reuse. **Example:** **Good:** Separate components for displaying product information and handling add-to-cart functionality. """vue // ProductInfo.vue <template> <div> <h2>{{ product.name }}</h2> <p>{{ product.description }}</p> <p>Price: ${{ product.price }}</p> </div> </template> <script setup> import { defineProps } from 'vue'; defineProps({ product: { type: Object, required: true } }); </script> """ """vue // AddToCartButton.vue <template> <button @click="addToCart">Add to Cart</button> </template> <script setup> import { defineEmits } from 'vue'; const emit = defineEmits(['add-to-cart']); const addToCart = () => { emit('add-to-cart'); }; </script> """ **Bad:** A single component handling both product display and cart functionality. """vue // Product.vue (Anti-pattern: God Component) <template> <div> <h2>{{ product.name }}</h2> <p>{{ product.description }}</p> <p>Price: ${{ product.price }}</p> <button @click="addToCart">Add to Cart</button> </div> </template> <script setup> import { reactive } from 'vue'; const product = reactive({ /* product data */ }); const addToCart = () => { // Complex cart logic here }; </script> """ ### 1.2 Component Naming **Standard:** Use PascalCase for component names. Component files should match the component name. **Why:** PascalCase clearly distinguishes Vue components from native HTML elements in templates, enhancing readability. Consistent file naming makes it easier to locate components within the project. **Do This:** Name components descriptively using PascalCase. e.g., "MyComponent.vue", "UserProfile.vue". **Don't Do This:** Use kebab-case or snake_case for component names, as it reduces readability within templates. **Example:** """ components/ ├── MyButton.vue ├── UserProfile.vue """ """vue // MyButton.vue <template> <button>{{ label }}</button> </template> <script setup> defineProps({ label: { type: String, required: true } }) </script> """ ### 1.3 Component Directory Structure **Standard:** Organize components into a logical directory structure based on feature, module, or component type (e.g., "components/", "views/", "pages/"). **Why:** A well-organized directory structure improves maintainability and makes it easier to navigate the codebase, especially in large projects. **Do This:** Group related components into subdirectories. Use "index.js" (or "index.ts") files to export components from a directory for easier imports. **Don't Do This:** Place all components in a single directory, as this becomes unwieldy as the project grows. **Example:** """ src/ ├── components/ │ ├── Button/ │ │ ├── Button.vue │ │ └── index.js │ ├── Input/ │ │ ├── Input.vue │ │ └── index.js """ """javascript // src/components/Button/index.js import Button from './Button.vue'; export { Button }; """ ### 1.4 Use of Global Components (Judiciously) **Standard:** Register components globally only when they are used across a large number of pages and are truly foundational (Utility components). **Why:** Global registration simplifies usage in templates, but excessive use can lead to larger bundle sizes and naming conflicts. **Do This:** Register commonly used utility components globally (e.g., a custom button component). Use local registration (importing components directly into the components that use them) for more specific components. **Don't Do This:** Globally register every component in the application, as this can negatively impact performance. **Example:** """javascript // main.js import { createApp } from 'vue'; import App from './App.vue'; import MyButton from './components/MyButton.vue'; const app = createApp(App); app.component('MyButton', MyButton); // Global registration app.mount('#app'); """ ## 2. Component Props and Emits ### 2.1 Prop Definition **Standard:** Define props explicitly with type validation, required status, and default values where appropriate. **Why:** Explicit prop definitions improve code clarity, prevent runtime errors, and provide better documentation. **Do This:** Use the "defineProps" macro in "<script setup>" for clear and type-safe prop declarations. **Don't Do This:** Omit prop definitions, rely solely on implicit prop passing, or skip type validation. **Example:** """vue <script setup> import { defineProps } from 'vue'; const props = defineProps({ message: { type: String, required: true }, count: { type: Number, default: 0 }, isActive: { type: Boolean, default: false }, items: { type: Array, default: () => [] // Important: use a factory function for arrays/objects! }, user: { type: Object, default: null // or a factory function if needed for complex object initialization } }); </script> <template> <p>{{ message }}</p> <p>Count: {{ count }}</p> <p>Is Active: {{ isActive }}</p> </template> """ ### 2.2 Prop Naming Conventions **Standard:** Use camelCase for prop names in the component definition and kebab-case when passing prop values in the template. **Why:** This convention aligns with JavaScript (camelCase) and HTML (kebab-case) standards, improving consistency and readability. **Do This:** Define props as "myPropName" in the component, and pass them as "my-prop-name" in the template. **Don't Do This:** Use inconsistent casing for props, which can lead to confusion and errors. **Example:** """vue // MyComponent.vue <script setup> import { defineProps } from 'vue'; defineProps({ myPropName: { type: String, required: true } }); </script> <template> <p>{{ myPropName }}</p> </template> """ """vue // ParentComponent.vue <template> <MyComponent my-prop-name="Hello" /> </template> <script setup> import MyComponent from './MyComponent.vue'; </script> """ ### 2.3 Emitting Events **Standard:** Define emitted events using "defineEmits" for clarity and type safety. Use descriptive event names. **Why:** Explicit event definitions improve code readability and help prevent errors related to event handling. Descriptive event names improve communication and maintainability. **Do This:** Use "defineEmits" in "<script setup>" to declare emitted events. Choose event names that clearly describe the event's purpose (e.g., "item-selected", "form-submitted"). **Don't Do This:** Emit events without defining them, use generic event names (e.g., "update"), or directly manipulate parent component state. **Example:** """vue // MyComponent.vue <template> <button @click="handleClick">Click Me</button> </template> <script setup> import { defineEmits } from 'vue'; const emit = defineEmits(['item-selected', 'form-submitted']); const handleClick = () => { emit('item-selected', { id: 123, name: 'Example' }); }; </script> """ """vue // ParentComponent.vue <template> <MyComponent @item-selected="handleItemSelected" /> </template> <script setup> import MyComponent from './MyComponent.vue'; const handleItemSelected = (item) => { console.log('Selected item:', item); }; </script> """ ### 2.4 Avoid Prop Mutation **Standard:** Treat props as read-only within the component. If you need to modify a prop's value, create a local data property. **Why:** Mutating props directly violates the one-way data flow principle of Vue.js, making it harder to track state changes and debug issues. **Do This:** Create a computed property or reactive variable to hold the local, mutable value. Emit an event to signal that an external update is needed. **Don't Do This:** Directly modify the value of a prop within the component. **Example:** **Good:** """vue // MyComponent.vue <template> <input type="text" :value="localValue" @input="updateValue"> </template> <script setup> import { defineProps, defineEmits, ref, computed } from 'vue'; const props = defineProps({ initialValue: { type: String, required: true } }); const emit = defineEmits(['update-value']); const localValue = ref(props.initialValue); const updateValue = (event) => { localValue.value = event.target.value; emit('update-value', localValue.value); }; </script> """ **Bad:** """vue // MyComponent.vue (Anti-pattern: Prop mutation) <template> <input type="text" :value="initialValue" @input="updateValue"> </template> <script setup> import { defineProps } from 'vue'; const props = defineProps({ initialValue: { type: String, required: true } }); const updateValue = (event) => { props.initialValue = event.target.value; // DON'T DO THIS! Mutating a prop. }; </script> """ ## 3. Component Communication ### 3.1 Props and Events for Parent-Child Communication **Standard:** Use props to pass data down from parent to child components, and events to communicate actions or state changes from child to parent components. **Why:** This unidirectional data flow makes state management predictable and easier to understand. **Do This:** Adhere to the props-down, events-up pattern. **Don't Do This:** Directly access or mutate parent component state from a child component without using events, or rely on global state for simple parent-child interactions. **Example:** (See examples in Props and Emits sections above.) ### 3.2 Provide / Inject **Standard:** Use "provide" and "inject" for passing data *down* the component tree, especially when props become cumbersome or when dealing with deeply nested components. Use with caution to avoid tight coupling. **Why:** "provide"/"inject" allows data to be shared across multiple levels of the component tree without needing prop drilling. **Caveat:** Heavy use of "provide/inject" can create implicit dependencies and make components harder to reason about. Use it judiciously. **Do This:** Use "provide" in a parent component to make data or methods available to its descendants. Use "inject" in descendant components to access the provided data or methods. Strongly consider Symbols as keys to avoid naming collisions. **Don't Do This:** Overuse "provide"/"inject" for simple parent-child communication, or use it to modify state directly without a clear pattern, as this makes tracking of state changes nearly impossible. **Example:** """vue // MyProvider.vue (Parent Component - Providing) <template> <div> <slot /> </div> </template> <script setup> import { provide, ref } from 'vue'; const theme = ref('light'); const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light'; }; provide('theme', { currentTheme: theme, toggleTheme: toggleTheme }); </script> """ """vue // MyConsumer.vue (Child Component - Injecting) <template> <div :class="injectedTheme.currentTheme"> <p>Current theme: {{ injectedTheme.currentTheme }}</p> <button @click="injectedTheme.toggleTheme">Toggle Theme</button> </div> </template> <script setup> import { inject } from 'vue'; const injectedTheme = inject('theme'); if (!injectedTheme) { console.warn('Theme was not provided!'); } </script> <style scoped> .light { background-color: #fff; color: #000; } .dark { background-color: #000; color: #fff; } </style> """ Consider using Symbols for keys to prevent collisions: """vue // Provider <script setup> import { provide, ref, Symbol } from 'vue'; const themeSymbol = Symbol('theme'); const theme = ref('light'); provide(themeSymbol, theme); // Providing with a Symbol as a key // Consumer <script setup> import { inject, Symbol } from 'vue'; const themeSymbol = Symbol('theme'); const theme = inject(themeSymbol); if (!theme){ console.warn('Theme was not provided!'); } </script> """ ### 3.3 State Management (Vuex or Pinia) **Standard:** Use a state management library (Vuex or Pinia) for complex application state that needs to be shared between many components or persisted across routes. *Pinia is now the recommended state store*. **Why:** State management libraries provide a centralized store for application state, making it easier to manage, track changes, and ensure consistency across components. **Do This:** Structure your state logically into modules (or stores in Pinia). Use actions to commit mutations, and use getters to derive computed state. Follow Pinia's composition API style. **Don't Do This:** Use local component state for data that needs to be shared globally, or directly mutate the state without using actions. **Example (Pinia):** """javascript // stores/counter.js import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), getters: { doubleCount: (state) => state.count * 2 }, actions: { increment() { this.count++; }, decrement() { this.count--; } } }); """ """vue // MyComponent.vue <template> <p>Count: {{ counter.count }}</p> <p>Double Count: {{ counter.doubleCount }}</p> <button @click="counter.increment">Increment</button> </template> <script setup> import { useCounterStore } from '../stores/counter'; const counter = useCounterStore(); </script> """ ## 4. Component Logic and Composition ### 4.1 Composition API **Standard:** Favor the Composition API over the Options API for new components. The Options API is still valuable for simpler components or when migrating existing code. **Why:** The Composition API offers superior code organization, reusability through composables, and better TypeScript support. **Do This:** Use "<script setup>" for concise and performant component definitions, leveraging "ref", "reactive", "computed", and lifecycle hooks. Extract reusable logic into composables. **Don't Do This:** Mix the Composition API and Options API excessively within the same component, as this can reduce readability. Avoid deeply nested reactive structures that hinder performance. **Example:** **Good:** """vue // useMouse.js (Composable) import { ref, onMounted, onUnmounted } from 'vue'; export function useMouse() { const x = ref(0); const y = ref(0); function update(event) { x.value = event.pageX; y.value = event.pageY; } onMounted(() => window.addEventListener('mousemove', update)); onUnmounted(() => window.removeEventListener('mousemove', update)); return { x, y }; } """ """vue // MouseTracker.vue <template> <p>Mouse position: x={{ x }}, y={{ y }}</p> </template> <script setup> import { useMouse } from './useMouse'; const { x, y } = useMouse(); </script> """ ### 4.2 Reusability with Composables **Standard:** Extract reusable component logic into composable functions ("composables"). **Why:** Composables promote code reuse, improve testability, and help maintain a DRY (Don't Repeat Yourself) codebase. **Do This:** Create composables for tasks such as data fetching, form validation, and managing external services. Prefix composable names with "use" (e.g., "useFetch", "useForm"). Return reactive state, computed properties, and functions from the composable. **Don't Do This:** Duplicate logic across multiple components. Place component-specific logic within composables, reducing their reusability. Name composables poorly. **Example:** (See example in Composition API section above.) ### 4.3 Computed Properties **Standard:** Use computed properties for deriving values from reactive state. **Why:** Computed properties are cached and only re-evaluated when their dependencies change, improving performance. They also provide a cleaner and more declarative way to express derived state. **Do This:** Use "computed" in "<script setup>" to create computed properties. **Don't Do This:** Perform complex or expensive operations directly in templates. **Example:** """vue <template> <p>Price: ${{ price }}</p> <p>Tax: ${{ tax }}</p> <p>Total: ${{ total }}</p> </template> <script setup> import { ref, computed } from 'vue'; const price = ref(100); const taxRate = ref(0.08); const tax = computed(() => price.value * taxRate.value); const total = computed(() => price.value + tax.value); </script> """ ### 4.4 Watchers **Standard:** Use watchers sparingly, generally only when you need to react to specific state changes and perform side effects (e.g., logging, calling an external API). Prefer computed properties for deriving state. **Why:** Watchers can make code harder to reason about because their execution is implicit. Computed properties are usually a better choice for deriving values. **Do This:** Use "watch" (or "watchEffect" when you don't need access to the previous value) in "<script setup>" to watch for changes in reactive state. **Don't Do This:** Overuse watchers, especially for tasks that can be handled by computed properties. **Example:** """vue <script setup> import { ref, watch } from 'vue'; const inputValue = ref(''); watch( inputValue, (newValue, oldValue) => { console.log('Input value changed:', newValue, oldValue); // Perform side effect, e.g., call an API: // myApi.updateValue(newValue); } ); </script> """ ## 5. Component Styling ### 5.1 Scoped Styles **Standard:** Use scoped styles in most components to encapsulate styles and prevent conflicts. **Why:** Scoped styles ensure that a component's styles only apply to that component's template, making it easier to maintain and reuse components. **Do This:** Add the "scoped" attribute to the "<style>" tag in your component. **Don't Do This:** Rely solely on global styles, as this can lead to unexpected style conflicts. **Example:** """vue <template> <div class="my-component"> <p>This is my component.</p> </div> </template> <style scoped> .my-component { background-color: #f0f0f0; padding: 16px; border: 1px solid #ccc; } </style> """ ### 5.2 CSS Preprocessors **Standard:** Use CSS preprocessors (e.g., Sass, Less, Stylus) to enhance styling with features like variables, mixins, and nesting. **Why:** CSS preprocessors improve code organization, reusability, and maintainability. **Do This:** Configure your build system to support your chosen CSS preprocessor. Use preprocessor features to write more concise and maintainable styles. Choose one preprocessor and stick to it. **Don't Do This:** Mix multiple CSS preprocessors within the same project. Overuse complex preprocessor features, making the styles harder to understand. **Example (using Sass):** """vue <template> <button class="primary-button">{{ label }}</button> </template> <script setup> defineProps({ label: { type: String, required: true } }); </script> <style scoped lang="scss"> $primary-color: #007bff; // Define a variable .primary-button { background-color: $primary-color; color: white; padding: 10px 20px; border: none; border-radius: 5px; &:hover { // Use nesting background-color: darken($primary-color, 10%); } } </style> """ ### 5.3 CSS Modules **Standard:** Consider CSS Modules for component styling to avoid naming collisions and improve CSS encapsulation, especially in larger projects. **Why:** CSS Modules automatically generate unique class names, preventing style conflicts between components. **Do This:** Use the "<style module>" attribute in your component. Access the generated class names using the "$style" property. **Don't Do This:** Mix CSS Modules with global styles or scoped styles within the same component. **Example:** """vue <template> <div :class="$style.myComponent"> <p :class="$style.text">This is my component.</p> </div> </template> <style module> .myComponent { background-color: #f0f0f0; padding: 16px; border: 1px solid #ccc; } .text { font-size: 16px; } </style> """ ### 5.4 Utility-First CSS (e.g., Tailwind CSS) **Standard:** Consider using a utility-first CSS framework (e.g., Tailwind CSS) for rapid UI development and consistent styling. **Why:** Utility-first frameworks provide a set of pre-defined utility classes that can be composed to create custom designs, reducing the need to write custom CSS. **Do This:** Install and configure your chosen utility-first framework. Use utility classes directly in your component templates to style elements. **Don't Do This:** Overuse custom CSS, defeating the purpose of the utility-first framework. Modify the framework's core styles unless absolutely necessary. **Example (using Tailwind CSS):** This example assumes Tailwind CSS is already configured. """vue <template> <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> {{ label }} </button> </template> <script setup> defineProps({ label: { type: String, required: true } }); </script> """ ## 6. Component Testing ### 6.1 Unit Testing **Standard:** Write unit tests for individual components to verify their behavior in isolation. **Why:** Unit tests help catch bugs early, improve code quality, and facilitate refactoring. **Do This:** Use a testing framework (e.g., Jest, Vitest) and a component testing library (e.g., Vue Test Utils, Testing Library). Test component inputs (props), outputs (emitted events), and rendered output. **Don't Do This:** Skip unit tests or write tests that only verify implementation details. **Example (using Vitest and Vue Test Utils):** """javascript // MyComponent.spec.js import { mount } from '@vue/test-utils'; import { describe, it, expect } from 'vitest'; import MyComponent from './MyComponent.vue'; describe('MyComponent', () => { it('renders the message prop', () => { const wrapper = mount(MyComponent, { props: { message: 'Hello, world!' } }); expect(wrapper.text()).toContain('Hello, world!'); }); it('emits an event when the button is clicked', async () => { const wrapper = mount(MyComponent); await wrapper.find('button').trigger('click'); expect(wrapper.emitted('custom-event')).toBeTruthy(); }); }); """ ### 6.2 End-to-End (E2E) Testing **Standard:** Write E2E tests to verify the overall application functionality and user workflows. **Why:** E2E tests ensure that all parts of the application work together correctly, simulating real user interactions. **Do This:** Use an E2E testing framework (e.g., Cypress, Playwright). Write tests that cover critical user flows, such as login, form submission, and data display. **Don't Do This:** Rely solely on E2E tests, as they can be slow and difficult to debug. E2E tests should complement unit tests, not replace them. This document provides a comprehensive guide to component design standards in Vue.js. By following these guidelines, you can build high-quality, maintainable, and scalable Vue.js applications. Remember to adapt these standards to your specific project needs and team preferences.
# Code Style and Conventions Standards for Vue.js This document outlines the coding style and conventions standards for Vue.js projects. Adhering to these standards ensures code readability, maintainability, and consistency across all projects. This guide is designed to be used by developers and as context for AI coding assistants. It’s aligned with the latest Vue.js features and best practices. ## 1. General Formatting ### 1.1. Whitespace and Indentation **Standard:** Use 2 spaces for indentation. Avoid tabs to ensure consistency across different editors and systems. **Do This:** """vue <template> <div> <p>Hello, Vue!</p> </div> </template> <script setup> const message = 'Hello, Vue!'; </script> """ **Don't Do This:** """vue <template> <div> <p>Hello, Vue!</p> </div> </template> <script setup> const message = 'Hello, Vue!'; </script> """ **Why:** Consistent indentation significantly improves code readability. Using spaces instead of tabs avoids rendering issues across different IDE configurations. ### 1.2. Line Length **Standard:** Limit line length to 120 characters. This enhances readability, especially on smaller screens or split-screen setups. **Do This:** """javascript const myVeryLongVariableName = 'This is a long string that should be broken into multiple lines to improve readability.'; const result = someFunction( myVeryLongVariableName, anotherLongVariableName, yetAnotherLongVariableName ); """ **Don't Do This:** """javascript const myVeryLongVariableName = 'This is a very long string that really hurts readability when it all exists on a single line!'; """ **Why:** Shorter lines improve readability, reducing horizontal scrolling and making it easier to compare different versions of the code. ### 1.3. File Structure and Organization **Standard:** Organize Vue components into separate files. Use a clear directory structure based on features, modules, or routes based on project scale and complexity. For larger projects, consider using a modular approach with separate directories for components, composables, and views. **Do This:** """ src/ ├── components/ │ ├── MyComponent.vue │ └── AnotherComponent.vue ├── composables/ │ ├── useMyComposable.js │ └── useAnotherComposable.js ├── views/ │ ├── HomeView.vue │ └── AboutView.vue └── App.vue """ **Don't Do This:** """ src/ ├── MyComponent.vue ├── AnotherComponent.vue ├── useMyComposable.js ├── HomeView.vue ├── AboutView.vue └── App.vue """ **Why:** Segregating concerns into separate files makes it easier to find, understand, and maintain code. ### 1.4. Component File Structure **Standard:** Organize Vue Single File Components (SFCs) in a standard order: "<template>", "<script>", "<style>". For "<script>", prefer using "<script setup>" syntax. **Do This:** """vue <template> <div> <h1>{{ message }}</h1> </div> </template> <script setup> import { ref } from 'vue'; const message = ref('Hello, Vue!'); </script> <style scoped> h1 { color: blue; } </style> """ **Don't Do This:** """vue <script> export default { data() { return { message: 'Hello, Vue!' } } } </script> <template> <div> <h1>{{ message }}</h1> </div> </template> <style scoped> h1 { color: blue; } </style> """ **Why:** A consistent structure makes it easy to navigate and understand components, making the development process faster and more predictable. "<script setup>" provides a concise and performant syntax. ## 2. Naming Conventions ### 2.1. Component Names **Standard:** Use PascalCase for component names. This applies to both file names and component names when registering them. Use descriptive names that clearly indicate the component’s purpose. **Do This:** """vue // MyButton.vue export default { name: 'MyButton' // ... } """ """vue <template> <MyButton /> </template> <script setup> import MyButton from './components/MyButton.vue'; </script> """ **Don't Do This:** """vue // my-button.vue (Incorrect filename) export default { name: 'myButton' // Incorrect component name // ... } """ **Why:** PascalCase distinguishes components from HTML elements and improves readability. Descriptive names convey the component's purpose. ### 2.2. Prop Names **Standard:** Use camelCase for prop names in JavaScript and kebab-case in the template. Define prop types explicitly. **Do This:** """vue // MyComponent.vue <template> <div> <h1>{{ myMessage }}</h1> </div> </template> <script setup> defineProps({ myMessage: { type: String, required: true } }); </script> """ """vue <template> <MyComponent my-message="Hello" /> </template> """ **Don't Do This:** """vue // MyComponent.vue <template> <div> <h1>{{ my_message }}</h1> </div> </template> <script> export default { props: ['my_message'] } </script> <template> <MyComponent myMessage = 'Hello'></MyComponent> </template> """ **Why:** camelCase is the standard JavaScript naming convention, while kebab-case is the standard for HTML attributes. Explicit prop type definitions enhance code reliability and maintainability. ### 2.3. Event Names **Standard:** Use kebab-case for event names emitted by components. This aligns with HTML attribute conventions. Prefix custom events with the component name to avoid naming conflicts. **Do This:** """vue // MyComponent.vue <script setup> import { defineEmits } from 'vue'; const emit = defineEmits(['my-component:item-selected']); const selectItem = (item) => { emit('my-component:item-selected', item); }; </script> """ """vue <template> <MyComponent @my-component:item-selected="handleItemSelected" /> </template> """ **Don't Do This:** """vue // MyComponent.vue <script> export default { methods: { selectItem(item) { this.$emit('itemSelected', item); } } } </script> <template> <MyComponent @itemSelected="handleItemSelected" /> </template> """ **Why:** kebab-case is the standard for HTML attributes (and therefore event listeners). Prefixing mitigates potential naming collisions when using multiple components. ### 2.4. Variable Names **Standard:** Use camelCase for variable names in JavaScript. Use descriptive and meaningful names. **Do This:** """javascript const numberOfItems = 10; const selectedItem = null; """ **Don't Do This:** """javascript const num = 10; const sel = null; """ **Why:** Descriptive variable names make code easier to understand and maintain. ### 2.5. Constant Names **Standard:** Use UPPER_SNAKE_CASE for constants. **Do This:** """javascript const MAX_ITEMS = 20; const API_URL = 'https://example.com/api'; """ **Don't Do This:** """javascript const maxItems = 20; const apiUrl = 'https://example.com/api'; """ **Why:** UPPER_SNAKE_CASE clearly identifies constants, improving code readability. ## 3. Stylistic Consistency ### 3.1. Quotes **Standard:** Use single quotes (') for strings unless you need to embed variables. **Do This:** """javascript const message = 'Hello, Vue!'; const greeting = "Hello, ${name}!"; """ **Don't Do This:** """javascript const message = "Hello, Vue!"; """ **Why:** Consistency in quote usage reduces cognitive load and makes code cleaner. ### 3.2. Curly Braces **Standard:** Always use curly braces for "if", "else", "for", and "while" statements, even if the block contains only one statement. **Do This:** """javascript if (isTrue) { console.log('It is true.'); } else { console.log('It is false.'); } """ **Don't Do This:** """javascript if (isTrue) console.log('It is true.'); else console.log('It is false.'); """ **Why:** Using curly braces consistently reduces ambiguity and potential errors. ### 3.3. Semicolons **Standard:** Use semicolons at the end of each statement. While JavaScript allows omitting semicolons in some cases, explicitly including them avoids potential pitfalls due to automatic semicolon insertion (ASI). **Do This:** """javascript const message = 'Hello, Vue!'; console.log(message); """ **Don't Do This:** """javascript const message = 'Hello, Vue!' console.log(message) """ **Why:** Explicit semicolons prevent unexpected behavior caused by ASI. ### 3.4. Template Syntax **Standard:** Use consistent template syntax. Prefer v-bind shorthand (":") and v-on shorthand ("@"). **Do This:** """vue <template> <button :disabled="isDisabled" @click="handleClick">Click me</button> </template> """ **Don't Do This:** """vue <template> <button v-bind:disabled="isDisabled" v-on:click="handleClick">Click me</button> </template> """ **Why:** Shorthand syntax is more concise and readable. ### 3.5. Component Props **Standard:** When passing multiple props to a component, use multiple lines for better readability, especially when the values are complex expressions. **Do This:** """vue <template> <MyComponent :prop1="value1" :prop2="value2" :prop3="value3" /> </template> """ **Don't Do This:** """vue <template> <MyComponent :prop1="value1" :prop2="value2" :prop3="value3" /> </template> """ **Why:** Multiline syntax enhances readability, especially when prop values are long or complex expressions. ## 4. Vue.js Specific Conventions ### 4.1. Using "<script setup>" **Standard:** Prefer "<script setup>" syntax for Vue 3 components. It offers better performance, more concise syntax, and improved type inference compared to the options API. **Do This:** """vue <template> <div> <h1>{{ message }}</h1> </div> </template> <script setup> import { ref } from 'vue'; const message = ref('Hello, Vue!'); </script> """ **Don't Do This:** """vue <template> <div> <h1>{{ message }}</h1> </div> </template> <script> export default { data() { return { message: 'Hello, Vue!' }; } }; </script> """ **Why:** "<script setup>" is more performant and reduces boilerplate code compared to the Options API. It handles component registration automatically and provides better type inference, which reduces the need for explicit type annotations. ### 4.2. Reactive Properties with "ref" and "reactive" **Standard:** Use "ref" for primitive values and "reactive" for complex objects. **Do This:** """javascript import { ref, reactive } from 'vue'; const count = ref(0); const user = reactive({ name: 'John Doe', age: 30 }); """ **Don't Do This:** """javascript import { reactive } from 'vue'; const count = reactive(0); // Incorrect, should use ref for primitive values. """ **Why:** "ref" creates a single reactive reference, suitable for primitive values. "reactive" makes all properties of an object reactive, which is more efficient for complex objects. ### 4.3. Computed Properties **Standard:** Use computed properties for derived data. Avoid performing complex logic directly in the template. **Do This:** """vue <template> <div> <p>Full Name: {{ fullName }}</p> </div> </template> <script setup> import { ref, computed } from 'vue'; const firstName = ref('John'); const lastName = ref('Doe'); const fullName = computed(() => "${firstName.value} ${lastName.value}"); </script> """ **Don't Do This:** """vue <template> <div> <p>Full Name: {{ firstName }} {{ lastName }}</p> <!-- Mixing reactive data directly --> </div> </template> <script setup> import { ref } from 'vue'; const firstName = ref('John'); const lastName = ref('Doe'); </script> """ **Why:** Computed properties cache their results and only re-evaluate when their dependencies change, which enhances performance. They also keep the template cleaner and easier to read. ### 4.4. Watchers **Standard:** Use watchers sparingly. Prefer computed properties when possible. Use watchers primarily for side effects, such as updating local storage or making API calls based on data changes. For simple reactions to a single reactive property, consider "watchEffect". **Do This:** """javascript import { ref, watch } from 'vue'; const count = ref(0); watch(count, (newValue, oldValue) => { console.log("Count changed from ${oldValue} to ${newValue}"); // Perform a side effect, e.g., saving to localStorage localStorage.setItem('count', newValue); }); """ **Don't Do This:** """javascript import { ref, watch } from 'vue'; const count = ref(0); const doubledCount = ref(0); watch(count, (newValue) => { doubledCount.value = newValue * 2; // Use a computed property instead! }); """ **Why:** Watchers execute arbitrary code in response to changes, which can make the application’s state harder to reason about. Computed properties are generally preferable for deriving data. ### 4.5. Emitting Events **Standard:** Always declare emitted events using "defineEmits". This improves type safety and documentation. **Do This:** """vue <script setup> const emit = defineEmits(['item-selected', 'delete']); const selectItem = (item) => { emit('item-selected', item); }; </script> """ **Don't Do This:** """vue <script setup> const selectItem = (item) => { emit('item-selected', item); // Implicitly emitting events }; </script> """ **Why:** Declaring emitted events provides type safety and enables Vue to validate event listeners, which helps catch errors early in development. ### 4.6. Component Communication **Standard:** Prefer props and events for parent-child component communication. For more complex use cases, consider using a dedicated state management library such as Pinia, or Vue's built-in "provide"/"inject" mechanism. **Do This:** """vue // ParentComponent.vue <template> <ChildComponent :message="parentMessage" @child-event="handleChildEvent" /> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const parentMessage = ref('Hello from parent!'); const handleChildEvent = (data) => { console.log('Received from child:', data); }; </script> """ """vue // ChildComponent.vue <template> <button @click="emitEvent">Send Message</button> </template> <script setup> import { defineProps, defineEmits } from 'vue'; defineProps({ message: { type: String, required: true } }); const emit = defineEmits(['child-event']); const emitEvent = () => { emit('child-event', 'Hello from child!'); }; </script> """ **Don't Do This:** """vue // Avoid directly mutating props from the child component // ParentComponent.vue <template> <ChildComponent :message="parentMessage" /> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const parentMessage = ref('Hello from parent!'); </script> // ChildComponent.vue <template> <div>{{ message }}</div> </template> <script setup> import { defineProps, onMounted } from 'vue'; const props = defineProps({ message: { type: String, required: true } }); onMounted(() => { props.message = "Trying to change this"; // WRONG! Mutating props is an anti-pattern. }) </script> """ **Why:** Props and events create a clear and predictable data flow. Directly mutating parent-provided props can lead to unpredictable bugs and should be avoided. ### 4.7. Component Directives **Standard:** Write custom directives using the directive lifecycle hooks ("created", "mounted", "updated", "unmounted"). Ensure proper cleanup in the "unmounted" hook to prevent memory leaks. **Do This:** """javascript // v-focus.js export default { mounted: (el) => { el.focus(); }, unmounted: (el) => { // Cleanup if needed } }; // In Component: import focusDirective from ‘./v-focus.js'; <input v-focus /> """ **Don't Do This:** """javascript // Do not create directives with side-effects that cannot be cleaned up. export default { mounted: (el) => { // No unmounted hook - memory leak likely setInterval(() => {console.log("doing something")}, 1000) } }; """ ### 4.8. Async/Await **Standard:** Use "async/await" consistently for asynchronous operations to improve code readability and maintainability. Handle errors with "try/catch" blocks. **Do This:** """javascript const fetchData = async () => { try { const response = await fetch('https://example.com/api/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } }; """ **Don't Do This:** """javascript const fetchData = () => { fetch('https://example.com/api/data') .then(response => response.json()) .then(data => { // ... }) .catch(error => { console.error('Error fetching data:', error); }); }; """ **Why:** "async/await" makes asynchronous code look and behave more like synchronous code, which improves readability and makes error handling easier and more consistent. Using "try/catch" ensures proper error handling for "async/await". ### 4.9. Lazy Loading Components **Standard:** Use lazy-loading for non-critical components, especially those below the fold or in less-used sections of the application. Use "Suspense" to provide a fallback while the component is loading. **Do This:** """vue <template> <div> <Suspense> <template #default> <MyComponent /> </template> <template #fallback> <div>Loading...</div> </template> </Suspense> </div> </template> <script setup> import { defineAsyncComponent } from 'vue'; const MyComponent = defineAsyncComponent(() => import('./MyComponent.vue')); </script> """ **Don't Do This:** Don't load all components eagerly. **Why:** Lazy loading improves initial page load performance by deferring the loading of non-critical components until they are needed. ## 5. Anti-Patterns ### 5.1. Mutating Props **Anti-Pattern:** Avoid directly mutating props passed to a component. Props should be treated as read-only data. If you need to modify the value of a prop, emit an event to the parent component and let the parent handle the modification. **Why:** Mutating props violates the one-way data flow principle, making it difficult to reason about the application's state and leading to unpredictable behavior. ### 5.2. Global State Mutation **Anti-Pattern:** Avoid directly mutating global state or Vuex store state outside of mutations (or actions in Vuex). This makes it difficult to track where state changes originate and can lead to unexpected side effects. Use Pinia for state management in new projects for a more streamlined experience. **Why:** Mutations provide a centralized and trackable way to modify state, making it easier to debug and maintain the application. Using Pinia simplifies state management and provides a more intuitive API. ### 5.3. Overusing "forceUpdate" **Anti-Pattern:** Avoid using "this.$forceUpdate()" unless absolutely necessary. This forces a re-render of the component and its children, which can be inefficient. Instead, rely on Vue's reactivity system to automatically update the component when its data changes. **Why:** "forceUpdate" bypasses Vue's reactivity system, which can lead to performance issues and unexpected behavior. Ensure that your data is reactive and that Vue is tracking dependencies correctly. ### 5.4. Excessive DOM Manipulation **Anti-Pattern:** Avoid directly manipulating the DOM using JavaScript as much as possible. Vue's virtual DOM and templating system are designed to efficiently update the DOM based on data changes. **Why:** Direct DOM manipulation can interfere with Vue's rendering process and lead to performance issues. Rely on Vue's templating system and directives to manage DOM updates. ### 5.5. Inefficient Loops **Anti-Pattern:** Avoid performing complex computations or API calls within "v-for" loops. This can lead to performance issues, especially when rendering large lists. **Why:** Complex operations inside loops can significantly slow down rendering. Pre-compute the necessary data or use techniques like pagination or virtualization to optimize performance. ## 6. Performance Optimization ### 6.1. Key Attribute in "v-for" **Standard:** Always use the "key" attribute when rendering lists with "v-for". The "key" attribute should be a unique identifier for each item in the list. **Do This:** """vue <template> <ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul> </template> """ **Why:** The "key" attribute helps Vue efficiently update the DOM when the list changes (e.g., items are added, removed, or reordered). Without the "key" attribute, Vue may re-render the entire list, leading to performance issues. Using index as a key is an anti-pattern unless the dataset will never change. ### 6.2. Component Caching **Standard:** Use "keep-alive" to cache frequently used components, especially those that are expensive to render. **Do This:** """vue <template> <keep-alive> <component :is="activeComponent" /> </keep-alive> </template> """ **Why:** "keep-alive" caches the component instance, preserving its state and reducing the need for re-rendering when the component is activated again. ### 6.3. Debouncing and Throttling **Standard:** Use debouncing and throttling to limit the rate at which event handlers are executed, especially for events like "input", "scroll", and "resize". **Why:** Debouncing and throttling prevent event handlers from being executed excessively, which can improve performance and reduce unnecessary computations or API calls. ### 6.4. Virtualized Lists **Standard:** Use virtualized lists for rendering large datasets. Virtualized lists only render the items that are currently visible, which significantly improves performance. **Why:** Virtualized lists reduce the amount of DOM that needs to be rendered, which improves scrolling performance and reduces memory usage. ### 6.5. Code Splitting **Standard:** Implement code splitting to break your application into smaller chunks that can be loaded on demand. This reduces the initial load time and improves performance. **Why:** Code splitting allows the browser to download only the code that is needed for the current page or feature, which improves the initial load time and reduces bandwidth usage. ## 7. Security Best Practices ### 7.1. Sanitizing User Input **Standard:** Always sanitize user input to prevent cross-site scripting (XSS) attacks. Use Vue's templating system, which automatically escapes HTML entities, or a dedicated sanitization library like DOMPurify. **Why:** Sanitizing user input prevents malicious code from being injected into your application, protecting users from XSS attacks. ### 7.2. Avoiding "eval" **Standard:** Avoid using the "eval" function, which can execute arbitrary JavaScript code. **Why:** "eval" can introduce security vulnerabilities if it's used to execute untrusted code. It also makes your code harder to debug and optimize. ### 7.3. Server-Side Rendering (SSR) Considerations **Standard:** When using server-side rendering (SSR), be aware of potential security risks such as XSS and cross-site request forgery (CSRF). Implement appropriate security measures on the server side, such as input validation, output encoding, and CSRF protection. **Why:** SSR introduces additional security considerations because the server is responsible for rendering the initial HTML. Failing to implement proper security measures can expose your application to attacks.
# 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.
# 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 "<script setup>") for improved code organization and reusability. **Do This:** """vue <template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script setup> import { ref } from 'vue'; const count = ref(0); const increment = () => { count.value++; }; </script> """ **Don't Do This:** (Options API for new components) """vue <template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script> export default { data() { return { count: 0 }; }, methods: { increment() { this.count++; } } }; </script> """ **Why This Matters:** The Composition API provides a more flexible and organized way to structure component logic, especially when combined with "<script setup>". It resolves many of the limitations of the Options API, particularly regarding code reuse and maintainability. "<script setup>" drastically cleans up the component definition. ### 2.3 Reusable Composables **Standard:** Extract reusable logic into composables. **Do This:** """javascript // src/composables/useCounter.js import { ref } from 'vue'; export function useCounter(initialValue = 0) { const count = ref(initialValue); const increment = () => { count.value++; }; const decrement = () => { count.value--; }; return { count, increment, decrement }; } // Component usage import { useCounter } from '@/composables/useCounter'; const { count, increment } = useCounter(10); """ **Don't Do This:** (Duplicating logic across multiple components) """vue <template> <!-- Component 1 --> count: {{ count }} <button @click="increment">Increment</button> </template> <script setup> import { ref } from 'vue'; const count = ref(0); const increment = () => { count.value++ } </script> <template> <!-- Component 2 repeats the entire pattern --> count: {{ count }} <button @click="increment">Increment</button> </template> <script setup> import { ref } from 'vue'; const count = ref(0); const increment = () => { count.value++ } </script> """ **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 <script setup> defineProps({ title: { type: String, required: true }, price: { type: Number, default: 0, validator: (value) => value >= 0 }, product: { type: Object, required: true, validator: (obj) => { return 'name' in obj && 'description' in obj } } }); </script> """ **Don't Do This:** (Omitting prop types and validations) """vue <script setup> defineProps(['title', 'price']); // Not ideal. No type information or validation. </script> """ **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 <script setup> const emit = defineEmits(['addToCart', 'removeFromCart']); const addToCart = () => { emit('addToCart', { productId: 123, quantity: 1 }); }; </script> """ **Don't Do This:** (Emitting events without declaration) """vue <script setup> const addToCart = () => { this.$emit('addToCart', { productId: 123, quantity: 1 }); // Avoid using "$emit" directly. }; </script> """ **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 <template> <component :is="currentComponent"></component> </template> <script setup> import { ref } from 'vue'; import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; const currentComponent = ref('ComponentA'); // Function to update the component dynamically. const changeComponent = (componentName) => { if (componentName === 'ComponentA') { currentComponent.value = ComponentA; // Or the string 'ComponentA', consistent with initial value } else if (componentName === 'ComponentB') { currentComponent.value = ComponentB; // Or the string 'ComponentB' } else { console.warn('Invalid component name:', componentName); } }; // Example usage: simulating a component change setTimeout(() => { changeComponent('ComponentB'); }, 3000); </script> """ **Don't Do This:** """vue <template> <component :is="userInput"></component> // RISK: Unsanitized user input directly determining the component. </template> """ **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 <template> <button @click="updateCount">Update Count</button> </template> <script setup> import { useCounterStore } from '../stores/counter'; const counter = useCounterStore(); const updateCount = () => { counter.count++; //BAD: avoid modifying the store state directly here } </script> """ **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.