1# packages/frontend-frameworks/fixtures/monorepo-workspace
2
3Exercises the **externalWorkspaces / input-hash memo machinery** with an app
4that imports code across a package boundary.
5
6## Layout
7
8```
9packages/frontend-frameworks/fixtures/monorepo-workspace/ ← fixture root (bun workspace member; harness cwd)
10 e2e.config.ts ← built-in Vite framework path, `root: "./app"`
11 app/ ← the Vite project root (SSR worker, no client assets)
12 src/server.ts ← imports ../../lib/src/greeting.ts
13 lib/ ← sibling directory with its own package.json + src
14 package.json ← makes lib/ a "workspace root" for the collector
15 src/greeting.ts
16```
17
18Deliberate choices:
19
20- **Vite framework path, not a framework package.** `e2e.config.ts` uses the
21 harness's built-in Vite implementation with the first-class `root` option
22 (`Options.root`, resolved against the fixture root and threaded by the
23 Cli/Server into `Framework.build`/`Framework.dev`). This isolates the
24 workspace machinery from framework churn.
25- **Relative import, no bun workspace-protocol tricks.** `lib/` is NOT a bun
26 workspace member (the root glob is `packages/frontend-frameworks/fixtures/*`, one level up). The app
27 reaches it via `../../lib/src/greeting.ts`; framework-core's collector
28 detects cross-boundary module ids **by path** (absolute id outside the
29 project root, not under `node_modules`) and resolves each to its nearest
30 `package.json` directory (`collectExternalWorkspaces`).
31- **The client environment's default entry is `index.html`.** A
32 worker-rendered app has none; `app/vite.config.ts` points the client build
33 at `src/client.ts`. (User-config-respecting behavior working as intended —
34 noted because a client-less SSR app cannot express "skip the client
35 environment" through `e2e.config.ts` alone.)
36
37## What the specs assert
38
39- `live` + `dev`: the app builds/serves and its SSR HTML + `/api/greeting`
40 JSON carry content imported from `lib/`.
41- `live`: `dist/build.json`'s `externalWorkspaces` contains the absolute path
42 of `lib/` — and does NOT contain `app/` or the fixture root.
43
44The suite runs ungated (`bun run test` → `playwright test`). The two harness
45gaps the original scaffold worked around are fixed upstream:
46
471. `writeBuildOutput` now `mkdir -p`s its target's parent, so the nested-root
48 build (Vite writes to `app/dist` while the harness persists
49 `<fixtureRoot>/dist/build.json`) no longer hits ENOENT.
502. `Options.root` is a first-class harness option threaded into
51 `FrameworkBuildOptions.root` / `FrameworkDevOptions.root` — the fixture no
52 longer wraps `makeViteFramework` in its own Framework layer.
53
54## Out of scope here: memo-busting assertions
55
56The aspirational spec — _editing `lib/src` busts the rebuild memo while an
57untouched rebuild stays memoized_ — is intentionally **skipped** in
58`test/smoke.test.ts`: the e2e harness has no build memoization by design
59(`e2e build` rebuilds unconditionally). The memo that consumes
60`externalWorkspaces` lives in alchemy's `Website`/`Command.Memo` machinery
61(`memo.workspaces: "auto"` hashes the workspace directories recorded in the
62build output), and the memo-bust behavior is asserted by an alchemy-side
63`Website` memo test (see `alchemy-effect/packages/alchemy/test/Cloudflare/Website/`).
64This fixture pins the prerequisite: the collector must report `lib/` so the
65memo layer has the right inputs.