@lovable.dev/vite-tanstack-config 2.12.1 → 2.13.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.
@@ -0,0 +1,329 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_crypto = require("node:crypto");
3
+ let node_fs = require("node:fs");
4
+ //#region src/hmr-gate/index.ts
5
+ const statusCorsOriginRe = /^(?:https:\/\/(?:[^/]+\.)?lovable\.dev|https?:\/\/(?:(?:[^/:]+\.)?localhost|127\.0\.0\.1|\[::1\]))(?::\d+)?$/;
6
+ function requestPathname(req) {
7
+ const url = req.url ?? "";
8
+ const queryStart = url.indexOf("?");
9
+ return queryStart === -1 ? url : url.slice(0, queryStart);
10
+ }
11
+ function gateStatusHeaders(req) {
12
+ const origin = req.headers.origin;
13
+ const headers = {
14
+ "Content-Type": "application/json",
15
+ Vary: "Origin"
16
+ };
17
+ if (typeof origin === "string" && statusCorsOriginRe.test(origin)) headers["Access-Control-Allow-Origin"] = origin;
18
+ return headers;
19
+ }
20
+ const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
21
+ const EDIT_FREE_RELOAD_MARKER = "edit-free";
22
+ /** Host-controlled default for `fullReload`; an explicit option still wins. */
23
+ const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
24
+ function hmrGatePlugin(options = {}) {
25
+ const fullReload = options.fullReload ?? process.env["LOVABLE_HMR_FULL_RELOAD"] !== "false";
26
+ const gatedEvents = new Set(options.events ?? [
27
+ "change",
28
+ "add",
29
+ "unlink"
30
+ ]);
31
+ const passthroughPatterns = options.passthrough ?? [];
32
+ const pending = /* @__PURE__ */ new Map();
33
+ const lastFlushedHashes = /* @__PURE__ */ new Map();
34
+ let originalEmit = null;
35
+ let server = null;
36
+ let transportMode = false;
37
+ let swallowedUpdates = 0;
38
+ let heldReload = false;
39
+ let passthroughReloads = 0;
40
+ const wrappedSends = [];
41
+ const drainMs = options.drainMs ?? 1500;
42
+ const maxHoldMs = options.maxHoldMs ?? 24e4;
43
+ const startupGraceMs = options.startupGraceMs ?? 5e3;
44
+ let startupGraceUntil = 0;
45
+ const patchPathRe = /^\/hmr_patch_\d+\.js(\?|$)/;
46
+ const parked = [];
47
+ let drainUntil = 0;
48
+ let restartTimer = null;
49
+ let reloadNeededListener = null;
50
+ const bufferedReloadNeeded = /* @__PURE__ */ new Map();
51
+ const pendingReplay = /* @__PURE__ */ new Map();
52
+ let reloadFailOpenTimer = null;
53
+ function replayBufferedReloadNeeded() {
54
+ if (reloadNeededListener === null || bufferedReloadNeeded.size === 0) return;
55
+ const entries = Array.from(bufferedReloadNeeded.entries());
56
+ bufferedReloadNeeded.clear();
57
+ for (const [client, payload] of entries) reloadNeededListener(payload, client);
58
+ }
59
+ function releaseParked() {
60
+ const count = parked.length;
61
+ for (const entry of parked.splice(0)) entry.release();
62
+ return count;
63
+ }
64
+ function initialBuildPending() {
65
+ const bundled = server?.environments?.client?.bundledDev;
66
+ return bundled !== void 0 && bundled.initialBuildCompleted !== true;
67
+ }
68
+ function gateSendTarget(target) {
69
+ const original = target.send.bind(target);
70
+ wrappedSends.push({
71
+ target,
72
+ original
73
+ });
74
+ target.send = (...args) => {
75
+ const frame = args[0];
76
+ const type = frame?.type;
77
+ if (type === "update") {
78
+ swallowedUpdates++;
79
+ return;
80
+ }
81
+ if (type === "full-reload") {
82
+ if (frame?.ifFallback === true || frame?.lovable?.hmrGate === EDIT_FREE_RELOAD_MARKER || Date.now() < startupGraceUntil || initialBuildPending()) {
83
+ passthroughReloads++;
84
+ return original(...args);
85
+ }
86
+ heldReload = true;
87
+ return;
88
+ }
89
+ return original(...args);
90
+ };
91
+ }
92
+ function fileContentHash(filePath) {
93
+ try {
94
+ return (0, node_crypto.createHash)("sha1").update((0, node_fs.readFileSync)(filePath)).digest("hex");
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ function isPassthrough(filePath) {
100
+ if (typeof filePath !== "string") return true;
101
+ if (filePath.includes("node_modules/.vite")) return true;
102
+ return passthroughPatterns.some((p) => filePath.includes(p));
103
+ }
104
+ return {
105
+ name: "hmr-gate",
106
+ apply: "serve",
107
+ configResolved(config) {
108
+ transportMode = config.experimental?.bundledDev === true;
109
+ },
110
+ configureServer(srv) {
111
+ server = srv;
112
+ srv.ws.on?.("connection", (socket) => {
113
+ try {
114
+ socket.send(JSON.stringify({
115
+ type: "custom",
116
+ event: DEV_SERVER_MODE_EVENT,
117
+ data: { mode: transportMode ? "bundled" : "classic" }
118
+ }));
119
+ } catch {}
120
+ });
121
+ if (transportMode) {
122
+ const startedAtMs = Date.now();
123
+ startupGraceUntil = startedAtMs + startupGraceMs;
124
+ const ws = srv.ws;
125
+ const hot = srv.environments?.client?.hot;
126
+ const target = typeof ws?.send === "function" ? ws : hot;
127
+ if (target && typeof target.send === "function") gateSendTarget(target);
128
+ const hotChannel = hot;
129
+ if (hotChannel && typeof hotChannel.on === "function") {
130
+ const originalOn = hotChannel.on.bind(hotChannel);
131
+ hotChannel.on = (event, listener) => {
132
+ if (event !== "vite:bundled-dev:reload-needed") return originalOn(event, listener);
133
+ reloadNeededListener = listener;
134
+ originalOn(event, (payload, client) => {
135
+ if (Date.now() < startupGraceUntil || initialBuildPending()) return listener(payload, client);
136
+ bufferedReloadNeeded.set(client, payload);
137
+ if (reloadFailOpenTimer === null) {
138
+ reloadFailOpenTimer = setTimeout(() => {
139
+ reloadFailOpenTimer = null;
140
+ replayBufferedReloadNeeded();
141
+ }, maxHoldMs);
142
+ reloadFailOpenTimer.unref?.();
143
+ }
144
+ });
145
+ };
146
+ }
147
+ srv.middlewares.use((req, _res, next) => {
148
+ if (req.method === "GET" && patchPathRe.test(req.url ?? "") && Date.now() >= drainUntil) {
149
+ let released = false;
150
+ const release = () => {
151
+ if (released) return;
152
+ released = true;
153
+ clearTimeout(timer);
154
+ const index = parked.indexOf(entry);
155
+ if (index !== -1) parked.splice(index, 1);
156
+ next();
157
+ };
158
+ const timer = setTimeout(release, maxHoldMs);
159
+ timer.unref?.();
160
+ const entry = { release };
161
+ parked.push(entry);
162
+ return;
163
+ }
164
+ next();
165
+ });
166
+ srv.middlewares.use((req, res, next) => {
167
+ if (req.method !== "POST" || requestPathname(req) !== "/__hmr_flush") return next();
168
+ const priorDrainUntil = drainUntil;
169
+ const released = releaseParked();
170
+ drainUntil = Date.now() + drainMs;
171
+ const updates = swallowedUpdates;
172
+ swallowedUpdates = 0;
173
+ const broadcastHeld = heldReload;
174
+ heldReload = false;
175
+ const reload = broadcastHeld || bufferedReloadNeeded.size > 0;
176
+ let flushRestarts = false;
177
+ if (reload) {
178
+ const releaseDelayMs = Math.max(released > 0 ? drainMs : 0, priorDrainUntil - Date.now(), 0);
179
+ const restart = srv.restart;
180
+ if (restartTimer !== null) clearTimeout(restartTimer);
181
+ if (reloadFailOpenTimer !== null) {
182
+ clearTimeout(reloadFailOpenTimer);
183
+ reloadFailOpenTimer = null;
184
+ }
185
+ if (!broadcastHeld && bufferedReloadNeeded.size > 0 && reloadNeededListener !== null) {
186
+ for (const [client, payload] of bufferedReloadNeeded) pendingReplay.set(client, payload);
187
+ bufferedReloadNeeded.clear();
188
+ restartTimer = setTimeout(() => {
189
+ restartTimer = null;
190
+ const entries = Array.from(pendingReplay.entries());
191
+ pendingReplay.clear();
192
+ for (const [client, payload] of entries) reloadNeededListener?.(payload, client);
193
+ }, releaseDelayMs);
194
+ restartTimer.unref?.();
195
+ } else if (typeof restart === "function") {
196
+ flushRestarts = true;
197
+ bufferedReloadNeeded.clear();
198
+ pendingReplay.clear();
199
+ restartTimer = setTimeout(() => {
200
+ restartTimer = null;
201
+ restart.call(srv).catch(() => {
202
+ if (wrappedSends.length > 0) wrappedSends[0].original({ type: "full-reload" });
203
+ });
204
+ }, releaseDelayMs);
205
+ restartTimer.unref?.();
206
+ } else if (wrappedSends.length > 0) {
207
+ bufferedReloadNeeded.clear();
208
+ pendingReplay.clear();
209
+ wrappedSends[0].original({ type: "full-reload" });
210
+ }
211
+ }
212
+ const body = JSON.stringify({
213
+ flushed: [],
214
+ count: released + updates,
215
+ released,
216
+ reload,
217
+ restartsOnReload: flushRestarts,
218
+ mode: "transport-patch-hold"
219
+ });
220
+ res.writeHead(200, { "Content-Type": "application/json" });
221
+ res.end(body);
222
+ });
223
+ srv.middlewares.use((req, res, next) => {
224
+ if (req.method !== "GET" || requestPathname(req) !== "/__hmr_gate") return next();
225
+ const body = JSON.stringify({
226
+ enabled: true,
227
+ pending: {},
228
+ count: parked.length + swallowedUpdates + (heldReload || bufferedReloadNeeded.size > 0 ? 1 : 0),
229
+ heldReload: heldReload || bufferedReloadNeeded.size > 0,
230
+ restartsOnReload: reloadNeededListener === null || heldReload,
231
+ startedAt: startedAtMs,
232
+ passthroughReloads,
233
+ mode: "transport-patch-hold"
234
+ });
235
+ res.writeHead(200, gateStatusHeaders(req));
236
+ res.end(body);
237
+ });
238
+ return;
239
+ }
240
+ const _emit = srv.watcher.emit.bind(srv.watcher);
241
+ originalEmit = _emit;
242
+ srv.watcher.emit = ((event, ...args) => {
243
+ if (typeof event === "string" && gatedEvents.has(event) && !isPassthrough(args[0])) {
244
+ const filePath = args[0];
245
+ if (event === "change" || event === "add") {
246
+ const hash = fileContentHash(filePath);
247
+ if (hash !== null && hash === lastFlushedHashes.get(filePath)) {
248
+ pending.delete(filePath);
249
+ return false;
250
+ }
251
+ }
252
+ let events = pending.get(filePath);
253
+ if (!events) {
254
+ events = /* @__PURE__ */ new Set();
255
+ pending.set(filePath, events);
256
+ }
257
+ events.add(event);
258
+ return false;
259
+ }
260
+ return _emit(event, ...args);
261
+ });
262
+ srv.middlewares.use((req, res, next) => {
263
+ if (req.method !== "POST" || requestPathname(req) !== "/__hmr_flush") return next();
264
+ const entries = Array.from(pending.entries());
265
+ pending.clear();
266
+ for (const [f] of entries) srv.moduleGraph.onFileChange(f);
267
+ if (entries.length > 0) {
268
+ for (const [f, events] of entries) {
269
+ for (const event of events) _emit(event, f);
270
+ const hash = fileContentHash(f);
271
+ if (hash === null) lastFlushedHashes.delete(f);
272
+ else lastFlushedHashes.set(f, hash);
273
+ }
274
+ if (fullReload) srv.ws.send({ type: "full-reload" });
275
+ }
276
+ const files = entries.map(([f]) => f);
277
+ const body = JSON.stringify({
278
+ flushed: files,
279
+ count: entries.length,
280
+ mode: fullReload ? "full-reload" : "granular"
281
+ });
282
+ res.writeHead(200, { "Content-Type": "application/json" });
283
+ res.end(body);
284
+ });
285
+ srv.middlewares.use((req, res, next) => {
286
+ if (req.method !== "GET" || requestPathname(req) !== "/__hmr_gate") return next();
287
+ const serialized = {};
288
+ for (const [f, events] of pending) serialized[f] = Array.from(events);
289
+ const body = JSON.stringify({
290
+ enabled: true,
291
+ pending: serialized,
292
+ count: pending.size,
293
+ mode: fullReload ? "full-reload" : "granular"
294
+ });
295
+ res.writeHead(200, gateStatusHeaders(req));
296
+ res.end(body);
297
+ });
298
+ },
299
+ closeWatcher() {
300
+ if (server && originalEmit) server.watcher.emit = originalEmit;
301
+ for (const { target, original } of wrappedSends) target.send = original;
302
+ wrappedSends.length = 0;
303
+ if (restartTimer !== null) {
304
+ clearTimeout(restartTimer);
305
+ restartTimer = null;
306
+ }
307
+ releaseParked();
308
+ if (reloadFailOpenTimer !== null) {
309
+ clearTimeout(reloadFailOpenTimer);
310
+ reloadFailOpenTimer = null;
311
+ }
312
+ bufferedReloadNeeded.clear();
313
+ pendingReplay.clear();
314
+ reloadNeededListener = null;
315
+ swallowedUpdates = 0;
316
+ heldReload = false;
317
+ passthroughReloads = 0;
318
+ drainUntil = 0;
319
+ pending.clear();
320
+ lastFlushedHashes.clear();
321
+ server = null;
322
+ originalEmit = null;
323
+ }
324
+ };
325
+ }
326
+ //#endregion
327
+ exports.DEV_SERVER_MODE_EVENT = DEV_SERVER_MODE_EVENT;
328
+ exports.FULL_RELOAD_ENV_VAR = FULL_RELOAD_ENV_VAR;
329
+ exports.hmrGatePlugin = hmrGatePlugin;
@@ -0,0 +1,27 @@
1
+ import { Plugin } from "vite";
2
+ //#region src/hmr-gate/index.d.ts
3
+ declare const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
4
+ /** Host-controlled default for `fullReload`; an explicit option still wins. */
5
+ declare const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
6
+ interface HmrGateOptions {
7
+ /** Use full-reload (default) vs granular per-module HMR. Unset reads
8
+ * `LOVABLE_HMR_FULL_RELOAD`, where only "false" selects granular. */
9
+ fullReload?: boolean;
10
+ /** Bundled dev only: ms the gate stays open after a flush so the client's
11
+ * queued patch fetches drain. Default 1500. */
12
+ drainMs?: number;
13
+ /** Bundled dev only: max ms a patch response is parked before it
14
+ * fail-opens. Default 240000. */
15
+ maxHoldMs?: number;
16
+ /** Bundled dev only: ms after server start during which full-reload frames
17
+ * (Vite's restart resync) pass through live. Default 5000. */
18
+ startupGraceMs?: number;
19
+ /** Watcher events to intercept. Default: all three — a freeze must catch adds and
20
+ * deletes, not just content changes (e.g. atomic writes / synced files surface as add/unlink). */
21
+ events?: ("change" | "add" | "unlink")[];
22
+ /** File path substrings that bypass the gate (always emitted immediately). */
23
+ passthrough?: string[];
24
+ }
25
+ declare function hmrGatePlugin(options?: HmrGateOptions): Plugin;
26
+ //#endregion
27
+ export { DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, HmrGateOptions, hmrGatePlugin };
@@ -0,0 +1,27 @@
1
+ import { Plugin } from "vite";
2
+ //#region src/hmr-gate/index.d.ts
3
+ declare const DEV_SERVER_MODE_EVENT = "lovable:dev-server-mode";
4
+ /** Host-controlled default for `fullReload`; an explicit option still wins. */
5
+ declare const FULL_RELOAD_ENV_VAR = "LOVABLE_HMR_FULL_RELOAD";
6
+ interface HmrGateOptions {
7
+ /** Use full-reload (default) vs granular per-module HMR. Unset reads
8
+ * `LOVABLE_HMR_FULL_RELOAD`, where only "false" selects granular. */
9
+ fullReload?: boolean;
10
+ /** Bundled dev only: ms the gate stays open after a flush so the client's
11
+ * queued patch fetches drain. Default 1500. */
12
+ drainMs?: number;
13
+ /** Bundled dev only: max ms a patch response is parked before it
14
+ * fail-opens. Default 240000. */
15
+ maxHoldMs?: number;
16
+ /** Bundled dev only: ms after server start during which full-reload frames
17
+ * (Vite's restart resync) pass through live. Default 5000. */
18
+ startupGraceMs?: number;
19
+ /** Watcher events to intercept. Default: all three — a freeze must catch adds and
20
+ * deletes, not just content changes (e.g. atomic writes / synced files surface as add/unlink). */
21
+ events?: ("change" | "add" | "unlink")[];
22
+ /** File path substrings that bypass the gate (always emitted immediately). */
23
+ passthrough?: string[];
24
+ }
25
+ declare function hmrGatePlugin(options?: HmrGateOptions): Plugin;
26
+ //#endregion
27
+ export { DEV_SERVER_MODE_EVENT, FULL_RELOAD_ENV_VAR, HmrGateOptions, hmrGatePlugin };