Architecture & File Structure
The monorepo layout, the three editions plus the shared design-system and mock API, the build pipeline, and how it all deploys.
This guide is for developers integrating or extending Taxi CRM 2026. The product is an
npm-workspaces monorepo with three UI editions that share one design layer and one mock
API. The HTML edition is the canonical source; the React and Vue editions consume a compiled
@taxi-crm/design-system package built from it.
Monorepo layout
taxi-crm-2024/ (root workspace)
├── apps/
│ ├── html/ canonical edition — Gulp + PUG + LESS → static HTML
│ │ ├── pug/ page templates + partials
│ │ └── assets/
│ │ ├── css/ ~70 LESS partials; style.less is the entry point
│ │ │ ├── style.less imports every partial
│ │ │ └── 01-variable.less design tokens (@-variables)
│ │ ├── js/
│ │ │ ├── core/app.js loaded on every page (app.*, API_CONFIG, i18n)
│ │ │ └── pages/<feature>/ one script per page
│ │ ├── locales/ 8-language translation JSON
│ │ └── img/
│ ├── react/ React 18 + Vite 5 edition
│ │ └── src/ (main.jsx, App.jsx)
│ └── vue/ Vue 3 + Vite 5 edition
│ └── src/ (main.js, App.vue)
├── packages/
│ └── design-system/ @taxi-crm/design-system (framework-agnostic)
│ ├── api-contract.mjs endpoints + createApi() helper
│ ├── build.mjs builds dist/ from apps/html
│ └── dist/ (gitignored) style.css, tokens.*, img/, locales/
├── services/
│ └── api/ Express 4 mock API (Node 20)
│ ├── server.js Cloud Run entrypoint
│ ├── index.js the Express app
│ └── src/ (routes, controllers, config.js, middlewares)
├── tools/
│ └── ship.mjs packaging tool (PUG/LESS → dist, ThemeForest scrub)
├── firebase.json Hosting sites + Cloud Run rewrite
└── package.json workspaces + root scripts
The workspaces are declared in the root package.json as apps/*, services/* and
packages/*.
The three editions
| Edition | Stack | How it builds | Data layer |
|---|---|---|---|
| HTML (canonical) | Gulp 4, PUG → HTML, LESS → CSS, jQuery 3.7 + Bootstrap 5 + plugins | tools/ship.mjs (PUG/LESS compile) | API_CONFIG in app.js |
| React | React 18, Vite 5 | vite build | @taxi-crm/design-system/api-contract |
| Vue | Vue 3, Vite 5 | vite build | @taxi-crm/design-system/api-contract |
The HTML edition holds the authoritative LESS, locales and image assets. The React and Vue editions don't re-implement the styling — they import the compiled outputs of the design-system package (see below), so all three editions stay visually identical.
The shared design-system package
@taxi-crm/design-system is a framework-agnostic layer consumed by every edition. Its
build.mjs (run with npm run ds:build) builds dist/ from apps/html:
| Output | How it's produced |
|---|---|
dist/style.css | LESS style.less compiled with the less package |
dist/tokens.json | the flat @name: value; variables extracted from 01-variable.less |
dist/tokens.css | the same tokens emitted as :root { --name: value; } custom properties |
dist/img/, dist/locales/ | copied from apps/html/assets (8 languages) |
The package's exports map exposes these to the Vite editions:
"exports": {
"./api-contract": "./api-contract.mjs",
"./style.css": "./dist/style.css",
"./tokens.css": "./dist/tokens.css",
"./tokens.json": "./dist/tokens.json"
}A React/Vue entry file imports them directly:
import '@taxi-crm/design-system/style.css';
import '@taxi-crm/design-system/tokens.css';
import { createApi, endpoints } from '@taxi-crm/design-system/api-contract';Because the CSS, tokens and locales all originate from the HTML edition, editing
01-variable.less and re-running ds:build re-themes all three editions at once.
The mock API service
services/api is an Express 4 app (Node 20) that returns static JSON fixtures so the
editions have data to render. Entry point server.js binds the app from index.js to the
port Cloud Run provides (defaulting to 8080 in development). It is a reference
implementation — read it to learn the exact response shape your real backend must return,
then replace it. Full details in Data & API Integration.
Build pipeline
apps/html (PUG + LESS) ──ship.mjs──▶ dist/public/taxi-crm/html (static HTML site)
apps/html/assets/css ──build.mjs─▶ packages/design-system/dist (style.css + tokens)
apps/react, apps/vue ──vite build─▶ per-edition dist/ (consume the design-system)
services/api ──ship.mjs──▶ dist/api (the Express app, minus node_modules)
| Command (run from root) | What it does |
|---|---|
npm run ds:build | Build the design-system dist/ from apps/html. |
npm run ship:html | Compile the HTML edition (PUG → HTML, LESS → CSS) into dist/public. |
npm run ship:api | Copy the Express app into dist/api. |
npm run ship:themeforest | Build a scrubbed, license-clean ThemeForest package + zip. |
npm run dev:api | Start the mock API locally (npm start in services/api). |
The HTML build is driven by tools/ship.mjs: it copies assets/ (stripping .less/.map),
compiles style.less → style.css with the less package, and renders every top-level
pug/*.pug to .html with pug. The themeforest mode additionally swaps demo photos for
placeholders and strips account-tied references (live API URL, Maps key, Font Awesome Kit).
Deployment
Production is Firebase Hosting (static site) plus Google Cloud Run (the API),
configured in firebase.json:
- The main Hosting site serves the static HTML from
dist/public, and redirects/→/taxi-crm/html/. - A second Hosting site rewrites all requests to the Cloud Run service
taxi-crm-apiin region asia-south1, so the API is reached same-origin under/api. - The mock API deploys with
npm run deploy:api(gcloud run deploy taxi-crm-api … --region asia-south1); the site deploys withnpm run deploy:html(firebase deploy --only hosting).npm run deployruns ship + both.
In development the React/Vue Vite dev servers proxy /api → http://localhost:8080 (the
local Express app), mirroring the production same-origin rewrite. See
Data & API Integration for the request/response contract.
Where to go next
- The data contract and mock API: Data & API Integration
- Theme, rebrand and extend: Customizing & Extending
- The shared helpers (HTML edition): JavaScript Helpers
- How the stylesheet is organised: CSS System
Was this page helpful?
