Derived Modular Arch
Guides

Vite + React

Canonical runnable DMA example on Vite + React.

This is the reference DMA example: a runnable mini-shop, full import graph, colocated stores, promotion of cart → services/. Other framework guides are variations of the same story.

Sources: examples/vite-react.

Composition root

DMA treats src/app/ as the composition root — same role as pages/ or routes/ in other stacks.

src/app/
├── app.tsx         # nav, badges, feature mounts, prop wiring
├── providers.tsx   # ThemeProvider from shared/ui
└── app.css         # global styles only here

app.tsx imports only */public/*:

import { CatalogPage } from "@/features/catalog/public/catalog-page";
import { addNotification } from "@/features/notifications/public/notifications-api";

<CatalogPage
  onAddedToCart={(name) => addNotification(`Added ${name}`)}
/>

Catalog → notifications glue via props — see Cross-module wiring.

Mini-shop: who owns what

ModuleState ownerImports
Catalogcatalog.store.ts (filters)services/cart, shared/*
Checkoutcheckout.store.ts (delivery)services/cart, shared/*
Notificationsnotifications.store.tsshared/*
Cartservices/cart/public/cart.tsshared/model
Profilestage-0 profile.tsxshared/ui

No catalog → notifications edge — only app → both modules.

Promotion: checkout → cart

The cart did not start in services/. When catalog also needed addCartItem, cart was promoted:

Before (1 consumer)              After (2 consumers)
features/checkout/              features/catalog/public/ ──┐
  checkout.store (cart)         features/checkout/public/ ─┼──► services/cart/public/cart.ts
                                checkout.store (delivery only)

dma check sees inbound edges and confirms cart belongs in services/, not inside a feature.

Where to put code

TaskFolderExample
Screen mounted from appfeatures/<name>/public/ or stage-0 features/<name>.tsxcatalog-page.tsx
Single-feature statecolocated *.store.tscheckout.store.ts
Scenario for 2+ modulesservices/<name>/public/cart.ts
UI primitive without product logicshared/ui/button.tsx
Pure helpershared/lib/format-currency.ts
Type for 2+ modulesshared/model/product.ts
Providersapp/providers.tsxThemeProvider

Data fetching

LayerFileRole
Transportshared/api/http.tsThin get<T>()
Feature APIfeatures/catalog/catalog.api.tsOnly catalog imports
Demo datapublic/catalog.jsonVite static

Other features do not import catalog.api.ts. Shared HTTP goes through shared/api/http.ts.

Styles

ApproachWhereExample
Global CSSapp/app.cssreset, shell
CSS Modulesnext to public/ entrycatalog-page.module.css
Plain CSScolocated in featurecheckout-page.css
SCSS modulestage-0 featureprofile.module.scss
CSS-in-JSinternal featurenotifications.styles.ts

Global styles — only in app/. Feature-specific skins do not belong in shared/.

Tests

Colocated *.test.ts next to code (Vitest):

  • catalog.store.test.ts — internal store
  • services/cart/public/cart.test.ts — service public API
  • shared/lib/format-currency.test.ts — portable helper

E2E (not in the example) — at app root (e2e/).

Commands

bun run dev
bun run build
bun run dma-check   # dma check .
bun run lint        # ESLint + @derived-modular/eslint-plugin
bun run test

What's next

On this page