what-compiler 0.11.0 → 0.11.1

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/index.js DELETED
@@ -1,2570 +0,0 @@
1
- // packages/compiler/src/babel-plugin.js
2
- var EVENT_MODIFIERS = /* @__PURE__ */ new Set(["preventDefault", "stopPropagation", "once", "capture", "passive", "self"]);
3
- var EVENT_OPTION_MODIFIERS = /* @__PURE__ */ new Set(["once", "capture", "passive"]);
4
- var VOID_HTML_ELEMENTS = /* @__PURE__ */ new Set([
5
- "area",
6
- "base",
7
- "br",
8
- "col",
9
- "embed",
10
- "hr",
11
- "img",
12
- "input",
13
- "link",
14
- "meta",
15
- "param",
16
- "source",
17
- "track",
18
- "wbr"
19
- ]);
20
- var DELEGATED_EVENTS = /* @__PURE__ */ new Set([
21
- "click",
22
- "input",
23
- "change",
24
- "keydown",
25
- "keyup",
26
- "submit",
27
- "focusin",
28
- "focusout",
29
- "mousedown",
30
- "mouseup"
31
- ]);
32
- var SAFE_GLOBAL_CALLS = /* @__PURE__ */ new Set([
33
- "Math",
34
- "Number",
35
- "String",
36
- "Boolean",
37
- "parseInt",
38
- "parseFloat",
39
- "isNaN",
40
- "isFinite",
41
- "encodeURIComponent",
42
- "decodeURIComponent",
43
- "encodeURI",
44
- "decodeURI",
45
- "JSON",
46
- "Date",
47
- "Array",
48
- "Object",
49
- "console",
50
- "RegExp"
51
- ]);
52
- var SIGNAL_CREATORS = /* @__PURE__ */ new Set([
53
- "useSignal",
54
- "signal",
55
- "computed",
56
- "useComputed",
57
- "useState",
58
- "useReducer",
59
- "createResource",
60
- "useSWR",
61
- "useQuery",
62
- "useInfiniteQuery"
63
- ]);
64
- function normalizeJsxText(value) {
65
- if (!/[\r\n]/.test(value)) {
66
- return value.replace(/\t/g, " ");
67
- }
68
- const lines = value.split(/\r\n|\n|\r/);
69
- let lastNonEmpty = -1;
70
- for (let i = 0; i < lines.length; i++) {
71
- if (/[^ \t]/.test(lines[i])) lastNonEmpty = i;
72
- }
73
- if (lastNonEmpty === -1) return "";
74
- let out = "";
75
- for (let i = 0; i < lines.length; i++) {
76
- let line = lines[i].replace(/\t/g, " ");
77
- const isFirst = i === 0;
78
- const isLast = i === lines.length - 1;
79
- if (!isFirst) line = line.replace(/^ +/, "");
80
- if (!isLast) line = line.replace(/ +$/, "");
81
- if (!line) continue;
82
- if (i !== lastNonEmpty) line += " ";
83
- out += line;
84
- }
85
- return out;
86
- }
87
- function whatBabelPlugin({ types: t }) {
88
- const _unknownModifierWarned = /* @__PURE__ */ new Set();
89
- const _forInfoWarned = /* @__PURE__ */ new Set();
90
- function hasEventModifiers(name, state) {
91
- if (!name.includes("__")) return false;
92
- if (!name.startsWith("on")) return false;
93
- const parts = name.split("__");
94
- const tail = parts.slice(1).filter((s) => s !== "");
95
- if (tail.length === 0) return false;
96
- if (true) {
97
- const unknown = tail.filter((m) => !EVENT_MODIFIERS.has(m));
98
- const filename = state && (state.filename || state.file && state.file.opts && state.file.opts.filename) || "<unknown>";
99
- for (const m of unknown) {
100
- const key = `${filename}::${m}`;
101
- if (!_unknownModifierWarned.has(key)) {
102
- _unknownModifierWarned.add(key);
103
- console.warn(
104
- `[what-compiler] Unknown event modifier "__${m}" in attribute "${name}" (${filename}). Known modifiers: ${[...EVENT_MODIFIERS].join(", ")}. Unknown segments are ignored.`
105
- );
106
- }
107
- }
108
- }
109
- return true;
110
- }
111
- function parseEventModifiers(name) {
112
- const delimiter = name.includes("|") ? "|" : "__";
113
- const parts = name.split(delimiter);
114
- const eventName = parts[0];
115
- const modifiers = parts.slice(1).filter((m) => EVENT_MODIFIERS.has(m));
116
- return { eventName, modifiers };
117
- }
118
- function isBindingAttribute(name) {
119
- return name.startsWith("bind:");
120
- }
121
- function getBindingProperty(name) {
122
- return name.slice(5);
123
- }
124
- function isComponent(name) {
125
- return /^[A-Z]/.test(name);
126
- }
127
- function isVoidHtmlElement(name) {
128
- return VOID_HTML_ELEMENTS.has(String(name).toLowerCase());
129
- }
130
- function getAttributeValue(value) {
131
- if (!value) return t.booleanLiteral(true);
132
- if (t.isJSXExpressionContainer(value)) return value.expression;
133
- if (t.isStringLiteral(value)) return value;
134
- return t.stringLiteral(value.value || "");
135
- }
136
- function normalizeAttrName(attrName) {
137
- if (attrName === "className") return "class";
138
- if (attrName === "htmlFor") return "for";
139
- return attrName;
140
- }
141
- function getAttrName(attr) {
142
- if (t.isJSXNamespacedName(attr.name)) {
143
- return `${attr.name.namespace.name}:${attr.name.name.name}`;
144
- }
145
- return typeof attr.name.name === "string" ? attr.name.name : String(attr.name.name);
146
- }
147
- function createEventHandler(handler, modifiers) {
148
- if (modifiers.length === 0) return handler;
149
- let wrappedHandler = handler;
150
- for (const mod of modifiers) {
151
- switch (mod) {
152
- case "preventDefault":
153
- wrappedHandler = t.arrowFunctionExpression(
154
- [t.identifier("e")],
155
- t.blockStatement([
156
- t.expressionStatement(
157
- t.callExpression(
158
- t.memberExpression(t.identifier("e"), t.identifier("preventDefault")),
159
- []
160
- )
161
- ),
162
- t.expressionStatement(
163
- t.callExpression(wrappedHandler, [t.identifier("e")])
164
- )
165
- ])
166
- );
167
- break;
168
- case "stopPropagation":
169
- wrappedHandler = t.arrowFunctionExpression(
170
- [t.identifier("e")],
171
- t.blockStatement([
172
- t.expressionStatement(
173
- t.callExpression(
174
- t.memberExpression(t.identifier("e"), t.identifier("stopPropagation")),
175
- []
176
- )
177
- ),
178
- t.expressionStatement(
179
- t.callExpression(wrappedHandler, [t.identifier("e")])
180
- )
181
- ])
182
- );
183
- break;
184
- case "self":
185
- wrappedHandler = t.arrowFunctionExpression(
186
- [t.identifier("e")],
187
- t.blockStatement([
188
- t.ifStatement(
189
- t.binaryExpression(
190
- "===",
191
- t.memberExpression(t.identifier("e"), t.identifier("target")),
192
- t.memberExpression(t.identifier("e"), t.identifier("currentTarget"))
193
- ),
194
- t.expressionStatement(
195
- t.callExpression(wrappedHandler, [t.identifier("e")])
196
- )
197
- )
198
- ])
199
- );
200
- break;
201
- case "once":
202
- case "capture":
203
- case "passive":
204
- break;
205
- }
206
- }
207
- return wrappedHandler;
208
- }
209
- function isSignalIdentifier(name, signalNames) {
210
- return signalNames.has(name);
211
- }
212
- function collectSignalNamesFromScope(path3) {
213
- const signalNames = /* @__PURE__ */ new Set();
214
- function extractFromDeclarator(decl) {
215
- const init = decl.init;
216
- if (!init || !t.isCallExpression(init)) return;
217
- const callee = init.callee;
218
- let calleeName = "";
219
- if (t.isIdentifier(callee)) {
220
- calleeName = callee.name;
221
- } else if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
222
- calleeName = callee.property.name;
223
- }
224
- if (SIGNAL_CREATORS.has(calleeName)) {
225
- const id = decl.id;
226
- if (t.isIdentifier(id)) {
227
- signalNames.add(id.name);
228
- } else if (t.isArrayPattern(id)) {
229
- for (const el of id.elements) {
230
- if (t.isIdentifier(el)) signalNames.add(el.name);
231
- }
232
- } else if (t.isObjectPattern(id)) {
233
- for (const prop of id.properties) {
234
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
235
- signalNames.add(prop.value.name);
236
- }
237
- }
238
- }
239
- }
240
- }
241
- let scope = path3.scope;
242
- while (scope) {
243
- for (const binding of Object.values(scope.bindings)) {
244
- if (binding.path.isVariableDeclarator()) {
245
- extractFromDeclarator(binding.path.node);
246
- }
247
- }
248
- const fnNode = scope.path && scope.path.node;
249
- if (fnNode && fnNode.params) {
250
- for (const param of fnNode.params) {
251
- if (t.isObjectPattern(param)) {
252
- for (const prop of param.properties) {
253
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
254
- signalNames.add(prop.value.name);
255
- } else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
256
- signalNames.add(prop.argument.name);
257
- }
258
- }
259
- }
260
- }
261
- }
262
- scope = scope.parent;
263
- }
264
- return signalNames;
265
- }
266
- function collectSignalNames(path3) {
267
- return collectSignalNamesFromScope(path3);
268
- }
269
- function isSafeGlobalCall(expr) {
270
- if (!t.isCallExpression(expr)) return false;
271
- const callee = expr.callee;
272
- if (t.isMemberExpression(callee) && t.isIdentifier(callee.object)) {
273
- return SAFE_GLOBAL_CALLS.has(callee.object.name);
274
- }
275
- if (t.isIdentifier(callee)) {
276
- return SAFE_GLOBAL_CALLS.has(callee.name);
277
- }
278
- return false;
279
- }
280
- function isUncertainReactive(expr, signalNames, importedIds) {
281
- if (!signalNames) return false;
282
- if (t.isCallExpression(expr)) {
283
- if (t.isIdentifier(expr.callee) && isSignalIdentifier(expr.callee.name, signalNames)) {
284
- return false;
285
- }
286
- if (importedIds && t.isIdentifier(expr.callee) && importedIds.has(expr.callee.name) && !SAFE_GLOBAL_CALLS.has(expr.callee.name)) {
287
- return false;
288
- }
289
- if (t.isMemberExpression(expr.callee) && t.isIdentifier(expr.callee.object) && isSignalIdentifier(expr.callee.object.name, signalNames)) {
290
- return false;
291
- }
292
- if (isSafeGlobalCall(expr)) return false;
293
- if (expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds))) {
294
- return true;
295
- }
296
- }
297
- return false;
298
- }
299
- function isPotentiallyReactive(expr, signalNames, importedIds) {
300
- if (!signalNames) signalNames = /* @__PURE__ */ new Set();
301
- if (t.isCallExpression(expr)) {
302
- if (t.isIdentifier(expr.callee) && isSignalIdentifier(expr.callee.name, signalNames)) {
303
- return true;
304
- }
305
- if (importedIds && t.isIdentifier(expr.callee) && importedIds.has(expr.callee.name)) {
306
- if (!SAFE_GLOBAL_CALLS.has(expr.callee.name)) {
307
- return true;
308
- }
309
- }
310
- if (t.isMemberExpression(expr.callee)) {
311
- if (t.isIdentifier(expr.callee.object) && isSignalIdentifier(expr.callee.object.name, signalNames)) {
312
- return true;
313
- }
314
- }
315
- if (isSafeGlobalCall(expr)) {
316
- return expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
317
- }
318
- if (t.isIdentifier(expr.callee)) {
319
- return expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
320
- }
321
- return isPotentiallyReactive(expr.callee, signalNames, importedIds) || expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
322
- }
323
- if (t.isIdentifier(expr)) {
324
- return isSignalIdentifier(expr.name, signalNames) || importedIds && importedIds.has(expr.name);
325
- }
326
- if (t.isMemberExpression(expr)) {
327
- return isPotentiallyReactive(expr.object, signalNames, importedIds);
328
- }
329
- if (t.isConditionalExpression(expr)) {
330
- return isPotentiallyReactive(expr.test, signalNames, importedIds) || isPotentiallyReactive(expr.consequent, signalNames, importedIds) || isPotentiallyReactive(expr.alternate, signalNames, importedIds);
331
- }
332
- if (t.isBinaryExpression(expr) || t.isLogicalExpression(expr)) {
333
- return isPotentiallyReactive(expr.left, signalNames, importedIds) || isPotentiallyReactive(expr.right, signalNames, importedIds);
334
- }
335
- if (t.isUnaryExpression(expr)) {
336
- return isPotentiallyReactive(expr.argument, signalNames, importedIds);
337
- }
338
- if (t.isTemplateLiteral(expr)) {
339
- return expr.expressions.some((e) => isPotentiallyReactive(e, signalNames, importedIds));
340
- }
341
- if (t.isObjectExpression(expr)) {
342
- return expr.properties.some(
343
- (prop) => t.isObjectProperty(prop) && isPotentiallyReactive(prop.value, signalNames, importedIds)
344
- );
345
- }
346
- if (t.isArrayExpression(expr)) {
347
- return expr.elements.some((el) => el && isPotentiallyReactive(el, signalNames, importedIds));
348
- }
349
- if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
350
- return false;
351
- }
352
- return false;
353
- }
354
- function tryLowerMapToMapArray(expr, state) {
355
- let mapCall = expr;
356
- let wrappedInArrow = false;
357
- if (t.isArrowFunctionExpression(expr) && expr.params.length === 0) {
358
- mapCall = expr.body;
359
- wrappedInArrow = true;
360
- }
361
- if (t.isConditionalExpression(mapCall)) {
362
- const loweredCon = tryLowerMapCall(mapCall.consequent, state);
363
- const loweredAlt = tryLowerMapCall(mapCall.alternate, state);
364
- if (loweredCon || loweredAlt) {
365
- const result = t.conditionalExpression(
366
- mapCall.test,
367
- loweredCon || mapCall.consequent,
368
- loweredAlt || mapCall.alternate
369
- );
370
- return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
371
- }
372
- return null;
373
- }
374
- if (t.isLogicalExpression(mapCall) && (mapCall.operator === "&&" || mapCall.operator === "||")) {
375
- const loweredRight = tryLowerMapCall(mapCall.right, state);
376
- if (loweredRight) {
377
- const result = t.logicalExpression(mapCall.operator, mapCall.left, loweredRight);
378
- return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
379
- }
380
- return null;
381
- }
382
- const lowered = tryLowerMapCall(mapCall, state);
383
- return lowered;
384
- }
385
- function tryLowerMapCall(mapCall, state) {
386
- if (!t.isCallExpression(mapCall)) return null;
387
- if (!t.isMemberExpression(mapCall.callee)) return null;
388
- if (!t.isIdentifier(mapCall.callee.property, { name: "map" })) return null;
389
- if (mapCall.arguments.length < 1) return null;
390
- const mapFn = mapCall.arguments[0];
391
- if (!t.isArrowFunctionExpression(mapFn) && !t.isFunctionExpression(mapFn)) return null;
392
- let returnExpr = null;
393
- if (t.isArrowFunctionExpression(mapFn)) {
394
- if (t.isExpression(mapFn.body)) {
395
- returnExpr = mapFn.body;
396
- } else if (t.isBlockStatement(mapFn.body)) {
397
- const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
398
- if (ret) returnExpr = ret.argument;
399
- }
400
- } else if (t.isFunctionExpression(mapFn)) {
401
- const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
402
- if (ret) returnExpr = ret.argument;
403
- }
404
- if (!returnExpr) return null;
405
- if (!t.isJSXElement(returnExpr)) return null;
406
- const attrs = returnExpr.openingElement.attributes;
407
- let keyAttr = null;
408
- for (const attr of attrs) {
409
- if (t.isJSXAttribute(attr) && getAttrName(attr) === "key") {
410
- keyAttr = attr;
411
- break;
412
- }
413
- }
414
- if (!keyAttr) {
415
- if (true) {
416
- const loc = returnExpr.loc;
417
- const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
418
- const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
419
- console.warn(
420
- `[what-compiler] .map() returning JSX without a \`key\` prop at ${fileName}${lineInfo}. Without a key, the list cannot use keyed reconciliation \u2014 items are re-created on every update. Add key={...} to enable efficient updates.`
421
- );
422
- }
423
- return null;
424
- }
425
- const keyValue = getAttributeValue(keyAttr.value);
426
- if (!keyValue) return null;
427
- returnExpr.openingElement.attributes = attrs.filter((a) => a !== keyAttr);
428
- const sourceObj = mapCall.callee.object;
429
- const source = t.arrowFunctionExpression([], sourceObj);
430
- const itemParam = mapFn.params[0] ? t.cloneNode(mapFn.params[0], true) : t.identifier("_item");
431
- const keyFn = t.arrowFunctionExpression([itemParam], t.cloneNode(keyValue, true));
432
- return t.callExpression(t.identifier("_$mapArray"), [
433
- source,
434
- mapFn,
435
- t.objectExpression([
436
- t.objectProperty(t.identifier("key"), keyFn),
437
- t.objectProperty(t.identifier("raw"), t.booleanLiteral(true))
438
- ])
439
- ]);
440
- }
441
- function isStaticChild(child) {
442
- if (t.isJSXText(child)) return true;
443
- if (t.isJSXExpressionContainer(child)) return false;
444
- if (t.isJSXElement(child)) {
445
- const el = child.openingElement;
446
- const tagName = el.name.name;
447
- if (isComponent(tagName)) return false;
448
- for (const attr of el.attributes) {
449
- if (t.isJSXSpreadAttribute(attr)) return false;
450
- const value = attr.value;
451
- if (t.isJSXExpressionContainer(value)) return false;
452
- }
453
- return child.children.every(isStaticChild);
454
- }
455
- return false;
456
- }
457
- function isDynamicAttr(attr) {
458
- if (t.isJSXSpreadAttribute(attr)) return true;
459
- if (!attr.value) return false;
460
- return t.isJSXExpressionContainer(attr.value);
461
- }
462
- function extractStaticHTML(node) {
463
- if (t.isJSXText(node)) {
464
- const text = normalizeJsxText(node.value);
465
- return text ? escapeHTML(text) : "";
466
- }
467
- if (t.isJSXExpressionContainer(node)) {
468
- if (t.isJSXEmptyExpression(node.expression)) return "";
469
- return "<!--$-->";
470
- }
471
- if (!t.isJSXElement(node)) return "";
472
- const el = node.openingElement;
473
- const tagName = el.name.name;
474
- if (isComponent(tagName)) return "";
475
- let html = `<${tagName}`;
476
- for (const attr of el.attributes) {
477
- if (t.isJSXSpreadAttribute(attr)) continue;
478
- const name = getAttrName(attr);
479
- if (name === "key") continue;
480
- if (name.startsWith("on") || name.startsWith("bind:") || name.includes("|")) continue;
481
- let domName = name;
482
- if (name === "className") domName = "class";
483
- if (name === "htmlFor") domName = "for";
484
- if (!attr.value) {
485
- html += ` ${domName}`;
486
- } else if (t.isStringLiteral(attr.value)) {
487
- html += ` ${domName}="${escapeAttr(attr.value.value)}"`;
488
- } else if (t.isJSXExpressionContainer(attr.value)) {
489
- continue;
490
- }
491
- }
492
- const selfClosing = node.openingElement.selfClosing;
493
- if (selfClosing && isVoidHtmlElement(tagName)) {
494
- html += ">";
495
- return html;
496
- }
497
- if (selfClosing) {
498
- html += `></${tagName}>`;
499
- return html;
500
- }
501
- html += ">";
502
- for (const child of node.children) {
503
- if (t.isJSXText(child)) {
504
- const text = normalizeJsxText(child.value);
505
- if (text) html += escapeHTML(text);
506
- } else if (t.isJSXExpressionContainer(child)) {
507
- if (!t.isJSXEmptyExpression(child.expression)) {
508
- html += "<!--$-->";
509
- }
510
- } else if (t.isJSXElement(child)) {
511
- if (isComponent(child.openingElement.name.name)) {
512
- html += "<!--$-->";
513
- } else {
514
- html += extractStaticHTML(child);
515
- }
516
- }
517
- }
518
- html += `</${tagName}>`;
519
- return html;
520
- }
521
- function escapeHTML(str) {
522
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
523
- }
524
- function escapeAttr(str) {
525
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
526
- }
527
- function transformElementFineGrained(path3, state) {
528
- const { node } = path3;
529
- const openingElement = node.openingElement;
530
- const tagName = openingElement.name.name;
531
- if (tagName === "For") {
532
- return transformForFineGrained(path3, state);
533
- }
534
- if (tagName === "Show") {
535
- return transformShowFineGrained(path3, state);
536
- }
537
- if (isComponent(tagName)) {
538
- return transformComponentFineGrained(path3, state);
539
- }
540
- const attributes = openingElement.attributes;
541
- const children = node.children;
542
- const allChildrenStatic = children.every(isStaticChild);
543
- const allAttrsStatic = attributes.every((attr) => !isDynamicAttr(attr));
544
- const noEvents = attributes.every((attr) => {
545
- if (t.isJSXSpreadAttribute(attr)) return false;
546
- const name = getAttrName(attr);
547
- return !name?.startsWith("on") && !name?.startsWith("bind:");
548
- });
549
- if (allChildrenStatic && allAttrsStatic && noEvents) {
550
- const html2 = extractStaticHTML(node);
551
- if (html2) {
552
- const tmplId2 = getOrCreateTemplate(state, html2);
553
- state.needsTemplate = true;
554
- return t.callExpression(t.identifier(tmplId2), []);
555
- }
556
- }
557
- const html = extractStaticHTML(node);
558
- if (!html) {
559
- const loc = node.loc;
560
- const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
561
- const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
562
- console.warn(
563
- `[what-compiler] Could not extract template for <${tagName}> at ${fileName}${lineInfo}. Falling back to h() for this element. This element could not be statically analyzed. Consider simplifying the JSX.`
564
- );
565
- state.needsH = true;
566
- return transformElementAsH(path3, state);
567
- }
568
- const tmplId = getOrCreateTemplate(state, html);
569
- state.needsTemplate = true;
570
- const elId = state.nextVarId();
571
- const statements = [
572
- t.variableDeclaration("const", [
573
- t.variableDeclarator(t.identifier(elId), t.callExpression(t.identifier(tmplId), []))
574
- ])
575
- ];
576
- applyDynamicAttrs(statements, elId, attributes, state, tagName);
577
- applyDynamicChildren(statements, elId, children, node, state);
578
- if (!state._pendingSetup) state._pendingSetup = [];
579
- state._pendingSetup.push(...statements);
580
- return t.identifier(elId);
581
- }
582
- function transformElementAsH(path3, state) {
583
- const { node } = path3;
584
- const openingElement = node.openingElement;
585
- const tagName = openingElement.name.name;
586
- const attributes = openingElement.attributes;
587
- const children = node.children;
588
- const props = [];
589
- for (const attr of attributes) {
590
- if (t.isJSXSpreadAttribute(attr)) continue;
591
- const attrName = getAttrName(attr);
592
- const value = getAttributeValue(attr.value);
593
- let domAttrName = normalizeAttrName(attrName);
594
- props.push(
595
- t.objectProperty(
596
- /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(domAttrName) ? t.identifier(domAttrName) : t.stringLiteral(domAttrName),
597
- value
598
- )
599
- );
600
- }
601
- const transformedChildren = [];
602
- for (const child of children) {
603
- if (t.isJSXText(child)) {
604
- const text = normalizeJsxText(child.value);
605
- if (text) transformedChildren.push(t.stringLiteral(text));
606
- } else if (t.isJSXExpressionContainer(child)) {
607
- if (!t.isJSXEmptyExpression(child.expression)) {
608
- transformedChildren.push(child.expression);
609
- }
610
- } else if (t.isJSXElement(child)) {
611
- transformedChildren.push(transformElementFineGrained({ node: child }, state));
612
- } else if (t.isJSXFragment(child)) {
613
- transformedChildren.push(transformFragmentFineGrained({ node: child }, state));
614
- }
615
- }
616
- const propsExpr = props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
617
- return t.callExpression(t.identifier("h"), [t.stringLiteral(tagName), propsExpr, ...transformedChildren]);
618
- }
619
- const VALUE_PROP_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select", "option"]);
620
- function applyDynamicAttrs(statements, elId, attributes, state, tagName) {
621
- function buildSetPropCall(propName, valueExpr) {
622
- if (propName === "class") {
623
- state.needsSetClass = true;
624
- return t.callExpression(t.identifier("_$setClass"), [t.identifier(elId), valueExpr]);
625
- }
626
- if (propName === "style") {
627
- state.needsSetStyle = true;
628
- return t.callExpression(t.identifier("_$setStyle"), [t.identifier(elId), valueExpr]);
629
- }
630
- if (propName === "value" && tagName && VALUE_PROP_TAGS.has(tagName)) {
631
- state.needsSetValue = true;
632
- return t.callExpression(t.identifier("_$setValue"), [t.identifier(elId), valueExpr]);
633
- }
634
- if (propName === "checked" && tagName === "input") {
635
- state.needsSetChecked = true;
636
- return t.callExpression(t.identifier("_$setChecked"), [t.identifier(elId), valueExpr]);
637
- }
638
- if (propName.startsWith("data-") || propName.startsWith("aria-")) {
639
- state.needsSetAttr = true;
640
- return t.callExpression(t.identifier("_$setAttr"), [
641
- t.identifier(elId),
642
- t.stringLiteral(propName),
643
- valueExpr
644
- ]);
645
- }
646
- state.needsSetProp = true;
647
- return t.callExpression(t.identifier("_$setProp"), [
648
- t.identifier(elId),
649
- t.stringLiteral(propName),
650
- valueExpr
651
- ]);
652
- }
653
- let delegateInitEmitted = false;
654
- function emitDelegateInit() {
655
- if (delegateInitEmitted) return;
656
- delegateInitEmitted = true;
657
- statements.push(
658
- t.expressionStatement(t.callExpression(t.identifier("_$delegate$"), []))
659
- );
660
- }
661
- for (const attr of attributes) {
662
- if (t.isJSXSpreadAttribute(attr)) {
663
- state.needsSpread = true;
664
- statements.push(
665
- t.expressionStatement(
666
- t.callExpression(t.identifier("_$spread"), [t.identifier(elId), attr.argument])
667
- )
668
- );
669
- continue;
670
- }
671
- const attrName = getAttrName(attr);
672
- if (attrName === "key") continue;
673
- if (attrName === "ref") {
674
- const refExpr = getAttributeValue(attr.value);
675
- statements.push(
676
- t.expressionStatement(
677
- t.conditionalExpression(
678
- t.binaryExpression(
679
- "===",
680
- t.unaryExpression("typeof", refExpr),
681
- t.stringLiteral("function")
682
- ),
683
- t.callExpression(t.cloneNode(refExpr), [t.identifier(elId)]),
684
- t.assignmentExpression(
685
- "=",
686
- t.memberExpression(t.cloneNode(refExpr), t.identifier("current")),
687
- t.identifier(elId)
688
- )
689
- )
690
- )
691
- );
692
- continue;
693
- }
694
- if (attrName.startsWith("on") && !attrName.includes("|") && !hasEventModifiers(attrName, state)) {
695
- const event = attrName.slice(2).toLowerCase();
696
- const handler = getAttributeValue(attr.value);
697
- if (DELEGATED_EVENTS.has(event)) {
698
- state.needsDelegation = true;
699
- if (!state.delegatedEvents) state.delegatedEvents = /* @__PURE__ */ new Set();
700
- state.delegatedEvents.add(event);
701
- emitDelegateInit();
702
- statements.push(
703
- t.expressionStatement(
704
- t.assignmentExpression(
705
- "=",
706
- t.memberExpression(
707
- t.identifier(elId),
708
- t.identifier(`$$${event}`)
709
- ),
710
- handler
711
- )
712
- )
713
- );
714
- } else {
715
- statements.push(
716
- t.expressionStatement(
717
- t.callExpression(
718
- t.memberExpression(t.identifier(elId), t.identifier("addEventListener")),
719
- [t.stringLiteral(event), handler]
720
- )
721
- )
722
- );
723
- }
724
- continue;
725
- }
726
- if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
727
- const { eventName, modifiers } = parseEventModifiers(attrName);
728
- const handler = getAttributeValue(attr.value);
729
- const wrappedHandler = createEventHandler(handler, modifiers);
730
- const event = eventName.slice(2).toLowerCase();
731
- const optionMods = modifiers.filter((m) => EVENT_OPTION_MODIFIERS.has(m));
732
- const addEventArgs = [t.stringLiteral(event), wrappedHandler];
733
- if (optionMods.length > 0) {
734
- const optsProps = optionMods.map(
735
- (m) => t.objectProperty(t.identifier(m), t.booleanLiteral(true))
736
- );
737
- addEventArgs.push(t.objectExpression(optsProps));
738
- }
739
- statements.push(
740
- t.expressionStatement(
741
- t.callExpression(
742
- t.memberExpression(t.identifier(elId), t.identifier("addEventListener")),
743
- addEventArgs
744
- )
745
- )
746
- );
747
- continue;
748
- }
749
- if (isBindingAttribute(attrName)) {
750
- const bindProp = getBindingProperty(attrName);
751
- const signalExpr = attr.value.expression;
752
- state.needsEffect = true;
753
- if (bindProp === "value") {
754
- statements.push(
755
- t.expressionStatement(
756
- t.callExpression(t.identifier("_$effect"), [
757
- t.arrowFunctionExpression([], t.assignmentExpression(
758
- "=",
759
- t.memberExpression(t.identifier(elId), t.identifier("value")),
760
- t.callExpression(t.cloneNode(signalExpr), [])
761
- ))
762
- ])
763
- )
764
- );
765
- statements.push(
766
- t.expressionStatement(
767
- t.callExpression(
768
- t.memberExpression(t.identifier(elId), t.identifier("addEventListener")),
769
- [
770
- t.stringLiteral("input"),
771
- t.arrowFunctionExpression(
772
- [t.identifier("e")],
773
- t.callExpression(
774
- t.memberExpression(t.cloneNode(signalExpr), t.identifier("set")),
775
- [t.memberExpression(
776
- t.memberExpression(t.identifier("e"), t.identifier("target")),
777
- t.identifier("value")
778
- )]
779
- )
780
- )
781
- ]
782
- )
783
- )
784
- );
785
- } else if (bindProp === "checked") {
786
- state.needsEffect = true;
787
- statements.push(
788
- t.expressionStatement(
789
- t.callExpression(t.identifier("_$effect"), [
790
- t.arrowFunctionExpression([], t.assignmentExpression(
791
- "=",
792
- t.memberExpression(t.identifier(elId), t.identifier("checked")),
793
- t.callExpression(t.cloneNode(signalExpr), [])
794
- ))
795
- ])
796
- )
797
- );
798
- statements.push(
799
- t.expressionStatement(
800
- t.callExpression(
801
- t.memberExpression(t.identifier(elId), t.identifier("addEventListener")),
802
- [
803
- t.stringLiteral("change"),
804
- t.arrowFunctionExpression(
805
- [t.identifier("e")],
806
- t.callExpression(
807
- t.memberExpression(t.cloneNode(signalExpr), t.identifier("set")),
808
- [t.memberExpression(
809
- t.memberExpression(t.identifier("e"), t.identifier("target")),
810
- t.identifier("checked")
811
- )]
812
- )
813
- )
814
- ]
815
- )
816
- )
817
- );
818
- }
819
- continue;
820
- }
821
- if (t.isJSXExpressionContainer(attr.value)) {
822
- const expr = attr.value.expression;
823
- const domName = normalizeAttrName(attrName);
824
- if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
825
- state.needsEffect = true;
826
- const valueExpr = t.isIdentifier(expr) && (isSignalIdentifier(expr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(expr.name)) ? t.callExpression(expr, []) : expr;
827
- const effectCall = t.callExpression(t.identifier("_$effect"), [
828
- t.arrowFunctionExpression([], buildSetPropCall(domName, valueExpr))
829
- ]);
830
- if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
831
- t.addComment(
832
- effectCall,
833
- "leading",
834
- " @what-dev: effect wrapping may be unnecessary \u2014 expression contains a non-signal function call with reactive args ",
835
- false
836
- );
837
- }
838
- statements.push(t.expressionStatement(effectCall));
839
- } else {
840
- statements.push(t.expressionStatement(buildSetPropCall(domName, expr)));
841
- }
842
- }
843
- }
844
- }
845
- function buildsDOM(node) {
846
- if (!node || typeof node !== "object") return false;
847
- if (Array.isArray(node)) return node.some(buildsDOM);
848
- if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
849
- if (node.type === "CallExpression" && node.callee && node.callee.type === "Identifier" && (node.callee.name === "_$mapArray" || node.callee.name === "mapArray")) {
850
- return true;
851
- }
852
- for (const key of Object.keys(node)) {
853
- if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
854
- const v = node[key];
855
- if (v && typeof v === "object" && buildsDOM(v)) return true;
856
- }
857
- return false;
858
- }
859
- function memoizeBranchCondition(expr, statements, state) {
860
- let testExpr = null;
861
- let isTernary = false;
862
- if (t.isConditionalExpression(expr)) {
863
- testExpr = expr.test;
864
- isTernary = true;
865
- } else if (t.isLogicalExpression(expr) && (expr.operator === "&&" || expr.operator === "||")) {
866
- testExpr = expr.left;
867
- } else {
868
- return expr;
869
- }
870
- if (!isPotentiallyReactive(testExpr, state.signalNames, state.importedIdentifiers)) return expr;
871
- const branches = isTernary ? [expr.consequent, expr.alternate] : [expr.right];
872
- if (!branches.some(buildsDOM)) return expr;
873
- const condId = state.nextMemoId();
874
- state.needsMemo = true;
875
- const memoBody = isTernary ? t.unaryExpression("!", t.unaryExpression("!", testExpr)) : testExpr;
876
- statements.push(
877
- t.variableDeclaration("const", [
878
- t.variableDeclarator(
879
- t.identifier(condId),
880
- t.callExpression(t.identifier("_$memo"), [
881
- t.arrowFunctionExpression([], memoBody)
882
- ])
883
- )
884
- ])
885
- );
886
- const condRead = t.callExpression(t.identifier(condId), []);
887
- return isTernary ? t.conditionalExpression(condRead, expr.consequent, expr.alternate) : t.logicalExpression(expr.operator, condRead, expr.right);
888
- }
889
- function applyDynamicChildren(statements, elId, children, parentNode, state) {
890
- const entries = [];
891
- let childIndex = 0;
892
- for (const child of children) {
893
- if (t.isJSXText(child)) {
894
- const text = normalizeJsxText(child.value);
895
- if (text) childIndex++;
896
- continue;
897
- }
898
- if (t.isJSXExpressionContainer(child)) {
899
- if (t.isJSXEmptyExpression(child.expression)) continue;
900
- entries.push({ type: "expression", child, childIndex });
901
- childIndex++;
902
- continue;
903
- }
904
- if (t.isJSXElement(child)) {
905
- const childTag = child.openingElement.name.name;
906
- if (isComponent(childTag) || childTag === "For" || childTag === "Show") {
907
- entries.push({ type: "component", child, childIndex });
908
- childIndex++;
909
- } else {
910
- const hasAnythingDynamic = child.openingElement.attributes.some(isDynamicAttr) || child.openingElement.attributes.some((a) => !t.isJSXSpreadAttribute(a) && getAttrName(a)?.startsWith("on")) || !child.children.every(isStaticChild);
911
- entries.push({ type: "static", child, childIndex, hasAnythingDynamic });
912
- childIndex++;
913
- }
914
- continue;
915
- }
916
- if (t.isJSXFragment(child)) {
917
- entries.push({ type: "fragment", child });
918
- }
919
- }
920
- const entriesNeedingRef = entries.filter(
921
- (e) => e.type === "expression" || e.type === "component" || e.type === "static" && e.hasAnythingDynamic
922
- );
923
- const needsPreCapture = entriesNeedingRef.length >= 2;
924
- const markerVars = /* @__PURE__ */ new Map();
925
- if (needsPreCapture) {
926
- let prevVar = null;
927
- let prevIndex = 0;
928
- for (const entry of entriesNeedingRef) {
929
- const idx = entry.childIndex;
930
- const markerVar = state.nextVarId();
931
- markerVars.set(idx, markerVar);
932
- let init;
933
- if (prevVar === null) {
934
- init = buildChildAccess(elId, idx);
935
- } else {
936
- init = t.identifier(prevVar);
937
- for (let i = prevIndex; i < idx; i++) {
938
- init = t.memberExpression(init, t.identifier("nextSibling"));
939
- }
940
- }
941
- statements.push(
942
- t.variableDeclaration("const", [
943
- t.variableDeclarator(t.identifier(markerVar), init)
944
- ])
945
- );
946
- prevVar = markerVar;
947
- prevIndex = idx;
948
- }
949
- }
950
- function getMarker(idx) {
951
- if (markerVars.has(idx)) {
952
- return t.identifier(markerVars.get(idx));
953
- }
954
- return buildChildAccess(elId, idx);
955
- }
956
- for (const entry of entries) {
957
- if (entry.type === "expression") {
958
- let expr = entry.child.expression;
959
- const marker = getMarker(entry.childIndex);
960
- state.needsInsert = true;
961
- let mapResult = tryLowerMapToMapArray(expr, state);
962
- if (mapResult) {
963
- state.needsMapArray = true;
964
- const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
965
- const isArrowAlready = t.isArrowFunctionExpression(mapResult);
966
- if (isArrowAlready && t.isExpression(mapResult.body)) {
967
- mapResult.body = memoizeBranchCondition(mapResult.body, statements, state);
968
- } else if (!isBareMapArray && !isArrowAlready) {
969
- mapResult = memoizeBranchCondition(mapResult, statements, state);
970
- }
971
- const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
972
- statements.push(
973
- t.expressionStatement(
974
- t.callExpression(t.identifier("_$insert"), [
975
- t.identifier(elId),
976
- insertArg,
977
- marker
978
- ])
979
- )
980
- );
981
- continue;
982
- }
983
- const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
984
- if (isMapArrayCall) {
985
- state.needsMapArray = true;
986
- if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
987
- statements.push(
988
- t.expressionStatement(
989
- t.callExpression(t.identifier("_$insert"), [
990
- t.identifier(elId),
991
- expr,
992
- marker
993
- ])
994
- )
995
- );
996
- continue;
997
- }
998
- if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
999
- expr = memoizeBranchCondition(expr, statements, state);
1000
- const insertCall = t.callExpression(t.identifier("_$insert"), [
1001
- t.identifier(elId),
1002
- t.arrowFunctionExpression([], expr),
1003
- marker
1004
- ]);
1005
- if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
1006
- t.addComment(
1007
- insertCall,
1008
- "leading",
1009
- " @what-dev: reactive wrapping may be unnecessary \u2014 expression contains a non-signal function call with reactive args ",
1010
- false
1011
- );
1012
- }
1013
- statements.push(t.expressionStatement(insertCall));
1014
- } else {
1015
- statements.push(
1016
- t.expressionStatement(
1017
- t.callExpression(t.identifier("_$insert"), [
1018
- t.identifier(elId),
1019
- expr,
1020
- marker
1021
- ])
1022
- )
1023
- );
1024
- }
1025
- continue;
1026
- }
1027
- if (entry.type === "component") {
1028
- const transformed = transformElementFineGrained({ node: entry.child }, state);
1029
- const marker = getMarker(entry.childIndex);
1030
- state.needsInsert = true;
1031
- statements.push(
1032
- t.expressionStatement(
1033
- t.callExpression(t.identifier("_$insert"), [
1034
- t.identifier(elId),
1035
- transformed,
1036
- marker
1037
- ])
1038
- )
1039
- );
1040
- continue;
1041
- }
1042
- if (entry.type === "static" && entry.hasAnythingDynamic) {
1043
- let childElRef;
1044
- if (markerVars.has(entry.childIndex)) {
1045
- childElRef = markerVars.get(entry.childIndex);
1046
- } else {
1047
- childElRef = state.nextVarId();
1048
- statements.push(
1049
- t.variableDeclaration("const", [
1050
- t.variableDeclarator(
1051
- t.identifier(childElRef),
1052
- buildChildAccess(elId, entry.childIndex)
1053
- )
1054
- ])
1055
- );
1056
- }
1057
- applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state, entry.child.openingElement.name.name);
1058
- applyDynamicChildren(statements, childElRef, entry.child.children, entry.child, state);
1059
- continue;
1060
- }
1061
- if (entry.type === "fragment") {
1062
- for (const fChild of entry.child.children) {
1063
- if (t.isJSXExpressionContainer(fChild) && !t.isJSXEmptyExpression(fChild.expression)) {
1064
- state.needsInsert = true;
1065
- let expr = fChild.expression;
1066
- if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1067
- expr = memoizeBranchCondition(expr, statements, state);
1068
- statements.push(
1069
- t.expressionStatement(
1070
- t.callExpression(t.identifier("_$insert"), [
1071
- t.identifier(elId),
1072
- t.arrowFunctionExpression([], expr)
1073
- ])
1074
- )
1075
- );
1076
- } else {
1077
- statements.push(
1078
- t.expressionStatement(
1079
- t.callExpression(t.identifier("_$insert"), [
1080
- t.identifier(elId),
1081
- expr
1082
- ])
1083
- )
1084
- );
1085
- }
1086
- }
1087
- }
1088
- }
1089
- }
1090
- }
1091
- function buildChildAccess(elId, index) {
1092
- if (index === 0) {
1093
- return t.memberExpression(t.identifier(elId), t.identifier("firstChild"));
1094
- }
1095
- let expr = t.memberExpression(t.identifier(elId), t.identifier("firstChild"));
1096
- for (let i = 0; i < index; i++) {
1097
- expr = t.memberExpression(expr, t.identifier("nextSibling"));
1098
- }
1099
- return expr;
1100
- }
1101
- function transformComponentFineGrained(path3, state) {
1102
- const { node } = path3;
1103
- const openingElement = node.openingElement;
1104
- const componentName = openingElement.name.name;
1105
- const attributes = openingElement.attributes;
1106
- const children = node.children;
1107
- let clientDirective = null;
1108
- const filteredAttrs = [];
1109
- for (const attr of attributes) {
1110
- if (t.isJSXAttribute(attr)) {
1111
- let name;
1112
- if (t.isJSXNamespacedName(attr.name)) {
1113
- name = `${attr.name.namespace.name}:${attr.name.name.name}`;
1114
- } else {
1115
- name = attr.name.name;
1116
- }
1117
- if (name && typeof name === "string" && name.startsWith("client:")) {
1118
- const mode = name.slice(7);
1119
- if (attr.value) {
1120
- clientDirective = { type: mode, value: attr.value.value };
1121
- } else {
1122
- clientDirective = { type: mode };
1123
- }
1124
- continue;
1125
- }
1126
- }
1127
- filteredAttrs.push(attr);
1128
- }
1129
- if (clientDirective) {
1130
- state.needsCreateComponent = true;
1131
- state.needsIsland = true;
1132
- const islandProps = [
1133
- t.objectProperty(t.identifier("component"), t.identifier(componentName)),
1134
- t.objectProperty(t.identifier("mode"), t.stringLiteral(clientDirective.type))
1135
- ];
1136
- if (clientDirective.value) {
1137
- islandProps.push(
1138
- t.objectProperty(t.identifier("mediaQuery"), t.stringLiteral(clientDirective.value))
1139
- );
1140
- }
1141
- for (const attr of filteredAttrs) {
1142
- if (t.isJSXSpreadAttribute(attr)) continue;
1143
- const attrName = getAttrName(attr);
1144
- const value = getAttributeValue(attr.value);
1145
- islandProps.push(t.objectProperty(t.identifier(attrName), value));
1146
- }
1147
- return t.callExpression(
1148
- t.identifier("_$createComponent"),
1149
- [t.identifier("Island"), t.objectExpression(islandProps), t.arrayExpression([])]
1150
- );
1151
- }
1152
- state.needsCreateComponent = true;
1153
- const props = [];
1154
- let hasSpread = false;
1155
- let spreadExpr = null;
1156
- for (const attr of filteredAttrs) {
1157
- if (t.isJSXSpreadAttribute(attr)) {
1158
- hasSpread = true;
1159
- spreadExpr = attr.argument;
1160
- continue;
1161
- }
1162
- const attrName = getAttrName(attr);
1163
- if (attrName === "key") continue;
1164
- if (isBindingAttribute(attrName)) {
1165
- const bindProp = getBindingProperty(attrName);
1166
- const signalExpr = attr.value.expression;
1167
- if (bindProp === "value") {
1168
- props.push(
1169
- t.objectProperty(t.identifier("value"), t.callExpression(t.cloneNode(signalExpr), []))
1170
- );
1171
- props.push(
1172
- t.objectProperty(
1173
- t.identifier("onInput"),
1174
- t.arrowFunctionExpression(
1175
- [t.identifier("e")],
1176
- t.callExpression(
1177
- t.memberExpression(t.cloneNode(signalExpr), t.identifier("set")),
1178
- [t.memberExpression(
1179
- t.memberExpression(t.identifier("e"), t.identifier("target")),
1180
- t.identifier("value")
1181
- )]
1182
- )
1183
- )
1184
- )
1185
- );
1186
- } else if (bindProp === "checked") {
1187
- props.push(
1188
- t.objectProperty(t.identifier("checked"), t.callExpression(t.cloneNode(signalExpr), []))
1189
- );
1190
- props.push(
1191
- t.objectProperty(
1192
- t.identifier("onChange"),
1193
- t.arrowFunctionExpression(
1194
- [t.identifier("e")],
1195
- t.callExpression(
1196
- t.memberExpression(t.cloneNode(signalExpr), t.identifier("set")),
1197
- [t.memberExpression(
1198
- t.memberExpression(t.identifier("e"), t.identifier("target")),
1199
- t.identifier("checked")
1200
- )]
1201
- )
1202
- )
1203
- )
1204
- );
1205
- }
1206
- continue;
1207
- }
1208
- if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
1209
- const { eventName, modifiers } = parseEventModifiers(attrName);
1210
- const handler = getAttributeValue(attr.value);
1211
- const wrappedHandler = createEventHandler(handler, modifiers);
1212
- props.push(t.objectProperty(t.identifier(eventName), wrappedHandler));
1213
- continue;
1214
- }
1215
- const value = getAttributeValue(attr.value);
1216
- props.push(
1217
- t.objectProperty(
1218
- /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(attrName) ? t.identifier(attrName) : t.stringLiteral(attrName),
1219
- value
1220
- )
1221
- );
1222
- }
1223
- const transformedChildren = [];
1224
- for (const child of children) {
1225
- if (t.isJSXText(child)) {
1226
- const text = normalizeJsxText(child.value);
1227
- if (text) transformedChildren.push(t.stringLiteral(text));
1228
- } else if (t.isJSXExpressionContainer(child)) {
1229
- if (!t.isJSXEmptyExpression(child.expression)) {
1230
- transformedChildren.push(child.expression);
1231
- }
1232
- } else if (t.isJSXElement(child)) {
1233
- transformedChildren.push(transformElementFineGrained({ node: child }, state));
1234
- } else if (t.isJSXFragment(child)) {
1235
- transformedChildren.push(transformFragmentFineGrained({ node: child }, state));
1236
- }
1237
- }
1238
- let propsExpr;
1239
- if (hasSpread) {
1240
- if (props.length > 0) {
1241
- propsExpr = t.callExpression(
1242
- t.memberExpression(t.identifier("Object"), t.identifier("assign")),
1243
- [t.objectExpression([]), spreadExpr, t.objectExpression(props)]
1244
- );
1245
- } else {
1246
- propsExpr = spreadExpr;
1247
- }
1248
- } else if (props.length > 0) {
1249
- propsExpr = t.objectExpression(props);
1250
- } else {
1251
- propsExpr = t.nullLiteral();
1252
- }
1253
- const childrenArray = transformedChildren.length > 0 ? t.arrayExpression(transformedChildren) : t.arrayExpression([]);
1254
- return t.callExpression(t.identifier("_$createComponent"), [t.identifier(componentName), propsExpr, childrenArray]);
1255
- }
1256
- function transformForFineGrained(path3, state) {
1257
- const { node } = path3;
1258
- const attributes = node.openingElement.attributes;
1259
- const children = node.children;
1260
- if (true) {
1261
- const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
1262
- if (!_forInfoWarned.has(fileName)) {
1263
- _forInfoWarned.add(fileName);
1264
- const loc = node.loc;
1265
- const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
1266
- console.info(
1267
- `[what-compiler] <For> at ${fileName}${lineInfo}: consider using .map() with a key prop instead. The compiler auto-lowers .map() to efficient keyed reconciliation. <For> is only needed for signal-wrapped item accessors (advanced).`
1268
- );
1269
- }
1270
- }
1271
- let eachExpr = null;
1272
- let keyExpr = null;
1273
- for (const attr of attributes) {
1274
- if (t.isJSXAttribute(attr)) {
1275
- const name = getAttrName(attr);
1276
- if (name === "each") eachExpr = getAttributeValue(attr.value);
1277
- else if (name === "key") keyExpr = getAttributeValue(attr.value);
1278
- }
1279
- }
1280
- if (!eachExpr) {
1281
- console.warn('[what-compiler] <For> element missing "each" attribute.');
1282
- state.needsH = true;
1283
- return transformElementAsH(path3, state);
1284
- }
1285
- let renderFn = null;
1286
- for (const child of children) {
1287
- if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
1288
- renderFn = child.expression;
1289
- break;
1290
- }
1291
- }
1292
- if (!renderFn) {
1293
- console.warn("[what-compiler] <For> element missing render function child.");
1294
- state.needsH = true;
1295
- return transformElementAsH(path3, state);
1296
- }
1297
- state.needsMapArray = true;
1298
- const args = [eachExpr, renderFn];
1299
- if (keyExpr) {
1300
- args.push(t.objectExpression([
1301
- t.objectProperty(t.identifier("key"), keyExpr)
1302
- ]));
1303
- }
1304
- return t.callExpression(t.identifier("_$mapArray"), args);
1305
- }
1306
- function transformShowFineGrained(path3, state) {
1307
- const { node } = path3;
1308
- const attributes = node.openingElement.attributes;
1309
- const children = node.children;
1310
- let whenExpr = null;
1311
- let fallbackExpr = null;
1312
- for (const attr of attributes) {
1313
- if (t.isJSXAttribute(attr)) {
1314
- const name = getAttrName(attr);
1315
- if (name === "when") whenExpr = getAttributeValue(attr.value);
1316
- else if (name === "fallback") fallbackExpr = getAttributeValue(attr.value);
1317
- }
1318
- }
1319
- if (!whenExpr) {
1320
- throw path3.buildCodeFrameError(
1321
- '<Show> requires a "when" prop. Example: <Show when={isOpen} fallback={null}>...</Show>'
1322
- );
1323
- }
1324
- let contentExpr = null;
1325
- for (const child of children) {
1326
- if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
1327
- contentExpr = child.expression;
1328
- break;
1329
- }
1330
- }
1331
- if (!contentExpr) {
1332
- const transformedChildren = [];
1333
- for (const child of children) {
1334
- if (t.isJSXText(child)) {
1335
- const text = normalizeJsxText(child.value);
1336
- if (text) transformedChildren.push(t.stringLiteral(text));
1337
- } else if (t.isJSXElement(child)) {
1338
- transformedChildren.push(transformElementFineGrained({ node: child }, state));
1339
- }
1340
- }
1341
- if (transformedChildren.length === 1) {
1342
- contentExpr = transformedChildren[0];
1343
- } else if (transformedChildren.length > 1) {
1344
- contentExpr = t.arrayExpression(transformedChildren);
1345
- } else {
1346
- contentExpr = t.nullLiteral();
1347
- }
1348
- }
1349
- let condition;
1350
- if (t.isCallExpression(whenExpr)) {
1351
- condition = whenExpr;
1352
- } else if (t.isArrowFunctionExpression(whenExpr) && t.isExpression(whenExpr.body)) {
1353
- condition = whenExpr.body;
1354
- } else if (t.isIdentifier(whenExpr) && (state.signalNames && isSignalIdentifier(whenExpr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(whenExpr.name))) {
1355
- condition = t.callExpression(whenExpr, []);
1356
- } else {
1357
- condition = whenExpr;
1358
- }
1359
- const vId = path3.scope ? path3.scope.generateUidIdentifier("v") : t.identifier("_v");
1360
- const contentIsFn = t.isFunction(contentExpr);
1361
- const consequent = contentIsFn ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
1362
- const alternate = fallbackExpr || t.nullLiteral();
1363
- if (isPotentiallyReactive(condition, state.signalNames, state.importedIdentifiers)) {
1364
- const condId = state.nextMemoId();
1365
- state.needsMemo = true;
1366
- const memoBody = contentIsFn ? condition : t.unaryExpression("!", t.unaryExpression("!", condition));
1367
- if (!state._pendingSetup) state._pendingSetup = [];
1368
- state._pendingSetup.push(
1369
- t.variableDeclaration("const", [
1370
- t.variableDeclarator(
1371
- t.identifier(condId),
1372
- t.callExpression(t.identifier("_$memo"), [
1373
- t.arrowFunctionExpression([], memoBody)
1374
- ])
1375
- )
1376
- ])
1377
- );
1378
- condition = t.callExpression(t.identifier(condId), []);
1379
- }
1380
- return t.arrowFunctionExpression([], t.blockStatement([
1381
- t.variableDeclaration("const", [
1382
- t.variableDeclarator(vId, condition)
1383
- ]),
1384
- t.returnStatement(
1385
- t.conditionalExpression(t.cloneNode(vId), consequent, alternate)
1386
- )
1387
- ]));
1388
- }
1389
- function lowerFragmentExprChild(expr, state) {
1390
- if (!state._pendingSetup) state._pendingSetup = [];
1391
- const setup = state._pendingSetup;
1392
- const mapResult = tryLowerMapToMapArray(expr, state);
1393
- if (mapResult) {
1394
- state.needsMapArray = true;
1395
- const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
1396
- const isArrowAlready = t.isArrowFunctionExpression(mapResult);
1397
- if (isArrowAlready && t.isExpression(mapResult.body)) {
1398
- mapResult.body = memoizeBranchCondition(mapResult.body, setup, state);
1399
- return mapResult;
1400
- }
1401
- if (isBareMapArray) return mapResult;
1402
- const memoized = memoizeBranchCondition(mapResult, setup, state);
1403
- return t.arrowFunctionExpression([], memoized);
1404
- }
1405
- const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
1406
- if (isMapArrayCall) {
1407
- state.needsMapArray = true;
1408
- if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
1409
- return expr;
1410
- }
1411
- if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1412
- expr = memoizeBranchCondition(expr, setup, state);
1413
- return t.arrowFunctionExpression([], expr);
1414
- }
1415
- return expr;
1416
- }
1417
- function transformFragmentFineGrained(path3, state) {
1418
- const { node } = path3;
1419
- const children = node.children;
1420
- const transformed = [];
1421
- for (const child of children) {
1422
- if (t.isJSXText(child)) {
1423
- const text = normalizeJsxText(child.value);
1424
- if (text) transformed.push(t.stringLiteral(text));
1425
- } else if (t.isJSXExpressionContainer(child)) {
1426
- if (!t.isJSXEmptyExpression(child.expression)) {
1427
- transformed.push(lowerFragmentExprChild(child.expression, state));
1428
- }
1429
- } else if (t.isJSXElement(child)) {
1430
- transformed.push(transformElementFineGrained({ node: child }, state));
1431
- } else if (t.isJSXFragment(child)) {
1432
- transformed.push(transformFragmentFineGrained({ node: child }, state));
1433
- }
1434
- }
1435
- if (transformed.length === 1) return transformed[0];
1436
- return t.arrayExpression(transformed);
1437
- }
1438
- function getOrCreateTemplate(state, html) {
1439
- if (state.templateMap.has(html)) {
1440
- return state.templateMap.get(html);
1441
- }
1442
- const id = `_tmpl$${state.templateCount++}`;
1443
- state.templateMap.set(html, id);
1444
- state.templates.push({ id, html });
1445
- return id;
1446
- }
1447
- function transformJsxRoot(path3, state, transform) {
1448
- const scope = path3.scope;
1449
- let cache = state._signalNamesCache;
1450
- if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
1451
- let names = cache.get(scope);
1452
- if (!names) {
1453
- names = collectSignalNamesFromScope(path3);
1454
- cache.set(scope, names);
1455
- }
1456
- state.signalNames = names;
1457
- state._pendingSetup = [];
1458
- const transformed = transform(path3, state);
1459
- const pending = state._pendingSetup;
1460
- state._pendingSetup = [];
1461
- if (pending.length > 0) {
1462
- let stmtPath = path3;
1463
- let crossedFunctionBoundary = false;
1464
- while (stmtPath && !stmtPath.isStatement()) {
1465
- if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
1466
- crossedFunctionBoundary = true;
1467
- }
1468
- stmtPath = stmtPath.parentPath;
1469
- }
1470
- const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
1471
- if (inStatementList && !crossedFunctionBoundary) {
1472
- stmtPath.insertBefore(pending);
1473
- path3.replaceWith(transformed);
1474
- } else {
1475
- pending.push(t.returnStatement(transformed));
1476
- path3.replaceWith(
1477
- t.callExpression(
1478
- t.arrowFunctionExpression([], t.blockStatement(pending)),
1479
- []
1480
- )
1481
- );
1482
- }
1483
- } else {
1484
- path3.replaceWith(transformed);
1485
- }
1486
- }
1487
- return {
1488
- name: "what-jsx-transform",
1489
- visitor: {
1490
- Program: {
1491
- enter(path3, state) {
1492
- state.needsTemplate = false;
1493
- state.needsInsert = false;
1494
- state.needsEffect = false;
1495
- state.needsMapArray = false;
1496
- state.needsSpread = false;
1497
- state.needsSetProp = false;
1498
- state.needsMemo = false;
1499
- state.needsSetClass = false;
1500
- state.needsSetStyle = false;
1501
- state.needsSetAttr = false;
1502
- state.needsSetValue = false;
1503
- state.needsSetChecked = false;
1504
- state.needsH = false;
1505
- state.needsCreateComponent = false;
1506
- state.needsFragment = false;
1507
- state.needsIsland = false;
1508
- state.needsDelegation = false;
1509
- state.delegatedEvents = /* @__PURE__ */ new Set();
1510
- state.templates = [];
1511
- state.templateMap = /* @__PURE__ */ new Map();
1512
- state.templateCount = 0;
1513
- state._varCounter = 0;
1514
- state._memoCounter = 0;
1515
- state._pendingSetup = [];
1516
- state.nextVarId = () => `_el$${state._varCounter++}`;
1517
- state.nextMemoId = () => `_c$${state._memoCounter++}`;
1518
- state.signalNames = /* @__PURE__ */ new Set();
1519
- state.importedIdentifiers = /* @__PURE__ */ new Set();
1520
- for (const node of path3.node.body) {
1521
- if (t.isImportDeclaration(node)) {
1522
- const source = node.source.value;
1523
- const isReactiveSource = source === "what-framework" || source.startsWith("what-framework/") || source === "what-core" || source.startsWith("what-core/") || source.startsWith("./") || source.startsWith("../");
1524
- for (const spec of node.specifiers) {
1525
- let localName = null;
1526
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.local)) {
1527
- localName = spec.local.name;
1528
- } else if (t.isImportDefaultSpecifier(spec) && t.isIdentifier(spec.local)) {
1529
- localName = spec.local.name;
1530
- } else if (t.isImportNamespaceSpecifier(spec) && t.isIdentifier(spec.local)) {
1531
- localName = spec.local.name;
1532
- }
1533
- if (localName) {
1534
- if (isReactiveSource || /^(use|create)[A-Z]/.test(localName)) {
1535
- state.importedIdentifiers.add(localName);
1536
- }
1537
- }
1538
- }
1539
- }
1540
- }
1541
- path3.traverse({
1542
- VariableDeclarator(declPath) {
1543
- const init = declPath.node.init;
1544
- if (!init || !t.isCallExpression(init)) return;
1545
- const callee = init.callee;
1546
- let calleeName = "";
1547
- if (t.isIdentifier(callee)) {
1548
- calleeName = callee.name;
1549
- } else if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
1550
- calleeName = callee.property.name;
1551
- }
1552
- if (SIGNAL_CREATORS.has(calleeName)) {
1553
- const id = declPath.node.id;
1554
- if (t.isIdentifier(id)) {
1555
- state.signalNames.add(id.name);
1556
- } else if (t.isArrayPattern(id)) {
1557
- for (const el of id.elements) {
1558
- if (t.isIdentifier(el)) state.signalNames.add(el.name);
1559
- }
1560
- } else if (t.isObjectPattern(id)) {
1561
- for (const prop of id.properties) {
1562
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
1563
- state.signalNames.add(prop.value.name);
1564
- }
1565
- }
1566
- }
1567
- }
1568
- }
1569
- });
1570
- },
1571
- exit(path3, state) {
1572
- for (const tmpl of state.templates.reverse()) {
1573
- const tmplCall = t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)]);
1574
- t.addComment(tmplCall, "leading", " @__PURE__ ");
1575
- path3.unshiftContainer(
1576
- "body",
1577
- t.variableDeclaration("const", [
1578
- t.variableDeclarator(t.identifier(tmpl.id), tmplCall)
1579
- ])
1580
- );
1581
- }
1582
- const fgSpecifiers = [];
1583
- if (state.needsTemplate) {
1584
- fgSpecifiers.push(
1585
- t.importSpecifier(t.identifier("_$template"), t.identifier("_$template"))
1586
- );
1587
- }
1588
- if (state.needsInsert) {
1589
- fgSpecifiers.push(
1590
- t.importSpecifier(t.identifier("_$insert"), t.identifier("insert"))
1591
- );
1592
- }
1593
- if (state.needsEffect) {
1594
- fgSpecifiers.push(
1595
- t.importSpecifier(t.identifier("_$effect"), t.identifier("effect"))
1596
- );
1597
- }
1598
- if (state.needsMapArray) {
1599
- fgSpecifiers.push(
1600
- t.importSpecifier(t.identifier("_$mapArray"), t.identifier("mapArray"))
1601
- );
1602
- }
1603
- if (state.needsSpread) {
1604
- fgSpecifiers.push(
1605
- t.importSpecifier(t.identifier("_$spread"), t.identifier("spread"))
1606
- );
1607
- }
1608
- if (state.needsSetProp) {
1609
- fgSpecifiers.push(
1610
- t.importSpecifier(t.identifier("_$setProp"), t.identifier("setProp"))
1611
- );
1612
- }
1613
- if (state.needsMemo) {
1614
- fgSpecifiers.push(
1615
- t.importSpecifier(t.identifier("_$memo"), t.identifier("memo"))
1616
- );
1617
- }
1618
- if (state.needsSetClass) {
1619
- fgSpecifiers.push(
1620
- t.importSpecifier(t.identifier("_$setClass"), t.identifier("setClass"))
1621
- );
1622
- }
1623
- if (state.needsSetStyle) {
1624
- fgSpecifiers.push(
1625
- t.importSpecifier(t.identifier("_$setStyle"), t.identifier("setStyle"))
1626
- );
1627
- }
1628
- if (state.needsSetAttr) {
1629
- fgSpecifiers.push(
1630
- t.importSpecifier(t.identifier("_$setAttr"), t.identifier("setAttr"))
1631
- );
1632
- }
1633
- if (state.needsSetValue) {
1634
- fgSpecifiers.push(
1635
- t.importSpecifier(t.identifier("_$setValue"), t.identifier("setValue"))
1636
- );
1637
- }
1638
- if (state.needsSetChecked) {
1639
- fgSpecifiers.push(
1640
- t.importSpecifier(t.identifier("_$setChecked"), t.identifier("setChecked"))
1641
- );
1642
- }
1643
- if (state.needsCreateComponent) {
1644
- fgSpecifiers.push(
1645
- t.importSpecifier(t.identifier("_$createComponent"), t.identifier("_$createComponent"))
1646
- );
1647
- }
1648
- if (state.needsDelegation) {
1649
- fgSpecifiers.push(
1650
- t.importSpecifier(t.identifier("_$delegateEvents"), t.identifier("delegateEvents"))
1651
- );
1652
- }
1653
- const coreSpecifiers = [];
1654
- if (state.needsH) {
1655
- coreSpecifiers.push(
1656
- t.importSpecifier(t.identifier("h"), t.identifier("h"))
1657
- );
1658
- }
1659
- if (state.needsFragment) {
1660
- coreSpecifiers.push(
1661
- t.importSpecifier(t.identifier("Fragment"), t.identifier("Fragment"))
1662
- );
1663
- }
1664
- if (state.needsIsland) {
1665
- coreSpecifiers.push(
1666
- t.importSpecifier(t.identifier("Island"), t.identifier("Island"))
1667
- );
1668
- }
1669
- if (fgSpecifiers.length > 0) {
1670
- let existingRenderImport = null;
1671
- for (const node of path3.node.body) {
1672
- if (t.isImportDeclaration(node) && (node.source.value === "what-framework/render" || node.source.value === "what-core/render")) {
1673
- existingRenderImport = node;
1674
- break;
1675
- }
1676
- }
1677
- if (existingRenderImport) {
1678
- const existingNames = new Set(
1679
- existingRenderImport.specifiers.filter((s) => t.isImportSpecifier(s)).map((s) => s.imported.name)
1680
- );
1681
- for (const spec of fgSpecifiers) {
1682
- if (!existingNames.has(spec.imported.name)) {
1683
- existingRenderImport.specifiers.push(spec);
1684
- }
1685
- }
1686
- } else {
1687
- path3.unshiftContainer(
1688
- "body",
1689
- t.importDeclaration(fgSpecifiers, t.stringLiteral("what-framework/render"))
1690
- );
1691
- }
1692
- }
1693
- if (coreSpecifiers.length > 0) {
1694
- addCoreImports(path3, t, coreSpecifiers);
1695
- }
1696
- if (state.needsDelegation && state.delegatedEvents && state.delegatedEvents.size > 0) {
1697
- const eventArray = t.arrayExpression(
1698
- [...state.delegatedEvents].map((e) => t.stringLiteral(e))
1699
- );
1700
- const helperFn = t.functionDeclaration(
1701
- t.identifier("_$delegate$"),
1702
- [],
1703
- t.blockStatement([
1704
- t.ifStatement(
1705
- t.identifier("_$delegated$"),
1706
- t.returnStatement()
1707
- ),
1708
- t.expressionStatement(
1709
- t.assignmentExpression("=", t.identifier("_$delegated$"), t.booleanLiteral(true))
1710
- ),
1711
- t.expressionStatement(
1712
- t.callExpression(t.identifier("_$delegateEvents"), [eventArray])
1713
- )
1714
- ])
1715
- );
1716
- path3.unshiftContainer("body", [
1717
- t.variableDeclaration("let", [
1718
- t.variableDeclarator(t.identifier("_$delegated$"), t.booleanLiteral(false))
1719
- ]),
1720
- helperFn
1721
- ]);
1722
- }
1723
- }
1724
- },
1725
- JSXElement(path3, state) {
1726
- transformJsxRoot(path3, state, transformElementFineGrained);
1727
- },
1728
- JSXFragment(path3, state) {
1729
- transformJsxRoot(path3, state, transformFragmentFineGrained);
1730
- }
1731
- }
1732
- };
1733
- }
1734
- function addCoreImports(path3, t, coreSpecifiers) {
1735
- let existingImport = null;
1736
- for (const node of path3.node.body) {
1737
- if (t.isImportDeclaration(node) && (node.source.value === "what-core" || node.source.value === "what-framework")) {
1738
- existingImport = node;
1739
- break;
1740
- }
1741
- }
1742
- if (existingImport) {
1743
- const existingNames = new Set(
1744
- existingImport.specifiers.filter((s) => t.isImportSpecifier(s)).map((s) => s.imported.name)
1745
- );
1746
- for (const spec of coreSpecifiers) {
1747
- if (!existingNames.has(spec.imported.name)) {
1748
- existingImport.specifiers.push(spec);
1749
- }
1750
- }
1751
- } else {
1752
- const importDecl = t.importDeclaration(
1753
- coreSpecifiers,
1754
- t.stringLiteral("what-framework")
1755
- );
1756
- path3.unshiftContainer("body", importDecl);
1757
- }
1758
- }
1759
-
1760
- // packages/compiler/src/vite-plugin.js
1761
- import path2 from "path";
1762
- import { transformSync } from "@babel/core";
1763
-
1764
- // packages/compiler/src/file-router.js
1765
- import fs from "fs";
1766
- import path from "path";
1767
- var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx", ".js", ".ts"]);
1768
- var IGNORED_FILES = /* @__PURE__ */ new Set(["_layout", "_error", "_loading", "_404"]);
1769
- function scanPages(pagesDir) {
1770
- const pages = [];
1771
- const layouts = [];
1772
- const apiRoutes = [];
1773
- function walk(dir, urlPrefix = "") {
1774
- if (!fs.existsSync(dir)) return;
1775
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1776
- for (const entry of entries) {
1777
- const fullPath = path.join(dir, entry.name);
1778
- if (entry.isDirectory()) {
1779
- const groupMatch = entry.name.match(/^\((.+)\)$/);
1780
- if (groupMatch) {
1781
- walk(fullPath, urlPrefix);
1782
- continue;
1783
- }
1784
- if (entry.name === "api" && urlPrefix === "") {
1785
- walkApi(fullPath, "/api");
1786
- continue;
1787
- }
1788
- walk(fullPath, urlPrefix + "/" + fileNameToSegment(entry.name));
1789
- continue;
1790
- }
1791
- const ext = path.extname(entry.name);
1792
- if (!PAGE_EXTENSIONS.has(ext)) continue;
1793
- const baseName = path.basename(entry.name, ext);
1794
- if (baseName === "_layout") {
1795
- layouts.push({
1796
- filePath: fullPath,
1797
- urlPrefix: urlPrefix || "/"
1798
- });
1799
- continue;
1800
- }
1801
- if (IGNORED_FILES.has(baseName)) continue;
1802
- const urlSegment = fileNameToSegment(baseName);
1803
- const routePath = baseName === "index" ? urlPrefix || "/" : urlPrefix + "/" + urlSegment;
1804
- pages.push({
1805
- filePath: fullPath,
1806
- routePath: normalizePath(routePath),
1807
- isDynamic: routePath.includes(":") || routePath.includes("*")
1808
- });
1809
- }
1810
- }
1811
- function walkApi(dir, urlPrefix) {
1812
- if (!fs.existsSync(dir)) return;
1813
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1814
- for (const entry of entries) {
1815
- const fullPath = path.join(dir, entry.name);
1816
- if (entry.isDirectory()) {
1817
- walkApi(fullPath, urlPrefix + "/" + fileNameToSegment(entry.name));
1818
- continue;
1819
- }
1820
- const ext = path.extname(entry.name);
1821
- if (!PAGE_EXTENSIONS.has(ext)) continue;
1822
- const baseName = path.basename(entry.name, ext);
1823
- const segment = fileNameToSegment(baseName);
1824
- const routePath = baseName === "index" ? urlPrefix : urlPrefix + "/" + segment;
1825
- apiRoutes.push({
1826
- filePath: fullPath,
1827
- routePath: normalizePath(routePath)
1828
- });
1829
- }
1830
- }
1831
- walk(pagesDir);
1832
- pages.sort((a, b) => {
1833
- const aWeight = routeWeight(a.routePath);
1834
- const bWeight = routeWeight(b.routePath);
1835
- return aWeight - bWeight;
1836
- });
1837
- return { pages, layouts, apiRoutes };
1838
- }
1839
- function fileNameToSegment(name) {
1840
- const catchAll = name.match(/^\[\.\.\.(\w+)\]$/);
1841
- if (catchAll) return "*" + catchAll[1];
1842
- const dynamic = name.match(/^\[(\w+)\]$/);
1843
- if (dynamic) return ":" + dynamic[1];
1844
- return name.toLowerCase();
1845
- }
1846
- function normalizePath(p) {
1847
- let result = p.replace(/\/+/g, "/");
1848
- if (result.length > 1 && result.endsWith("/")) {
1849
- result = result.slice(0, -1);
1850
- }
1851
- return result || "/";
1852
- }
1853
- function routeWeight(path3) {
1854
- if (path3.includes("*")) return 100;
1855
- if (path3.includes(":")) return 10;
1856
- return 0;
1857
- }
1858
- function extractPageConfig(source) {
1859
- const match = source.match(
1860
- /export\s+const\s+page\s*=\s*(\{[^}]*\})/s
1861
- );
1862
- if (!match) {
1863
- return { mode: "client" };
1864
- }
1865
- try {
1866
- const obj = match[1].replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":').replace(/,\s*}/g, "}").replace(/\/\/[^\n]*/g, "");
1867
- return { mode: "client", ...JSON.parse(obj) };
1868
- } catch {
1869
- return { mode: "client" };
1870
- }
1871
- }
1872
- function detectPageExports(source) {
1873
- return {
1874
- hasLoader: /export\s+(?:async\s+)?(?:const|let|var|function)\s+loader\b/.test(source),
1875
- hasGetStaticPaths: /export\s+(?:async\s+)?(?:const|let|var|function)\s+getStaticPaths\b/.test(source),
1876
- hasPageConfig: /export\s+const\s+page\b/.test(source)
1877
- };
1878
- }
1879
- function generateRoutesModule(pagesDir, rootDir) {
1880
- const { pages, layouts, apiRoutes } = scanPages(pagesDir);
1881
- const imports = [];
1882
- const routeEntries = [];
1883
- const layoutMap = /* @__PURE__ */ new Map();
1884
- layouts.forEach((layout, i) => {
1885
- const varName = `_layout${i}`;
1886
- const relPath = toImportPath(layout.filePath, rootDir);
1887
- imports.push(`import ${varName} from '${relPath}';`);
1888
- layoutMap.set(layout.urlPrefix, varName);
1889
- });
1890
- pages.forEach((page, i) => {
1891
- const varName = `_page${i}`;
1892
- const relPath = toImportPath(page.filePath, rootDir);
1893
- imports.push(`import ${varName} from '${relPath}';`);
1894
- let pageConfig = { mode: "client" };
1895
- let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
1896
- try {
1897
- const source = fs.readFileSync(page.filePath, "utf-8");
1898
- pageConfig = extractPageConfig(source);
1899
- detected = detectPageExports(source);
1900
- } catch {
1901
- }
1902
- const layoutVar = findLayout(page.routePath, layoutMap);
1903
- const entry = {
1904
- path: page.routePath,
1905
- component: varName,
1906
- mode: pageConfig.mode || "client",
1907
- layout: layoutVar || null,
1908
- hasLoader: detected.hasLoader
1909
- };
1910
- routeEntries.push(entry);
1911
- });
1912
- const apiEntries = [];
1913
- apiRoutes.forEach((route, i) => {
1914
- const varName = `_api${i}`;
1915
- const relPath = toImportPath(route.filePath, rootDir);
1916
- imports.push(`import * as ${varName} from '${relPath}';`);
1917
- apiEntries.push({
1918
- path: route.routePath,
1919
- handlers: varName
1920
- });
1921
- });
1922
- const lines = [
1923
- "// Auto-generated by What Framework file router",
1924
- "// Do not edit \u2014 changes will be overwritten",
1925
- "",
1926
- ...imports,
1927
- "",
1928
- "export const routes = [",
1929
- ...routeEntries.map(
1930
- (r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""}${r.hasLoader ? ", hasLoader: true" : ""} },`
1931
- ),
1932
- "];",
1933
- "",
1934
- `export const apiRoutes = [`,
1935
- ...apiEntries.map(
1936
- (r) => ` { path: '${r.path}', handlers: ${r.handlers} },`
1937
- ),
1938
- "];",
1939
- "",
1940
- // Export page modes for the build system
1941
- "export const pageModes = {",
1942
- ...routeEntries.map(
1943
- (r) => ` '${r.path}': '${r.mode}',`
1944
- ),
1945
- "};"
1946
- ];
1947
- return lines.join("\n");
1948
- }
1949
- function toImportPath(filePath, rootDir) {
1950
- const rel = path.relative(rootDir, filePath);
1951
- return "/" + rel.split(path.sep).join("/");
1952
- }
1953
- function findLayout(routePath, layoutMap) {
1954
- const segments = routePath.split("/").filter(Boolean);
1955
- while (segments.length > 0) {
1956
- const prefix = "/" + segments.join("/");
1957
- if (layoutMap.has(prefix)) return layoutMap.get(prefix);
1958
- segments.pop();
1959
- }
1960
- if (layoutMap.has("/")) return layoutMap.get("/");
1961
- return null;
1962
- }
1963
-
1964
- // packages/compiler/src/error-overlay.js
1965
- var OVERLAY_STYLES = `
1966
- :host {
1967
- position: fixed;
1968
- inset: 0;
1969
- z-index: 99999;
1970
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
1971
- }
1972
-
1973
- .backdrop {
1974
- position: fixed;
1975
- inset: 0;
1976
- background: rgba(0, 0, 0, 0.66);
1977
- }
1978
-
1979
- .panel {
1980
- position: fixed;
1981
- inset: 2rem;
1982
- overflow: auto;
1983
- background: #1a1a2e;
1984
- border: 1px solid #2a2a4a;
1985
- border-radius: 12px;
1986
- box-shadow: 0 25px 80px rgba(0, 0, 0, 0.5);
1987
- color: #e0e0e0;
1988
- }
1989
-
1990
- .header {
1991
- display: flex;
1992
- align-items: center;
1993
- justify-content: space-between;
1994
- padding: 1rem 1.5rem;
1995
- border-bottom: 1px solid #2a2a4a;
1996
- background: #16163a;
1997
- border-radius: 12px 12px 0 0;
1998
- }
1999
-
2000
- .header-left {
2001
- display: flex;
2002
- align-items: center;
2003
- gap: 0.75rem;
2004
- }
2005
-
2006
- .header-right {
2007
- display: flex;
2008
- align-items: center;
2009
- gap: 0.5rem;
2010
- }
2011
-
2012
- .logo {
2013
- width: 28px;
2014
- height: 28px;
2015
- background: linear-gradient(135deg, #2563eb, #1d4ed8);
2016
- border-radius: 6px;
2017
- display: grid;
2018
- place-items: center;
2019
- font-weight: 800;
2020
- font-size: 14px;
2021
- color: #fff;
2022
- }
2023
-
2024
- .brand {
2025
- font-size: 14px;
2026
- font-weight: 600;
2027
- color: #a0a0c0;
2028
- }
2029
-
2030
- .tag {
2031
- font-size: 11px;
2032
- padding: 2px 8px;
2033
- border-radius: 4px;
2034
- font-weight: 600;
2035
- }
2036
-
2037
- .tag-error {
2038
- background: #3b1219;
2039
- color: #f87171;
2040
- }
2041
-
2042
- .tag-warning {
2043
- background: #3b2f19;
2044
- color: #fbbf24;
2045
- }
2046
-
2047
- .close-btn, .copy-btn {
2048
- background: none;
2049
- border: 1px solid #3a3a5a;
2050
- color: #a0a0c0;
2051
- border-radius: 6px;
2052
- padding: 4px 12px;
2053
- cursor: pointer;
2054
- font-family: inherit;
2055
- font-size: 12px;
2056
- }
2057
-
2058
- .close-btn:hover, .copy-btn:hover {
2059
- background: #2a2a4a;
2060
- color: #fff;
2061
- }
2062
-
2063
- .copy-btn.copied {
2064
- border-color: #22c55e;
2065
- color: #22c55e;
2066
- }
2067
-
2068
- .body {
2069
- padding: 1.5rem;
2070
- }
2071
-
2072
- .error-title {
2073
- font-size: 16px;
2074
- font-weight: 700;
2075
- color: #f87171;
2076
- margin: 0 0 0.5rem;
2077
- }
2078
-
2079
- .error-message {
2080
- font-size: 14px;
2081
- color: #e0e0e0;
2082
- margin: 0 0 1rem;
2083
- line-height: 1.6;
2084
- white-space: pre-wrap;
2085
- }
2086
-
2087
- .file-path {
2088
- display: inline-flex;
2089
- align-items: center;
2090
- gap: 0.5rem;
2091
- font-size: 12px;
2092
- color: #818cf8;
2093
- margin-bottom: 1rem;
2094
- padding: 0.25rem 0;
2095
- }
2096
-
2097
- .code-frame {
2098
- background: #0d0d1a;
2099
- border: 1px solid #2a2a4a;
2100
- border-radius: 8px;
2101
- overflow-x: auto;
2102
- margin-bottom: 1rem;
2103
- }
2104
-
2105
- .code-line {
2106
- display: flex;
2107
- padding: 0 1rem;
2108
- font-size: 13px;
2109
- line-height: 1.7;
2110
- }
2111
-
2112
- .code-line.highlight {
2113
- background: rgba(248, 113, 113, 0.1);
2114
- }
2115
-
2116
- .line-number {
2117
- color: #4a4a6a;
2118
- min-width: 3ch;
2119
- text-align: right;
2120
- margin-right: 1rem;
2121
- user-select: none;
2122
- }
2123
-
2124
- .line-content {
2125
- white-space: pre;
2126
- }
2127
-
2128
- .tip {
2129
- margin-top: 1rem;
2130
- padding: 0.75rem 1rem;
2131
- background: #1a2744;
2132
- border: 1px solid #1e3a5f;
2133
- border-radius: 8px;
2134
- font-size: 13px;
2135
- color: #93c5fd;
2136
- line-height: 1.5;
2137
- }
2138
-
2139
- .tip-label {
2140
- font-weight: 700;
2141
- color: #60a5fa;
2142
- }
2143
-
2144
- .stack {
2145
- margin-top: 1rem;
2146
- font-size: 12px;
2147
- color: #6a6a8a;
2148
- white-space: pre-wrap;
2149
- line-height: 1.5;
2150
- }
2151
- `;
2152
- var OVERLAY_ELEMENT = `
2153
- class WhatErrorOverlay extends HTMLElement {
2154
- constructor(err) {
2155
- super();
2156
- this.root = this.attachShadow({ mode: 'open' });
2157
- this.root.innerHTML = '<style>${OVERLAY_STYLES}</style>';
2158
- this._err = err;
2159
- this.show(err);
2160
- }
2161
-
2162
- // --- Inlined helper: escapeHTML ---
2163
- _escapeHTML(str) {
2164
- return String(str)
2165
- .replace(/&/g, '&amp;')
2166
- .replace(/</g, '&lt;')
2167
- .replace(/>/g, '&gt;')
2168
- .replace(/"/g, '&quot;');
2169
- }
2170
-
2171
- // --- Inlined helper: cleanStack ---
2172
- _cleanStack(stack) {
2173
- return stack
2174
- .split('\\n')
2175
- .filter(function(line) { return line.indexOf('node_modules') === -1; })
2176
- .slice(0, 10)
2177
- .join('\\n');
2178
- }
2179
-
2180
- // --- Inlined helper: getTip ---
2181
- _getTip(err) {
2182
- var msg = (err.message || '').toLowerCase();
2183
-
2184
- if (msg.indexOf('infinite') !== -1 && msg.indexOf('effect') !== -1) {
2185
- return 'An effect is writing to a signal it also reads. Use untrack() to read without subscribing, or move the write to a different effect.';
2186
- }
2187
- if (msg.indexOf('jsx') !== -1 && msg.indexOf('unexpected') !== -1) {
2188
- return 'Make sure your vite.config includes the What compiler plugin: import what from "what-compiler/vite"';
2189
- }
2190
- if (msg.indexOf('not a function') !== -1 && msg.indexOf('signal') !== -1) {
2191
- return 'Signals are functions: call sig() to read, sig(value) to write. Check you are not destructuring a signal.';
2192
- }
2193
- if (msg.indexOf('hydrat') !== -1) {
2194
- return 'Hydration mismatches happen when SSR output differs from client render. Ensure server and client see the same initial state.';
2195
- }
2196
- // New tips for common mistakes
2197
- if (msg.indexOf('signal') !== -1 && msg.indexOf('without') !== -1 && msg.indexOf('call') !== -1) {
2198
- return 'Signals must be called to read their value. Use {count()} in JSX, not {count}. The parentheses trigger the reactive subscription.';
2199
- }
2200
- if (msg.indexOf('innerhtml') !== -1 && msg.indexOf('__html') !== -1) {
2201
- return 'Raw innerHTML is blocked for security. Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead.';
2202
- }
2203
- if ((msg.indexOf('innerhtml') !== -1 || msg.indexOf('xss') !== -1) && msg.indexOf('raw string') !== -1) {
2204
- return 'Raw innerHTML is a security risk (XSS). Wrap your HTML in an object: innerHTML={{ __html: yourString }}.';
2205
- }
2206
- if (msg.indexOf('cleanup') !== -1 && (msg.indexOf('effect') !== -1 || msg.indexOf('listener') !== -1)) {
2207
- return 'Effects that add event listeners or timers should return a cleanup function: effect(() => { el.addEventListener(...); return () => el.removeEventListener(...); })';
2208
- }
2209
- if (msg.indexOf('route') !== -1 && (msg.indexOf('not found') !== -1 || msg.indexOf('404') !== -1 || msg.indexOf('no match') !== -1)) {
2210
- return 'No route matched the current URL. Check that your route paths are correct and you have a catch-all or 404 route defined.';
2211
- }
2212
- if (msg.indexOf('key') !== -1 && (msg.indexOf('missing') !== -1 || msg.indexOf('list') !== -1 || msg.indexOf('each') !== -1)) {
2213
- return 'Lists need unique keys for efficient DOM updates. Add a key prop: items.map(item => <Item key={item.id} />)';
2214
- }
2215
- return '';
2216
- }
2217
-
2218
- // --- Build overlay HTML ---
2219
- _buildHTML(err) {
2220
- var isCompilerError = err._isCompilerError || err.plugin === 'vite-plugin-what';
2221
- var type = isCompilerError ? 'Compiler Error' : 'Runtime Error';
2222
- var tagClass = isCompilerError ? 'tag-error' : 'tag-warning';
2223
-
2224
- var codeFrame = '';
2225
- var rawFrame = err.frame || err._frame;
2226
- if (rawFrame) {
2227
- var lines = rawFrame.split('\\n');
2228
- var frameLines = '';
2229
- for (var i = 0; i < lines.length; i++) {
2230
- var line = lines[i];
2231
- var isHighlight = line.trimStart().startsWith('>');
2232
- var cleaned = line.replace(/^\\s*>\\s?/, ' ').replace(/^\\s{2}/, '');
2233
- var match = cleaned.match(/^(\\s*\\d+)\\s*\\|(.*)$/);
2234
- if (match) {
2235
- frameLines += '<div class="code-line' + (isHighlight ? ' highlight' : '') + '"><span class="line-number">' + match[1].trim() + '</span><span class="line-content">' + this._escapeHTML(match[2]) + '</span></div>';
2236
- } else if (cleaned.trim().startsWith('|')) {
2237
- frameLines += '<div class="code-line highlight"><span class="line-number"></span><span class="line-content" style="color:#f87171">' + this._escapeHTML(cleaned.replace(/^\\s*\\|/, '')) + '</span></div>';
2238
- }
2239
- }
2240
- if (frameLines) {
2241
- codeFrame = '<div class="code-frame">' + frameLines + '</div>';
2242
- }
2243
- }
2244
-
2245
- var filePath = err.id || (err.loc && err.loc.file) || '';
2246
- var lineNum = (err.loc && err.loc.line != null) ? err.loc.line : '';
2247
- var col = (err.loc && err.loc.column != null) ? err.loc.column : '';
2248
- var location = filePath
2249
- ? '<div class="file-path">' + this._escapeHTML(filePath) + (lineNum ? ':' + lineNum : '') + (col ? ':' + col : '') + '</div>'
2250
- : '';
2251
-
2252
- var tip = this._getTip(err);
2253
- var tipHTML = tip ? '<div class="tip"><span class="tip-label">Tip: </span>' + this._escapeHTML(tip) + '</div>' : '';
2254
-
2255
- var stack = (err.stack && !isCompilerError)
2256
- ? '<div class="stack">' + this._escapeHTML(this._cleanStack(err.stack)) + '</div>'
2257
- : '';
2258
-
2259
- return '<div class="backdrop"></div>'
2260
- + '<div class="panel">'
2261
- + '<div class="header">'
2262
- + '<div class="header-left">'
2263
- + '<div class="logo">W</div>'
2264
- + '<span class="brand">What Framework</span>'
2265
- + '<span class="tag ' + tagClass + '">' + type + '</span>'
2266
- + '</div>'
2267
- + '<div class="header-right">'
2268
- + '<button class="copy-btn">Copy Error</button>'
2269
- + '<button class="close-btn">Dismiss (Esc)</button>'
2270
- + '</div>'
2271
- + '</div>'
2272
- + '<div class="body">'
2273
- + '<h2 class="error-title">' + this._escapeHTML(err.name || 'Error') + '</h2>'
2274
- + location
2275
- + '<pre class="error-message">' + this._escapeHTML(err.message || String(err)) + '</pre>'
2276
- + codeFrame
2277
- + tipHTML
2278
- + stack
2279
- + '</div>'
2280
- + '</div>';
2281
- }
2282
-
2283
- show(err) {
2284
- var template = document.createElement('template');
2285
- template.innerHTML = this._buildHTML(err);
2286
- this.root.appendChild(template.content.cloneNode(true));
2287
-
2288
- // Close handlers
2289
- var self = this;
2290
- var closeBtn = this.root.querySelector('.close-btn');
2291
- if (closeBtn) closeBtn.addEventListener('click', function() { self.close(); });
2292
- var backdrop = this.root.querySelector('.backdrop');
2293
- if (backdrop) backdrop.addEventListener('click', function() { self.close(); });
2294
- document.addEventListener('keydown', this._onKey = function(e) {
2295
- if (e.key === 'Escape') self.close();
2296
- });
2297
-
2298
- // Copy Error button
2299
- var copyBtn = this.root.querySelector('.copy-btn');
2300
- if (copyBtn) {
2301
- copyBtn.addEventListener('click', function() {
2302
- self._copyError(copyBtn);
2303
- });
2304
- }
2305
- }
2306
-
2307
- _copyError(btn) {
2308
- var err = this._err;
2309
- var data = {
2310
- name: err.name || 'Error',
2311
- message: err.message || String(err),
2312
- file: err.id || (err.loc && err.loc.file) || null,
2313
- line: (err.loc && err.loc.line != null) ? err.loc.line : null,
2314
- column: (err.loc && err.loc.column != null) ? err.loc.column : null,
2315
- stack: err.stack ? this._cleanStack(err.stack) : null,
2316
- framework: 'What Framework',
2317
- timestamp: new Date().toISOString()
2318
- };
2319
-
2320
- var text = JSON.stringify(data, null, 2);
2321
- if (navigator.clipboard && navigator.clipboard.writeText) {
2322
- navigator.clipboard.writeText(text).then(function() {
2323
- btn.textContent = 'Copied!';
2324
- btn.classList.add('copied');
2325
- setTimeout(function() {
2326
- btn.textContent = 'Copy Error';
2327
- btn.classList.remove('copied');
2328
- }, 2000);
2329
- }).catch(function() {
2330
- // Fallback: select text
2331
- prompt('Copy error details:', text);
2332
- });
2333
- } else {
2334
- prompt('Copy error details:', text);
2335
- }
2336
- }
2337
-
2338
- close() {
2339
- document.removeEventListener('keydown', this._onKey);
2340
- this.remove();
2341
- }
2342
- }
2343
-
2344
- if (!customElements.get('what-error-overlay')) {
2345
- customElements.define('what-error-overlay', WhatErrorOverlay);
2346
- }
2347
- `;
2348
- function setupErrorOverlay(server) {
2349
- const origSend = server.ws.send.bind(server.ws);
2350
- server.ws.send = function(payload) {
2351
- if (payload?.type === "error") {
2352
- if (payload.err?.plugin === "vite-plugin-what") {
2353
- payload.err._isCompilerError = true;
2354
- }
2355
- }
2356
- return origSend(payload);
2357
- };
2358
- }
2359
-
2360
- // packages/compiler/src/vite-plugin.js
2361
- var VIRTUAL_ROUTES_ID = "virtual:what-routes";
2362
- var RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ROUTES_ID;
2363
- var COMPONENT_EXPORT_RE = /export\s+(?:default\s+)?function\s+([A-Z]\w*)/;
2364
- var UTILITY_FILE_RE = /(?:store|signal|state|context|util|helper|lib|config)\b/i;
2365
- function whatVitePlugin(options = {}) {
2366
- const {
2367
- // File extensions to process
2368
- include = /\.[jt]sx$/,
2369
- // Files to exclude
2370
- exclude = /node_modules/,
2371
- // Enable source maps
2372
- sourceMaps = true,
2373
- // Production optimizations
2374
- production = false,
2375
- // Pages directory (relative to project root)
2376
- pages = "src/pages",
2377
- // HMR: enabled by default in dev, disabled in production
2378
- hot = !production,
2379
- // Resolve the `production` exports condition (dist/*.min.js — pre-minified,
2380
- // dev warnings compiled out) during `vite build`. Set to false to build
2381
- // against package sources instead — needed e.g. in a monorepo where
2382
- // workspace-linked dist/ output may be stale or absent. See config() below.
2383
- prodBundles = true
2384
- } = options;
2385
- let rootDir = "";
2386
- let pagesDir = "";
2387
- let server = null;
2388
- let isDevMode = false;
2389
- return {
2390
- name: "vite-plugin-what",
2391
- configResolved(config) {
2392
- rootDir = config.root;
2393
- pagesDir = path2.resolve(rootDir, pages);
2394
- isDevMode = config.command === "serve";
2395
- },
2396
- configureServer(devServer) {
2397
- server = devServer;
2398
- setupErrorOverlay(devServer);
2399
- devServer.watcher.on("add", (file) => {
2400
- if (file.startsWith(pagesDir)) {
2401
- const mod = devServer.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);
2402
- if (mod) {
2403
- devServer.moduleGraph.invalidateModule(mod);
2404
- devServer.ws.send({ type: "full-reload" });
2405
- }
2406
- }
2407
- });
2408
- devServer.watcher.on("unlink", (file) => {
2409
- if (file.startsWith(pagesDir)) {
2410
- const mod = devServer.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);
2411
- if (mod) {
2412
- devServer.moduleGraph.invalidateModule(mod);
2413
- devServer.ws.send({ type: "full-reload" });
2414
- }
2415
- }
2416
- });
2417
- },
2418
- // Resolve virtual module
2419
- resolveId(id) {
2420
- if (id === VIRTUAL_ROUTES_ID) {
2421
- return RESOLVED_VIRTUAL_ID;
2422
- }
2423
- },
2424
- // Generate the routes module
2425
- load(id) {
2426
- if (id === RESOLVED_VIRTUAL_ID) {
2427
- return generateRoutesModule(pagesDir, rootDir);
2428
- }
2429
- },
2430
- // Transform JSX files
2431
- transform(code, id) {
2432
- if (!include.test(id)) return null;
2433
- if (exclude && exclude.test(id)) return null;
2434
- try {
2435
- const result = transformSync(code, {
2436
- filename: id,
2437
- sourceMaps,
2438
- // Hermetic transform (SPRINT v0.11 C7): never load the project's
2439
- // babel.config.js/.babelrc. A user's React preset or unrelated
2440
- // plugins corrupting What's JSX output is a debugging nightmare —
2441
- // and scanning the disk for config files on every transform is
2442
- // wasted I/O in dev.
2443
- configFile: false,
2444
- babelrc: false,
2445
- plugins: [
2446
- [whatBabelPlugin, { production }]
2447
- ],
2448
- parserOpts: {
2449
- plugins: ["jsx", "typescript"]
2450
- }
2451
- });
2452
- if (!result || !result.code) {
2453
- return null;
2454
- }
2455
- let outputCode = result.code;
2456
- if (hot && isDevMode && !production) {
2457
- const isComponentFile = isComponentModule(code, id);
2458
- if (isComponentFile) {
2459
- outputCode += generateHMRBoundary(id);
2460
- }
2461
- }
2462
- return {
2463
- code: outputCode,
2464
- map: result.map
2465
- };
2466
- } catch (error) {
2467
- error.plugin = "vite-plugin-what";
2468
- if (!error.id) error.id = id;
2469
- if (error.loc === void 0 && error._loc) {
2470
- error.loc = { file: id, line: error._loc.line, column: error._loc.column };
2471
- }
2472
- console.error(`[what] Error transforming ${id}:`, error.message);
2473
- throw error;
2474
- }
2475
- },
2476
- // HMR: detect component vs utility files and handle accordingly
2477
- handleHotUpdate({ file, server: devServer, modules }) {
2478
- if (!hot) return;
2479
- if (!include.test(file)) return;
2480
- if (exclude && exclude.test(file)) return;
2481
- if (isUtilityFile(file)) {
2482
- devServer.ws.send({ type: "full-reload" });
2483
- return [];
2484
- }
2485
- return;
2486
- },
2487
- // Configure for development
2488
- config(config, { mode, command }) {
2489
- const useProdCondition = command === "build" && mode === "production" && prodBundles;
2490
- return {
2491
- ...useProdCondition ? { resolve: { conditions: ["production"] } } : {},
2492
- esbuild: {
2493
- // Preserve JSX so our babel plugin handles it -- don't let esbuild transform it
2494
- jsx: "preserve"
2495
- },
2496
- optimizeDeps: {
2497
- // Exclude framework packages from Vite's dependency pre-bundling.
2498
- //
2499
- // Bug class this prevents — "dual module instance":
2500
- // The compiler emits `import { ... } from 'what-framework/render'`
2501
- // (a subpath resolved to the source file). Meanwhile user code
2502
- // imports `'what-framework'` (the package entry). If Vite
2503
- // pre-bundles `'what-framework'` into an esbuild chunk under
2504
- // node_modules/.vite, those two import paths resolve to two
2505
- // *different* module instances. Module-scoped state — the
2506
- // `componentStack` used by createComponent, effect ownership,
2507
- // the signal subscriber registry — is duplicated, so a signal
2508
- // created in user code never notifies effects created via the
2509
- // compiler-emitted path, and `getCurrentComponent()` returns
2510
- // undefined inside components mounted through compiler output.
2511
- //
2512
- // Why `exclude` is the right knob:
2513
- // `include` would force pre-bundling of the package entry, which
2514
- // does not resolve the subpath import the compiler emits — so the
2515
- // split persists. Using `exclude` tells Vite to skip the optimizer
2516
- // for these packages and serve them via the normal module graph,
2517
- // where both the package entry and the `/render` subpath share
2518
- // a single ESM module record.
2519
- //
2520
- // Regression symptom if this is removed:
2521
- // Components mount but lifecycle hooks (onMount, onCleanup) and
2522
- // shared store state silently no-op; effects don't re-run on
2523
- // signal writes from user code; SSR/CSR hydration mismatches.
2524
- exclude: ["what-framework", "what-core", "what-compiler", "what-router"]
2525
- }
2526
- };
2527
- }
2528
- };
2529
- }
2530
- function isComponentModule(source, filePath) {
2531
- if (COMPONENT_EXPORT_RE.test(source)) return true;
2532
- if (filePath.includes("/pages/") || filePath.includes("\\pages\\")) return true;
2533
- return false;
2534
- }
2535
- function isUtilityFile(filePath) {
2536
- const basename = path2.basename(filePath, path2.extname(filePath));
2537
- return UTILITY_FILE_RE.test(basename);
2538
- }
2539
- function generateHMRBoundary(filePath) {
2540
- return `
2541
-
2542
- // --- What Framework HMR Boundary ---
2543
- if (import.meta.hot) {
2544
- import.meta.hot.accept((newModule) => {
2545
- if (newModule) {
2546
- // Signal to the What runtime that this module was hot-updated
2547
- if (window.__WHAT_HMR_ACCEPT__) {
2548
- window.__WHAT_HMR_ACCEPT__(${JSON.stringify(filePath)}, newModule);
2549
- }
2550
- }
2551
- });
2552
- }
2553
- `;
2554
- }
2555
-
2556
- // packages/compiler/src/runtime.js
2557
- import { h, Fragment, mount, Island } from "what-core";
2558
- export {
2559
- Fragment,
2560
- Island,
2561
- whatBabelPlugin as babelPlugin,
2562
- extractPageConfig,
2563
- generateRoutesModule,
2564
- h,
2565
- mount,
2566
- scanPages,
2567
- whatVitePlugin as vitePlugin,
2568
- whatVitePlugin as what
2569
- };
2570
- //# sourceMappingURL=index.js.map