1import {
2 frameworkSite,
3 staticConfigFromAssets,
4 type FrameworkSiteProps,
5} from "./FrameworkSite.ts";
6
7/** The framework-integration package that drives the Vite build. */
8export const VITE_FRAMEWORK_SPECIFIER = "@alchemy.run/frontend-frameworks/vite";
9
10/** The Node container deploy target for the Vite build. */
11export const VITE_NODE_TARGET_SPECIFIER =
12 "@alchemy.run/frontend-frameworks/vite/node";
13
14const viteOptions = (props: ViteProps) =>
15 props.vite !== undefined &&
16 (props.vite.outDir !== undefined || props.vite.base !== undefined)
17 ? { vite: props.vite }
18 : undefined;
19
20export interface ViteProps extends FrameworkSiteProps {
21 /**
22 * Serializable Vite config merged OVER the project's own `vite.config.*`
23 * (which loads natively, plugins included).
24 */
25 vite?: {
26 /**
27 * Build output directory, relative to `rootDir`.
28 * @default the project config's `build.outDir` (vite's default: "dist")
29 */
30 outDir?: string;
31 /** Public base path the site deploys under (vite's `base`). */
32 base?: string;
33 };
34}
35
36/**
37 * Deploy a plain [Vite](https://vite.dev) application to Cloud Run: a service
38 * serving the `vite build` output from a tiny static-file server. For
39 * client-only projects — React/Vue/Solid SPAs, `index.html` multi-page
40 * apps — whose entire deployable output is static assets.
41 *
42 * The build runs through `@alchemy.run/frontend-frameworks/vite` with the
43 * `@alchemy.run/frontend-frameworks/vite/node` deploy target — the package
44 * must be installed in your project. Your project's own `vite.config.*`
45 * (plugins included) drives the build.
46 *
47 * During `alchemy dev` the site is Vite's own dev server (native HMR) and
48 * no cloud resources are created — the site's `url` is the dev server's
49 * local address. `Alchemy.remote()` opts back into the full deployment.
50 *
51 *
52 * ### Creating Vite Sites
53 * **Example:** Basic Vite SPA
54 * ```typescript
55 * const site = yield* GCP.Website.Vite("Web");
56 * ```
57 *
58 * **Example:** Project in a Subdirectory
59 * ```typescript
60 * const site = yield* GCP.Website.Vite("Web", {
61 * rootDir: "./app",
62 * });
63 * ```
64 *
65 * ### Multi-Page Sites
66 * **Example:** Per-Route HTML Pages with a 404 Page
67 * ```typescript
68 * const site = yield* GCP.Website.Vite("Docs", {
69 * assets: { notFoundHandling: "404-page" },
70 * });
71 * ```
72 *
73 * ### Cloud Run Settings
74 * **Example:** Warm Instance with More Memory
75 * ```typescript
76 * const site = yield* GCP.Website.Vite("Web", {
77 * scaling: { minInstanceCount: 1 },
78 * resources: { limits: { cpu: "1", memory: "1Gi" } },
79 * });
80 * ```
81 *
82 * ### Build Configuration
83 * **Example:** Custom Output Directory and Base Path
84 * ```typescript
85 * const site = yield* GCP.Website.Vite("Docs", {
86 * vite: { outDir: "build", base: "/docs/" },
87 * });
88 * ```
89 *
90 * ### Local Development
91 * **Example:** Vite Dev Server Under `alchemy dev`
92 * ```typescript
93 * // `alchemy dev` starts `vite` programmatically: site.url is the local
94 * // dev server (HMR included); no Cloud Run service is created.
95 * const site = yield* GCP.Website.Vite("Web");
96 * ```
97 *
98 * @resource
99 * @product Website
100 */
101export const Vite = (id: string, props: ViteProps = {}) =>
102 frameworkSite(id, props, {
103 name: "Vite",
104 framework: VITE_FRAMEWORK_SPECIFIER,
105 target: VITE_NODE_TARGET_SPECIFIER,
106 options: {
107 ...viteOptions(props),
108 notFoundHandling: "spa",
109 htmlHandling: props.assets?.htmlHandling,
110 },
111 static: staticConfigFromAssets(props.assets, {
112 notFoundHandling: "single-page-application",
113 }),
114 });