@@ -85,6 +85,158 @@ changes to components, islands, and CSS are reflected in the browser instantly
8585without a full page reload. This is powered by Prefresh, Preact's fast refresh
8686implementation.
8787
88+ ## Migrating from the Builder to Vite
89+
90+ If your Fresh 2 project was created with ` --builder ` (or predates the Vite
91+ plugin), it uses the legacy [ ` Builder ` ] ( /docs/advanced/builder ) class wired up
92+ in ` dev.ts ` . Migrating to Vite is mostly a matter of swapping ` dev.ts ` for a
93+ ` vite.config.ts ` , moving CSS into the module graph, and updating ` deno.json ` .
94+
95+ ### 1. Update ` deno.json `
96+
97+ Add the Vite plugin and ` vite ` itself to your imports, drop the Builder-only
98+ Tailwind packages (if any), and point ` compilerOptions.types ` at Vite's client
99+ types so HMR and asset imports type-check:
100+
101+ ``` diff deno.json
102+ {
103+ "nodeModulesDir": "manual",
104+ "tasks": {
105+ - "dev": "deno run -A --watch=static/,routes/ dev.ts",
106+ - "build": "deno run -A dev.ts build",
107+ + "dev": "vite",
108+ + "build": "vite build",
109+ "start": "deno serve -A _fresh/server.js"
110+ },
111+ "imports": {
112+ "fresh": "jsr:@fresh/core@^2",
113+ "preact": "npm:preact@^10",
114+ "@preact/signals": "npm:@preact/signals@^2",
115+ + "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1",
116+ + "vite": "npm:vite@^7",
117+ + "@types/babel__core": "npm:@types/babel__core@^7"
118+ },
119+ "compilerOptions": {
120+ "jsx": "precompile",
121+ "jsxImportSource": "preact",
122+ + "types": ["vite/client"]
123+ }
124+ }
125+ ```
126+
127+ If you were using ` @fresh/plugin-tailwind ` / ` @fresh/plugin-tailwindcss-v3 ` ,
128+ remove those imports — Vite has a first-party Tailwind plugin (see step 4).
129+
130+ ### 2. Replace ` dev.ts ` with ` vite.config.ts `
131+
132+ Delete ` dev.ts ` and create a ` vite.config.ts ` at the project root:
133+
134+ ``` ts vite.config.ts
135+ import { defineConfig } from " vite" ;
136+ import { fresh } from " @fresh/plugin-vite" ;
137+
138+ export default defineConfig ({
139+ plugins: [fresh ()],
140+ });
141+ ```
142+
143+ If you passed options to ` new Builder({ ... }) ` (custom ` serverEntry ` ,
144+ ` islandDir ` , ` routeDir ` , ` staticDir ` , ` ignore ` ), pass the equivalent options to
145+ ` fresh({ ... }) ` — the names match. See [ Configuration] ( #configuration ) above.
146+
147+ Any ` builder.registerIsland("jsr:@scope/pkg/Island.tsx") ` calls become
148+ ` fresh({ islandSpecifiers: ["jsr:@scope/pkg/Island.tsx"] }) ` .
149+
150+ ### 3. Add a ` client.ts ` entry
151+
152+ The Builder discovered CSS by scanning ` static/ ` . Vite needs CSS to be part of
153+ the module graph so it can hash, bundle, and hot-reload it. Move your stylesheet
154+ out of ` static/ ` and import it from a new ` client.ts ` file:
155+
156+ ``` diff Project structure
157+ <project root>
158+ - ├── static/styles.css
159+ + ├── assets/styles.css
160+ + ├── client.ts
161+ ├── vite.config.ts
162+ └── main.ts
163+ ```
164+
165+ ``` ts client.ts
166+ // Import CSS files here for hot module reloading to work.
167+ import " ./assets/styles.css" ;
168+ ```
169+
170+ Then remove the manual ` <link> ` from your app wrapper — Vite injects the
171+ stylesheet for you:
172+
173+ ``` diff routes/_app.tsx
174+ <head>
175+ <meta charset="utf-8" />
176+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
177+ <title>My App</title>
178+ - <link rel="stylesheet" href="/styles.css" />
179+ </head>
180+ ```
181+
182+ Static assets that are not part of the JS/CSS graph (favicons, images served by
183+ URL, robots.txt, …) stay in ` static/ ` .
184+
185+ ### 4. Switch the Tailwind plugin (if applicable)
186+
187+ Replace the Builder-side Tailwind plugin with the official Vite plugin:
188+
189+ ``` diff deno.json
190+ "imports": {
191+ - "@fresh/plugin-tailwind": "jsr:@fresh/plugin-tailwind@^1",
192+ - "@tailwindcss/postcss": "npm:@tailwindcss/postcss@^4",
193+ - "postcss": "npm:postcss@^8",
194+ + "@tailwindcss/vite": "npm:@tailwindcss/vite@^4",
195+ "tailwindcss": "npm:tailwindcss@^4"
196+ }
197+ ```
198+
199+ ``` ts vite.config.ts
200+ import { defineConfig } from " vite" ;
201+ import { fresh } from " @fresh/plugin-vite" ;
202+ import tailwindcss from " @tailwindcss/vite" ;
203+
204+ export default defineConfig ({
205+ plugins: [fresh (), tailwindcss ()],
206+ });
207+ ```
208+
209+ Make sure your stylesheet starts with ` @import "tailwindcss"; ` and is imported
210+ from ` client.ts ` .
211+
212+ ### 5. Verify
213+
214+ Run ` deno install ` to pull in the new npm packages, then:
215+
216+ ``` sh Terminal
217+ deno task dev # starts Vite with HMR
218+ deno task build # writes _fresh/server.js and client assets
219+ deno task start # deno serve -A _fresh/server.js
220+ ```
221+
222+ The output layout under ` _fresh/ ` is the same as the Builder produced, so
223+ deployment configuration (Deno Deploy, Docker, ` deno compile ` ) does not need to
224+ change.
225+
226+ ### Checklist
227+
228+ - [ ] ` dev.ts ` removed, ` vite.config.ts ` added
229+ - [ ] ` client.ts ` created and imports your CSS
230+ - [ ] Stylesheet moved out of ` static/ ` and the ` <link> ` removed from ` _app.tsx `
231+ - [ ] ` deno.json ` tasks point at ` vite ` / ` vite build `
232+ - [ ] ` @fresh/plugin-vite ` , ` vite ` , and ` @types/babel__core ` in ` imports `
233+ - [ ] ` "vite/client" ` in ` compilerOptions.types `
234+ - [ ] Tailwind (if used) switched to ` @tailwindcss/vite `
235+
236+ > [ info] : If you get stuck, run ` deno run -Ar jsr:@fresh/init ` in a scratch
237+ > directory and diff the generated project against yours — the generator is the
238+ > source of truth for a working Vite-based Fresh setup.
239+
88240## Debugging
89241
90242To debug Vite resolution issues, run Vite with the ` --debug ` flag:
0 commit comments