| 1 | # packages/frontend-frameworks/fixtures/monorepo-workspace |
| 2 | |
| 3 | Exercises the **externalWorkspaces / input-hash memo machinery** with an app |
| 4 | that imports code across a package boundary. |
| 5 | |
| 6 | ## Layout |
| 7 | |
| 8 | ``` |
| 9 | packages/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 | |
| 18 | Deliberate 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 | |
| 44 | The suite runs ungated (`bun run test` → `playwright test`). The two harness |
| 45 | gaps the original scaffold worked around are fixed upstream: |
| 46 | |
| 47 | 1. `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. |
| 50 | 2. `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 | |
| 56 | The aspirational spec — _editing `lib/src` busts the rebuild memo while an |
| 57 | untouched 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 |
| 62 | build output), and the memo-bust behavior is asserted by an alchemy-side |
| 63 | `Website` memo test (see `alchemy-effect/packages/alchemy/test/Cloudflare/Website/`). |
| 64 | This fixture pins the prerequisite: the collector must report `lib/` so the |
| 65 | memo layer has the right inputs. |