1import cloudflareVitePlugin, {
2 type CloudflareVitePluginOptions,
3} from "@alchemy.run/cloudflare-runtime/vite";
4import * as FrameworkCore from "@alchemy.run/frontend-frameworks/core";
5import * as Context from "effect/Context";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";
9import * as Layer from "effect/Layer";
10import type * as Scope from "effect/Scope";
11import type * as ViteModule from "vite";
12import { Cwd } from "./Cwd.ts";
13
14export class ViteError extends Data.TaggedError<"ViteError">("ViteError")<{
15 readonly message: string;
16 readonly cause?: unknown;
17}> {}
18
19export type BuildOutput = FrameworkCore.BuildOutput;
20
21export class Vite extends Context.Service<
22 Vite,
23 {
24 readonly build: (
25 pluginOptions?: CloudflareVitePluginOptions,
26 config?: ViteModule.InlineConfig,
27 ) => Effect.Effect<BuildOutput, ViteError>;
28 readonly dev: (
29 pluginOptions?: CloudflareVitePluginOptions,
30 config?: ViteModule.InlineConfig,
31 ) => Effect.Effect<
32 { url: string; server: ViteModule.ViteDevServer },
33 ViteError,
34 Scope.Scope
35 >;
36 }
37>()("@alchemy/Vite") {}
38
39export const ViteLive = Layer.effect(
40 Vite,
41 Effect.gen(function* () {
42 const fs = yield* FileSystem.FileSystem;
43 const cwd = yield* Cwd;
44
45 const load = (
46 root: string = cwd,
47 ): Effect.Effect<typeof ViteModule, ViteError> =>
48 FrameworkCore.loadProjectModule<typeof ViteModule>(root, "vite").pipe(
49 Effect.mapError(
50 (error) =>
51 new ViteError({
52 message: "Failed to load Vite",
53 cause: error.cause,
54 }),
55 ),
56 );
57
58 return Vite.of({
59 build: Effect.fn(function* (pluginOptions, config) {
60 const vite = yield* load(config?.root);
61 const collector = yield* FrameworkCore.makeBuildOutputCollector({
62 entryEnvironment: pluginOptions?.viteEnvironments?.entry,
63 }).pipe(Effect.provideService(FileSystem.FileSystem, fs));
64 yield* Effect.tryPromise({
65 try: async () => {
66 const builder = await vite.createBuilder(
67 {
68 ...config,
69 plugins: [
70 ...(config?.plugins ?? []),
71 cloudflareVitePlugin(pluginOptions),
72 collector.plugin,
73 ],
74 },
75 null,
76 );
77 await builder.buildApp();
78 },
79 catch: (error) =>
80 new ViteError({ message: "Failed to build", cause: error }),
81 });
82 return yield* collector
83 .collect()
84 .pipe(
85 Effect.mapError(
86 (error) =>
87 new ViteError({ message: error.message, cause: error.cause }),
88 ),
89 );
90 }),
91 dev: Effect.fn(function* (pluginOptions, config) {
92 const vite = yield* load(config?.root);
93 const server = yield* Effect.acquireRelease(
94 Effect.tryPromise({
95 try: async () => {
96 const server = await vite.createServer({
97 ...config,
98 plugins: [
99 ...(config?.plugins ?? []),
100 cloudflareVitePlugin(pluginOptions),
101 ],
102 });
103 return await server.listen();
104 },
105 catch: (error) =>
106 new ViteError({
107 message: "Failed to run development server",
108 cause: error,
109 }),
110 }),
111 (server) => Effect.promise(async () => await server.close()),
112 );
113 const url = server.resolvedUrls?.local[0];
114 if (!url) {
115 throw new ViteError({
116 message: "Could not get URL of development server",
117 });
118 }
119 return {
120 url,
121 server,
122 };
123 }),
124 });
125 }),
126);