Engineering/Coding Standards/TypeScript/Vue/Vue Project Structure And Component Design/

Vue 3 Project Structure & Component Design Standards · VUE-01

Project Structure

Use TypeScript for Type Safety · VUE-01.1 · MUST

Typescript enhances code reliability, improves developer experience and catches errors at compile-time.

Apply a Consistent and Organised Vue Project Structure · VUE-01.2 · MUST

To aide maintainability and scalability, a consistent project structure must be used.

The following is a default Vue project structure:

  • src/: Contains all source files.
  • components/: Reusable components.
  • views/: Components linked to routes.
  • assets/: Static assets (images, fonts, etc.).
  • store/: Pinia stores (if using Pinia for state management)/
  • router/: Routing configuration.

When using a framework which has a different project structure, then that project structure must be applied instead. See NUXT-01.1 Nuxt Project Structure Standard.

Follow a Consistent Project Structure · VUE-01.3 · MUST

Encourage maintainable order and predictability in the codebase, making it easier to navigate and manage.

Apply Consistent Error Handling Practices · VUE-01.4 · MUST

Ensure a uniform approach to error handling, making it easier to debug and maintain.

Optimize Performance with Lazy Loading · VUE-01.5 · MUST

Enhance user experience by optimising initial load times through reduced unnecessary computations.

Lazy Loading Example (index.vue)

const routes = [
  {
    path: '/home',
    name: 'Home',
    component: () => import('../views/Home.vue'), // Lazy-loaded component
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue'), // Lazy-loaded component
  },
];

Optimise Performance with Debouncing · VUE-01.6 · MUST

Enhance user experience by optimising how frequently a function is called when responding to user input.

Debouncing ensures that a function is not called too frequently in response to user input, such as typing in a search box. This helps improve performance by limiting the rate at which expensive operations (like API calls) are triggered.

Debouncing Example

<template>
  <input v-model="query" @input="debouncedSearch" placeholder="Search..." />
</template>

<script setup>
import { ref, watch } from 'vue';
import debounce from 'lodash';

const query = ref('');

const search = (term) => {
  // Perform search or API call
  expensiveSearchOperation(term);
};

// Debounce the search function to limit the rate of API calls
const debouncedSearch = debounce(() => search(query.value), 300);
</script>

Be Cautious When Using External Dependencies · VUE-01.7 · MUST

While external libraries can speed up development, they can introduce risks in the forms of: security vulnerabilities, bugs, performance costs, inconsistent UI designs and maintenance issues.

Dependencies should never be added if they offer simple functionality which can be implemented with reasonable effort.

This minimizes the risk of being blocked by external bugs, ensures better control over the code, and reduces reliance on third-party updates and maintenance.

Use Provide/Inject API for Dependency Injection · VUE-01.8 · SHOULD

This provides efficiently managed dependencies and state across component hierarchies without prop drilling.

Component Design

Leverage Script Setup Syntax for Single-File Components · VUE-01.9 · MUST

By utilising

Use a Consistent Naming Convention · VUE-01.11 · MUST

Improve codebase readibility and consistency, making it easier for teams to work together.

Use Scoped Styles for Component CSS · VUE-01.12 · MUST

Ensure that styles are encapsulated and do not leak into other components.

Utilize Async Components for Code Splitting · VUE-01.13 · MUST

Optimise performance by lazy-loading components and reducing initial bundle size.

Use Composition API for Logic and State Management · VUE-01.14 · MUST

The Composition API offers code reusability, maintainability and readibility by encouraging structuring of more organised components. By confining each part of logic we also enhance our scalability.

This is considered a better practice than the options API, which can lead to logic being scattered across different lifecycle hooks and options.

Prefer Template Refs Over IDs for DOM Manipulation · VUE-01.15 · MUST

Provide a more Vue-centric and reactive way to access and manipulate DOM elements.

Template Ref (✅)

<template>
  <input ref="usernameInput" type="text" placeholder="Enter username" />
</template>

<script="setup">
  import { ref } from 'vue';

  const usernameInput = ref(null);

  const focusInput = () => {
    if (usernameInput.value) {
      usernameInput.value.focus(); // Directly accessing the input element
    }
  };
</script>

Template Id (❌)

<template>
  <input id="usernameInput" type="text" placeholder="Enter username" />
</template>
<script setup>
  const focusInput = () => {
    // Bad Practice
    const inputElement = document.getElementById('usernameInput');
    if (inputElement) {
      inputElement.focus(); // Accessing the input element via ID
    }
  };
</script>

Handle Side Effects with Watchers and Lifecycle Hooks · VUE-01.16 · MUST

Clearly separate side effects from main component logic, improving maintainability and testability.

Watchers in Vue are used to perform actions in response to changes in reactive data properties. If you have a ref or reactive in your component of which has a value you watch to react to.

Example

watch(searchQuery, (newQuery) => {
  if (newQuery) {
    fetchResults(newQuery);
  }
});

Use computed for Derived State · VUE-01.17 · MUST

Leverage Vue’s computed properties to optimize performance and improve code maintainably.

Performance

By assigning composables to const, we also leverage caching to avoid unecessary computations.

<template>
  <div>
    <p>Full Name: {{ fullName }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const firstName = ref('John');
const lastName = ref('Doe');

// Any update to firstName or lastName will cause the value of fullName to change.
const fullName = computed(() => <code>${firstName.value} ${lastName.value}</code>);
</script>

Avoid Complex Logic in Templates · VUE-01.10 · SHOULD

Keep templates clean and declarative; complex logic should be moved to the setup function or composables.

If you have complex logic in a template, the template is doing too much and is likely an indicator to split login into composables or helpers.