Skip to content

feat(cloudflare): consume distilled Vite manifests#615

Draft
agcty wants to merge 4 commits into
alchemy-run:mainfrom
agcty:codex/cloudflare-vite-manifest-consumer
Draft

feat(cloudflare): consume distilled Vite manifests#615
agcty wants to merge 4 commits into
alchemy-run:mainfrom
agcty:codex/cloudflare-vite-manifest-consumer

Conversation

@agcty

@agcty agcty commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Status / stack

Draft until the distilled Cloudflare Vite plugin side lands or publishes an equivalent manifest contract.

Dependency chain:

This PR is intentionally scoped to consuming the distilled manifest and preserving Alchemy's existing resource/binding ownership model. It does not add remote Worker HMR; alchemy dev remains local Vite + local Cloudflare runtime, with supported bindings local or remote-backed depending on the binding type.

Broader roadmap context is tracked in #621: Vite/framework owns the app entrypoint and Worker module graph, while Alchemy owns resources, bindings, deploy diffs, and uploads. That follow-up roadmap covers the polished alchemy dev binding loop; this PR stays on the deploy-side manifest consumer.

Summary

  • Read distilled __distilled-build.json after Vite builds and upload the manifest-selected Worker module set with explicit module content types.
  • Pass compatibility, asset routing, and advanced viteEnvironment topology into the distilled Vite build/dev plugin so RSC/custom Vite environment graphs use Alchemy's injected plugin consistently.
  • Fail closed when a Vite Worker/server output does not emit the distilled manifest; pure assets-only builds can still proceed without one.
  • Include Vite build-affecting deploy inputs in the stored hash.input so VITE env, asset routing, compatibility, and topology-only changes do not get skipped.
  • Hash upload content types for manifest, Rolldown, script, and prebuilt Worker module bundles so deploy diffs reflect upload shape.
  • Document the Vite-owned Worker + local Durable Object/RSC patterns and add one-worker Vite + Durable Object plus SPA manifest-spoof fixture coverage.
  • Add an Alchemy-owned React Router RSC fixture: the app config uses only @vitejs/plugin-rsc; Cloudflare.Vite injects the distilled Cloudflare Vite plugin and deploys the resulting manifest module set.

Reviewer-round fixes

  • Removed the misleading internal requireBuildManifest flag; any Vite server/Worker output now requires the distilled manifest.
  • Forwarded viteEnvironment through alchemy dev, not just deploy.
  • Restricted manifest discovery to the plugin contract location beside the build root, so public/__distilled-build.json copied into client assets cannot poison SPA/static builds.
  • Fixed VITE env input handling so Effect values are materialized before unwrapping Redacted, while still only exposing VITE_ primitives to the Vite build.
  • Made the DO fixture direct-build and live-deploy bundle hash assertion use matching compatibility and asset-routing inputs.

Architecture

The supported path is one Vite-owned Worker with Alchemy-owned bindings:

  • Vite/framework owns the Worker entrypoint and emitted module graph.
  • The distilled build manifest tells Alchemy which files form the deployable Worker module set.
  • Alchemy owns resource declarations, bindings, compatibility settings, assets routing, Durable Object metadata/migrations, and deploy/diff behavior.

This directly supports the common “one Vite Worker with bindings, including local Durable Object bindings” case without asking users to create a separate backend Worker. It avoids merging two fetch handlers; the Vite Worker exports the handler and DO classes, and Alchemy attaches the resources to that same Worker.

Verification

  • bun x oxfmt package.json packages/alchemy/package.json packages/alchemy/test/Cloudflare/Website/react-router-rsc-fixture/package.json packages/alchemy/test/Cloudflare/Website/Vite.test.ts packages/alchemy/test/Cloudflare/Website/react-router-rsc-fixture
  • cd packages/alchemy && bun run build passed. The build still prints the existing cloudflare:workers external warning from the Workers runtime shim.
  • bun run --filter alchemy test test/Cloudflare/Website/Vite.test.ts -t "Vite: React Router RSC deploys from a distilled manifest" passed against Cloudflare with a temporary scoped token. This direct-builds the RSC fixture, asserts the manifest main/module set/assets/compatibility shape, deploys through Cloudflare.Vite, verifies the deployed bundle hash matches the direct manifest bundle, hits /, /about, and /worker-render, then destroys the Worker.
  • Earlier fixture coverage on this branch still covers SPA manifest-spoof rejection and one Vite Worker with a local Durable Object binding.
  • Local dev smoke with packages/alchemy/test/Cloudflare/Website/vite-do-fixture: alchemy dev served the app at http://127.0.0.1:1337/; the browser showed Vite DO fixture hydrated; /api/reset, /api/count, and /api/current exercised the local DO binding.

Notes / follow-ups

  • alchemy dev currently gives local Vite HMR plus local distilled runtime, not remote Worker HMR. Some bindings are local-backed (for example local DOs/queues/services), while others use remote Cloudflare services (for example R2/KV/D1/Vectorize). Remote Worker HMR plus automatic infra reconciliation is a separate follow-up design.
  • The manifest consumer intentionally rejects unsupported future multi-worker manifest entries for now, so a future expanded manifest fails closed rather than silently deploying the wrong topology.

@agcty

agcty commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Local verification / stack note, since this PR currently depends on the Cloudflare Tools side of the stack:

  • This PR expects the distilled Cloudflare Vite plugin change from alchemy-run/cloudflare-tools#50. The published @distilled.cloud/cloudflare-vite-plugin@0.11.0 does not emit dist/__distilled-build.json yet.
  • If this PR is tested by itself against the published plugin, the manifest-path tests fail with the expected error: Vite build produced a Worker output without __distilled-build.json.
  • For local verification before cloudflare-tools#50 is merged/published, build the local Cloudflare Tools branch and temporarily link these packages into packages/alchemy/node_modules/@distilled.cloud/:
    • cloudflare-tools/packages/cloudflare-vite-plugin
    • cloudflare-tools/packages/cloudflare-rolldown-plugin
  • With that local link in place, bun run --filter alchemy test -- test/Cloudflare/Website/Vite.test.ts passed 7/7 against live Cloudflare credentials.
  • The follow-up dev-bindings PR (alchemy-run/alchemy-effect#622) was also verified independently for its main behavior with the targeted live TanStack/R2 dev test in the normal restored lockfile state.

Example local linking shape:

cd cloudflare-tools
git checkout feat/rsc-build
bun install
bun run build --filter='@distilled.cloud/cloudflare-vite-plugin' --filter='@distilled.cloud/cloudflare-rolldown-plugin'

cd ../alchemy-effect
bun install
rm packages/alchemy/node_modules/@distilled.cloud/cloudflare-vite-plugin
rm packages/alchemy/node_modules/@distilled.cloud/cloudflare-rolldown-plugin
ln -s /absolute/path/to/cloudflare-tools/packages/cloudflare-vite-plugin packages/alchemy/node_modules/@distilled.cloud/cloudflare-vite-plugin
ln -s /absolute/path/to/cloudflare-tools/packages/cloudflare-rolldown-plugin packages/alchemy/node_modules/@distilled.cloud/cloudflare-rolldown-plugin

@agcty agcty force-pushed the codex/cloudflare-vite-manifest-consumer branch from 205af29 to 79fea45 Compare June 18, 2026 09:46
@agcty agcty force-pushed the codex/cloudflare-vite-manifest-consumer branch from ffcf665 to 98b9f7c Compare June 18, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants