isotone 1.0.9 → 1.1.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/AGENTS.md +6 -2
- package/CHANGELOG.md +15 -0
- package/README.md +5 -1
- package/dist/{chunk-MXGHTQB2.js → chunk-B3JNZITE.js} +2 -2
- package/dist/{chunk-MXGHTQB2.js.map → chunk-B3JNZITE.js.map} +1 -1
- package/dist/{chunk-JGIX4XEH.js → chunk-FKEKBZ3E.js} +16 -3
- package/dist/chunk-FKEKBZ3E.js.map +1 -0
- package/dist/{chunk-OSETWKGE.js → chunk-PUXQGUMQ.js} +18 -4
- package/dist/chunk-PUXQGUMQ.js.map +1 -0
- package/dist/{chunk-UT2NLGBM.js → chunk-TGAU3WTF.js} +27 -5
- package/dist/chunk-TGAU3WTF.js.map +1 -0
- package/dist/cli.js +14 -9
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +20 -5
- package/dist/core.js +2 -2
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +10 -10
- package/dist/serve.js +1 -1
- package/dist/server.js +2 -2
- package/dist/vite-plugin.d.ts +8 -0
- package/dist/vite-plugin.js +712 -35
- package/dist/vite-plugin.js.map +1 -1
- package/docs/assets/preview.png +0 -0
- package/docs/variants.md +67 -5
- package/package.json +2 -1
- package/dist/chunk-JGIX4XEH.js.map +0 -1
- package/dist/chunk-OSETWKGE.js.map +0 -1
- package/dist/chunk-UT2NLGBM.js.map +0 -1
package/dist/vite-plugin.js
CHANGED
|
@@ -12,17 +12,20 @@ import {
|
|
|
12
12
|
overrideSourcePath,
|
|
13
13
|
readActive,
|
|
14
14
|
readVariant,
|
|
15
|
+
readVariants,
|
|
15
16
|
resolveOverride,
|
|
17
|
+
variantDir,
|
|
16
18
|
variantsDir,
|
|
17
19
|
variantsResponse
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-TGAU3WTF.js";
|
|
19
21
|
import {
|
|
20
|
-
VARIANTS_ENDPOINT
|
|
21
|
-
|
|
22
|
+
VARIANTS_ENDPOINT,
|
|
23
|
+
overlayGroups
|
|
24
|
+
} from "./chunk-PUXQGUMQ.js";
|
|
22
25
|
import {
|
|
23
26
|
UPDATE_ENDPOINT,
|
|
24
27
|
startUpdateCheck
|
|
25
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-B3JNZITE.js";
|
|
26
29
|
import {
|
|
27
30
|
NOTES_ENDPOINT,
|
|
28
31
|
ensureDirIgnored,
|
|
@@ -34,12 +37,445 @@ import "./chunk-A4DX2V4C.js";
|
|
|
34
37
|
// src/vite/index.ts
|
|
35
38
|
import { existsSync, mkdirSync, readFileSync, realpathSync } from "fs";
|
|
36
39
|
import { dirname, isAbsolute, join, relative, resolve } from "path";
|
|
37
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
normalizePath
|
|
42
|
+
} from "vite";
|
|
43
|
+
|
|
44
|
+
// src/vite/exports.ts
|
|
45
|
+
var ROUTE_EXPORTS = /* @__PURE__ */ new Set([
|
|
46
|
+
"loader",
|
|
47
|
+
"action",
|
|
48
|
+
"clientLoader",
|
|
49
|
+
"clientAction",
|
|
50
|
+
"headers",
|
|
51
|
+
"links",
|
|
52
|
+
"meta",
|
|
53
|
+
"handle",
|
|
54
|
+
"shouldRevalidate",
|
|
55
|
+
"middleware",
|
|
56
|
+
"clientMiddleware",
|
|
57
|
+
"unstable_middleware",
|
|
58
|
+
"unstable_clientMiddleware",
|
|
59
|
+
"HydrateFallback"
|
|
60
|
+
]);
|
|
61
|
+
var IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
62
|
+
function scanExports(code) {
|
|
63
|
+
const names = /* @__PURE__ */ new Set();
|
|
64
|
+
const lines = code.split("\n");
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const line = (lines[i] ?? "").replace(/^(?:\/\*.*?\*\/\s*)+/, "");
|
|
67
|
+
if (!line.startsWith("export")) continue;
|
|
68
|
+
const rest = line.slice("export".length);
|
|
69
|
+
if (rest !== "" && !/^[\s{*]/.test(rest)) continue;
|
|
70
|
+
const body = rest.trim();
|
|
71
|
+
if (body.startsWith("{") || body.startsWith("type {")) {
|
|
72
|
+
if (body.startsWith("type")) continue;
|
|
73
|
+
let clause = body;
|
|
74
|
+
while (!clause.includes("}") && i + 1 < lines.length) clause += ` ${lines[++i]}`;
|
|
75
|
+
const inner = clause.slice(clause.indexOf("{") + 1, clause.indexOf("}"));
|
|
76
|
+
for (const raw of inner.split(",")) {
|
|
77
|
+
const spec = raw.replace(/\/\/.*$/, "").trim();
|
|
78
|
+
if (!spec || spec.startsWith("type ")) continue;
|
|
79
|
+
const as = spec.split(/\s+as\s+/);
|
|
80
|
+
const name = (as[1] ?? as[0] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
81
|
+
if (!name) return null;
|
|
82
|
+
names.add(name);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (/^type\s*\*/.test(body)) continue;
|
|
87
|
+
const star = body.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)/);
|
|
88
|
+
if (star?.[1]) {
|
|
89
|
+
names.add(star[1]);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (body.startsWith("*")) return null;
|
|
93
|
+
if (/^default\b/.test(body)) {
|
|
94
|
+
names.add("default");
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (/^(?:declare\s|type\s|interface\s)/.test(body)) continue;
|
|
98
|
+
if (/^(?:=|import\s|namespace\s|module\s|as\s)/.test(body)) return null;
|
|
99
|
+
const decl = body.match(
|
|
100
|
+
/^(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)|^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)|^(?:const\s+)?enum\s+([A-Za-z_$][\w$]*)/
|
|
101
|
+
);
|
|
102
|
+
const declared = decl?.[1] ?? decl?.[2] ?? decl?.[3];
|
|
103
|
+
if (declared) {
|
|
104
|
+
names.add(declared);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const variable = body.match(/^(?:const|let|var)\s+(.*)$/);
|
|
108
|
+
if (variable) {
|
|
109
|
+
const target = (variable[1] ?? "").trim();
|
|
110
|
+
if (target.startsWith("{") || target.startsWith("[")) return null;
|
|
111
|
+
const name = target.match(/^([A-Za-z_$][\w$]*)/)?.[1];
|
|
112
|
+
if (!name) return null;
|
|
113
|
+
if (hasSecondDeclarator(target)) return null;
|
|
114
|
+
names.add(name);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
for (const name of names) if (name !== "default" && !IDENT.test(name)) return null;
|
|
120
|
+
return [...names];
|
|
121
|
+
}
|
|
122
|
+
function isRouteModule(names) {
|
|
123
|
+
return names.includes("default") && names.some((name) => ROUTE_EXPORTS.has(name));
|
|
124
|
+
}
|
|
125
|
+
function hasSecondDeclarator(declaration) {
|
|
126
|
+
const text = declaration.replace(/\/\/.*$/, "").replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/g, '""');
|
|
127
|
+
let depth = 0;
|
|
128
|
+
let initialized = false;
|
|
129
|
+
for (let i = 0; i < text.length; i++) {
|
|
130
|
+
const ch = text[i];
|
|
131
|
+
if (ch === "(" || ch === "[" || ch === "{" || ch === "<") depth++;
|
|
132
|
+
else if (ch === ")" || ch === "]" || ch === "}" || ch === ">" && text[i - 1] !== "=") depth = Math.max(0, depth - 1);
|
|
133
|
+
else if (depth === 0 && ch === "=" && text[i + 1] !== "=" && text[i + 1] !== ">" && text[i - 1] !== "=") initialized = true;
|
|
134
|
+
else if (depth === 0 && ch === "," && initialized) return true;
|
|
135
|
+
}
|
|
136
|
+
return depth === 0 && /,\s*$/.test(text);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/vite/live.ts
|
|
140
|
+
var VERSION_QUERY = "isotone";
|
|
141
|
+
var BASE_VERSION = "_base";
|
|
142
|
+
var LIVE_MODULE = "virtual:isotone/live";
|
|
143
|
+
var RESOLVED_LIVE_MODULE = "\0isotone-live";
|
|
144
|
+
var ACTIVE_EVENT = "isotone:active";
|
|
145
|
+
function liveStore(win, cfg) {
|
|
146
|
+
if (win.__isotoneLive && win.__isotoneLive.version === 1) return win.__isotoneLive;
|
|
147
|
+
const PARAM = "v";
|
|
148
|
+
const DEFAULT = "default";
|
|
149
|
+
const groups = cfg.groups;
|
|
150
|
+
const byId = new Map(groups.map((group) => [group.id, group]));
|
|
151
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
152
|
+
const files = /* @__PURE__ */ new Map();
|
|
153
|
+
const history = win.history;
|
|
154
|
+
const replace = history.replaceState;
|
|
155
|
+
const push = history.pushState;
|
|
156
|
+
const decode = (text) => {
|
|
157
|
+
try {
|
|
158
|
+
return decodeURIComponent(text.replace(/\+/g, " "));
|
|
159
|
+
} catch {
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const readParam = (search2) => {
|
|
164
|
+
for (const part of search2.replace(/^\?/, "").split("&")) {
|
|
165
|
+
const at = part.indexOf("=");
|
|
166
|
+
const key = at < 0 ? part : part.slice(0, at);
|
|
167
|
+
if (decode(key) === PARAM) return decode(at < 0 ? "" : part.slice(at + 1));
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
};
|
|
171
|
+
const parse = (search2) => {
|
|
172
|
+
const raw = readParam(search2);
|
|
173
|
+
if (raw === null) return { picks: null, foreign: [] };
|
|
174
|
+
const foreign = [];
|
|
175
|
+
const picks = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const pair of raw.split(",")) {
|
|
177
|
+
if (!pair) continue;
|
|
178
|
+
const at = pair.indexOf(":");
|
|
179
|
+
const group = at < 0 ? void 0 : byId.get(pair.slice(0, at));
|
|
180
|
+
const value = at < 0 ? "" : pair.slice(at + 1);
|
|
181
|
+
if (group && (value === DEFAULT || group.members.includes(value))) picks.set(group.id, value);
|
|
182
|
+
else foreign.push(pair);
|
|
183
|
+
}
|
|
184
|
+
if (picks.size === 0) return { picks: null, foreign };
|
|
185
|
+
return { picks: [...picks.values()].filter((value) => value !== DEFAULT), foreign };
|
|
186
|
+
};
|
|
187
|
+
const search = (set) => {
|
|
188
|
+
const { foreign } = parse(win.location.search);
|
|
189
|
+
const mine = [];
|
|
190
|
+
for (const group of groups) {
|
|
191
|
+
const pick = group.members.find((member) => set.includes(member));
|
|
192
|
+
if (pick) mine.push(`${group.id}:${pick}`);
|
|
193
|
+
}
|
|
194
|
+
const value = [...foreign, ...mine].join(",");
|
|
195
|
+
const kept = win.location.search.replace(/^\?/, "").split("&").filter((part) => {
|
|
196
|
+
if (!part) return false;
|
|
197
|
+
const at = part.indexOf("=");
|
|
198
|
+
return decode(at < 0 ? part : part.slice(0, at)) !== PARAM;
|
|
199
|
+
});
|
|
200
|
+
if (value) kept.push(`${PARAM}=${encodeURIComponent(value).replace(/%3A/gi, ":").replace(/%2C/gi, ",")}`);
|
|
201
|
+
return kept.length ? `?${kept.join("&")}` : "";
|
|
202
|
+
};
|
|
203
|
+
const writeUrl = (set) => {
|
|
204
|
+
const next = `${win.location.pathname}${search(set)}${win.location.hash}`;
|
|
205
|
+
if (next !== `${win.location.pathname}${win.location.search}${win.location.hash}`) {
|
|
206
|
+
replace.call(history, history.state, "", next);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
const fromUrl = parse(win.location.search).picks;
|
|
210
|
+
const server = cfg.active.slice();
|
|
211
|
+
let active = fromUrl ?? server.slice();
|
|
212
|
+
let explicit = fromUrl !== null;
|
|
213
|
+
let reloading = false;
|
|
214
|
+
const versionOf = (ids, set) => {
|
|
215
|
+
for (const id of set) if (ids.includes(id)) return id;
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
const needsReload = (from, to) => {
|
|
219
|
+
for (const file of files.values()) {
|
|
220
|
+
if (file.pinned && versionOf(file.ids, from) !== versionOf(file.ids, to)) return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
};
|
|
224
|
+
const emit = () => {
|
|
225
|
+
for (const listener of listeners) listener();
|
|
226
|
+
};
|
|
227
|
+
const reload = () => {
|
|
228
|
+
reloading = true;
|
|
229
|
+
win.location.reload();
|
|
230
|
+
};
|
|
231
|
+
let syncing = false;
|
|
232
|
+
const freshLoad = () => {
|
|
233
|
+
try {
|
|
234
|
+
const entry = win.performance.getEntriesByType("navigation")[0];
|
|
235
|
+
if (entry?.type) return entry.type !== "reload";
|
|
236
|
+
return win.performance.navigation?.type !== 1;
|
|
237
|
+
} catch {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
const syncServer = () => {
|
|
242
|
+
if (syncing || !freshLoad()) return;
|
|
243
|
+
syncing = true;
|
|
244
|
+
const key = "isotone:live-sync";
|
|
245
|
+
const target = JSON.stringify(active);
|
|
246
|
+
try {
|
|
247
|
+
const last = JSON.parse(win.sessionStorage.getItem(key) ?? "null");
|
|
248
|
+
if (last && last.target === target && Date.now() - last.at < 1e4) return;
|
|
249
|
+
win.sessionStorage.setItem(key, JSON.stringify({ target, at: Date.now() }));
|
|
250
|
+
} catch {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const send = win.__isotoneFetch ?? win.fetch.bind(win);
|
|
254
|
+
send(`${cfg.endpoint}/active`, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: { "Content-Type": "application/json" },
|
|
257
|
+
body: JSON.stringify({ active })
|
|
258
|
+
}).then((res) => {
|
|
259
|
+
if (res.ok) reload();
|
|
260
|
+
}, () => {
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
const keep = (original) => function(...args) {
|
|
264
|
+
const result = original.apply(this, args);
|
|
265
|
+
if (explicit && parse(win.location.search).picks === null) writeUrl(active);
|
|
266
|
+
return result;
|
|
267
|
+
};
|
|
268
|
+
history.pushState = keep(push);
|
|
269
|
+
history.replaceState = keep(replace);
|
|
270
|
+
win.addEventListener("popstate", () => {
|
|
271
|
+
if (explicit) writeUrl(active);
|
|
272
|
+
});
|
|
273
|
+
const store = {
|
|
274
|
+
version: 1,
|
|
275
|
+
get active() {
|
|
276
|
+
return active;
|
|
277
|
+
},
|
|
278
|
+
get server() {
|
|
279
|
+
return server;
|
|
280
|
+
},
|
|
281
|
+
get explicit() {
|
|
282
|
+
return explicit;
|
|
283
|
+
},
|
|
284
|
+
subscribe(listener) {
|
|
285
|
+
listeners.add(listener);
|
|
286
|
+
return () => {
|
|
287
|
+
listeners.delete(listener);
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
register(key, ids, pinned) {
|
|
291
|
+
files.set(key, { ids, pinned });
|
|
292
|
+
if (cfg.ssr && pinned && win.document.readyState !== "complete" && versionOf(ids, active) !== versionOf(ids, server)) {
|
|
293
|
+
queueMicrotask(syncServer);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
needsReload(next) {
|
|
297
|
+
return needsReload(active, next);
|
|
298
|
+
},
|
|
299
|
+
select(next) {
|
|
300
|
+
if (reloading) return true;
|
|
301
|
+
const reloadNeeded = needsReload(active, next);
|
|
302
|
+
active = next.slice();
|
|
303
|
+
explicit = next.length > 0;
|
|
304
|
+
writeUrl(active);
|
|
305
|
+
if (reloadNeeded) return true;
|
|
306
|
+
emit();
|
|
307
|
+
return false;
|
|
308
|
+
},
|
|
309
|
+
follow(next) {
|
|
310
|
+
if (explicit || reloading) return;
|
|
311
|
+
const same = next.length === active.length && next.every((id) => active.includes(id));
|
|
312
|
+
if (same) return;
|
|
313
|
+
const reloadNeeded = needsReload(active, next);
|
|
314
|
+
active = next.slice();
|
|
315
|
+
if (reloadNeeded) reload();
|
|
316
|
+
else emit();
|
|
317
|
+
},
|
|
318
|
+
reload
|
|
319
|
+
};
|
|
320
|
+
win.__isotoneLive = store;
|
|
321
|
+
return store;
|
|
322
|
+
}
|
|
323
|
+
function liveBinder(React, store) {
|
|
324
|
+
const MEMO = /* @__PURE__ */ Symbol.for("react.memo");
|
|
325
|
+
const FORWARD = /* @__PURE__ */ Symbol.for("react.forward_ref");
|
|
326
|
+
const LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
|
|
327
|
+
const refAsProp = Number.parseInt(React.version, 10) >= 19;
|
|
328
|
+
const BUILTIN = /* @__PURE__ */ new Set([
|
|
329
|
+
"length",
|
|
330
|
+
"name",
|
|
331
|
+
"prototype",
|
|
332
|
+
"caller",
|
|
333
|
+
"arguments",
|
|
334
|
+
"displayName",
|
|
335
|
+
"$$typeof",
|
|
336
|
+
"render",
|
|
337
|
+
"type",
|
|
338
|
+
"compare",
|
|
339
|
+
"_payload",
|
|
340
|
+
"_init"
|
|
341
|
+
]);
|
|
342
|
+
const isClass = (value) => typeof value === "function" && /^class[\s{]/.test(Function.prototype.toString.call(value));
|
|
343
|
+
const isComponent = (name, value, jsx) => {
|
|
344
|
+
if (typeof value === "function") {
|
|
345
|
+
const proto = value.prototype;
|
|
346
|
+
if (proto?.isReactComponent) return true;
|
|
347
|
+
if (isClass(value)) return false;
|
|
348
|
+
if (name !== "default") return /^[A-Z]/.test(name);
|
|
349
|
+
const own = value.name ?? "";
|
|
350
|
+
return /^[A-Z]/.test(own) || jsx && (own === "" || own === "default");
|
|
351
|
+
}
|
|
352
|
+
if (!value || typeof value !== "object") return false;
|
|
353
|
+
const tag = value.$$typeof;
|
|
354
|
+
return tag === MEMO || tag === FORWARD || tag === LAZY;
|
|
355
|
+
};
|
|
356
|
+
return {
|
|
357
|
+
file(key, ids, modules, names) {
|
|
358
|
+
const pick = (set) => {
|
|
359
|
+
for (const id of set) {
|
|
360
|
+
const hit = ids.indexOf(id);
|
|
361
|
+
if (hit >= 0) return hit;
|
|
362
|
+
}
|
|
363
|
+
const base = ids.indexOf(null);
|
|
364
|
+
return base >= 0 ? base : 0;
|
|
365
|
+
};
|
|
366
|
+
const out = {};
|
|
367
|
+
const renders = /* @__PURE__ */ new Map();
|
|
368
|
+
const jsx = /x$/.test(key);
|
|
369
|
+
let pinned = false;
|
|
370
|
+
for (const name of names) {
|
|
371
|
+
const values = modules.map((mod) => mod[name]);
|
|
372
|
+
const first = values[0];
|
|
373
|
+
if (values.every((value) => value === first)) {
|
|
374
|
+
out[name] = first;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const defined = values.filter((value) => value !== void 0);
|
|
378
|
+
if (defined.length > 0 && defined.every((value) => isComponent(name, value, jsx))) {
|
|
379
|
+
const render = (props, ref) => {
|
|
380
|
+
const index = React.useSyncExternalStore(
|
|
381
|
+
store.subscribe,
|
|
382
|
+
() => pick(store.active),
|
|
383
|
+
() => pick(store.server)
|
|
384
|
+
);
|
|
385
|
+
const component = values[index];
|
|
386
|
+
if (component == null) return null;
|
|
387
|
+
return React.createElement(component, ref == null ? props : { ...props, ref });
|
|
388
|
+
};
|
|
389
|
+
renders.set(name, render);
|
|
390
|
+
const wrapper = refAsProp ? (props) => render(props) : React.forwardRef(render);
|
|
391
|
+
const label = name === "default" ? defined[0].displayName ?? defined[0].name ?? "Variant" : name;
|
|
392
|
+
if (typeof wrapper === "function") Object.defineProperty(wrapper, "name", { value: label });
|
|
393
|
+
wrapper.displayName = label;
|
|
394
|
+
const statics = /* @__PURE__ */ new Set();
|
|
395
|
+
for (const value of defined) {
|
|
396
|
+
for (const prop of Object.getOwnPropertyNames(value)) if (!BUILTIN.has(prop)) statics.add(prop);
|
|
397
|
+
}
|
|
398
|
+
for (const prop of statics) {
|
|
399
|
+
if (prop in wrapper && !Object.getOwnPropertyDescriptor(wrapper, prop)?.configurable) continue;
|
|
400
|
+
Object.defineProperty(wrapper, prop, {
|
|
401
|
+
configurable: true,
|
|
402
|
+
enumerable: true,
|
|
403
|
+
get: () => values[pick(store.active)]?.[prop]
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
out[name] = wrapper;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
pinned = true;
|
|
410
|
+
out[name] = values[pick(store.active)];
|
|
411
|
+
}
|
|
412
|
+
store.register(key, ids, pinned);
|
|
413
|
+
Object.defineProperty(out, "__isotoneRender", {
|
|
414
|
+
value: (name, props, ref) => {
|
|
415
|
+
const render = renders.get(name);
|
|
416
|
+
if (render) return render(props, ref);
|
|
417
|
+
const value = out[name];
|
|
418
|
+
return typeof value === "function" || value && typeof value === "object" ? React.createElement(value, ref == null ? props : { ...props, ref }) : null;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return out;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function liveStoreScript(cfg) {
|
|
426
|
+
return `(${liveStore.toString()})(window, ${safeJson(cfg)});`;
|
|
427
|
+
}
|
|
428
|
+
function liveRuntimeModule(fallback) {
|
|
429
|
+
return [
|
|
430
|
+
`import * as React from 'react';`,
|
|
431
|
+
`const store = (${liveStore.toString()})(window, ${safeJson(fallback)});`,
|
|
432
|
+
`export const live = (${liveBinder.toString()})(React, store);`,
|
|
433
|
+
`if (import.meta.hot) import.meta.hot.on(${JSON.stringify(ACTIVE_EVENT)}, (data) => store.follow(data.active));`,
|
|
434
|
+
""
|
|
435
|
+
].join("\n");
|
|
436
|
+
}
|
|
437
|
+
function dispatcherModule(key, basename, versions, names) {
|
|
438
|
+
const lines = [`import { live as __isotone } from ${JSON.stringify(LIVE_MODULE)};`];
|
|
439
|
+
versions.forEach((id, i) => {
|
|
440
|
+
const spec = `./${basename}?${VERSION_QUERY}=${id ?? BASE_VERSION}`;
|
|
441
|
+
lines.push(`import * as __v${i} from ${JSON.stringify(spec)};`);
|
|
442
|
+
});
|
|
443
|
+
const hoisted = (name) => name === "default" || /^[A-Z][\w$]*$/.test(name);
|
|
444
|
+
const local = (name) => name === "default" ? "__isotoneDefault" : name;
|
|
445
|
+
for (const name of names.filter(hoisted)) {
|
|
446
|
+
const head = name === "default" ? "export default function __isotoneDefault" : `export function ${name}`;
|
|
447
|
+
lines.push(`${head}(props, ref) { return __f.__isotoneRender(${JSON.stringify(name)}, props, ref); }`);
|
|
448
|
+
}
|
|
449
|
+
lines.push(
|
|
450
|
+
`const __f = __isotone.file(${JSON.stringify(key)}, ${JSON.stringify(versions)}, [${versions.map((_, i) => `__v${i}`).join(", ")}], ${JSON.stringify(names)});`
|
|
451
|
+
);
|
|
452
|
+
names.forEach((name, i) => {
|
|
453
|
+
if (hoisted(name)) {
|
|
454
|
+
lines.push(`${local(name)} = __f[${JSON.stringify(name)}];`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
lines.push(`const __e${i} = __f[${JSON.stringify(name)}];`);
|
|
458
|
+
lines.push(`export { __e${i} as ${name} };`);
|
|
459
|
+
});
|
|
460
|
+
lines.push("");
|
|
461
|
+
return lines.join("\n");
|
|
462
|
+
}
|
|
463
|
+
function safeJson(value) {
|
|
464
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/vite/index.ts
|
|
38
468
|
var DEFAULT_FILE = ".iteration-feedback.json";
|
|
39
469
|
var INJECTED = /* @__PURE__ */ new WeakSet();
|
|
40
470
|
var BUNDLE_PATH = "/@isotone/panel.js";
|
|
41
471
|
var MANIFEST = "variant.json";
|
|
42
472
|
var SPECIAL_QUERY = /[?&](raw|url|inline|no-inline|worker|sharedworker|html-proxy)(?:[&=]|$)/;
|
|
473
|
+
var COMPARES_TYPES = /\.type\s*[!=]==?|[!=]==?\s*[\w$.]+\.type\b/;
|
|
474
|
+
function versionOfId(id) {
|
|
475
|
+
const at = id?.indexOf("?") ?? -1;
|
|
476
|
+
if (!id || at < 0) return null;
|
|
477
|
+
return new URLSearchParams(id.slice(at + 1)).get(VERSION_QUERY);
|
|
478
|
+
}
|
|
43
479
|
function isInside(dir, path) {
|
|
44
480
|
const rel = relative(dir, path);
|
|
45
481
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
@@ -65,7 +501,133 @@ function iteration(options = {}) {
|
|
|
65
501
|
let activeFile = join(vopts.root, ACTIVE_FILE);
|
|
66
502
|
let activeIds;
|
|
67
503
|
const overrides = /* @__PURE__ */ new Map();
|
|
68
|
-
const swapping = /* @__PURE__ */ new
|
|
504
|
+
const swapping = /* @__PURE__ */ new Map();
|
|
505
|
+
let live = false;
|
|
506
|
+
let hasReact = false;
|
|
507
|
+
let ssrHost = false;
|
|
508
|
+
let manifests = null;
|
|
509
|
+
let byFile = null;
|
|
510
|
+
let addedFiles = null;
|
|
511
|
+
const dispatched = /* @__PURE__ */ new Map();
|
|
512
|
+
let reloadTimer = null;
|
|
513
|
+
const readManifests = () => {
|
|
514
|
+
manifests ??= readVariants(vopts);
|
|
515
|
+
return manifests;
|
|
516
|
+
};
|
|
517
|
+
const versionsByFile = () => {
|
|
518
|
+
if (byFile) return byFile;
|
|
519
|
+
byFile = /* @__PURE__ */ new Map();
|
|
520
|
+
for (const variant of readManifests()) {
|
|
521
|
+
for (const key of Object.keys(variant.files)) {
|
|
522
|
+
const path = normalizePath(join(vopts.root, key));
|
|
523
|
+
byFile.set(path, [...byFile.get(path) ?? [], variant.id]);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return byFile;
|
|
527
|
+
};
|
|
528
|
+
const filesOnlyInVariants = () => {
|
|
529
|
+
if (addedFiles) return addedFiles;
|
|
530
|
+
addedFiles = /* @__PURE__ */ new Map();
|
|
531
|
+
for (const path of versionsByFile().keys()) {
|
|
532
|
+
if (existsSync(path)) continue;
|
|
533
|
+
const stem = path.replace(/\.[cm]?[jt]sx?$/, "");
|
|
534
|
+
addedFiles.set(stem, [...addedFiles.get(stem) ?? [], path]);
|
|
535
|
+
}
|
|
536
|
+
return addedFiles;
|
|
537
|
+
};
|
|
538
|
+
const forgetManifests = () => {
|
|
539
|
+
manifests = null;
|
|
540
|
+
byFile = null;
|
|
541
|
+
addedFiles = null;
|
|
542
|
+
overrides.clear();
|
|
543
|
+
};
|
|
544
|
+
const groupsOf = (list) => overlayGroups(list).map((group) => ({ id: group.id, members: group.members.map((m) => m.id) }));
|
|
545
|
+
const liveConfig = () => ({
|
|
546
|
+
groups: groupsOf(readManifests()),
|
|
547
|
+
active: currentActive(),
|
|
548
|
+
ssr: ssrHost,
|
|
549
|
+
endpoint: VARIANTS_ENDPOINT
|
|
550
|
+
});
|
|
551
|
+
const overrideOf = (id, path) => {
|
|
552
|
+
const key = normalizePath(relative(vopts.root, path));
|
|
553
|
+
const manifest = readManifests().find((v) => v.id === id);
|
|
554
|
+
if (!manifest || !Object.hasOwn(manifest.files, key)) return null;
|
|
555
|
+
const file2 = join(variantDir(vopts, id), key);
|
|
556
|
+
return existsSync(file2) ? file2 : null;
|
|
557
|
+
};
|
|
558
|
+
const dispatchPlan = (path) => {
|
|
559
|
+
if (!/\.[cm]?[jt]sx?$/.test(path)) return null;
|
|
560
|
+
const ids = versionsByFile().get(path);
|
|
561
|
+
if (!ids?.length) return null;
|
|
562
|
+
const versions = [];
|
|
563
|
+
const names = /* @__PURE__ */ new Set();
|
|
564
|
+
const sources = [];
|
|
565
|
+
if (existsSync(path)) sources.push([null, path]);
|
|
566
|
+
for (const id of ids) {
|
|
567
|
+
const file2 = overrideOf(id, path);
|
|
568
|
+
if (file2) sources.push([id, file2]);
|
|
569
|
+
}
|
|
570
|
+
for (const [id, file2] of sources) {
|
|
571
|
+
const code = readFileSync(file2, "utf8");
|
|
572
|
+
if (COMPARES_TYPES.test(code)) return null;
|
|
573
|
+
const found = scanExports(code);
|
|
574
|
+
if (!found || isRouteModule(found)) return null;
|
|
575
|
+
for (const name of found) names.add(name);
|
|
576
|
+
versions.push(id);
|
|
577
|
+
}
|
|
578
|
+
if (versions.length === 0 || names.size === 0) return null;
|
|
579
|
+
return { key: normalizePath(relative(vopts.root, path)), versions, names: [...names].sort() };
|
|
580
|
+
};
|
|
581
|
+
const namesChanged = (path) => {
|
|
582
|
+
const plan = dispatchPlan(path);
|
|
583
|
+
return !plan || plan.names.join(",") !== dispatched.get(path);
|
|
584
|
+
};
|
|
585
|
+
let reloadPage = () => {
|
|
586
|
+
};
|
|
587
|
+
const swapWaiters = /* @__PURE__ */ new Set();
|
|
588
|
+
const hotModules = (changed, modulesOf, env) => {
|
|
589
|
+
if (!variantsOptions) return void 0;
|
|
590
|
+
const client = env === null || env === "client";
|
|
591
|
+
if (isInside(vDir, changed)) {
|
|
592
|
+
overrides.clear();
|
|
593
|
+
const hit = overrideSourcePath(vopts, changed);
|
|
594
|
+
if (!hit || hit.file === MANIFEST) return [];
|
|
595
|
+
const source = normalizePath(join(vopts.root, hit.file));
|
|
596
|
+
if (live && client && dispatched.has(source)) {
|
|
597
|
+
if (namesChanged(source)) {
|
|
598
|
+
reloadPage();
|
|
599
|
+
return [];
|
|
600
|
+
}
|
|
601
|
+
return [...modulesOf(source) ?? []].filter((m) => versionOfId(m.id) === hit.id);
|
|
602
|
+
}
|
|
603
|
+
if (!currentActive().includes(hit.id)) return [];
|
|
604
|
+
return [...modulesOf(source) ?? []];
|
|
605
|
+
}
|
|
606
|
+
const seen = swapping.get(changed);
|
|
607
|
+
let swap = false;
|
|
608
|
+
if (seen) {
|
|
609
|
+
if (env === null) {
|
|
610
|
+
swapping.delete(changed);
|
|
611
|
+
swap = true;
|
|
612
|
+
} else if (!seen.has(env)) {
|
|
613
|
+
seen.add(env);
|
|
614
|
+
swap = true;
|
|
615
|
+
for (const check of [...swapWaiters]) check();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const path = normalizePath(changed);
|
|
619
|
+
if (live && client && dispatched.has(path)) {
|
|
620
|
+
if (swap) return [];
|
|
621
|
+
if (namesChanged(path)) {
|
|
622
|
+
reloadPage();
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
return [...modulesOf(path) ?? []].filter((m) => versionOfId(m.id) === BASE_VERSION);
|
|
626
|
+
}
|
|
627
|
+
if (swap) return void 0;
|
|
628
|
+
if (overridable(changed) && overrideFor(currentActive(), changed)) return [];
|
|
629
|
+
return void 0;
|
|
630
|
+
};
|
|
69
631
|
const currentActive = () => {
|
|
70
632
|
if (isBuild) return parseVariantList(process.env.ISOTONE_VARIANT);
|
|
71
633
|
if (activeIds === void 0) activeIds = readActive(vopts);
|
|
@@ -96,29 +658,67 @@ function iteration(options = {}) {
|
|
|
96
658
|
};
|
|
97
659
|
vDir = variantsDir(vopts);
|
|
98
660
|
activeFile = join(vopts.root, ACTIVE_FILE);
|
|
661
|
+
hasReact = (config.plugins ?? []).some((p) => /react/i.test(p.name));
|
|
662
|
+
ssrHost = config.appType !== "spa" && config.appType !== "mpa";
|
|
663
|
+
forgetManifests();
|
|
664
|
+
},
|
|
665
|
+
async resolveId(source, importer, opts = {}) {
|
|
666
|
+
if (!variantsOptions) return null;
|
|
667
|
+
if (source === LIVE_MODULE) return RESOLVED_LIVE_MODULE;
|
|
668
|
+
if (!importer) return null;
|
|
669
|
+
const marker = source.indexOf(`?${VERSION_QUERY}=`);
|
|
670
|
+
if (live && marker > 0 && source.startsWith("./")) {
|
|
671
|
+
const dir = dirname(importer.replace(/[?#].*$/, ""));
|
|
672
|
+
return normalizePath(resolve(dir, source.slice(0, marker))) + source.slice(marker);
|
|
673
|
+
}
|
|
674
|
+
const added = filesOnlyInVariants();
|
|
675
|
+
if (added.size === 0 || source.startsWith("\0")) return null;
|
|
676
|
+
const bare = source.replace(/[?#].*$/, "");
|
|
677
|
+
const leaf = bare.slice(bare.lastIndexOf("/") + 1).replace(/\.[cm]?[jt]sx?$/, "");
|
|
678
|
+
const candidates = [...added.entries()].filter(([stem]) => stem.endsWith(`/${leaf}`) || stem.endsWith(`/${leaf}/index`));
|
|
679
|
+
if (candidates.length === 0) return null;
|
|
680
|
+
const resolved = await this.resolve(source, importer, { ...opts, skipSelf: true });
|
|
681
|
+
if (resolved) return resolved;
|
|
682
|
+
const importerPath = importer.replace(/[?#].*$/, "");
|
|
683
|
+
const target = bare.startsWith(".") ? normalizePath(resolve(dirname(importerPath), bare)).replace(/\.[cm]?[jt]sx?$/, "") : null;
|
|
684
|
+
const tail = bare.split("/").slice(1).join("/").replace(/\.[cm]?[jt]sx?$/, "");
|
|
685
|
+
const hits = candidates.filter(([stem]) => target ? stem === target || stem === `${target}/index` : tail !== "" && (stem.endsWith(`/${tail}`) || stem.endsWith(`/${tail}/index`)));
|
|
686
|
+
if (hits.length !== 1) return null;
|
|
687
|
+
const paths = hits[0]?.[1] ?? [];
|
|
688
|
+
return paths.length === 1 ? paths[0] : null;
|
|
99
689
|
},
|
|
100
|
-
load(id) {
|
|
690
|
+
load(id, opts) {
|
|
101
691
|
if (!variantsOptions || SPECIAL_QUERY.test(id)) return null;
|
|
692
|
+
if (id === RESOLVED_LIVE_MODULE) return { code: liveRuntimeModule(liveConfig()), map: null };
|
|
102
693
|
const path = id.replace(/[?#].*$/, "");
|
|
103
694
|
if (!overridable(path)) return null;
|
|
695
|
+
const query = id.slice(path.length).replace(/^\?/, "").replace(/#.*$/, "");
|
|
696
|
+
const version = live ? new URLSearchParams(query).get(VERSION_QUERY) : null;
|
|
697
|
+
if (version !== null) {
|
|
698
|
+
const from = version === BASE_VERSION ? existsSync(path) ? path : null : overrideOf(version, path);
|
|
699
|
+
return { code: from ? readFileSync(from, "utf8") : "export {};\n", map: null };
|
|
700
|
+
}
|
|
701
|
+
if (live && !opts?.ssr && query === "") {
|
|
702
|
+
const plan = dispatchPlan(path);
|
|
703
|
+
if (plan) {
|
|
704
|
+
dispatched.set(path, plan.names.join(","));
|
|
705
|
+
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
706
|
+
return { code: dispatcherModule(plan.key, name, plan.versions, plan.names), map: null };
|
|
707
|
+
}
|
|
708
|
+
}
|
|
104
709
|
const override = overrideFor(currentActive(), path);
|
|
105
710
|
if (!override) return null;
|
|
106
711
|
return { code: readFileSync(override, "utf8"), map: null };
|
|
107
712
|
},
|
|
713
|
+
// Vite 6 and later call this once per environment and skip
|
|
714
|
+
// handleHotUpdate; Vite 5 only knows handleHotUpdate.
|
|
715
|
+
hotUpdate(ctx) {
|
|
716
|
+
const env = this.environment;
|
|
717
|
+
return hotModules(ctx.file, (f) => env.moduleGraph.getModulesByFile(f), env.name);
|
|
718
|
+
},
|
|
108
719
|
handleHotUpdate(ctx) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (isInside(vDir, changed)) {
|
|
112
|
-
overrides.clear();
|
|
113
|
-
const hit = overrideSourcePath(vopts, changed);
|
|
114
|
-
if (!hit || hit.file === MANIFEST) return [];
|
|
115
|
-
if (!currentActive().includes(hit.id)) return [];
|
|
116
|
-
const source = normalizePath(join(vopts.root, hit.file));
|
|
117
|
-
return [...server.moduleGraph.getModulesByFile(source) ?? []];
|
|
118
|
-
}
|
|
119
|
-
if (swapping.delete(changed)) return;
|
|
120
|
-
if (overridable(changed) && overrideFor(currentActive(), changed)) return [];
|
|
121
|
-
return;
|
|
720
|
+
const { server } = ctx;
|
|
721
|
+
return hotModules(ctx.file, (f) => server.moduleGraph.getModulesByFile(f), null);
|
|
122
722
|
},
|
|
123
723
|
configureServer(server) {
|
|
124
724
|
if (!isDev) return;
|
|
@@ -167,13 +767,32 @@ function iteration(options = {}) {
|
|
|
167
767
|
});
|
|
168
768
|
if (variantsOptions) {
|
|
169
769
|
activeIds = readActive(vopts);
|
|
770
|
+
live = (variantsOptions.live ?? hasReact) && server.environments !== void 0;
|
|
771
|
+
const clientGraph = () => server.environments?.client?.moduleGraph ?? server.moduleGraph;
|
|
772
|
+
const signature = () => JSON.stringify(readManifests().map((v) => [v.id, v.group ?? "", Object.keys(v.files).sort()]));
|
|
773
|
+
let listed = live ? signature() : "";
|
|
774
|
+
reloadPage = () => {
|
|
775
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
776
|
+
reloadTimer = setTimeout(() => {
|
|
777
|
+
reloadTimer = null;
|
|
778
|
+
const graph = clientGraph();
|
|
779
|
+
const paths = /* @__PURE__ */ new Set([...dispatched.keys(), ...versionsByFile().keys()]);
|
|
780
|
+
for (const path of paths) {
|
|
781
|
+
for (const mod of graph.getModulesByFile(path) ?? []) graph.invalidateModule(mod);
|
|
782
|
+
}
|
|
783
|
+
const runtime = graph.getModuleById(RESOLVED_LIVE_MODULE);
|
|
784
|
+
if (runtime) graph.invalidateModule(runtime);
|
|
785
|
+
dispatched.clear();
|
|
786
|
+
server.ws.send({ type: "full-reload", path: "*" });
|
|
787
|
+
}, 50);
|
|
788
|
+
};
|
|
170
789
|
const activate = (next, removedFiles = []) => {
|
|
171
790
|
const prev = currentActive();
|
|
172
791
|
const changed = [
|
|
173
792
|
...prev.filter((id) => !next.includes(id)),
|
|
174
793
|
...next.filter((id) => !prev.includes(id))
|
|
175
794
|
];
|
|
176
|
-
if (changed.length === 0 && removedFiles.length === 0) return;
|
|
795
|
+
if (changed.length === 0 && removedFiles.length === 0) return Promise.resolve();
|
|
177
796
|
activeIds = next;
|
|
178
797
|
overrides.clear();
|
|
179
798
|
const sources = new Set(removedFiles.map((file2) => join(vopts.root, file2)));
|
|
@@ -183,12 +802,39 @@ function iteration(options = {}) {
|
|
|
183
802
|
sources.add(join(vopts.root, key));
|
|
184
803
|
}
|
|
185
804
|
}
|
|
805
|
+
const emitted = [];
|
|
186
806
|
for (const source of sources) {
|
|
187
807
|
const loaded = server.moduleGraph.getModulesByFile(normalizePath(source));
|
|
188
808
|
if (!loaded || loaded.size === 0) continue;
|
|
189
|
-
swapping.
|
|
809
|
+
swapping.set(source, /* @__PURE__ */ new Set());
|
|
810
|
+
emitted.push(source);
|
|
190
811
|
server.watcher.emit("change", source);
|
|
191
812
|
}
|
|
813
|
+
const settled = serverSawSwap(emitted);
|
|
814
|
+
if (live) void settled.then(() => server.ws.send({ type: "custom", event: ACTIVE_EVENT, data: { active: next } }));
|
|
815
|
+
return settled;
|
|
816
|
+
};
|
|
817
|
+
const serverSawSwap = (files) => {
|
|
818
|
+
const envs = Object.keys(server.environments ?? {}).filter((name) => name !== "client");
|
|
819
|
+
if (!live || files.length === 0 || envs.length === 0) return Promise.resolve();
|
|
820
|
+
return new Promise((done) => {
|
|
821
|
+
const check = () => {
|
|
822
|
+
const seen = files.every((file2) => {
|
|
823
|
+
const by = swapping.get(file2);
|
|
824
|
+
return !by || envs.every((env) => by.has(env));
|
|
825
|
+
});
|
|
826
|
+
if (!seen) return;
|
|
827
|
+
finish();
|
|
828
|
+
};
|
|
829
|
+
const timer = setTimeout(() => finish(), 1e3);
|
|
830
|
+
const finish = () => {
|
|
831
|
+
clearTimeout(timer);
|
|
832
|
+
swapWaiters.delete(check);
|
|
833
|
+
setTimeout(done, 30);
|
|
834
|
+
};
|
|
835
|
+
swapWaiters.add(check);
|
|
836
|
+
check();
|
|
837
|
+
});
|
|
192
838
|
};
|
|
193
839
|
mkdirSync(dirname(activeFile), { recursive: true });
|
|
194
840
|
ensureFileIgnored(activeFile);
|
|
@@ -210,13 +856,21 @@ function iteration(options = {}) {
|
|
|
210
856
|
return;
|
|
211
857
|
}
|
|
212
858
|
(method === "POST" ? readNodeJson(req) : Promise.resolve(void 0)).then((body) => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
217
|
-
|
|
859
|
+
let result;
|
|
860
|
+
try {
|
|
861
|
+
result = variantsResponse(vopts, { method, url: req.url, body });
|
|
862
|
+
} catch (error) {
|
|
863
|
+
sendFailure(res, error);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const send = () => {
|
|
867
|
+
answer(res, () => result);
|
|
868
|
+
};
|
|
869
|
+
if (result.status === 200 && method === "POST") {
|
|
218
870
|
const updated = result.body;
|
|
219
|
-
activate(updated.active, updated.changedFiles);
|
|
871
|
+
activate(updated.active, updated.changedFiles).then(send, send);
|
|
872
|
+
} else {
|
|
873
|
+
send();
|
|
220
874
|
}
|
|
221
875
|
}).catch((error) => sendFailure(res, error));
|
|
222
876
|
});
|
|
@@ -225,13 +879,34 @@ function iteration(options = {}) {
|
|
|
225
879
|
server.watcher.on("all", (_event, changed) => {
|
|
226
880
|
const path = resolve(changed);
|
|
227
881
|
if (path === activeFile) {
|
|
228
|
-
activate(readActive(vopts));
|
|
882
|
+
void activate(readActive(vopts));
|
|
229
883
|
} else if (isInside(vDir, path)) {
|
|
230
|
-
|
|
884
|
+
forgetManifests();
|
|
885
|
+
if (live) {
|
|
886
|
+
const now = signature();
|
|
887
|
+
if (now !== listed) {
|
|
888
|
+
listed = now;
|
|
889
|
+
reloadPage();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
231
892
|
}
|
|
232
893
|
});
|
|
233
894
|
}
|
|
234
|
-
|
|
895
|
+
const liveScript = () => live ? liveStoreScript(liveConfig()) : null;
|
|
896
|
+
if (options.panel === false) {
|
|
897
|
+
if (!live) return;
|
|
898
|
+
server.middlewares.use((req, res, next) => {
|
|
899
|
+
if (!(req.headers.accept ?? "").includes("text/html")) {
|
|
900
|
+
next();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
delete req.headers["if-none-match"];
|
|
904
|
+
delete req.headers["if-modified-since"];
|
|
905
|
+
injectHtmlResponse(res, null, liveScript);
|
|
906
|
+
next();
|
|
907
|
+
});
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
235
910
|
server.middlewares.use(BUNDLE_PATH, (_req, res) => {
|
|
236
911
|
res.setHeader("content-type", "text/javascript; charset=utf-8");
|
|
237
912
|
res.setHeader("cache-control", "no-store");
|
|
@@ -245,7 +920,7 @@ function iteration(options = {}) {
|
|
|
245
920
|
}
|
|
246
921
|
delete req.headers["if-none-match"];
|
|
247
922
|
delete req.headers["if-modified-since"];
|
|
248
|
-
injectHtmlResponse(res, mountCode);
|
|
923
|
+
injectHtmlResponse(res, mountCode, liveScript);
|
|
249
924
|
next();
|
|
250
925
|
});
|
|
251
926
|
}
|
|
@@ -286,7 +961,7 @@ function mountModuleSource(rule, key, variants, shortcut, theme) {
|
|
|
286
961
|
else window.addEventListener('load', start, { once: true });
|
|
287
962
|
`;
|
|
288
963
|
}
|
|
289
|
-
function injectHtmlResponse(res, code) {
|
|
964
|
+
function injectHtmlResponse(res, code, head = () => null) {
|
|
290
965
|
if (INJECTED.has(res)) return;
|
|
291
966
|
INJECTED.add(res);
|
|
292
967
|
const originalWrite = res.write.bind(res);
|
|
@@ -336,9 +1011,11 @@ function injectHtmlResponse(res, code) {
|
|
|
336
1011
|
buffering = false;
|
|
337
1012
|
const body = Buffer.concat(chunks).toString("utf8");
|
|
338
1013
|
if (body.includes("<html") && (body.includes("<head") || body.includes("</body>"))) {
|
|
339
|
-
const script = `<script type="module" data-isotone-inject>${code}</script>`;
|
|
1014
|
+
const script = code === null ? "" : `<script type="module" data-isotone-inject>${code}</script>`;
|
|
1015
|
+
const store = head();
|
|
1016
|
+
const early = store ? `<script data-isotone-live>${store.replace(/<\/script/gi, "<\\/script")}</script>` : "";
|
|
340
1017
|
if (!res.headersSent) originalSetHeader("Cache-Control", "no-store");
|
|
341
|
-
return originalEnd(withNativeFetch(injectIntoHead(body, script)), cb);
|
|
1018
|
+
return originalEnd(withNativeFetch(injectIntoHead(body, `${early}${script}`)), cb);
|
|
342
1019
|
}
|
|
343
1020
|
return originalEnd(Buffer.concat(chunks), cb);
|
|
344
1021
|
});
|