|
| 1 | +# Detailed Plan: Universal Deploy Phase 5 — Idiomatic Vite Full-Stack Integration |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Phase 4 delivered a working Vite-centric full-stack runtime: one `cedar dev` |
| 6 | +command, API HMR through Vite's SSR module graph, and a Vite-built UD Node |
| 7 | +server entry for `cedar serve`. However, the underlying architecture is an |
| 8 | +**incremental bridge** that still deviates from what the Vite team recommends |
| 9 | +for full-stack frameworks. |
| 10 | + |
| 11 | +Phase 5 closes that architectural gap by making Cedar's Vite integration |
| 12 | +**idiomatic**: |
| 13 | + |
| 14 | +- **Dev**: one Vite dev server with a single visible port and API middleware |
| 15 | + inline — no separate HTTP listener for the API side |
| 16 | +- **Build**: Vite's `buildApp()` API (or the builder `buildApp()` hook) used to |
| 17 | + build the **client** and **api** environments together in a single build pass, |
| 18 | + with environments declared in `vite.config` |
| 19 | + |
| 20 | +This is foundational infrastructure, not a user-facing feature. It makes later |
| 21 | +phases (per-route UD registration, SSR rebuild) simpler and more robust by |
| 22 | +ensuring Cedar's Vite integration follows the same patterns as the rest of the |
| 23 | +Vite full-stack ecosystem. |
| 24 | + |
| 25 | +## Why Phase 5 Exists |
| 26 | + |
| 27 | +Phase 4 took the shortest path to user-facing wins. It runs two HTTP listeners |
| 28 | +in dev (web Vite server + API Vite SSR + Fastify) and uses three separate |
| 29 | +`viteBuild()` calls in production. That was the right trade-off for Phase 4, |
| 30 | +but it leaves technical debt that compounds if not addressed before the next |
| 31 | +major milestones. |
| 32 | + |
| 33 | +### Two problems to solve |
| 34 | + |
| 35 | +**1. Two-listener dev model** |
| 36 | + |
| 37 | +`cedar-unified-dev` starts: |
| 38 | + |
| 39 | +- a Vite client dev server on `webPort` |
| 40 | +- a Vite SSR dev server (`middlewareMode: true`) + Fastify on `apiPort` |
| 41 | + |
| 42 | +The browser still conceptually targets two origins. Auth flows, CORS, and |
| 43 | +cookie handling are more complex than they need to be because the API is not |
| 44 | +served from the same origin as the web assets. |
| 45 | + |
| 46 | +**2. Fragmented build pipeline** |
| 47 | + |
| 48 | +`buildApiWithVite()`, `buildUDApiServer()`, and the web client build each call |
| 49 | +`viteBuild()` standalone. There are three independent Vite builds with no shared |
| 50 | +module graph, no shared transform pipeline, and no coordinated invalidation. |
| 51 | + |
| 52 | +## Goals |
| 53 | + |
| 54 | +### Primary Goals |
| 55 | + |
| 56 | +- Replace the two-listener dev model with a **single Vite dev server** that |
| 57 | + handles both web and API requests on one visible port |
| 58 | +- Reimplement API request handling as **Vite middleware** (via |
| 59 | + `configureServer` hook or equivalent) rather than a separate Fastify listener |
| 60 | +- Adopt **`buildApp()` with declared environments** for production builds, |
| 61 | + replacing standalone `viteBuild()` calls for each side |
| 62 | +- Ensure the custom Fastify compatibility lane (Lane B) is **not affected** by |
| 63 | + these changes |
| 64 | + |
| 65 | +### Secondary Goals |
| 66 | + |
| 67 | +- Preserve all existing Phase 4 dev behavior: HMR, GraphQL, auth, functions, |
| 68 | + GraphiQL, direct `curl` |
| 69 | +- Maintain backward compatibility for the `cedar dev` CLI contract |
| 70 | +- Keep build output paths stable so `cedar serve` continues to work unchanged |
| 71 | + |
| 72 | +## Non-Goals |
| 73 | + |
| 74 | +- Adding new user-facing features (this is an internal architecture phase) |
| 75 | +- Changing the Cedar handler contract or middleware model |
| 76 | +- Rebuilding SSR or RSC (that is Phase 7) |
| 77 | +- Formalizing per-route UD registration (that is Phase 6) |
| 78 | +- Removing the custom Fastify compatibility lane |
| 79 | +- Supporting arbitrary Fastify plugins in the default runtime path |
| 80 | + |
| 81 | +## Workstreams |
| 82 | + |
| 83 | +## Workstream 1: Single-Listener Dev Server |
| 84 | + |
| 85 | +### Objective |
| 86 | + |
| 87 | +Move API request handling from a separate Fastify listener into the Vite dev |
| 88 | +server's middleware pipeline. |
| 89 | + |
| 90 | +### Current State |
| 91 | + |
| 92 | +`apiDevServer.ts` creates a Vite SSR dev server (`middlewareMode: true`) and |
| 93 | +mounts a Fastify app on a separate port. `cedarDevDispatcherPlugin` exists in |
| 94 | +`@cedarjs/vite` but is not installed in the dev server. The Fastify app handles: |
| 95 | + |
| 96 | +- body parsing (`fastify-raw-body`) |
| 97 | +- URL data extraction (`fastify-url-data`) |
| 98 | +- route matching to the `LAMBDA_FUNCTIONS` registry |
| 99 | +- GraphQL Yoga streaming via `createFetchRequestFromFastify` |
| 100 | +- content-type parsing for form data and multipart |
| 101 | + |
| 102 | +### Target State |
| 103 | + |
| 104 | +A single `createServer()` call in `cedar-unified-dev` that: |
| 105 | + |
| 106 | +- starts one Vite dev server on the visible port |
| 107 | +- installs `cedarDevDispatcherPlugin` (already built in Phase 4) into the |
| 108 | + `configureServer` middleware pipeline |
| 109 | +- routes API requests to Cedar's aggregate fetch dispatcher directly |
| 110 | +- falls through to Vite's normal web handling for non-API requests |
| 111 | + |
| 112 | +### Tasks |
| 113 | + |
| 114 | +- install `cedarDevDispatcherPlugin` into the web Vite dev server's |
| 115 | + `configureServer` hook (the plugin was built in Phase 4 but not wired up) |
| 116 | +- replace Fastify routing with fetch-native request classification and dispatch |
| 117 | +- implement body parsing as a utility function (or use a WHATWG-compatible |
| 118 | + parser) rather than a Fastify plugin |
| 119 | +- mount GraphQL Yoga directly inside the middleware pipeline using its |
| 120 | + `handle(request, context)` method, which already expects a Fetch `Request` |
| 121 | +- preserve the `LAMBDA_FUNCTIONS` registry and HMR invalidation logic — only |
| 122 | + the HTTP transport layer changes |
| 123 | +- ensure request context enrichment (cookies, params, query, auth state) still |
| 124 | + flows correctly without Fastify's `req`/`reply` objects |
| 125 | +- preserve error surfacing: backend errors should still be visible in both |
| 126 | + terminal and HTTP response where appropriate |
| 127 | + |
| 128 | +### Blockers to Resolve |
| 129 | + |
| 130 | +- GraphQL Yoga's `handle()` method expects a Fetch `Request` and returns a |
| 131 | + Fetch `Response`. This is already nearly the target shape, but the current |
| 132 | + code wraps it in `getAsyncStoreInstance().run()` inside a Fastify handler. |
| 133 | + That AsyncLocalStorage context needs to be established in the middleware |
| 134 | + pipeline instead. |
| 135 | +- The `requestHandler` helper from `@cedarjs/api-server/requestHandlers` is |
| 136 | + currently coupled to Fastify `req`/`reply` objects. It may need a thin |
| 137 | + fetch-native wrapper, or the helper itself may need to be split into |
| 138 | + transport-agnostic and Fastify-specific variants. |
| 139 | + |
| 140 | +### Deliverable |
| 141 | + |
| 142 | +- `cedar dev` runs on a single visible port with no separate API listener |
| 143 | +- API requests (GraphQL, auth, functions) are handled inline via Vite middleware |
| 144 | +- Web requests (assets, HMR, SPA fallback) continue through Vite's normal path |
| 145 | + |
| 146 | +## Workstream 2: `buildApp()` with Declared Environments |
| 147 | + |
| 148 | +### Objective |
| 149 | + |
| 150 | +Replace the three separate `viteBuild()` invocations with a single `buildApp()` |
| 151 | +call that declares `client` and `api` environments. |
| 152 | + |
| 153 | +### Current State |
| 154 | + |
| 155 | +Production build uses three standalone Vite builds: |
| 156 | + |
| 157 | +1. `buildApiWithVite()` — builds API functions with `ssr: true` and |
| 158 | + `preserveModules: true` |
| 159 | +2. `buildUDApiServer()` — builds the UD Node server entry with the |
| 160 | + `cedarUniversalDeployPlugin` and `node()` plugin |
| 161 | +3. `cedar-vite-build` binary — builds the web client bundle |
| 162 | + |
| 163 | +These share no module graph, no transform pipeline, and no invalidation. |
| 164 | +Alias resolution, Babel plugin ordering, and externalization logic can diverge |
| 165 | +silently. |
| 166 | + |
| 167 | +### Target State |
| 168 | + |
| 169 | +A unified Vite config that declares: |
| 170 | + |
| 171 | +```ts |
| 172 | +// Simplified illustration |
| 173 | +export default defineConfig({ |
| 174 | + environments: { |
| 175 | + client: { |
| 176 | + // web browser bundle (SPA assets) |
| 177 | + build: { |
| 178 | + outDir: 'web/dist', |
| 179 | + // ... |
| 180 | + }, |
| 181 | + }, |
| 182 | + api: { |
| 183 | + // server-side API entry (Cedar aggregate fetchable) |
| 184 | + build: { |
| 185 | + ssr: true, |
| 186 | + outDir: 'api/dist', |
| 187 | + // ... |
| 188 | + }, |
| 189 | + }, |
| 190 | + }, |
| 191 | +}) |
| 192 | +``` |
| 193 | + |
| 194 | +A single `buildApp()` call builds both environments from the same module graph. |
| 195 | + |
| 196 | +### Tasks |
| 197 | + |
| 198 | +- evaluate Vite's `buildApp()` API stability and feature completeness for Cedar's |
| 199 | + use case (check current Vite version support) |
| 200 | +- merge the three existing build configurations into one unified config with |
| 201 | + declared environments |
| 202 | +- ensure `node()` from `@universal-deploy/node/vite` works correctly within the |
| 203 | + `buildApp()` environment model |
| 204 | +- ensure the web client build's special requirements (cwd, PostCSS/Tailwind |
| 205 | + resolution, etc.) are preserved in the unified config |
| 206 | +- verify that output paths remain stable so `cedar serve` does not need changes |
| 207 | +- add the `api` environment to the Vite config used by `cedar build` |
| 208 | + |
| 209 | +### Blockers to Resolve |
| 210 | + |
| 211 | +- `buildApp()` may not be fully stable or documented in the Vite version Cedar |
| 212 | + pins. This needs investigation before committing to the migration. |
| 213 | +- The web client build currently runs via a separate binary |
| 214 | + (`cedar-vite-build`) with its own config file. That binary changes `cwd` |
| 215 | + to the web directory for PostCSS/Tailwind correctness. The unified build |
| 216 | + needs to preserve that behavior or find an alternative. |
| 217 | + |
| 218 | +### Deliverable |
| 219 | + |
| 220 | +- `cedar build` uses a single `buildApp()` invocation for both client and api |
| 221 | + environments |
| 222 | +- Output directories and artifacts remain compatible with `cedar serve` |
| 223 | + |
| 224 | +## Suggested Sequencing |
| 225 | + |
| 226 | +1. **Single-listener dev first** — this is the higher-impact change for daily |
| 227 | + developer experience and should be validated before layering `buildApp()` on |
| 228 | + top of it |
| 229 | +2. **`buildApp()` second** — the build consolidation is less user-visible and |
| 230 | + can be done in parallel with single-listener testing, but it should not be |
| 231 | + released before single-listener is stable |
| 232 | + |
| 233 | +## Relationship to Other Phases |
| 234 | + |
| 235 | +- **Phase 4**: this phase replaces the incremental bridge with the idiomatic |
| 236 | + architecture. Phase 4 must be stable before starting this work. |
| 237 | +- **Phase 6 (UD per-route registration)**: single-listener makes per-route |
| 238 | + dispatch simpler because there's only one request classification layer to |
| 239 | + reason about. The dispatcher that routes to per-route entries is the same |
| 240 | + middleware that currently routes to the aggregate entry. |
| 241 | +- **Phase 7 (SSR rebuild)**: `buildApp()` with declared environments is |
| 242 | + prerequisite for SSR because SSR will add a third environment (`ssr` for |
| 243 | + HTML streaming / RSC). The build infrastructure must already support |
| 244 | + multiple environments. |
| 245 | + |
| 246 | +## Exit Criteria |
| 247 | + |
| 248 | +- `cedar dev` runs a single Vite dev server on one visible port for the default |
| 249 | + runtime path |
| 250 | +- API requests are handled inline via Vite middleware, not by a separate |
| 251 | + Fastify listener |
| 252 | +- `cedar build` uses `buildApp()` with declared `client` and `api` |
| 253 | + environments in a single build pass |
| 254 | +- All existing Phase 4 functionality (HMR, GraphQL, auth, functions, GraphiQL, |
| 255 | + direct `curl`, `cedar serve`) continues to work |
| 256 | +- The custom Fastify compatibility lane is unaffected |
| 257 | + |
| 258 | +## Risks |
| 259 | + |
| 260 | +- `buildApp()` API may not be mature enough in the pinned Vite version |
| 261 | +- Moving body parsing and request enrichment out of Fastify may surface edge |
| 262 | + cases in auth providers or GraphQL Yoga plugins that currently depend on |
| 263 | + Fastify-specific request shapes |
| 264 | +- Single-listener dev may complicate debugging if API and web errors are |
| 265 | + interleaved in the same Vite server output |
| 266 | +- The web client build's `cwd` sensitivity (PostCSS/Tailwind) may resist |
| 267 | + merging into a unified config |
| 268 | + |
| 269 | +## Deliverables |
| 270 | + |
| 271 | +- refactored `cedar-unified-dev` using single Vite dev server with inline API |
| 272 | + middleware |
| 273 | +- refactored `cedar build` using `buildApp()` with `client` and `api` |
| 274 | + environments |
| 275 | +- updated documentation reflecting the single-port dev model and unified build |
0 commit comments