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