@lovable.dev/vite-tanstack-config 2.13.1 → 2.15.0
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/dist/hmr-gate.cjs +270 -1
- package/dist/hmr-gate.d.cts +26 -1
- package/dist/hmr-gate.d.ts +26 -1
- package/dist/hmr-gate.js +269 -2
- package/package.json +1 -1
package/dist/hmr-gate.cjs
CHANGED
|
@@ -18,6 +18,31 @@ function gateStatusHeaders(req) {
|
|
|
18
18
|
return headers;
|
|
19
19
|
}
|
|
20
20
|
const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
|
|
21
|
+
const BOOT_PROGRESS_EVENT = "lovable:boot-progress";
|
|
22
|
+
const BOOT_PROGRESS_MIN_INTERVAL_MS = 500;
|
|
23
|
+
const UPDATE_PROGRESS_EVENT = "lovable:update-progress";
|
|
24
|
+
const UPDATE_PROGRESS_MIN_INTERVAL_MS = 500;
|
|
25
|
+
const UPDATE_SETTLE_MS = 800;
|
|
26
|
+
const UPDATE_FLUSH_SETTLE_MS = 5e3;
|
|
27
|
+
const UPDATE_MIN_MODULES = 10;
|
|
28
|
+
const UPDATE_MIN_DURATION_MS = 1500;
|
|
29
|
+
function firstBuildError(error) {
|
|
30
|
+
if (error == null || typeof error !== "object") return null;
|
|
31
|
+
const errors = error.errors;
|
|
32
|
+
const e = Array.isArray(errors) && errors.length > 0 ? errors[0] : error;
|
|
33
|
+
const message = typeof e.message === "string" ? e.message : "Build failed";
|
|
34
|
+
let file = null;
|
|
35
|
+
if (typeof e.loc?.file === "string") file = e.loc.file;
|
|
36
|
+
else if (typeof e.id === "string") file = e.id;
|
|
37
|
+
return {
|
|
38
|
+
message,
|
|
39
|
+
file,
|
|
40
|
+
line: typeof e.loc?.line === "number" ? e.loc.line : null
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function hookEnvironmentName(context) {
|
|
44
|
+
return context.environment?.name ?? "shared";
|
|
45
|
+
}
|
|
21
46
|
const EDIT_FREE_RELOAD_MARKER = "edit-free";
|
|
22
47
|
/** Host-controlled default for `fullReload`; an explicit option still wins. */
|
|
23
48
|
const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
|
|
@@ -33,6 +58,180 @@ function hmrGatePlugin(options = {}) {
|
|
|
33
58
|
const lastFlushedHashes = /* @__PURE__ */ new Map();
|
|
34
59
|
let originalEmit = null;
|
|
35
60
|
let server = null;
|
|
61
|
+
let generation = null;
|
|
62
|
+
const transformsByEnv = /* @__PURE__ */ new Map();
|
|
63
|
+
const buildErrorsByEnv = /* @__PURE__ */ new Map();
|
|
64
|
+
let bootProgressSentAt = 0;
|
|
65
|
+
let bootProgressTimer = null;
|
|
66
|
+
let lastBootProgressKey = "";
|
|
67
|
+
function currentBuildError() {
|
|
68
|
+
return buildErrorsByEnv.values().next().value ?? null;
|
|
69
|
+
}
|
|
70
|
+
function buildErrorKey(error) {
|
|
71
|
+
return error ? `${error.message}:${error.file ?? ""}:${error.line ?? ""}` : "";
|
|
72
|
+
}
|
|
73
|
+
function bootProgressData() {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const idle = (name) => {
|
|
76
|
+
const at = transformsByEnv.get(name)?.lastTransformAt;
|
|
77
|
+
return at === void 0 ? null : now - at;
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
ssrModules: transformsByEnv.get("ssr")?.ids.size ?? 0,
|
|
81
|
+
clientModules: transformsByEnv.get("client")?.ids.size ?? 0,
|
|
82
|
+
ssrIdleMs: idle("ssr"),
|
|
83
|
+
clientIdleMs: idle("client"),
|
|
84
|
+
buildError: currentBuildError()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function bootProgressKey(data) {
|
|
88
|
+
return `${data.ssrModules}:${data.clientModules}:${buildErrorKey(data.buildError)}`;
|
|
89
|
+
}
|
|
90
|
+
function broadcastBootProgress() {
|
|
91
|
+
const data = bootProgressData();
|
|
92
|
+
const key = bootProgressKey(data);
|
|
93
|
+
if (key === lastBootProgressKey) return;
|
|
94
|
+
lastBootProgressKey = key;
|
|
95
|
+
bootProgressSentAt = Date.now();
|
|
96
|
+
server?.ws.send({
|
|
97
|
+
type: "custom",
|
|
98
|
+
event: BOOT_PROGRESS_EVENT,
|
|
99
|
+
data
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function scheduleBootProgress() {
|
|
103
|
+
if (bootProgressTimer !== null) return;
|
|
104
|
+
const waitMs = bootProgressSentAt + BOOT_PROGRESS_MIN_INTERVAL_MS - Date.now();
|
|
105
|
+
if (waitMs <= 0) {
|
|
106
|
+
broadcastBootProgress();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
bootProgressTimer = setTimeout(() => {
|
|
110
|
+
bootProgressTimer = null;
|
|
111
|
+
broadcastBootProgress();
|
|
112
|
+
}, waitMs);
|
|
113
|
+
bootProgressTimer.unref?.();
|
|
114
|
+
}
|
|
115
|
+
let updateBatchSeq = 0;
|
|
116
|
+
let updateBatch = null;
|
|
117
|
+
let updateSentAt = 0;
|
|
118
|
+
let lastUpdateKey = "";
|
|
119
|
+
let updateTimer = null;
|
|
120
|
+
let updateSettleTimer = null;
|
|
121
|
+
function updateBatchModuleCount(b) {
|
|
122
|
+
return (b.idsByEnv.get("ssr")?.size ?? 0) + (b.idsByEnv.get("client")?.size ?? 0);
|
|
123
|
+
}
|
|
124
|
+
function updateBatchEarned(b) {
|
|
125
|
+
const total = updateBatchModuleCount(b);
|
|
126
|
+
if (total >= UPDATE_MIN_MODULES) return true;
|
|
127
|
+
return total > 0 && Date.now() - b.openedAt >= UPDATE_MIN_DURATION_MS;
|
|
128
|
+
}
|
|
129
|
+
function broadcastUpdateProgress(opts = {}) {
|
|
130
|
+
const b = updateBatch;
|
|
131
|
+
if (b === null) return;
|
|
132
|
+
const trigger = opts.trigger ?? b.trigger;
|
|
133
|
+
if (trigger !== "restart" && !updateBatchEarned(b)) return;
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const data = {
|
|
136
|
+
batch: b.id,
|
|
137
|
+
trigger,
|
|
138
|
+
ssrModules: b.idsByEnv.get("ssr")?.size ?? 0,
|
|
139
|
+
clientModules: b.idsByEnv.get("client")?.size ?? 0,
|
|
140
|
+
idleMs: b.lastTransformAt === 0 ? null : now - b.lastTransformAt,
|
|
141
|
+
done: opts.done ?? false,
|
|
142
|
+
buildError: currentBuildError()
|
|
143
|
+
};
|
|
144
|
+
const key = `${data.batch}:${data.trigger}:${data.ssrModules}:${data.clientModules}:${data.done}:${buildErrorKey(data.buildError)}`;
|
|
145
|
+
if (key === lastUpdateKey) return;
|
|
146
|
+
lastUpdateKey = key;
|
|
147
|
+
updateSentAt = now;
|
|
148
|
+
server?.ws.send({
|
|
149
|
+
type: "custom",
|
|
150
|
+
event: UPDATE_PROGRESS_EVENT,
|
|
151
|
+
data
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function scheduleUpdateProgress() {
|
|
155
|
+
if (updateTimer !== null) return;
|
|
156
|
+
const waitMs = updateSentAt + UPDATE_PROGRESS_MIN_INTERVAL_MS - Date.now();
|
|
157
|
+
if (waitMs <= 0) {
|
|
158
|
+
broadcastUpdateProgress();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
updateTimer = setTimeout(() => {
|
|
162
|
+
updateTimer = null;
|
|
163
|
+
broadcastUpdateProgress();
|
|
164
|
+
}, waitMs);
|
|
165
|
+
updateTimer.unref?.();
|
|
166
|
+
}
|
|
167
|
+
function clearUpdateTimers() {
|
|
168
|
+
if (updateTimer !== null) {
|
|
169
|
+
clearTimeout(updateTimer);
|
|
170
|
+
updateTimer = null;
|
|
171
|
+
}
|
|
172
|
+
if (updateSettleTimer !== null) {
|
|
173
|
+
clearTimeout(updateSettleTimer);
|
|
174
|
+
updateSettleTimer = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function armUpdateSettle(delayMs) {
|
|
178
|
+
if (updateSettleTimer !== null) clearTimeout(updateSettleTimer);
|
|
179
|
+
updateSettleTimer = setTimeout(() => {
|
|
180
|
+
updateSettleTimer = null;
|
|
181
|
+
if (updateBatch === null) return;
|
|
182
|
+
if (updateTimer !== null) {
|
|
183
|
+
clearTimeout(updateTimer);
|
|
184
|
+
updateTimer = null;
|
|
185
|
+
}
|
|
186
|
+
broadcastUpdateProgress({ done: true });
|
|
187
|
+
updateBatch = null;
|
|
188
|
+
}, delayMs);
|
|
189
|
+
updateSettleTimer.unref?.();
|
|
190
|
+
}
|
|
191
|
+
function openWatchBatchIfNone() {
|
|
192
|
+
if (updateBatch !== null) return;
|
|
193
|
+
updateBatchSeq++;
|
|
194
|
+
updateBatch = {
|
|
195
|
+
id: updateBatchSeq,
|
|
196
|
+
trigger: "watch",
|
|
197
|
+
openedAt: Date.now(),
|
|
198
|
+
idsByEnv: /* @__PURE__ */ new Map(),
|
|
199
|
+
lastTransformAt: 0
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function noteUpdateFlush() {
|
|
203
|
+
if (updateBatch === null) {
|
|
204
|
+
updateBatchSeq++;
|
|
205
|
+
updateBatch = {
|
|
206
|
+
id: updateBatchSeq,
|
|
207
|
+
trigger: "flush",
|
|
208
|
+
openedAt: Date.now(),
|
|
209
|
+
idsByEnv: /* @__PURE__ */ new Map(),
|
|
210
|
+
lastTransformAt: 0
|
|
211
|
+
};
|
|
212
|
+
} else {
|
|
213
|
+
updateBatch.trigger = "flush";
|
|
214
|
+
if (updateBatch.lastTransformAt === 0) updateBatch.openedAt = Date.now();
|
|
215
|
+
}
|
|
216
|
+
scheduleUpdateProgress();
|
|
217
|
+
armUpdateSettle(updateBatch.lastTransformAt === 0 ? UPDATE_FLUSH_SETTLE_MS : UPDATE_SETTLE_MS);
|
|
218
|
+
}
|
|
219
|
+
function noteUpdateRestart() {
|
|
220
|
+
if (updateBatch === null) noteUpdateFlush();
|
|
221
|
+
clearUpdateTimers();
|
|
222
|
+
broadcastUpdateProgress({
|
|
223
|
+
trigger: "restart",
|
|
224
|
+
done: true
|
|
225
|
+
});
|
|
226
|
+
updateBatch = null;
|
|
227
|
+
}
|
|
228
|
+
function resetUpdateProgress() {
|
|
229
|
+
clearUpdateTimers();
|
|
230
|
+
updateBatch = null;
|
|
231
|
+
updateBatchSeq = 0;
|
|
232
|
+
updateSentAt = 0;
|
|
233
|
+
lastUpdateKey = "";
|
|
234
|
+
}
|
|
36
235
|
let classicFailOpenTimer = null;
|
|
37
236
|
let transportMode = false;
|
|
38
237
|
let swallowedUpdates = 0;
|
|
@@ -77,6 +276,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
77
276
|
const type = frame?.type;
|
|
78
277
|
if (type === "update") {
|
|
79
278
|
swallowedUpdates++;
|
|
279
|
+
openWatchBatchIfNone();
|
|
80
280
|
return;
|
|
81
281
|
}
|
|
82
282
|
if (type === "full-reload") {
|
|
@@ -85,6 +285,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
85
285
|
return original(...args);
|
|
86
286
|
}
|
|
87
287
|
heldReload = true;
|
|
288
|
+
openWatchBatchIfNone();
|
|
88
289
|
return;
|
|
89
290
|
}
|
|
90
291
|
return original(...args);
|
|
@@ -107,15 +308,67 @@ function hmrGatePlugin(options = {}) {
|
|
|
107
308
|
apply: "serve",
|
|
108
309
|
configResolved(config) {
|
|
109
310
|
transportMode = config.experimental?.bundledDev === true;
|
|
311
|
+
const rawGeneration = process.env.LOVABLE_SPAWN_GENERATION;
|
|
312
|
+
const parsed = rawGeneration === void 0 || rawGeneration === "" ? null : Number(rawGeneration);
|
|
313
|
+
generation = parsed !== null && Number.isSafeInteger(parsed) ? parsed : null;
|
|
314
|
+
},
|
|
315
|
+
transform(_code, id, options) {
|
|
316
|
+
const name = this.environment?.name ?? (options?.ssr === true ? "ssr" : "client");
|
|
317
|
+
let progress = transformsByEnv.get(name);
|
|
318
|
+
if (!progress) {
|
|
319
|
+
progress = {
|
|
320
|
+
ids: /* @__PURE__ */ new Set(),
|
|
321
|
+
lastTransformAt: 0
|
|
322
|
+
};
|
|
323
|
+
transformsByEnv.set(name, progress);
|
|
324
|
+
}
|
|
325
|
+
progress.ids.add(id);
|
|
326
|
+
progress.lastTransformAt = Date.now();
|
|
327
|
+
scheduleBootProgress();
|
|
328
|
+
if (updateBatch !== null) {
|
|
329
|
+
let batchIds = updateBatch.idsByEnv.get(name);
|
|
330
|
+
if (!batchIds) {
|
|
331
|
+
batchIds = /* @__PURE__ */ new Set();
|
|
332
|
+
updateBatch.idsByEnv.set(name, batchIds);
|
|
333
|
+
}
|
|
334
|
+
batchIds.add(id);
|
|
335
|
+
updateBatch.lastTransformAt = Date.now();
|
|
336
|
+
scheduleUpdateProgress();
|
|
337
|
+
if (updateBatch.trigger === "flush") armUpdateSettle(UPDATE_SETTLE_MS);
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
},
|
|
341
|
+
buildStart() {
|
|
342
|
+
buildErrorsByEnv.delete(hookEnvironmentName(this));
|
|
343
|
+
scheduleBootProgress();
|
|
344
|
+
},
|
|
345
|
+
buildEnd(error) {
|
|
346
|
+
const name = hookEnvironmentName(this);
|
|
347
|
+
const parsed = error == null ? null : firstBuildError(error);
|
|
348
|
+
if (parsed === null) buildErrorsByEnv.delete(name);
|
|
349
|
+
else buildErrorsByEnv.set(name, parsed);
|
|
350
|
+
scheduleBootProgress();
|
|
110
351
|
},
|
|
111
352
|
configureServer(srv) {
|
|
112
353
|
server = srv;
|
|
354
|
+
transformsByEnv.clear();
|
|
355
|
+
buildErrorsByEnv.clear();
|
|
356
|
+
lastBootProgressKey = "";
|
|
357
|
+
resetUpdateProgress();
|
|
113
358
|
srv.ws.on?.("connection", (socket) => {
|
|
114
359
|
try {
|
|
115
360
|
socket.send(JSON.stringify({
|
|
116
361
|
type: "custom",
|
|
117
362
|
event: DEV_SERVER_MODE_EVENT,
|
|
118
|
-
data: {
|
|
363
|
+
data: {
|
|
364
|
+
mode: transportMode ? "bundled" : "classic",
|
|
365
|
+
...generation !== null ? { generation } : {}
|
|
366
|
+
}
|
|
367
|
+
}));
|
|
368
|
+
socket.send(JSON.stringify({
|
|
369
|
+
type: "custom",
|
|
370
|
+
event: BOOT_PROGRESS_EVENT,
|
|
371
|
+
data: bootProgressData()
|
|
119
372
|
}));
|
|
120
373
|
} catch {}
|
|
121
374
|
});
|
|
@@ -135,6 +388,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
135
388
|
originalOn(event, (payload, client) => {
|
|
136
389
|
if (Date.now() < startupGraceUntil || initialBuildPending()) return listener(payload, client);
|
|
137
390
|
bufferedReloadNeeded.set(client, payload);
|
|
391
|
+
openWatchBatchIfNone();
|
|
138
392
|
if (reloadFailOpenTimer === null) {
|
|
139
393
|
reloadFailOpenTimer = setTimeout(() => {
|
|
140
394
|
reloadFailOpenTimer = null;
|
|
@@ -174,6 +428,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
174
428
|
const broadcastHeld = heldReload;
|
|
175
429
|
heldReload = false;
|
|
176
430
|
const reload = broadcastHeld || bufferedReloadNeeded.size > 0;
|
|
431
|
+
if (released > 0 || updates > 0 || reload) noteUpdateFlush();
|
|
177
432
|
let flushRestarts = false;
|
|
178
433
|
if (reload) {
|
|
179
434
|
const releaseDelayMs = Math.max(released > 0 ? drainMs : 0, priorDrainUntil - Date.now(), 0);
|
|
@@ -195,6 +450,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
195
450
|
restartTimer.unref?.();
|
|
196
451
|
} else if (typeof restart === "function") {
|
|
197
452
|
flushRestarts = true;
|
|
453
|
+
noteUpdateRestart();
|
|
198
454
|
bufferedReloadNeeded.clear();
|
|
199
455
|
pendingReplay.clear();
|
|
200
456
|
restartTimer = setTimeout(() => {
|
|
@@ -245,6 +501,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
245
501
|
clearTimeout(classicFailOpenTimer);
|
|
246
502
|
classicFailOpenTimer = null;
|
|
247
503
|
}
|
|
504
|
+
if (pending.size > 0) noteUpdateFlush();
|
|
248
505
|
const entries = Array.from(pending.entries());
|
|
249
506
|
pending.clear();
|
|
250
507
|
for (const [f] of entries) srv.moduleGraph.onFileChange(f);
|
|
@@ -279,6 +536,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
279
536
|
pending.set(filePath, events);
|
|
280
537
|
}
|
|
281
538
|
events.add(event);
|
|
539
|
+
openWatchBatchIfNone();
|
|
282
540
|
if (classicFailOpenTimer === null) {
|
|
283
541
|
classicFailOpenTimer = setTimeout(() => {
|
|
284
542
|
classicFailOpenTimer = null;
|
|
@@ -333,6 +591,15 @@ function hmrGatePlugin(options = {}) {
|
|
|
333
591
|
clearTimeout(classicFailOpenTimer);
|
|
334
592
|
classicFailOpenTimer = null;
|
|
335
593
|
}
|
|
594
|
+
if (bootProgressTimer !== null) {
|
|
595
|
+
clearTimeout(bootProgressTimer);
|
|
596
|
+
bootProgressTimer = null;
|
|
597
|
+
}
|
|
598
|
+
transformsByEnv.clear();
|
|
599
|
+
buildErrorsByEnv.clear();
|
|
600
|
+
bootProgressSentAt = 0;
|
|
601
|
+
lastBootProgressKey = "";
|
|
602
|
+
resetUpdateProgress();
|
|
336
603
|
bufferedReloadNeeded.clear();
|
|
337
604
|
pendingReplay.clear();
|
|
338
605
|
reloadNeededListener = null;
|
|
@@ -348,6 +615,8 @@ function hmrGatePlugin(options = {}) {
|
|
|
348
615
|
};
|
|
349
616
|
}
|
|
350
617
|
//#endregion
|
|
618
|
+
exports.BOOT_PROGRESS_EVENT = BOOT_PROGRESS_EVENT;
|
|
351
619
|
exports.DEV_SERVER_MODE_EVENT = DEV_SERVER_MODE_EVENT;
|
|
352
620
|
exports.FULL_RELOAD_ENV_VAR = FULL_RELOAD_ENV_VAR;
|
|
621
|
+
exports.UPDATE_PROGRESS_EVENT = UPDATE_PROGRESS_EVENT;
|
|
353
622
|
exports.hmrGatePlugin = hmrGatePlugin;
|
package/dist/hmr-gate.d.cts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
2
|
//#region src/hmr-gate/index.d.ts
|
|
3
3
|
declare const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
|
|
4
|
+
declare const BOOT_PROGRESS_EVENT = "lovable:boot-progress";
|
|
5
|
+
declare const UPDATE_PROGRESS_EVENT = "lovable:update-progress";
|
|
6
|
+
type UpdateTrigger = "flush" | "watch" | "restart";
|
|
7
|
+
interface UpdateProgressData {
|
|
8
|
+
batch: number;
|
|
9
|
+
trigger: UpdateTrigger;
|
|
10
|
+
ssrModules: number;
|
|
11
|
+
clientModules: number;
|
|
12
|
+
idleMs: number | null;
|
|
13
|
+
done: boolean;
|
|
14
|
+
buildError: BootProgressData["buildError"];
|
|
15
|
+
}
|
|
16
|
+
interface BootProgressData {
|
|
17
|
+
ssrModules: number;
|
|
18
|
+
clientModules: number;
|
|
19
|
+
/** ms since the newest transform, measured on the server's own clock so the
|
|
20
|
+
browser never differences two machines' timestamps. */
|
|
21
|
+
ssrIdleMs: number | null;
|
|
22
|
+
clientIdleMs: number | null;
|
|
23
|
+
buildError: {
|
|
24
|
+
message: string;
|
|
25
|
+
file: string | null;
|
|
26
|
+
line: number | null;
|
|
27
|
+
} | null;
|
|
28
|
+
}
|
|
4
29
|
/** Host-controlled default for `fullReload`; an explicit option still wins. */
|
|
5
30
|
declare const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
|
|
6
31
|
interface HmrGateOptions {
|
|
@@ -24,4 +49,4 @@ interface HmrGateOptions {
|
|
|
24
49
|
}
|
|
25
50
|
declare function hmrGatePlugin(options?: HmrGateOptions): Plugin;
|
|
26
51
|
//#endregion
|
|
27
|
-
export { DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, HmrGateOptions, hmrGatePlugin };
|
|
52
|
+
export { BOOT_PROGRESS_EVENT, BootProgressData, DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, HmrGateOptions, UPDATE_PROGRESS_EVENT, UpdateProgressData, UpdateTrigger, hmrGatePlugin };
|
package/dist/hmr-gate.d.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
2
|
//#region src/hmr-gate/index.d.ts
|
|
3
3
|
declare const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
|
|
4
|
+
declare const BOOT_PROGRESS_EVENT = "lovable:boot-progress";
|
|
5
|
+
declare const UPDATE_PROGRESS_EVENT = "lovable:update-progress";
|
|
6
|
+
type UpdateTrigger = "flush" | "watch" | "restart";
|
|
7
|
+
interface UpdateProgressData {
|
|
8
|
+
batch: number;
|
|
9
|
+
trigger: UpdateTrigger;
|
|
10
|
+
ssrModules: number;
|
|
11
|
+
clientModules: number;
|
|
12
|
+
idleMs: number | null;
|
|
13
|
+
done: boolean;
|
|
14
|
+
buildError: BootProgressData["buildError"];
|
|
15
|
+
}
|
|
16
|
+
interface BootProgressData {
|
|
17
|
+
ssrModules: number;
|
|
18
|
+
clientModules: number;
|
|
19
|
+
/** ms since the newest transform, measured on the server's own clock so the
|
|
20
|
+
browser never differences two machines' timestamps. */
|
|
21
|
+
ssrIdleMs: number | null;
|
|
22
|
+
clientIdleMs: number | null;
|
|
23
|
+
buildError: {
|
|
24
|
+
message: string;
|
|
25
|
+
file: string | null;
|
|
26
|
+
line: number | null;
|
|
27
|
+
} | null;
|
|
28
|
+
}
|
|
4
29
|
/** Host-controlled default for `fullReload`; an explicit option still wins. */
|
|
5
30
|
declare const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
|
|
6
31
|
interface HmrGateOptions {
|
|
@@ -24,4 +49,4 @@ interface HmrGateOptions {
|
|
|
24
49
|
}
|
|
25
50
|
declare function hmrGatePlugin(options?: HmrGateOptions): Plugin;
|
|
26
51
|
//#endregion
|
|
27
|
-
export { DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, HmrGateOptions, hmrGatePlugin };
|
|
52
|
+
export { BOOT_PROGRESS_EVENT, BootProgressData, DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, HmrGateOptions, UPDATE_PROGRESS_EVENT, UpdateProgressData, UpdateTrigger, hmrGatePlugin };
|
package/dist/hmr-gate.js
CHANGED
|
@@ -17,6 +17,31 @@ function gateStatusHeaders(req) {
|
|
|
17
17
|
return headers;
|
|
18
18
|
}
|
|
19
19
|
const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
|
|
20
|
+
const BOOT_PROGRESS_EVENT = "lovable:boot-progress";
|
|
21
|
+
const BOOT_PROGRESS_MIN_INTERVAL_MS = 500;
|
|
22
|
+
const UPDATE_PROGRESS_EVENT = "lovable:update-progress";
|
|
23
|
+
const UPDATE_PROGRESS_MIN_INTERVAL_MS = 500;
|
|
24
|
+
const UPDATE_SETTLE_MS = 800;
|
|
25
|
+
const UPDATE_FLUSH_SETTLE_MS = 5e3;
|
|
26
|
+
const UPDATE_MIN_MODULES = 10;
|
|
27
|
+
const UPDATE_MIN_DURATION_MS = 1500;
|
|
28
|
+
function firstBuildError(error) {
|
|
29
|
+
if (error == null || typeof error !== "object") return null;
|
|
30
|
+
const errors = error.errors;
|
|
31
|
+
const e = Array.isArray(errors) && errors.length > 0 ? errors[0] : error;
|
|
32
|
+
const message = typeof e.message === "string" ? e.message : "Build failed";
|
|
33
|
+
let file = null;
|
|
34
|
+
if (typeof e.loc?.file === "string") file = e.loc.file;
|
|
35
|
+
else if (typeof e.id === "string") file = e.id;
|
|
36
|
+
return {
|
|
37
|
+
message,
|
|
38
|
+
file,
|
|
39
|
+
line: typeof e.loc?.line === "number" ? e.loc.line : null
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function hookEnvironmentName(context) {
|
|
43
|
+
return context.environment?.name ?? "shared";
|
|
44
|
+
}
|
|
20
45
|
const EDIT_FREE_RELOAD_MARKER = "edit-free";
|
|
21
46
|
/** Host-controlled default for `fullReload`; an explicit option still wins. */
|
|
22
47
|
const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
|
|
@@ -32,6 +57,180 @@ function hmrGatePlugin(options = {}) {
|
|
|
32
57
|
const lastFlushedHashes = /* @__PURE__ */ new Map();
|
|
33
58
|
let originalEmit = null;
|
|
34
59
|
let server = null;
|
|
60
|
+
let generation = null;
|
|
61
|
+
const transformsByEnv = /* @__PURE__ */ new Map();
|
|
62
|
+
const buildErrorsByEnv = /* @__PURE__ */ new Map();
|
|
63
|
+
let bootProgressSentAt = 0;
|
|
64
|
+
let bootProgressTimer = null;
|
|
65
|
+
let lastBootProgressKey = "";
|
|
66
|
+
function currentBuildError() {
|
|
67
|
+
return buildErrorsByEnv.values().next().value ?? null;
|
|
68
|
+
}
|
|
69
|
+
function buildErrorKey(error) {
|
|
70
|
+
return error ? `${error.message}:${error.file ?? ""}:${error.line ?? ""}` : "";
|
|
71
|
+
}
|
|
72
|
+
function bootProgressData() {
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const idle = (name) => {
|
|
75
|
+
const at = transformsByEnv.get(name)?.lastTransformAt;
|
|
76
|
+
return at === void 0 ? null : now - at;
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
ssrModules: transformsByEnv.get("ssr")?.ids.size ?? 0,
|
|
80
|
+
clientModules: transformsByEnv.get("client")?.ids.size ?? 0,
|
|
81
|
+
ssrIdleMs: idle("ssr"),
|
|
82
|
+
clientIdleMs: idle("client"),
|
|
83
|
+
buildError: currentBuildError()
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function bootProgressKey(data) {
|
|
87
|
+
return `${data.ssrModules}:${data.clientModules}:${buildErrorKey(data.buildError)}`;
|
|
88
|
+
}
|
|
89
|
+
function broadcastBootProgress() {
|
|
90
|
+
const data = bootProgressData();
|
|
91
|
+
const key = bootProgressKey(data);
|
|
92
|
+
if (key === lastBootProgressKey) return;
|
|
93
|
+
lastBootProgressKey = key;
|
|
94
|
+
bootProgressSentAt = Date.now();
|
|
95
|
+
server?.ws.send({
|
|
96
|
+
type: "custom",
|
|
97
|
+
event: BOOT_PROGRESS_EVENT,
|
|
98
|
+
data
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function scheduleBootProgress() {
|
|
102
|
+
if (bootProgressTimer !== null) return;
|
|
103
|
+
const waitMs = bootProgressSentAt + BOOT_PROGRESS_MIN_INTERVAL_MS - Date.now();
|
|
104
|
+
if (waitMs <= 0) {
|
|
105
|
+
broadcastBootProgress();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
bootProgressTimer = setTimeout(() => {
|
|
109
|
+
bootProgressTimer = null;
|
|
110
|
+
broadcastBootProgress();
|
|
111
|
+
}, waitMs);
|
|
112
|
+
bootProgressTimer.unref?.();
|
|
113
|
+
}
|
|
114
|
+
let updateBatchSeq = 0;
|
|
115
|
+
let updateBatch = null;
|
|
116
|
+
let updateSentAt = 0;
|
|
117
|
+
let lastUpdateKey = "";
|
|
118
|
+
let updateTimer = null;
|
|
119
|
+
let updateSettleTimer = null;
|
|
120
|
+
function updateBatchModuleCount(b) {
|
|
121
|
+
return (b.idsByEnv.get("ssr")?.size ?? 0) + (b.idsByEnv.get("client")?.size ?? 0);
|
|
122
|
+
}
|
|
123
|
+
function updateBatchEarned(b) {
|
|
124
|
+
const total = updateBatchModuleCount(b);
|
|
125
|
+
if (total >= UPDATE_MIN_MODULES) return true;
|
|
126
|
+
return total > 0 && Date.now() - b.openedAt >= UPDATE_MIN_DURATION_MS;
|
|
127
|
+
}
|
|
128
|
+
function broadcastUpdateProgress(opts = {}) {
|
|
129
|
+
const b = updateBatch;
|
|
130
|
+
if (b === null) return;
|
|
131
|
+
const trigger = opts.trigger ?? b.trigger;
|
|
132
|
+
if (trigger !== "restart" && !updateBatchEarned(b)) return;
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
const data = {
|
|
135
|
+
batch: b.id,
|
|
136
|
+
trigger,
|
|
137
|
+
ssrModules: b.idsByEnv.get("ssr")?.size ?? 0,
|
|
138
|
+
clientModules: b.idsByEnv.get("client")?.size ?? 0,
|
|
139
|
+
idleMs: b.lastTransformAt === 0 ? null : now - b.lastTransformAt,
|
|
140
|
+
done: opts.done ?? false,
|
|
141
|
+
buildError: currentBuildError()
|
|
142
|
+
};
|
|
143
|
+
const key = `${data.batch}:${data.trigger}:${data.ssrModules}:${data.clientModules}:${data.done}:${buildErrorKey(data.buildError)}`;
|
|
144
|
+
if (key === lastUpdateKey) return;
|
|
145
|
+
lastUpdateKey = key;
|
|
146
|
+
updateSentAt = now;
|
|
147
|
+
server?.ws.send({
|
|
148
|
+
type: "custom",
|
|
149
|
+
event: UPDATE_PROGRESS_EVENT,
|
|
150
|
+
data
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function scheduleUpdateProgress() {
|
|
154
|
+
if (updateTimer !== null) return;
|
|
155
|
+
const waitMs = updateSentAt + UPDATE_PROGRESS_MIN_INTERVAL_MS - Date.now();
|
|
156
|
+
if (waitMs <= 0) {
|
|
157
|
+
broadcastUpdateProgress();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
updateTimer = setTimeout(() => {
|
|
161
|
+
updateTimer = null;
|
|
162
|
+
broadcastUpdateProgress();
|
|
163
|
+
}, waitMs);
|
|
164
|
+
updateTimer.unref?.();
|
|
165
|
+
}
|
|
166
|
+
function clearUpdateTimers() {
|
|
167
|
+
if (updateTimer !== null) {
|
|
168
|
+
clearTimeout(updateTimer);
|
|
169
|
+
updateTimer = null;
|
|
170
|
+
}
|
|
171
|
+
if (updateSettleTimer !== null) {
|
|
172
|
+
clearTimeout(updateSettleTimer);
|
|
173
|
+
updateSettleTimer = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function armUpdateSettle(delayMs) {
|
|
177
|
+
if (updateSettleTimer !== null) clearTimeout(updateSettleTimer);
|
|
178
|
+
updateSettleTimer = setTimeout(() => {
|
|
179
|
+
updateSettleTimer = null;
|
|
180
|
+
if (updateBatch === null) return;
|
|
181
|
+
if (updateTimer !== null) {
|
|
182
|
+
clearTimeout(updateTimer);
|
|
183
|
+
updateTimer = null;
|
|
184
|
+
}
|
|
185
|
+
broadcastUpdateProgress({ done: true });
|
|
186
|
+
updateBatch = null;
|
|
187
|
+
}, delayMs);
|
|
188
|
+
updateSettleTimer.unref?.();
|
|
189
|
+
}
|
|
190
|
+
function openWatchBatchIfNone() {
|
|
191
|
+
if (updateBatch !== null) return;
|
|
192
|
+
updateBatchSeq++;
|
|
193
|
+
updateBatch = {
|
|
194
|
+
id: updateBatchSeq,
|
|
195
|
+
trigger: "watch",
|
|
196
|
+
openedAt: Date.now(),
|
|
197
|
+
idsByEnv: /* @__PURE__ */ new Map(),
|
|
198
|
+
lastTransformAt: 0
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function noteUpdateFlush() {
|
|
202
|
+
if (updateBatch === null) {
|
|
203
|
+
updateBatchSeq++;
|
|
204
|
+
updateBatch = {
|
|
205
|
+
id: updateBatchSeq,
|
|
206
|
+
trigger: "flush",
|
|
207
|
+
openedAt: Date.now(),
|
|
208
|
+
idsByEnv: /* @__PURE__ */ new Map(),
|
|
209
|
+
lastTransformAt: 0
|
|
210
|
+
};
|
|
211
|
+
} else {
|
|
212
|
+
updateBatch.trigger = "flush";
|
|
213
|
+
if (updateBatch.lastTransformAt === 0) updateBatch.openedAt = Date.now();
|
|
214
|
+
}
|
|
215
|
+
scheduleUpdateProgress();
|
|
216
|
+
armUpdateSettle(updateBatch.lastTransformAt === 0 ? UPDATE_FLUSH_SETTLE_MS : UPDATE_SETTLE_MS);
|
|
217
|
+
}
|
|
218
|
+
function noteUpdateRestart() {
|
|
219
|
+
if (updateBatch === null) noteUpdateFlush();
|
|
220
|
+
clearUpdateTimers();
|
|
221
|
+
broadcastUpdateProgress({
|
|
222
|
+
trigger: "restart",
|
|
223
|
+
done: true
|
|
224
|
+
});
|
|
225
|
+
updateBatch = null;
|
|
226
|
+
}
|
|
227
|
+
function resetUpdateProgress() {
|
|
228
|
+
clearUpdateTimers();
|
|
229
|
+
updateBatch = null;
|
|
230
|
+
updateBatchSeq = 0;
|
|
231
|
+
updateSentAt = 0;
|
|
232
|
+
lastUpdateKey = "";
|
|
233
|
+
}
|
|
35
234
|
let classicFailOpenTimer = null;
|
|
36
235
|
let transportMode = false;
|
|
37
236
|
let swallowedUpdates = 0;
|
|
@@ -76,6 +275,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
76
275
|
const type = frame?.type;
|
|
77
276
|
if (type === "update") {
|
|
78
277
|
swallowedUpdates++;
|
|
278
|
+
openWatchBatchIfNone();
|
|
79
279
|
return;
|
|
80
280
|
}
|
|
81
281
|
if (type === "full-reload") {
|
|
@@ -84,6 +284,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
84
284
|
return original(...args);
|
|
85
285
|
}
|
|
86
286
|
heldReload = true;
|
|
287
|
+
openWatchBatchIfNone();
|
|
87
288
|
return;
|
|
88
289
|
}
|
|
89
290
|
return original(...args);
|
|
@@ -106,15 +307,67 @@ function hmrGatePlugin(options = {}) {
|
|
|
106
307
|
apply: "serve",
|
|
107
308
|
configResolved(config) {
|
|
108
309
|
transportMode = config.experimental?.bundledDev === true;
|
|
310
|
+
const rawGeneration = process.env.LOVABLE_SPAWN_GENERATION;
|
|
311
|
+
const parsed = rawGeneration === void 0 || rawGeneration === "" ? null : Number(rawGeneration);
|
|
312
|
+
generation = parsed !== null && Number.isSafeInteger(parsed) ? parsed : null;
|
|
313
|
+
},
|
|
314
|
+
transform(_code, id, options) {
|
|
315
|
+
const name = this.environment?.name ?? (options?.ssr === true ? "ssr" : "client");
|
|
316
|
+
let progress = transformsByEnv.get(name);
|
|
317
|
+
if (!progress) {
|
|
318
|
+
progress = {
|
|
319
|
+
ids: /* @__PURE__ */ new Set(),
|
|
320
|
+
lastTransformAt: 0
|
|
321
|
+
};
|
|
322
|
+
transformsByEnv.set(name, progress);
|
|
323
|
+
}
|
|
324
|
+
progress.ids.add(id);
|
|
325
|
+
progress.lastTransformAt = Date.now();
|
|
326
|
+
scheduleBootProgress();
|
|
327
|
+
if (updateBatch !== null) {
|
|
328
|
+
let batchIds = updateBatch.idsByEnv.get(name);
|
|
329
|
+
if (!batchIds) {
|
|
330
|
+
batchIds = /* @__PURE__ */ new Set();
|
|
331
|
+
updateBatch.idsByEnv.set(name, batchIds);
|
|
332
|
+
}
|
|
333
|
+
batchIds.add(id);
|
|
334
|
+
updateBatch.lastTransformAt = Date.now();
|
|
335
|
+
scheduleUpdateProgress();
|
|
336
|
+
if (updateBatch.trigger === "flush") armUpdateSettle(UPDATE_SETTLE_MS);
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
},
|
|
340
|
+
buildStart() {
|
|
341
|
+
buildErrorsByEnv.delete(hookEnvironmentName(this));
|
|
342
|
+
scheduleBootProgress();
|
|
343
|
+
},
|
|
344
|
+
buildEnd(error) {
|
|
345
|
+
const name = hookEnvironmentName(this);
|
|
346
|
+
const parsed = error == null ? null : firstBuildError(error);
|
|
347
|
+
if (parsed === null) buildErrorsByEnv.delete(name);
|
|
348
|
+
else buildErrorsByEnv.set(name, parsed);
|
|
349
|
+
scheduleBootProgress();
|
|
109
350
|
},
|
|
110
351
|
configureServer(srv) {
|
|
111
352
|
server = srv;
|
|
353
|
+
transformsByEnv.clear();
|
|
354
|
+
buildErrorsByEnv.clear();
|
|
355
|
+
lastBootProgressKey = "";
|
|
356
|
+
resetUpdateProgress();
|
|
112
357
|
srv.ws.on?.("connection", (socket) => {
|
|
113
358
|
try {
|
|
114
359
|
socket.send(JSON.stringify({
|
|
115
360
|
type: "custom",
|
|
116
361
|
event: DEV_SERVER_MODE_EVENT,
|
|
117
|
-
data: {
|
|
362
|
+
data: {
|
|
363
|
+
mode: transportMode ? "bundled" : "classic",
|
|
364
|
+
...generation !== null ? { generation } : {}
|
|
365
|
+
}
|
|
366
|
+
}));
|
|
367
|
+
socket.send(JSON.stringify({
|
|
368
|
+
type: "custom",
|
|
369
|
+
event: BOOT_PROGRESS_EVENT,
|
|
370
|
+
data: bootProgressData()
|
|
118
371
|
}));
|
|
119
372
|
} catch {}
|
|
120
373
|
});
|
|
@@ -134,6 +387,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
134
387
|
originalOn(event, (payload, client) => {
|
|
135
388
|
if (Date.now() < startupGraceUntil || initialBuildPending()) return listener(payload, client);
|
|
136
389
|
bufferedReloadNeeded.set(client, payload);
|
|
390
|
+
openWatchBatchIfNone();
|
|
137
391
|
if (reloadFailOpenTimer === null) {
|
|
138
392
|
reloadFailOpenTimer = setTimeout(() => {
|
|
139
393
|
reloadFailOpenTimer = null;
|
|
@@ -173,6 +427,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
173
427
|
const broadcastHeld = heldReload;
|
|
174
428
|
heldReload = false;
|
|
175
429
|
const reload = broadcastHeld || bufferedReloadNeeded.size > 0;
|
|
430
|
+
if (released > 0 || updates > 0 || reload) noteUpdateFlush();
|
|
176
431
|
let flushRestarts = false;
|
|
177
432
|
if (reload) {
|
|
178
433
|
const releaseDelayMs = Math.max(released > 0 ? drainMs : 0, priorDrainUntil - Date.now(), 0);
|
|
@@ -194,6 +449,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
194
449
|
restartTimer.unref?.();
|
|
195
450
|
} else if (typeof restart === "function") {
|
|
196
451
|
flushRestarts = true;
|
|
452
|
+
noteUpdateRestart();
|
|
197
453
|
bufferedReloadNeeded.clear();
|
|
198
454
|
pendingReplay.clear();
|
|
199
455
|
restartTimer = setTimeout(() => {
|
|
@@ -244,6 +500,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
244
500
|
clearTimeout(classicFailOpenTimer);
|
|
245
501
|
classicFailOpenTimer = null;
|
|
246
502
|
}
|
|
503
|
+
if (pending.size > 0) noteUpdateFlush();
|
|
247
504
|
const entries = Array.from(pending.entries());
|
|
248
505
|
pending.clear();
|
|
249
506
|
for (const [f] of entries) srv.moduleGraph.onFileChange(f);
|
|
@@ -278,6 +535,7 @@ function hmrGatePlugin(options = {}) {
|
|
|
278
535
|
pending.set(filePath, events);
|
|
279
536
|
}
|
|
280
537
|
events.add(event);
|
|
538
|
+
openWatchBatchIfNone();
|
|
281
539
|
if (classicFailOpenTimer === null) {
|
|
282
540
|
classicFailOpenTimer = setTimeout(() => {
|
|
283
541
|
classicFailOpenTimer = null;
|
|
@@ -332,6 +590,15 @@ function hmrGatePlugin(options = {}) {
|
|
|
332
590
|
clearTimeout(classicFailOpenTimer);
|
|
333
591
|
classicFailOpenTimer = null;
|
|
334
592
|
}
|
|
593
|
+
if (bootProgressTimer !== null) {
|
|
594
|
+
clearTimeout(bootProgressTimer);
|
|
595
|
+
bootProgressTimer = null;
|
|
596
|
+
}
|
|
597
|
+
transformsByEnv.clear();
|
|
598
|
+
buildErrorsByEnv.clear();
|
|
599
|
+
bootProgressSentAt = 0;
|
|
600
|
+
lastBootProgressKey = "";
|
|
601
|
+
resetUpdateProgress();
|
|
335
602
|
bufferedReloadNeeded.clear();
|
|
336
603
|
pendingReplay.clear();
|
|
337
604
|
reloadNeededListener = null;
|
|
@@ -347,4 +614,4 @@ function hmrGatePlugin(options = {}) {
|
|
|
347
614
|
};
|
|
348
615
|
}
|
|
349
616
|
//#endregion
|
|
350
|
-
export { DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, hmrGatePlugin };
|
|
617
|
+
export { BOOT_PROGRESS_EVENT, DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, UPDATE_PROGRESS_EVENT, hmrGatePlugin };
|