Monorepo Structure

Structuring BookNest as a Monorepo

BookNest exists twice, as Framework7's web app and this chapter's phone app, and both compute the same cart total from the same six books. A monorepo keeps both apps in one repository, with the shared logic in a package of its own. npm 2,036 workspaces suffice; Turborepo 226,392 (2.11.4) and Nx 180,159 (23.2.1) add cached, parallel tasks for many packages. A trial layout, with a Vite 25,978 page standing in for the Framework7 572,986 app:

booknest-mono: two apps and one shared packageJSX
booknest-mono/
  package.json            { "private": true, "workspaces": ["apps/*", "packages/*"] }
  apps/mobile/            create-expo-app (SDK 57); depends on "@booknest/core": "*"
  apps/web/               a Vite page; depends on "@booknest/core": "*"
  packages/core/          "name": "@booknest/core", "main": "index.js"
    index.js              exports books (from booknest-data.json) and cartTotal(items)

npm install at the root links each workspace into node_modules/@booknest/, so both apps import { books, cartTotal } from '@booknest/core'. Expo 6,418 's documentation suggests workspace:*, but npm 9.2.0 and 11.20.0 both stopped with EUNSUPPORTEDPROTOCOL: with npm, write *. Since SDK 52 Expo configures Metro for monorepos by itself:

Checking the links, then building both apps from the monorepoShell
npm ls @booknest/core
npm run build --workspace @booknest/web | grep "assets/"
cd apps/mobile && npx expo export --platform android 2>&1 | grep -E "Bundled|\.hbc"
Output
booknest-mono@ /home/dev/lane-ch3/booknest-mono
├── @booknest/core@1.0.0 -> ./packages/core
├─┬ @booknest/mobile@1.0.0 -> ./apps/mobile
│ └── @booknest/core@1.0.0 deduped -> ./packages/core
└─┬ @booknest/web@0.0.0 -> ./apps/web
  └── @booknest/core@1.0.0 deduped -> ./packages/core
dist/assets/index-BtN4EBmb.js  2.15 kB │ gzip: 1.15 kB
Android Bundled 4378ms apps/mobile/index.js (580 modules)
_expo/static/js/android/index-2b9f300e33c565ef8cbd5b8a137d8755.hbc (1.4MB)

Metro watched the whole repository (note the apps/mobile/index.js path) and bundled the shared package with no metro.config.js. Keep one version of react and react-native in the repository, since two copies in one app fail at run time, and keep shared packages free of native modules, as plain JavaScript like these cart rules.