1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3import {
4 AssetWorkerInner,
5 type Env as BaseEnv,
6} from "../../internal/workers-shared/workers/asset-worker/index.ts";
7
8const UNKNOWN_HOST = "http://localhost";
9
10interface Env extends BaseEnv {
11 __VITE_HTML_EXISTS__: Fetcher;
12 __VITE_FETCH_HTML__: Fetcher;
13 __VITE_HEADERS__: Record<string, string | Array<string> | undefined>;
14}
15
16/**
17 * Asset worker that integrates with the Vite dev server.
18 *
19 * - {@link unstable_exists} delegates HTML lookups to Vite so that virtual
20 * HTML routes and files in `publicDir` are discoverable even though they
21 * are not in the on-disk manifest at startup.
22 * - {@link unstable_getByETag} streams the HTML body back through Vite so
23 * `transformIndexHtml` runs (enabling HMR, script injection, etc.).
24 * - {@link fetch} strips `ETag` / `Cache-Control` (Vite has its own caching
25 * semantics during dev) and appends any custom headers configured via
26 * `server.headers` in the Vite config.
27 *
28 * Mirrors the behavior of `CustomAssetWorker` in
29 * `upstream/workers-sdk/packages/vite-plugin-cloudflare/src/workers/asset-worker`.
30 */
31export default class ViteAssetWorker extends AssetWorkerInner<Env> {
32 override async fetch(request: Request): Promise<Response> {
33 const response = await super.fetch(request);
34 const modified = new Response(response.body, response);
35 modified.headers.delete("ETag");
36 modified.headers.delete("Cache-Control");
37
38 for (const [key, value] of Object.entries(this.env.__VITE_HEADERS__)) {
39 if (Array.isArray(value)) {
40 for (const item of value) {
41 modified.headers.append(key, item);
42 }
43 } else if (value !== undefined) {
44 modified.headers.set(key, String(value));
45 }
46 }
47
48 return modified;
49 }
50
51 override async unstable_getByETag(eTag: string) {
52 const url = new URL(eTag, UNKNOWN_HOST);
53 const response = await this.env.__VITE_FETCH_HTML__.fetch(url);
54 if (!response.body) {
55 throw new Error(`Unexpected error. No HTML found for "${eTag}".`);
56 }
57 return {
58 readableStream: response.body,
59 contentType: "text/html",
60 cacheStatus: "MISS",
61 } as const;
62 }
63
64 override async unstable_exists(pathname: string) {
65 // Guard against `//` collapsing into an invalid URL.
66 const url = new URL(pathname.replace(/^\/{2,}/, "/"), UNKNOWN_HOST);
67 const response = await this.env.__VITE_HTML_EXISTS__.fetch(url);
68 return response.json() as Promise<string | null>;
69 }
70}