|
| 1 | +--- |
| 2 | +title: Guide |
| 3 | +--- |
| 4 | + |
| 5 | +## Brief overview & Development guide |
| 6 | + |
| 7 | +NuxtStore is a platform-agnostic ecommerce frontend solution that allows seamless integration with various ecommerce backends! It provides tools for building customizable, high-performance online storefronts. |
| 8 | + |
| 9 | +This guide will provide you with an overview of the project structure, tools and best practices to help you get started quickly. |
| 10 | + |
| 11 | +## Project Structure |
| 12 | + |
| 13 | +This project follows a modular and scalable architecture, by decoupling the composable frontend from the server middleware allowing for easy collaboration among multiple teams and ensuring consistent development practices across the codebase. |
| 14 | + |
| 15 | +The architecture includes the following apps: |
| 16 | + |
| 17 | +- `server` - NuxtStore server middleware powered by **[Fastify](https://fastify.dev/)** |
| 18 | +- `web` - A typescript based web application powered by **[Nuxt](https://nuxtjs.org)** and **[Vue](https://vuejs.org)** |
| 19 | + |
| 20 | + |
| 21 | +> **Note**: These two standalone modules are capable of being integrated with any backend service with consistent APIs without extra configuration to setup. |
| 22 | +
|
| 23 | +#### Web application |
| 24 | + |
| 25 | +Web app follows a typical Nuxt.js directory [structure](https://nuxt.com/docs/guide/directory-structure/nuxt) with a few tweaks: |
| 26 | + |
| 27 | +```shell |
| 28 | + |
| 29 | +apps/ |
| 30 | + └── root/ |
| 31 | + ├── ... |
| 32 | + ├── assets/ # Static assets |
| 33 | + ├── components/ |
| 34 | + │ ├── atoms/ # Atomic components related to feature (e.g atoms/cart/NsAddToCartButton.vue) |
| 35 | + │ ├── molecules/ # Molecular components made using atoms (e.g molecules/form/NsEmailInput.vue) |
| 36 | + │ └── organisms/ # Large components made using atoms and molecules (e.g organisms/address/NsAddressCard.vue) |
| 37 | + ├── composables/ # Custom hooks composing reactive logic |
| 38 | + ├── constants/ |
| 39 | + │ ├── api.ts # API-related constants (HTTP methods) |
| 40 | + │ └── ui.ts # UI-related constants (colors, breakpoints) |
| 41 | + ├── data/ # Static data or JSON files |
| 42 | + ├── layouts/ # Nuxt Layouts |
| 43 | + ├── middleware/ # Client side route middlewares |
| 44 | + │ ├── auth.ts |
| 45 | + │ └── ... |
| 46 | + ├── pages/ # Pages |
| 47 | + │ ├── index.vue # App home page component |
| 48 | + │ └── ... |
| 49 | + ├── plugins/ # App plugins run on both client and server |
| 50 | + ├── public/ # Public assets |
| 51 | + ├── server/ # In-app backend server |
| 52 | + ├── shared/ |
| 53 | + │ ├── types/ # Type definitions |
| 54 | + │ └── utils/ # Non-reactive helper functions |
| 55 | + ├── stores/ # Pinia stores for state management |
| 56 | + │ ├── auth.store.ts |
| 57 | + │ └── cart.store.ts |
| 58 | + ├── tests/ # Component and feature unit tests |
| 59 | + ├── eslint.config.mjs # Linter rules |
| 60 | + ├── features.json # Feature flags |
| 61 | + ├── app.vue # Application entry point |
| 62 | + ├── nuxt.config.ts # Nuxt.js configuration |
| 63 | + ├── package.json # Package entry point |
| 64 | + ├── tailwind.config.ts # TailwindCSS configuration |
| 65 | + ├── tsconfig.json # TypeScript configuration |
| 66 | + ├── vitest.config.ts # Vitest configuration |
| 67 | + └── ... |
| 68 | + |
| 69 | +``` |
| 70 | + |
| 71 | +List of essential directories: |
| 72 | + |
| 73 | +- `components` NuxtStore UI components, like `ProductCard` or `Review` |
| 74 | +- `stores` Pinia store containing global state, getters and mutators |
| 75 | +- `composables` Contains reusable composition functions, e.g. data fetchers and stateful helpers |
| 76 | +- `shared` Contains types and utilities [shared](https://github.com/nuxt/nuxt/releases/tag/v3.14.0) across client and server e.g `product.type.ts`. Auto-import support will follow in next major release. |
| 77 | +- `tests` Contains mocks for components, composables and store actions |
| 78 | + |
| 79 | +## Project Guide |
| 80 | + |
| 81 | +This project follows a few conventions to help with organizing your code: |
| 82 | + |
| 83 | +- Each function is located in a dedicated module and exported from the `index.ts` file. |
| 84 | +- In this nuxt application, avoid importing auto-imported APIs (ref, onMounted etc.) or compiler macros (defineProps, defineEmits etc.) and PrimeVue components (Button, InputText, Dialog, Carousel etc.) |
| 85 | +- Names are short, descriptive and must follow our consistent naming convention ([guide](https://docs.vaah.dev/guide/code)) |
| 86 | +- Follow this [nuxt guide](https://docs.vaah.dev/guide/nuxt) to avoid common mistakes and comply with industry standard practices |
| 87 | +- Functions (including composables and utils) are exported using explicit named exports instead of anonymous exports |
| 88 | +- Actions or functions are defined following the trivial function definitions (`func() { }`) **instead of arrow functions** (`const func = () => { }`) to create visible distinction between constants and methods |
| 89 | +- This project follows [gitflow](https://docs.vaah.dev/guide/git) as the branching strategy |
| 90 | +- Feature branch name must follow a simple convention such as |
| 91 | +`feature/<purpose>-<page_name>-<subject>`. Subject can be component or specific feature |
| 92 | +(e.g **ui-cart-summary**, **ui-layout-footer**, **flow-checkout-payment**, **action-cart-quantity**) |
| 93 | +- [JS doc comments](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) are added at necessary places to define the structure of a complex entity or explain the construct of a method |
| 94 | + |
| 95 | +> **Note:** In order to add code comments, we prefix the action with a `@` for contributors reference such as **@todo:** or **@debug:** |
| 96 | +
|
| 97 | +### Components |
| 98 | + |
| 99 | +NuxtStore UI follows [atomic design principle](https://atomicdesign.bradfrost.com/chapter-2/) to develop components and it leverages [PrimeVue](https://primevue.org/) as the building blocks of storefront components. All components are auto-imported by their name (no path-prefix) and are located inside subfolders in the `components` directory. |
| 100 | + |
| 101 | +- Introduction to project components: |
| 102 | + |
| 103 | + - Consists of representational components that are designed to fulfill project requirements |
| 104 | + - Components should be placed in dedicated folders inside components directory |
| 105 | + - Root of the component folder has `atoms`, `molecules` and `organisms` |
| 106 | + - Each component name must be prefixed with **"Ns"** (e.g **NsProductCard**) |
| 107 | + - TypeScript types are located inside the SFC for ease of access and coupling |
| 108 | + - Tests for components are located in the `/tests/components` folder (e.g **NsUserAddress.spec.ts**) |
| 109 | + - Folders inside /atoms, /molecules and /organisms must follow their purpose |
| 110 | + |
| 111 | +Expected directory structure: |
| 112 | + |
| 113 | +```shell |
| 114 | +components/ |
| 115 | + └── atoms/ |
| 116 | + └── cart/ |
| 117 | + └── NsAddToCartButton.vue |
| 118 | + └── NsCartIcon.vue |
| 119 | + └── ui/ |
| 120 | + └── NsHeader.vue |
| 121 | + └── NsFooter.vue |
| 122 | + └── ... |
| 123 | + └── molecules/ |
| 124 | + └── form/ |
| 125 | + └── NsEmailInput.vue |
| 126 | + └── NsPasswordInput.vue |
| 127 | + └── ... |
| 128 | + └── organisms/ |
| 129 | + └── address/ |
| 130 | + └── NsAddressCard.vue |
| 131 | + └── cart/ |
| 132 | + └── NsCartOverview.vue |
| 133 | + └── forms/ |
| 134 | + └── NsSignUpForm.vue |
| 135 | + └── NsAddressForm.vue |
| 136 | + └── ... |
| 137 | +``` |
| 138 | + |
| 139 | +For more information about available NuxtStore components for Vue (Nuxt), check out [documentation](). |
| 140 | + |
| 141 | +- **Convention:** |
| 142 | + |
| 143 | + - Vue (Nuxt) components should follow `PascalCase` pattern (`CategoryFilters`, `Heading`) |
| 144 | + - The types for component's props should be named `{Component}Props` and exist in the same SFC as the component. For example, `GalleryProps` or `HeadingProps` |
| 145 | + - Line of code in a single file component must not exceed 200 (formatted) |
| 146 | + |
| 147 | +- **Prop Declaration:** In vue, there are multiple ways to define component props especially with typescript. [[checkout official docs](https://vuejs.org/guide/typescript/composition-api.html#typing-component-props)] To ensure consistency and still allow customisability, we have adopted the following pattern. |
| 148 | + |
| 149 | + - Multiword props are defined in `camelCase` and passed in template as `kebab-case` |
| 150 | + |
| 151 | +Example: |
| 152 | +```ts |
| 153 | +export type CartActionProps = { |
| 154 | + icon?: string, |
| 155 | + label?: string, |
| 156 | + ... |
| 157 | +}; |
| 158 | + |
| 159 | +const props = withDefaults(defineProps<CartActionProps>(), { |
| 160 | + icon: "pi pi-cart", |
| 161 | + label: "Add to cart" |
| 162 | +}); |
| 163 | +``` |
| 164 | + |
| 165 | +### State Management |
| 166 | + |
| 167 | +We are using [Pinia state management](https://pinia.vuejs.org/ssr/nuxt.html) to lock the data responsibility along with the composition functions (composables). |
| 168 | + |
| 169 | +- Pinia stores are defined in the style of [Setup stores](https://pinia.vuejs.org/core-concepts/#Setup-Stores), keeping performance and modern standards in view |
| 170 | +- Each store file must end with a `.store.ts` extension |
| 171 | +- Store methods are as modular and reusable as possible and do not perform side effects. If required, new action is created in order to handle `post{Action}` (e.g **addToCart** , **postAddToCart**) |
| 172 | +- [`useState`](https://nuxt.com/docs/getting-started/state-management) is used in very narrow context where creating global store for shared state doesn't make sense |
| 173 | +- Sequentially a store structure should have state >> getters >> actions for readability |
| 174 | + |
| 175 | +### Composables |
| 176 | + |
| 177 | +Composables are useful when stateful logic has to be reused across components - e.g. controlling component state or leverage lifecycle hooks or accessing DOM api. |
| 178 | +Project composables are located on top level inside the `composables/` directory. |
| 179 | + |
| 180 | +**Convention:** |
| 181 | +- Each composable should be prefixed with `use` keyword (`useCartStatus`) |
| 182 | +- Composables should follow `camelCase` pattern (`useProductReviews`) |
| 183 | +- Composables follow single responsibility principle. Avoid calling side effects inside composables |
| 184 | +- This project inherits most of the composables from [vueuse](https://vueuse.org/guide/best-practice.html) library. Always search for an already available `vueuse` composable before writing on own for common tasks or DOM manipulation. |
| 185 | +- [Composables](https://vuejs.org/guide/reusability/composables.html#conventions-and-best-practices) should only be called in synchronous environment and on top level. Remember to clean attached event handlers or timeouts if defined inside a composable using vue's unmount hooks |
| 186 | +- Built-in Nuxt composables must be called in the right context to avoid critical errors such as [Nuxt instance unavailable](https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables) |
| 187 | + |
| 188 | +> **Note:** State connected to the certain composable is read-only and reactive. They are modified by the internal modifiers (setters) |
| 189 | +
|
| 190 | +### Utils |
| 191 | + |
| 192 | +Main purpose of utils is to encapsulate non-reactive helper method logic and draw a semantic boundary between reactive composables and other auto-imported utility functions. Utils are contained inside the Shared/ folder. This project follows some simple conventions for utils: |
| 193 | + |
| 194 | +- Project utils end with a `.utils.ts` extension (e.g format.utils.ts, wishlist.utils.ts) |
| 195 | +- `utils/index.utils.ts` contains all the shared functions across all contexts/features (e.g **isEqual\<T\>(a,b)** compares two entities for equality) |
| 196 | +- Named exports are preferred over default export (e.g export function cleanUserInput(){ }) |
| 197 | +- One utility method can have multiple exports, allowing relevant functions to be grouped together |
| 198 | +- Utility functions are synchronous and do not communicate with any reactive source (store/composables) |
| 199 | + |
| 200 | +### Localization |
| 201 | + |
| 202 | +NuxtStore ships with a basic setup for i18n localization powered by the [Nuxt-i18n](https://i18n.nuxtjs.org) library. Project locale translations are stored in `locale/[namespace].json` files. Translations are grouped by _features_, and imported only where required to minimize bundle size. |
| 203 | +Refer to the [Nuxt-i18n](https://i18n.nuxtjs.org) documentation for translating content with SSR support. |
| 204 | + |
| 205 | +### Testing |
| 206 | + |
| 207 | +The project provides a basic setup for testing TypeScript code with [Vitest](https://vitest.dev/) and [Vue Test Utils](https://test-utils.vuejs.org/guide/) for testing Vue (Nuxt) components and [pinia stores](https://pinia.vuejs.org/cookbook/testing.html#Mocking-the-returned-value-of-an-action). |
| 208 | + |
| 209 | +Testing configuration files: (runs on nuxt environment and `happy-dom`) |
| 210 | + |
| 211 | +- `vitest.config.ts` - Test runner config file |
| 212 | + |
| 213 | +Testing commands: |
| 214 | + |
| 215 | +- `npm run unittest` - Run all the test scripts in the project (in watch mode by default) |
| 216 | +- `npm run unittest <Component>` - Run specific component/feature mocks |
| 217 | + |
| 218 | +### Conventions enforced by automated tooling |
| 219 | + |
| 220 | +To help you code with best practices in mind, this boilerplate comes with some automated tooling. |
| 221 | + |
| 222 | +- All test descriptions follow naming convention `test('should <component>... ')` |
| 223 | +- Automatic code linting is managed by [nuxt-eslint-module](https://eslint.nuxt.com/packages/config) |
| 224 | + |
| 225 | +### Cache control |
| 226 | + |
| 227 | +As the initial Nuxt setup is not caching images generated by `NuxtImg`, the `nuxt.config.ts` in this repository has been extended to cache those images as well (by setting proper headers): |
| 228 | + |
| 229 | +```ts |
| 230 | +routeRules: { |
| 231 | + '/_ipx/**': { headers: { 'cache-control': `public, max-age=31536000, immutable` } }, |
| 232 | + '/icons/**': { headers: { 'cache-control': `public, max-age=31536000, immutable` } }, |
| 233 | + '/favicon.ico': { headers: { 'cache-control': `putypescriptblic, max-age=31536000, immutable` } }, |
| 234 | +}, |
| 235 | +``` |
| 236 | + |
| 237 | +You can read about more possible rules in the [Nuxt documentation](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering). |
| 238 | + |
| 239 | +#### More about performance |
| 240 | + |
| 241 | +Additional performance good practices and vue project guide can be found [HERE](https://docs.vaah.dev/guide/vue-and-nuxt-performance-improvement). |
| 242 | + |
| 243 | +### Recommended IDE setup |
| 244 | + |
| 245 | +1. Visual Studio Code (with prettier_extension @`v9.14.0` exact and the latest version of microsoft eslint_extension) |
| 246 | +2. webstorm (for developers comfortable in jetbrains IDE like phpstorm with no extra configuration - |
| 247 | +[ws_eslint_customisation_guide](https://www.jetbrains.com/help/webstorm/eslint.html#ws_js_eslint_activate)) |
0 commit comments