Skip to content

Implementation

General Structure

The general folder structure relies on a DDD (Domain-Driven Design) approach - where everything related to a feature remains in the same folder (domain). Additionally, the src itself is considered as a root domain, containing all the global components for the app.

I/O

To maintain consistency across all domains and improve dependency injection, all folders have a barrel file to manage the export of their content. This approach actually mimics how Vue uses the Event Bus within its SFC components. When a child component needs to send data to a parent component, it will emit the data. The same happens with this project's domains - each domain "emits" all relevant features to the parent via a barrel file.

We also set up aliases to increase maintainability in dependency injection. Below we have the most common aliases:

ts
// representation of vite.config.ts
function getPath(path: string) {
  return fileURLToPath(new URL(path, import.meta.url));
}

{
  alias: {
      '@': getPath('./src'),
      '@Components': getPath('./src/components'),
      '@Composables': getPath('./src/composables'),
      '@Constants': getPath('./src/constants'),
      '@Directives': getPath('./src/directives'),
      '@Enums': getPath('./src/enums'),
      '@Helpers': getPath('./src/helpers'),
      '@Layouts': getPath('./src/layouts'),
      '@Libraries': getPath('./src/libraries'),
      '@Locales': getPath('./src/locales'),
      '@Plugins': getPath('./src/plugins'),
      '@Providers': getPath('./src/providers'),
      '@Services': getPath('./src/services'),
      '@Stores': getPath('./src/stores'),
      '@Styles': getPath('./src/styles'),
      '@Types': getPath('./src/types'),
      '@Views': getPath('./src/views'),

      // Modules
      '@AdminModule': getPath('./src/modules/Admin'),
      '@UserModule': getPath('./src/modules/User'),
    },
}

Components

The components rely heavily on the CDD (Component-Driven Development) approach, borrowing the classification from Atomic Design. We start from the smallest piece of UI possible and then group them up to create more intricate UI sections. They are named in ascending order to improve DevX. Here it is:

atoms → molecules → organisms → templates → layouts → views

  • atoms: Can import only assets or one other atom component.

  • molecules: Can import several atoms components, assets, or one other molecule level component.

  • organisms: Can import several atoms and molecules components, assets, or one other organism level component.

  • templates: Can import several atoms, molecules, organisms, and one other template component. They are rare and usually exist within a module folder.

  • views: Can import everything. These are the pages where the router will point to.

There are also some special components:

  • unique: Following Vue best practices, these kinds of components will appear only once in each view. (TheTopNav, TheFooter, TheAside, TheModal)

  • layouts: These components will use several unique components to keep everything DRY in the views. They usually have a slot to be more dynamic. (TheDefaultLayout, TheAuthLayout)

INFO

An important feature of this architecture is that there are levels to be respected when composing the UI. A component will only import a component from the same level or from an inferior level.

DANGER

Also, to avoid cyclic redundancy, when using a same-level component, the barrel file of the domain must not be used. That component will be imported using the default approach.

Here is an example:

👎🏼 BAD

vue
// BaseButton
<script setup lang="ts">
import { BaseText } from './index';
</script>

👍🏼 GOOD

vue
// BaseButton
<script setup lang="ts">
import BaseText from '../BaseText/BaseText.vue';
</script>

Another key feature of this approach is that all global UI components are by definition dummy components, meaning they do not make any kind of requests to APIs or stores. They have a single responsibility: render the template. So they will only receive data by props, and that data should already be properly sanitized. This will increase the reusability of the UI and also ease up the testing process. 🚀

Prefixes

Components in the project follow the Atomic Design Methodology, separating themselves as Atoms, Molecules and Organisms. To easily identify the level of a component - and improving the efficiency overhaul as a consequence - prefixes are to be set with the name of the component, that being A for Atoms, M for Molecules and O for Organisms:

  • AComponent
  • MComponent
  • OComponent

The same does not apply for Templates and Layouts, as they have the suffix Layout and Template to identify them:

  • TheComponentLayout
  • ComponentTemplate

Suffixes

Similar to prefixes, suffixes are used to identify the scope of a certain file and what it refers to. The normal use cases for this are as follows:

  • example.constant.ts
  • example.helper.ts
  • example.plugin.ts
  • example.store.ts
  • example.type.ts
  • example.provider.ts
  • example.service.ts

Folder Structure

Here you will find a folder-by-folder explanation. This structure is highly inspired by DDD, Modular Architecture, and also the work done by Nuxt.js and Astro.js.

javascript
/
  // file settings will live outside of src
  /.vscode
    // IDE settings
  /.histoire
    // storybook
  /docs
    // this documentation that you are reading right now
  /public
    // vue standard
  /src
    /assets
    /components
    /composables
    /constants
    /directives
    /enums
    /helpers
    /layouts
    /libraries
    /locales
    /modules
    /plugins
    /providers
    /services
    /stores
    /styles
    /types
    /views
    App.vue
    main.ts

Folder Details

/assets

  • images
  • fonts
  • local SVGs
  • mock data files

/components

  • atoms
  • molecules
  • organisms
  • templates
  • unique

/composables

  • global project composables

/constants

  • global project constants

/directives

  • global project directives

/enums

  • global project enums

/helpers

  • global helpers (formatters, etc.)

/layouts

  • top-level layouts

/libraries

Here you will find the settings for all the libraries that are used in the project. Instead of randomly setting things up in main.ts, we've created a folder for each library used and set it up there. Then there is an array of library settings being exported by this domain. The main.ts imports that array and with a simple forEach loop, all libraries are smoothly injected into the app. Nice, huh?

/locales

  • internationalization files

/modules

These are the actual domains of the app (Auth, User, etc.). Here are the key points of these kinds of components; they:

  • can have pretty much anything that the root-level domain has (components, assets, composables, etc.).
  • follow the same pattern as the root domain (dummy and atomic components, barrel files, etc.).
  • will also have the providers and the services (GraphQL, REST, etc.).
  • will be used in views to actually compose the features.

/plugins

Same approach as libraries. Again, a single line to register all plugins. Such a beautiful thing!

/providers

  • project providers (Axios, GraphQL, APIs)

/services

  • global project services

/stores

  • global project stores (useUiStore, useSessionStore)

/styles

  • global styles and CSS variables

/types

  • global TS types and a shortcut for modules and UI types. This alias will be heavily used in views and modules.

/views

These are accessible via Router and they are responsible for:

  • composing the pages
  • requesting data from APIs
  • sanitizing data from APIs
  • providing data for inner modules or components
  • handling user events and interactions

This approach centers the request and data handling in a single file. Zero lookup for the source of your component calls - it will always be in the views! 😃