@donkeylabs/adapter-sveltekit 2.0.12 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/vite.ts +145 -8
package/package.json
CHANGED
package/src/vite.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { Plugin, ViteDevServer } from "vite";
|
|
10
10
|
import { spawn, type ChildProcess, exec } from "node:child_process";
|
|
11
11
|
import { resolve, join } from "node:path";
|
|
12
|
-
import { watch, type FSWatcher } from "node:fs";
|
|
12
|
+
import { watch, type FSWatcher, existsSync } from "node:fs";
|
|
13
13
|
import { promisify } from "node:util";
|
|
14
14
|
|
|
15
15
|
const execAsync = promisify(exec);
|
|
@@ -41,6 +41,19 @@ export interface DevPluginOptions {
|
|
|
41
41
|
* @default "./src/server"
|
|
42
42
|
*/
|
|
43
43
|
watchDir?: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Enable hot reload for route files (dev mode only).
|
|
47
|
+
* When a route file changes, the router will be reloaded without server restart.
|
|
48
|
+
* @default true
|
|
49
|
+
*/
|
|
50
|
+
hotReloadRoutes?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Glob patterns for route files to watch for hot reload.
|
|
54
|
+
* @default ["**\/routes\/**\/*.ts"]
|
|
55
|
+
*/
|
|
56
|
+
routePatterns?: string[];
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
// Check if running with Bun runtime (bun --bun)
|
|
@@ -87,6 +100,8 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
87
100
|
backendPort = 3001,
|
|
88
101
|
watchTypes = true,
|
|
89
102
|
watchDir = "./src/server",
|
|
103
|
+
hotReloadRoutes = true,
|
|
104
|
+
routePatterns = ["**/routes/**/*.ts", "**/routes/**/*.js"],
|
|
90
105
|
} = options;
|
|
91
106
|
|
|
92
107
|
// State for subprocess mode
|
|
@@ -96,6 +111,7 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
96
111
|
// State for in-process mode
|
|
97
112
|
let appServer: any = null;
|
|
98
113
|
let serverReady = false;
|
|
114
|
+
let viteServer: ViteDevServer | null = null;
|
|
99
115
|
|
|
100
116
|
// State for file watcher
|
|
101
117
|
let fileWatcher: FSWatcher | null = null;
|
|
@@ -103,11 +119,82 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
103
119
|
let lastGenerationTime = 0;
|
|
104
120
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
105
121
|
|
|
122
|
+
// State for hot reload
|
|
123
|
+
let hotReloadTimer: ReturnType<typeof setTimeout> | null = null;
|
|
124
|
+
const HOT_RELOAD_DEBOUNCE_MS = 100;
|
|
125
|
+
|
|
106
126
|
const COOLDOWN_MS = 2000;
|
|
107
127
|
const DEBOUNCE_MS = 500;
|
|
108
128
|
|
|
109
129
|
// Patterns to ignore (generated files)
|
|
110
|
-
const IGNORED_PATTERNS = [/schema\.ts$/, /\.d\.ts$/];
|
|
130
|
+
const IGNORED_PATTERNS = [/schema\.ts$/, /\.d\.ts$/, /api\.ts$/];
|
|
131
|
+
|
|
132
|
+
// Check if a file matches route patterns
|
|
133
|
+
function isRouteFile(filename: string): boolean {
|
|
134
|
+
return routePatterns.some((pattern) => {
|
|
135
|
+
// Simple glob matching for common patterns
|
|
136
|
+
const regexPattern = pattern
|
|
137
|
+
.replace(/\*\*/g, ".*")
|
|
138
|
+
.replace(/\*/g, "[^/]*")
|
|
139
|
+
.replace(/\./g, "\\.");
|
|
140
|
+
return new RegExp(regexPattern).test(filename);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Hot reload a route file
|
|
145
|
+
async function hotReloadRoute(filepath: string) {
|
|
146
|
+
if (!appServer || !serverReady || !viteServer || !hotReloadRoutes) return;
|
|
147
|
+
|
|
148
|
+
console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Hot reloading route:", filepath);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Invalidate the module in Vite's cache
|
|
152
|
+
const mod = viteServer.moduleGraph.getModuleById(filepath);
|
|
153
|
+
if (mod) {
|
|
154
|
+
viteServer.moduleGraph.invalidateModule(mod);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Re-import the module with cache busting
|
|
158
|
+
const timestamp = Date.now();
|
|
159
|
+
const moduleUrl = `${filepath}?t=${timestamp}`;
|
|
160
|
+
|
|
161
|
+
// Import the fresh module
|
|
162
|
+
const freshModule = await viteServer.ssrLoadModule(moduleUrl);
|
|
163
|
+
|
|
164
|
+
// Find the router export (could be named 'router', 'default', or end with 'Router')
|
|
165
|
+
let newRouter = freshModule.router || freshModule.default;
|
|
166
|
+
if (!newRouter) {
|
|
167
|
+
for (const key of Object.keys(freshModule)) {
|
|
168
|
+
if (key.endsWith("Router") && typeof freshModule[key]?.getRoutes === "function") {
|
|
169
|
+
newRouter = freshModule[key];
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (newRouter && typeof newRouter.getRoutes === "function") {
|
|
176
|
+
// Get the router prefix
|
|
177
|
+
const prefix = newRouter.getPrefix?.() || "";
|
|
178
|
+
if (prefix) {
|
|
179
|
+
appServer.reloadRouter(prefix, newRouter);
|
|
180
|
+
console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Route hot reload complete:", prefix);
|
|
181
|
+
} else {
|
|
182
|
+
// If no prefix, rebuild all routes
|
|
183
|
+
appServer.rebuildRouteMap();
|
|
184
|
+
console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Route map rebuilt");
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
console.warn("\x1b[33m[donkeylabs-dev]\x1b[0m No router export found in:", filepath);
|
|
188
|
+
}
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
console.error("\x1b[31m[donkeylabs-dev]\x1b[0m Hot reload error:", err.message);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function debouncedHotReload(filepath: string) {
|
|
195
|
+
if (hotReloadTimer) clearTimeout(hotReloadTimer);
|
|
196
|
+
hotReloadTimer = setTimeout(() => hotReloadRoute(filepath), HOT_RELOAD_DEBOUNCE_MS);
|
|
197
|
+
}
|
|
111
198
|
|
|
112
199
|
function shouldIgnoreFile(filename: string): boolean {
|
|
113
200
|
return IGNORED_PATTERNS.some((pattern) => pattern.test(filename));
|
|
@@ -122,7 +209,7 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
122
209
|
console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Server files changed, regenerating types...");
|
|
123
210
|
|
|
124
211
|
try {
|
|
125
|
-
await execAsync("
|
|
212
|
+
await execAsync("bun run gen:types");
|
|
126
213
|
console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Types regenerated successfully");
|
|
127
214
|
} catch (e: any) {
|
|
128
215
|
console.error("\x1b[31m[donkeylabs-dev]\x1b[0m Error regenerating types:", e.message);
|
|
@@ -132,23 +219,65 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
132
219
|
}
|
|
133
220
|
}
|
|
134
221
|
|
|
222
|
+
async function ensureTypesGenerated() {
|
|
223
|
+
// Check if the client file exists (common locations)
|
|
224
|
+
const clientPaths = [
|
|
225
|
+
resolve(process.cwd(), "src/lib/api.ts"),
|
|
226
|
+
resolve(process.cwd(), "src/api.ts"),
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const clientExists = clientPaths.some((p) => existsSync(p));
|
|
230
|
+
if (clientExists) return;
|
|
231
|
+
|
|
232
|
+
console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Generated client not found, running initial type generation...");
|
|
233
|
+
isGenerating = true;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await execAsync("bun run gen:types");
|
|
237
|
+
console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Initial types generated successfully");
|
|
238
|
+
} catch (e: any) {
|
|
239
|
+
console.warn("\x1b[33m[donkeylabs-dev]\x1b[0m Initial type generation failed:", e.message);
|
|
240
|
+
console.warn("\x1b[33m[donkeylabs-dev]\x1b[0m Run 'bun run gen:types' manually to generate types");
|
|
241
|
+
} finally {
|
|
242
|
+
isGenerating = false;
|
|
243
|
+
lastGenerationTime = Date.now();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
135
247
|
function debouncedRegenerate() {
|
|
136
248
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
137
249
|
debounceTimer = setTimeout(regenerateTypes, DEBOUNCE_MS);
|
|
138
250
|
}
|
|
139
251
|
|
|
140
252
|
function startFileWatcher() {
|
|
141
|
-
if (
|
|
253
|
+
if (fileWatcher) return;
|
|
254
|
+
if (!watchTypes && !hotReloadRoutes) return;
|
|
142
255
|
|
|
143
256
|
const watchPath = resolve(process.cwd(), watchDir);
|
|
144
257
|
try {
|
|
145
258
|
fileWatcher = watch(watchPath, { recursive: true }, (_eventType, filename) => {
|
|
146
259
|
if (!filename) return;
|
|
147
|
-
if (!filename.endsWith(".ts")) return;
|
|
260
|
+
if (!filename.endsWith(".ts") && !filename.endsWith(".js")) return;
|
|
148
261
|
if (shouldIgnoreFile(filename)) return;
|
|
149
|
-
|
|
262
|
+
|
|
263
|
+
const fullPath = join(watchPath, filename);
|
|
264
|
+
|
|
265
|
+
// Check if this is a route file for hot reload
|
|
266
|
+
if (hotReloadRoutes && isRouteFile(filename)) {
|
|
267
|
+
debouncedHotReload(fullPath);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Also trigger type regeneration
|
|
271
|
+
if (watchTypes) {
|
|
272
|
+
debouncedRegenerate();
|
|
273
|
+
}
|
|
150
274
|
});
|
|
151
|
-
|
|
275
|
+
|
|
276
|
+
const features = [
|
|
277
|
+
watchTypes ? "type generation" : null,
|
|
278
|
+
hotReloadRoutes ? "hot reload" : null,
|
|
279
|
+
].filter(Boolean).join(", ");
|
|
280
|
+
console.log(`\x1b[36m[donkeylabs-dev]\x1b[0m Watching ${watchDir} for ${features}...`);
|
|
152
281
|
} catch (err) {
|
|
153
282
|
console.warn(`\x1b[33m[donkeylabs-dev]\x1b[0m Could not watch ${watchDir}:`, err);
|
|
154
283
|
}
|
|
@@ -156,6 +285,7 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
156
285
|
|
|
157
286
|
function stopFileWatcher() {
|
|
158
287
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
288
|
+
if (hotReloadTimer) clearTimeout(hotReloadTimer);
|
|
159
289
|
if (fileWatcher) {
|
|
160
290
|
fileWatcher.close();
|
|
161
291
|
fileWatcher = null;
|
|
@@ -183,7 +313,13 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
183
313
|
async configureServer(server: ViteDevServer) {
|
|
184
314
|
const serverEntryResolved = resolve(process.cwd(), serverEntry);
|
|
185
315
|
|
|
186
|
-
//
|
|
316
|
+
// Store vite server reference for hot reload
|
|
317
|
+
viteServer = server;
|
|
318
|
+
|
|
319
|
+
// Ensure types are generated on first start (if client file doesn't exist)
|
|
320
|
+
await ensureTypesGenerated();
|
|
321
|
+
|
|
322
|
+
// Start file watcher for auto type regeneration and hot reload
|
|
187
323
|
startFileWatcher();
|
|
188
324
|
|
|
189
325
|
if (isBunRuntime) {
|
|
@@ -576,6 +712,7 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
|
|
|
576
712
|
|
|
577
713
|
async closeBundle() {
|
|
578
714
|
stopFileWatcher();
|
|
715
|
+
viteServer = null;
|
|
579
716
|
if (backendProcess) {
|
|
580
717
|
backendProcess.kill();
|
|
581
718
|
backendProcess = null;
|