@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 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: { mode: transportMode ? "bundled" : "classic" }
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;
@@ -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 };
@@ -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: { mode: transportMode ? "bundled" : "classic" }
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/vite-tanstack-config",
3
- "version": "2.13.1",
3
+ "version": "2.15.0",
4
4
  "description": "Vite config wrapper for Lovable TanStack Start projects",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",