what-compiler 0.8.4 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/babel-plugin.js +539 -87
- package/dist/babel-plugin.js.map +2 -2
- package/dist/babel-plugin.min.js +1 -1
- package/dist/babel-plugin.min.js.map +3 -3
- package/dist/file-router.js +78 -2
- package/dist/file-router.js.map +2 -2
- package/dist/file-router.min.js +3 -2
- package/dist/file-router.min.js.map +3 -3
- package/dist/index.js +595 -93
- package/dist/index.js.map +2 -2
- package/dist/index.min.js +6 -6
- package/dist/index.min.js.map +3 -3
- package/dist/vite-plugin.js +595 -93
- package/dist/vite-plugin.js.map +2 -2
- package/dist/vite-plugin.min.js +6 -6
- package/dist/vite-plugin.min.js.map +3 -3
- package/package.json +2 -2
- package/src/babel-plugin.js +898 -105
- package/src/file-router.js +104 -2
- package/src/vite-plugin.js +60 -3
package/dist/vite-plugin.js
CHANGED
|
@@ -65,9 +65,56 @@ var SIGNAL_CREATORS = /* @__PURE__ */ new Set([
|
|
|
65
65
|
"useQuery",
|
|
66
66
|
"useInfiniteQuery"
|
|
67
67
|
]);
|
|
68
|
+
function normalizeJsxText(value) {
|
|
69
|
+
if (!/[\r\n]/.test(value)) {
|
|
70
|
+
return value.replace(/\t/g, " ");
|
|
71
|
+
}
|
|
72
|
+
const lines = value.split(/\r\n|\n|\r/);
|
|
73
|
+
let lastNonEmpty = -1;
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
if (/[^ \t]/.test(lines[i])) lastNonEmpty = i;
|
|
76
|
+
}
|
|
77
|
+
if (lastNonEmpty === -1) return "";
|
|
78
|
+
let out = "";
|
|
79
|
+
for (let i = 0; i < lines.length; i++) {
|
|
80
|
+
let line = lines[i].replace(/\t/g, " ");
|
|
81
|
+
const isFirst = i === 0;
|
|
82
|
+
const isLast = i === lines.length - 1;
|
|
83
|
+
if (!isFirst) line = line.replace(/^ +/, "");
|
|
84
|
+
if (!isLast) line = line.replace(/ +$/, "");
|
|
85
|
+
if (!line) continue;
|
|
86
|
+
if (i !== lastNonEmpty) line += " ";
|
|
87
|
+
out += line;
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
68
91
|
function whatBabelPlugin({ types: t }) {
|
|
92
|
+
const _unknownModifierWarned = /* @__PURE__ */ new Set();
|
|
93
|
+
const _forInfoWarned = /* @__PURE__ */ new Set();
|
|
94
|
+
function hasEventModifiers(name, state) {
|
|
95
|
+
if (!name.includes("__")) return false;
|
|
96
|
+
if (!name.startsWith("on")) return false;
|
|
97
|
+
const parts = name.split("__");
|
|
98
|
+
const tail = parts.slice(1).filter((s) => s !== "");
|
|
99
|
+
if (tail.length === 0) return false;
|
|
100
|
+
if (true) {
|
|
101
|
+
const unknown = tail.filter((m) => !EVENT_MODIFIERS.has(m));
|
|
102
|
+
const filename = state && (state.filename || state.file && state.file.opts && state.file.opts.filename) || "<unknown>";
|
|
103
|
+
for (const m of unknown) {
|
|
104
|
+
const key = `${filename}::${m}`;
|
|
105
|
+
if (!_unknownModifierWarned.has(key)) {
|
|
106
|
+
_unknownModifierWarned.add(key);
|
|
107
|
+
console.warn(
|
|
108
|
+
`[what-compiler] Unknown event modifier "__${m}" in attribute "${name}" (${filename}). Known modifiers: ${[...EVENT_MODIFIERS].join(", ")}. Unknown segments are ignored.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
69
115
|
function parseEventModifiers(name) {
|
|
70
|
-
const
|
|
116
|
+
const delimiter = name.includes("|") ? "|" : "__";
|
|
117
|
+
const parts = name.split(delimiter);
|
|
71
118
|
const eventName = parts[0];
|
|
72
119
|
const modifiers = parts.slice(1).filter((m) => EVENT_MODIFIERS.has(m));
|
|
73
120
|
return { eventName, modifiers };
|
|
@@ -197,22 +244,20 @@ function whatBabelPlugin({ types: t }) {
|
|
|
197
244
|
}
|
|
198
245
|
let scope = path3.scope;
|
|
199
246
|
while (scope) {
|
|
200
|
-
for (const
|
|
247
|
+
for (const binding of Object.values(scope.bindings)) {
|
|
201
248
|
if (binding.path.isVariableDeclarator()) {
|
|
202
249
|
extractFromDeclarator(binding.path.node);
|
|
203
250
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
}
|
|
251
|
+
}
|
|
252
|
+
const fnNode = scope.path && scope.path.node;
|
|
253
|
+
if (fnNode && fnNode.params) {
|
|
254
|
+
for (const param of fnNode.params) {
|
|
255
|
+
if (t.isObjectPattern(param)) {
|
|
256
|
+
for (const prop of param.properties) {
|
|
257
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
|
|
258
|
+
signalNames.add(prop.value.name);
|
|
259
|
+
} else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
|
|
260
|
+
signalNames.add(prop.argument.name);
|
|
216
261
|
}
|
|
217
262
|
}
|
|
218
263
|
}
|
|
@@ -280,7 +325,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
280
325
|
return isPotentiallyReactive(expr.callee, signalNames, importedIds) || expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
|
|
281
326
|
}
|
|
282
327
|
if (t.isIdentifier(expr)) {
|
|
283
|
-
return isSignalIdentifier(expr.name, signalNames);
|
|
328
|
+
return isSignalIdentifier(expr.name, signalNames) || importedIds && importedIds.has(expr.name);
|
|
284
329
|
}
|
|
285
330
|
if (t.isMemberExpression(expr)) {
|
|
286
331
|
return isPotentiallyReactive(expr.object, signalNames, importedIds);
|
|
@@ -310,6 +355,93 @@ function whatBabelPlugin({ types: t }) {
|
|
|
310
355
|
}
|
|
311
356
|
return false;
|
|
312
357
|
}
|
|
358
|
+
function tryLowerMapToMapArray(expr, state) {
|
|
359
|
+
let mapCall = expr;
|
|
360
|
+
let wrappedInArrow = false;
|
|
361
|
+
if (t.isArrowFunctionExpression(expr) && expr.params.length === 0) {
|
|
362
|
+
mapCall = expr.body;
|
|
363
|
+
wrappedInArrow = true;
|
|
364
|
+
}
|
|
365
|
+
if (t.isConditionalExpression(mapCall)) {
|
|
366
|
+
const loweredCon = tryLowerMapCall(mapCall.consequent, state);
|
|
367
|
+
const loweredAlt = tryLowerMapCall(mapCall.alternate, state);
|
|
368
|
+
if (loweredCon || loweredAlt) {
|
|
369
|
+
const result = t.conditionalExpression(
|
|
370
|
+
mapCall.test,
|
|
371
|
+
loweredCon || mapCall.consequent,
|
|
372
|
+
loweredAlt || mapCall.alternate
|
|
373
|
+
);
|
|
374
|
+
return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (t.isLogicalExpression(mapCall) && (mapCall.operator === "&&" || mapCall.operator === "||")) {
|
|
379
|
+
const loweredRight = tryLowerMapCall(mapCall.right, state);
|
|
380
|
+
if (loweredRight) {
|
|
381
|
+
const result = t.logicalExpression(mapCall.operator, mapCall.left, loweredRight);
|
|
382
|
+
return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
const lowered = tryLowerMapCall(mapCall, state);
|
|
387
|
+
return lowered;
|
|
388
|
+
}
|
|
389
|
+
function tryLowerMapCall(mapCall, state) {
|
|
390
|
+
if (!t.isCallExpression(mapCall)) return null;
|
|
391
|
+
if (!t.isMemberExpression(mapCall.callee)) return null;
|
|
392
|
+
if (!t.isIdentifier(mapCall.callee.property, { name: "map" })) return null;
|
|
393
|
+
if (mapCall.arguments.length < 1) return null;
|
|
394
|
+
const mapFn = mapCall.arguments[0];
|
|
395
|
+
if (!t.isArrowFunctionExpression(mapFn) && !t.isFunctionExpression(mapFn)) return null;
|
|
396
|
+
let returnExpr = null;
|
|
397
|
+
if (t.isArrowFunctionExpression(mapFn)) {
|
|
398
|
+
if (t.isExpression(mapFn.body)) {
|
|
399
|
+
returnExpr = mapFn.body;
|
|
400
|
+
} else if (t.isBlockStatement(mapFn.body)) {
|
|
401
|
+
const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
|
|
402
|
+
if (ret) returnExpr = ret.argument;
|
|
403
|
+
}
|
|
404
|
+
} else if (t.isFunctionExpression(mapFn)) {
|
|
405
|
+
const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
|
|
406
|
+
if (ret) returnExpr = ret.argument;
|
|
407
|
+
}
|
|
408
|
+
if (!returnExpr) return null;
|
|
409
|
+
if (!t.isJSXElement(returnExpr)) return null;
|
|
410
|
+
const attrs = returnExpr.openingElement.attributes;
|
|
411
|
+
let keyAttr = null;
|
|
412
|
+
for (const attr of attrs) {
|
|
413
|
+
if (t.isJSXAttribute(attr) && getAttrName(attr) === "key") {
|
|
414
|
+
keyAttr = attr;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!keyAttr) {
|
|
419
|
+
if (true) {
|
|
420
|
+
const loc = returnExpr.loc;
|
|
421
|
+
const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
|
|
422
|
+
const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
|
|
423
|
+
console.warn(
|
|
424
|
+
`[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.`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const keyValue = getAttributeValue(keyAttr.value);
|
|
430
|
+
if (!keyValue) return null;
|
|
431
|
+
returnExpr.openingElement.attributes = attrs.filter((a) => a !== keyAttr);
|
|
432
|
+
const sourceObj = mapCall.callee.object;
|
|
433
|
+
const source = t.arrowFunctionExpression([], sourceObj);
|
|
434
|
+
const itemParam = mapFn.params[0] ? t.cloneNode(mapFn.params[0], true) : t.identifier("_item");
|
|
435
|
+
const keyFn = t.arrowFunctionExpression([itemParam], t.cloneNode(keyValue, true));
|
|
436
|
+
return t.callExpression(t.identifier("_$mapArray"), [
|
|
437
|
+
source,
|
|
438
|
+
mapFn,
|
|
439
|
+
t.objectExpression([
|
|
440
|
+
t.objectProperty(t.identifier("key"), keyFn),
|
|
441
|
+
t.objectProperty(t.identifier("raw"), t.booleanLiteral(true))
|
|
442
|
+
])
|
|
443
|
+
]);
|
|
444
|
+
}
|
|
313
445
|
function isStaticChild(child) {
|
|
314
446
|
if (t.isJSXText(child)) return true;
|
|
315
447
|
if (t.isJSXExpressionContainer(child)) return false;
|
|
@@ -333,7 +465,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
333
465
|
}
|
|
334
466
|
function extractStaticHTML(node) {
|
|
335
467
|
if (t.isJSXText(node)) {
|
|
336
|
-
const text = node.value
|
|
468
|
+
const text = normalizeJsxText(node.value);
|
|
337
469
|
return text ? escapeHTML(text) : "";
|
|
338
470
|
}
|
|
339
471
|
if (t.isJSXExpressionContainer(node)) {
|
|
@@ -373,7 +505,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
373
505
|
html += ">";
|
|
374
506
|
for (const child of node.children) {
|
|
375
507
|
if (t.isJSXText(child)) {
|
|
376
|
-
const text = child.value
|
|
508
|
+
const text = normalizeJsxText(child.value);
|
|
377
509
|
if (text) html += escapeHTML(text);
|
|
378
510
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
379
511
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -400,15 +532,15 @@ function whatBabelPlugin({ types: t }) {
|
|
|
400
532
|
const { node } = path3;
|
|
401
533
|
const openingElement = node.openingElement;
|
|
402
534
|
const tagName = openingElement.name.name;
|
|
403
|
-
if (isComponent(tagName)) {
|
|
404
|
-
return transformComponentFineGrained(path3, state);
|
|
405
|
-
}
|
|
406
535
|
if (tagName === "For") {
|
|
407
536
|
return transformForFineGrained(path3, state);
|
|
408
537
|
}
|
|
409
538
|
if (tagName === "Show") {
|
|
410
539
|
return transformShowFineGrained(path3, state);
|
|
411
540
|
}
|
|
541
|
+
if (isComponent(tagName)) {
|
|
542
|
+
return transformComponentFineGrained(path3, state);
|
|
543
|
+
}
|
|
412
544
|
const attributes = openingElement.attributes;
|
|
413
545
|
const children = node.children;
|
|
414
546
|
const allChildrenStatic = children.every(isStaticChild);
|
|
@@ -445,7 +577,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
445
577
|
t.variableDeclarator(t.identifier(elId), t.callExpression(t.identifier(tmplId), []))
|
|
446
578
|
])
|
|
447
579
|
];
|
|
448
|
-
applyDynamicAttrs(statements, elId, attributes, state);
|
|
580
|
+
applyDynamicAttrs(statements, elId, attributes, state, tagName);
|
|
449
581
|
applyDynamicChildren(statements, elId, children, node, state);
|
|
450
582
|
if (!state._pendingSetup) state._pendingSetup = [];
|
|
451
583
|
state._pendingSetup.push(...statements);
|
|
@@ -473,7 +605,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
473
605
|
const transformedChildren = [];
|
|
474
606
|
for (const child of children) {
|
|
475
607
|
if (t.isJSXText(child)) {
|
|
476
|
-
const text = child.value
|
|
608
|
+
const text = normalizeJsxText(child.value);
|
|
477
609
|
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
478
610
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
479
611
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -488,8 +620,33 @@ function whatBabelPlugin({ types: t }) {
|
|
|
488
620
|
const propsExpr = props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
|
|
489
621
|
return t.callExpression(t.identifier("h"), [t.stringLiteral(tagName), propsExpr, ...transformedChildren]);
|
|
490
622
|
}
|
|
491
|
-
|
|
623
|
+
const VALUE_PROP_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select", "option"]);
|
|
624
|
+
function applyDynamicAttrs(statements, elId, attributes, state, tagName) {
|
|
492
625
|
function buildSetPropCall(propName, valueExpr) {
|
|
626
|
+
if (propName === "class") {
|
|
627
|
+
state.needsSetClass = true;
|
|
628
|
+
return t.callExpression(t.identifier("_$setClass"), [t.identifier(elId), valueExpr]);
|
|
629
|
+
}
|
|
630
|
+
if (propName === "style") {
|
|
631
|
+
state.needsSetStyle = true;
|
|
632
|
+
return t.callExpression(t.identifier("_$setStyle"), [t.identifier(elId), valueExpr]);
|
|
633
|
+
}
|
|
634
|
+
if (propName === "value" && tagName && VALUE_PROP_TAGS.has(tagName)) {
|
|
635
|
+
state.needsSetValue = true;
|
|
636
|
+
return t.callExpression(t.identifier("_$setValue"), [t.identifier(elId), valueExpr]);
|
|
637
|
+
}
|
|
638
|
+
if (propName === "checked" && tagName === "input") {
|
|
639
|
+
state.needsSetChecked = true;
|
|
640
|
+
return t.callExpression(t.identifier("_$setChecked"), [t.identifier(elId), valueExpr]);
|
|
641
|
+
}
|
|
642
|
+
if (propName.startsWith("data-") || propName.startsWith("aria-")) {
|
|
643
|
+
state.needsSetAttr = true;
|
|
644
|
+
return t.callExpression(t.identifier("_$setAttr"), [
|
|
645
|
+
t.identifier(elId),
|
|
646
|
+
t.stringLiteral(propName),
|
|
647
|
+
valueExpr
|
|
648
|
+
]);
|
|
649
|
+
}
|
|
493
650
|
state.needsSetProp = true;
|
|
494
651
|
return t.callExpression(t.identifier("_$setProp"), [
|
|
495
652
|
t.identifier(elId),
|
|
@@ -497,6 +654,14 @@ function whatBabelPlugin({ types: t }) {
|
|
|
497
654
|
valueExpr
|
|
498
655
|
]);
|
|
499
656
|
}
|
|
657
|
+
let delegateInitEmitted = false;
|
|
658
|
+
function emitDelegateInit() {
|
|
659
|
+
if (delegateInitEmitted) return;
|
|
660
|
+
delegateInitEmitted = true;
|
|
661
|
+
statements.push(
|
|
662
|
+
t.expressionStatement(t.callExpression(t.identifier("_$delegate$"), []))
|
|
663
|
+
);
|
|
664
|
+
}
|
|
500
665
|
for (const attr of attributes) {
|
|
501
666
|
if (t.isJSXSpreadAttribute(attr)) {
|
|
502
667
|
state.needsSpread = true;
|
|
@@ -530,20 +695,21 @@ function whatBabelPlugin({ types: t }) {
|
|
|
530
695
|
);
|
|
531
696
|
continue;
|
|
532
697
|
}
|
|
533
|
-
if (attrName.startsWith("on") && !attrName.includes("|")) {
|
|
698
|
+
if (attrName.startsWith("on") && !attrName.includes("|") && !hasEventModifiers(attrName, state)) {
|
|
534
699
|
const event = attrName.slice(2).toLowerCase();
|
|
535
700
|
const handler = getAttributeValue(attr.value);
|
|
536
701
|
if (DELEGATED_EVENTS.has(event)) {
|
|
537
702
|
state.needsDelegation = true;
|
|
538
703
|
if (!state.delegatedEvents) state.delegatedEvents = /* @__PURE__ */ new Set();
|
|
539
704
|
state.delegatedEvents.add(event);
|
|
705
|
+
emitDelegateInit();
|
|
540
706
|
statements.push(
|
|
541
707
|
t.expressionStatement(
|
|
542
708
|
t.assignmentExpression(
|
|
543
709
|
"=",
|
|
544
710
|
t.memberExpression(
|
|
545
711
|
t.identifier(elId),
|
|
546
|
-
t.identifier(
|
|
712
|
+
t.identifier(`$$${event}`)
|
|
547
713
|
),
|
|
548
714
|
handler
|
|
549
715
|
)
|
|
@@ -561,7 +727,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
561
727
|
}
|
|
562
728
|
continue;
|
|
563
729
|
}
|
|
564
|
-
if (attrName.startsWith("on") && attrName.includes("|")) {
|
|
730
|
+
if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
|
|
565
731
|
const { eventName, modifiers } = parseEventModifiers(attrName);
|
|
566
732
|
const handler = getAttributeValue(attr.value);
|
|
567
733
|
const wrappedHandler = createEventHandler(handler, modifiers);
|
|
@@ -661,8 +827,9 @@ function whatBabelPlugin({ types: t }) {
|
|
|
661
827
|
const domName = normalizeAttrName(attrName);
|
|
662
828
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
663
829
|
state.needsEffect = true;
|
|
830
|
+
const valueExpr = t.isIdentifier(expr) && (isSignalIdentifier(expr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(expr.name)) ? t.callExpression(expr, []) : expr;
|
|
664
831
|
const effectCall = t.callExpression(t.identifier("_$effect"), [
|
|
665
|
-
t.arrowFunctionExpression([], buildSetPropCall(domName,
|
|
832
|
+
t.arrowFunctionExpression([], buildSetPropCall(domName, valueExpr))
|
|
666
833
|
]);
|
|
667
834
|
if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
668
835
|
t.addComment(
|
|
@@ -679,12 +846,56 @@ function whatBabelPlugin({ types: t }) {
|
|
|
679
846
|
}
|
|
680
847
|
}
|
|
681
848
|
}
|
|
849
|
+
function buildsDOM(node) {
|
|
850
|
+
if (!node || typeof node !== "object") return false;
|
|
851
|
+
if (Array.isArray(node)) return node.some(buildsDOM);
|
|
852
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
|
|
853
|
+
if (node.type === "CallExpression" && node.callee && node.callee.type === "Identifier" && (node.callee.name === "_$mapArray" || node.callee.name === "mapArray")) {
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
for (const key of Object.keys(node)) {
|
|
857
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
|
|
858
|
+
const v = node[key];
|
|
859
|
+
if (v && typeof v === "object" && buildsDOM(v)) return true;
|
|
860
|
+
}
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
function memoizeBranchCondition(expr, statements, state) {
|
|
864
|
+
let testExpr = null;
|
|
865
|
+
let isTernary = false;
|
|
866
|
+
if (t.isConditionalExpression(expr)) {
|
|
867
|
+
testExpr = expr.test;
|
|
868
|
+
isTernary = true;
|
|
869
|
+
} else if (t.isLogicalExpression(expr) && (expr.operator === "&&" || expr.operator === "||")) {
|
|
870
|
+
testExpr = expr.left;
|
|
871
|
+
} else {
|
|
872
|
+
return expr;
|
|
873
|
+
}
|
|
874
|
+
if (!isPotentiallyReactive(testExpr, state.signalNames, state.importedIdentifiers)) return expr;
|
|
875
|
+
const branches = isTernary ? [expr.consequent, expr.alternate] : [expr.right];
|
|
876
|
+
if (!branches.some(buildsDOM)) return expr;
|
|
877
|
+
const condId = state.nextMemoId();
|
|
878
|
+
state.needsMemo = true;
|
|
879
|
+
const memoBody = isTernary ? t.unaryExpression("!", t.unaryExpression("!", testExpr)) : testExpr;
|
|
880
|
+
statements.push(
|
|
881
|
+
t.variableDeclaration("const", [
|
|
882
|
+
t.variableDeclarator(
|
|
883
|
+
t.identifier(condId),
|
|
884
|
+
t.callExpression(t.identifier("_$memo"), [
|
|
885
|
+
t.arrowFunctionExpression([], memoBody)
|
|
886
|
+
])
|
|
887
|
+
)
|
|
888
|
+
])
|
|
889
|
+
);
|
|
890
|
+
const condRead = t.callExpression(t.identifier(condId), []);
|
|
891
|
+
return isTernary ? t.conditionalExpression(condRead, expr.consequent, expr.alternate) : t.logicalExpression(expr.operator, condRead, expr.right);
|
|
892
|
+
}
|
|
682
893
|
function applyDynamicChildren(statements, elId, children, parentNode, state) {
|
|
683
894
|
const entries = [];
|
|
684
895
|
let childIndex = 0;
|
|
685
896
|
for (const child of children) {
|
|
686
897
|
if (t.isJSXText(child)) {
|
|
687
|
-
const text = child.value
|
|
898
|
+
const text = normalizeJsxText(child.value);
|
|
688
899
|
if (text) childIndex++;
|
|
689
900
|
continue;
|
|
690
901
|
}
|
|
@@ -713,22 +924,31 @@ function whatBabelPlugin({ types: t }) {
|
|
|
713
924
|
const entriesNeedingRef = entries.filter(
|
|
714
925
|
(e) => e.type === "expression" || e.type === "component" || e.type === "static" && e.hasAnythingDynamic
|
|
715
926
|
);
|
|
716
|
-
const
|
|
717
|
-
const needsPreCapture = entriesNeedingRef.length >= 2 && hasDynamicInsert;
|
|
927
|
+
const needsPreCapture = entriesNeedingRef.length >= 2;
|
|
718
928
|
const markerVars = /* @__PURE__ */ new Map();
|
|
719
929
|
if (needsPreCapture) {
|
|
930
|
+
let prevVar = null;
|
|
931
|
+
let prevIndex = 0;
|
|
720
932
|
for (const entry of entriesNeedingRef) {
|
|
721
|
-
const
|
|
933
|
+
const idx = entry.childIndex;
|
|
722
934
|
const markerVar = state.nextVarId();
|
|
723
|
-
markerVars.set(
|
|
935
|
+
markerVars.set(idx, markerVar);
|
|
936
|
+
let init;
|
|
937
|
+
if (prevVar === null) {
|
|
938
|
+
init = buildChildAccess(elId, idx);
|
|
939
|
+
} else {
|
|
940
|
+
init = t.identifier(prevVar);
|
|
941
|
+
for (let i = prevIndex; i < idx; i++) {
|
|
942
|
+
init = t.memberExpression(init, t.identifier("nextSibling"));
|
|
943
|
+
}
|
|
944
|
+
}
|
|
724
945
|
statements.push(
|
|
725
946
|
t.variableDeclaration("const", [
|
|
726
|
-
t.variableDeclarator(
|
|
727
|
-
t.identifier(markerVar),
|
|
728
|
-
buildChildAccess(elId, entry.childIndex)
|
|
729
|
-
)
|
|
947
|
+
t.variableDeclarator(t.identifier(markerVar), init)
|
|
730
948
|
])
|
|
731
949
|
);
|
|
950
|
+
prevVar = markerVar;
|
|
951
|
+
prevIndex = idx;
|
|
732
952
|
}
|
|
733
953
|
}
|
|
734
954
|
function getMarker(idx) {
|
|
@@ -739,10 +959,48 @@ function whatBabelPlugin({ types: t }) {
|
|
|
739
959
|
}
|
|
740
960
|
for (const entry of entries) {
|
|
741
961
|
if (entry.type === "expression") {
|
|
742
|
-
|
|
962
|
+
let expr = entry.child.expression;
|
|
743
963
|
const marker = getMarker(entry.childIndex);
|
|
744
964
|
state.needsInsert = true;
|
|
965
|
+
let mapResult = tryLowerMapToMapArray(expr, state);
|
|
966
|
+
if (mapResult) {
|
|
967
|
+
state.needsMapArray = true;
|
|
968
|
+
const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
|
|
969
|
+
const isArrowAlready = t.isArrowFunctionExpression(mapResult);
|
|
970
|
+
if (isArrowAlready && t.isExpression(mapResult.body)) {
|
|
971
|
+
mapResult.body = memoizeBranchCondition(mapResult.body, statements, state);
|
|
972
|
+
} else if (!isBareMapArray && !isArrowAlready) {
|
|
973
|
+
mapResult = memoizeBranchCondition(mapResult, statements, state);
|
|
974
|
+
}
|
|
975
|
+
const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
|
|
976
|
+
statements.push(
|
|
977
|
+
t.expressionStatement(
|
|
978
|
+
t.callExpression(t.identifier("_$insert"), [
|
|
979
|
+
t.identifier(elId),
|
|
980
|
+
insertArg,
|
|
981
|
+
marker
|
|
982
|
+
])
|
|
983
|
+
)
|
|
984
|
+
);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
|
|
988
|
+
if (isMapArrayCall) {
|
|
989
|
+
state.needsMapArray = true;
|
|
990
|
+
if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
|
|
991
|
+
statements.push(
|
|
992
|
+
t.expressionStatement(
|
|
993
|
+
t.callExpression(t.identifier("_$insert"), [
|
|
994
|
+
t.identifier(elId),
|
|
995
|
+
expr,
|
|
996
|
+
marker
|
|
997
|
+
])
|
|
998
|
+
)
|
|
999
|
+
);
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
745
1002
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
1003
|
+
expr = memoizeBranchCondition(expr, statements, state);
|
|
746
1004
|
const insertCall = t.callExpression(t.identifier("_$insert"), [
|
|
747
1005
|
t.identifier(elId),
|
|
748
1006
|
t.arrowFunctionExpression([], expr),
|
|
@@ -800,7 +1058,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
800
1058
|
])
|
|
801
1059
|
);
|
|
802
1060
|
}
|
|
803
|
-
applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state);
|
|
1061
|
+
applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state, entry.child.openingElement.name.name);
|
|
804
1062
|
applyDynamicChildren(statements, childElRef, entry.child.children, entry.child, state);
|
|
805
1063
|
continue;
|
|
806
1064
|
}
|
|
@@ -808,8 +1066,9 @@ function whatBabelPlugin({ types: t }) {
|
|
|
808
1066
|
for (const fChild of entry.child.children) {
|
|
809
1067
|
if (t.isJSXExpressionContainer(fChild) && !t.isJSXEmptyExpression(fChild.expression)) {
|
|
810
1068
|
state.needsInsert = true;
|
|
811
|
-
|
|
1069
|
+
let expr = fChild.expression;
|
|
812
1070
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
1071
|
+
expr = memoizeBranchCondition(expr, statements, state);
|
|
813
1072
|
statements.push(
|
|
814
1073
|
t.expressionStatement(
|
|
815
1074
|
t.callExpression(t.identifier("_$insert"), [
|
|
@@ -950,7 +1209,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
950
1209
|
}
|
|
951
1210
|
continue;
|
|
952
1211
|
}
|
|
953
|
-
if (attrName.startsWith("on") && attrName.includes("|")) {
|
|
1212
|
+
if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
|
|
954
1213
|
const { eventName, modifiers } = parseEventModifiers(attrName);
|
|
955
1214
|
const handler = getAttributeValue(attr.value);
|
|
956
1215
|
const wrappedHandler = createEventHandler(handler, modifiers);
|
|
@@ -968,7 +1227,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
968
1227
|
const transformedChildren = [];
|
|
969
1228
|
for (const child of children) {
|
|
970
1229
|
if (t.isJSXText(child)) {
|
|
971
|
-
const text = child.value
|
|
1230
|
+
const text = normalizeJsxText(child.value);
|
|
972
1231
|
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
973
1232
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
974
1233
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -1002,10 +1261,24 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1002
1261
|
const { node } = path3;
|
|
1003
1262
|
const attributes = node.openingElement.attributes;
|
|
1004
1263
|
const children = node.children;
|
|
1264
|
+
if (true) {
|
|
1265
|
+
const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
|
|
1266
|
+
if (!_forInfoWarned.has(fileName)) {
|
|
1267
|
+
_forInfoWarned.add(fileName);
|
|
1268
|
+
const loc = node.loc;
|
|
1269
|
+
const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
|
|
1270
|
+
console.info(
|
|
1271
|
+
`[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).`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1005
1275
|
let eachExpr = null;
|
|
1276
|
+
let keyExpr = null;
|
|
1006
1277
|
for (const attr of attributes) {
|
|
1007
|
-
if (t.isJSXAttribute(attr)
|
|
1008
|
-
|
|
1278
|
+
if (t.isJSXAttribute(attr)) {
|
|
1279
|
+
const name = getAttrName(attr);
|
|
1280
|
+
if (name === "each") eachExpr = getAttributeValue(attr.value);
|
|
1281
|
+
else if (name === "key") keyExpr = getAttributeValue(attr.value);
|
|
1009
1282
|
}
|
|
1010
1283
|
}
|
|
1011
1284
|
if (!eachExpr) {
|
|
@@ -1026,11 +1299,124 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1026
1299
|
return transformElementAsH(path3, state);
|
|
1027
1300
|
}
|
|
1028
1301
|
state.needsMapArray = true;
|
|
1029
|
-
|
|
1302
|
+
const args = [eachExpr, renderFn];
|
|
1303
|
+
if (keyExpr) {
|
|
1304
|
+
args.push(t.objectExpression([
|
|
1305
|
+
t.objectProperty(t.identifier("key"), keyExpr)
|
|
1306
|
+
]));
|
|
1307
|
+
}
|
|
1308
|
+
return t.callExpression(t.identifier("_$mapArray"), args);
|
|
1030
1309
|
}
|
|
1031
1310
|
function transformShowFineGrained(path3, state) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1311
|
+
const { node } = path3;
|
|
1312
|
+
const attributes = node.openingElement.attributes;
|
|
1313
|
+
const children = node.children;
|
|
1314
|
+
let whenExpr = null;
|
|
1315
|
+
let fallbackExpr = null;
|
|
1316
|
+
for (const attr of attributes) {
|
|
1317
|
+
if (t.isJSXAttribute(attr)) {
|
|
1318
|
+
const name = getAttrName(attr);
|
|
1319
|
+
if (name === "when") whenExpr = getAttributeValue(attr.value);
|
|
1320
|
+
else if (name === "fallback") fallbackExpr = getAttributeValue(attr.value);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (!whenExpr) {
|
|
1324
|
+
throw path3.buildCodeFrameError(
|
|
1325
|
+
'<Show> requires a "when" prop. Example: <Show when={isOpen} fallback={null}>...</Show>'
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
let contentExpr = null;
|
|
1329
|
+
for (const child of children) {
|
|
1330
|
+
if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
|
|
1331
|
+
contentExpr = child.expression;
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (!contentExpr) {
|
|
1336
|
+
const transformedChildren = [];
|
|
1337
|
+
for (const child of children) {
|
|
1338
|
+
if (t.isJSXText(child)) {
|
|
1339
|
+
const text = normalizeJsxText(child.value);
|
|
1340
|
+
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
1341
|
+
} else if (t.isJSXElement(child)) {
|
|
1342
|
+
transformedChildren.push(transformElementFineGrained({ node: child }, state));
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (transformedChildren.length === 1) {
|
|
1346
|
+
contentExpr = transformedChildren[0];
|
|
1347
|
+
} else if (transformedChildren.length > 1) {
|
|
1348
|
+
contentExpr = t.arrayExpression(transformedChildren);
|
|
1349
|
+
} else {
|
|
1350
|
+
contentExpr = t.nullLiteral();
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
let condition;
|
|
1354
|
+
if (t.isCallExpression(whenExpr)) {
|
|
1355
|
+
condition = whenExpr;
|
|
1356
|
+
} else if (t.isArrowFunctionExpression(whenExpr) && t.isExpression(whenExpr.body)) {
|
|
1357
|
+
condition = whenExpr.body;
|
|
1358
|
+
} else if (t.isIdentifier(whenExpr) && (state.signalNames && isSignalIdentifier(whenExpr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(whenExpr.name))) {
|
|
1359
|
+
condition = t.callExpression(whenExpr, []);
|
|
1360
|
+
} else {
|
|
1361
|
+
condition = whenExpr;
|
|
1362
|
+
}
|
|
1363
|
+
const vId = path3.scope ? path3.scope.generateUidIdentifier("v") : t.identifier("_v");
|
|
1364
|
+
const contentIsFn = t.isFunction(contentExpr);
|
|
1365
|
+
const consequent = contentIsFn ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
|
|
1366
|
+
const alternate = fallbackExpr || t.nullLiteral();
|
|
1367
|
+
if (isPotentiallyReactive(condition, state.signalNames, state.importedIdentifiers)) {
|
|
1368
|
+
const condId = state.nextMemoId();
|
|
1369
|
+
state.needsMemo = true;
|
|
1370
|
+
const memoBody = contentIsFn ? condition : t.unaryExpression("!", t.unaryExpression("!", condition));
|
|
1371
|
+
if (!state._pendingSetup) state._pendingSetup = [];
|
|
1372
|
+
state._pendingSetup.push(
|
|
1373
|
+
t.variableDeclaration("const", [
|
|
1374
|
+
t.variableDeclarator(
|
|
1375
|
+
t.identifier(condId),
|
|
1376
|
+
t.callExpression(t.identifier("_$memo"), [
|
|
1377
|
+
t.arrowFunctionExpression([], memoBody)
|
|
1378
|
+
])
|
|
1379
|
+
)
|
|
1380
|
+
])
|
|
1381
|
+
);
|
|
1382
|
+
condition = t.callExpression(t.identifier(condId), []);
|
|
1383
|
+
}
|
|
1384
|
+
return t.arrowFunctionExpression([], t.blockStatement([
|
|
1385
|
+
t.variableDeclaration("const", [
|
|
1386
|
+
t.variableDeclarator(vId, condition)
|
|
1387
|
+
]),
|
|
1388
|
+
t.returnStatement(
|
|
1389
|
+
t.conditionalExpression(t.cloneNode(vId), consequent, alternate)
|
|
1390
|
+
)
|
|
1391
|
+
]));
|
|
1392
|
+
}
|
|
1393
|
+
function lowerFragmentExprChild(expr, state) {
|
|
1394
|
+
if (!state._pendingSetup) state._pendingSetup = [];
|
|
1395
|
+
const setup = state._pendingSetup;
|
|
1396
|
+
const mapResult = tryLowerMapToMapArray(expr, state);
|
|
1397
|
+
if (mapResult) {
|
|
1398
|
+
state.needsMapArray = true;
|
|
1399
|
+
const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
|
|
1400
|
+
const isArrowAlready = t.isArrowFunctionExpression(mapResult);
|
|
1401
|
+
if (isArrowAlready && t.isExpression(mapResult.body)) {
|
|
1402
|
+
mapResult.body = memoizeBranchCondition(mapResult.body, setup, state);
|
|
1403
|
+
return mapResult;
|
|
1404
|
+
}
|
|
1405
|
+
if (isBareMapArray) return mapResult;
|
|
1406
|
+
const memoized = memoizeBranchCondition(mapResult, setup, state);
|
|
1407
|
+
return t.arrowFunctionExpression([], memoized);
|
|
1408
|
+
}
|
|
1409
|
+
const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
|
|
1410
|
+
if (isMapArrayCall) {
|
|
1411
|
+
state.needsMapArray = true;
|
|
1412
|
+
if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
|
|
1413
|
+
return expr;
|
|
1414
|
+
}
|
|
1415
|
+
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
1416
|
+
expr = memoizeBranchCondition(expr, setup, state);
|
|
1417
|
+
return t.arrowFunctionExpression([], expr);
|
|
1418
|
+
}
|
|
1419
|
+
return expr;
|
|
1034
1420
|
}
|
|
1035
1421
|
function transformFragmentFineGrained(path3, state) {
|
|
1036
1422
|
const { node } = path3;
|
|
@@ -1038,11 +1424,11 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1038
1424
|
const transformed = [];
|
|
1039
1425
|
for (const child of children) {
|
|
1040
1426
|
if (t.isJSXText(child)) {
|
|
1041
|
-
const text = child.value
|
|
1427
|
+
const text = normalizeJsxText(child.value);
|
|
1042
1428
|
if (text) transformed.push(t.stringLiteral(text));
|
|
1043
1429
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
1044
1430
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
1045
|
-
transformed.push(child.expression);
|
|
1431
|
+
transformed.push(lowerFragmentExprChild(child.expression, state));
|
|
1046
1432
|
}
|
|
1047
1433
|
} else if (t.isJSXElement(child)) {
|
|
1048
1434
|
transformed.push(transformElementFineGrained({ node: child }, state));
|
|
@@ -1062,6 +1448,46 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1062
1448
|
state.templates.push({ id, html });
|
|
1063
1449
|
return id;
|
|
1064
1450
|
}
|
|
1451
|
+
function transformJsxRoot(path3, state, transform) {
|
|
1452
|
+
const scope = path3.scope;
|
|
1453
|
+
let cache = state._signalNamesCache;
|
|
1454
|
+
if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
|
|
1455
|
+
let names = cache.get(scope);
|
|
1456
|
+
if (!names) {
|
|
1457
|
+
names = collectSignalNamesFromScope(path3);
|
|
1458
|
+
cache.set(scope, names);
|
|
1459
|
+
}
|
|
1460
|
+
state.signalNames = names;
|
|
1461
|
+
state._pendingSetup = [];
|
|
1462
|
+
const transformed = transform(path3, state);
|
|
1463
|
+
const pending = state._pendingSetup;
|
|
1464
|
+
state._pendingSetup = [];
|
|
1465
|
+
if (pending.length > 0) {
|
|
1466
|
+
let stmtPath = path3;
|
|
1467
|
+
let crossedFunctionBoundary = false;
|
|
1468
|
+
while (stmtPath && !stmtPath.isStatement()) {
|
|
1469
|
+
if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
|
|
1470
|
+
crossedFunctionBoundary = true;
|
|
1471
|
+
}
|
|
1472
|
+
stmtPath = stmtPath.parentPath;
|
|
1473
|
+
}
|
|
1474
|
+
const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
|
|
1475
|
+
if (inStatementList && !crossedFunctionBoundary) {
|
|
1476
|
+
stmtPath.insertBefore(pending);
|
|
1477
|
+
path3.replaceWith(transformed);
|
|
1478
|
+
} else {
|
|
1479
|
+
pending.push(t.returnStatement(transformed));
|
|
1480
|
+
path3.replaceWith(
|
|
1481
|
+
t.callExpression(
|
|
1482
|
+
t.arrowFunctionExpression([], t.blockStatement(pending)),
|
|
1483
|
+
[]
|
|
1484
|
+
)
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
} else {
|
|
1488
|
+
path3.replaceWith(transformed);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1065
1491
|
return {
|
|
1066
1492
|
name: "what-jsx-transform",
|
|
1067
1493
|
visitor: {
|
|
@@ -1073,6 +1499,12 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1073
1499
|
state.needsMapArray = false;
|
|
1074
1500
|
state.needsSpread = false;
|
|
1075
1501
|
state.needsSetProp = false;
|
|
1502
|
+
state.needsMemo = false;
|
|
1503
|
+
state.needsSetClass = false;
|
|
1504
|
+
state.needsSetStyle = false;
|
|
1505
|
+
state.needsSetAttr = false;
|
|
1506
|
+
state.needsSetValue = false;
|
|
1507
|
+
state.needsSetChecked = false;
|
|
1076
1508
|
state.needsH = false;
|
|
1077
1509
|
state.needsCreateComponent = false;
|
|
1078
1510
|
state.needsFragment = false;
|
|
@@ -1083,8 +1515,10 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1083
1515
|
state.templateMap = /* @__PURE__ */ new Map();
|
|
1084
1516
|
state.templateCount = 0;
|
|
1085
1517
|
state._varCounter = 0;
|
|
1518
|
+
state._memoCounter = 0;
|
|
1086
1519
|
state._pendingSetup = [];
|
|
1087
1520
|
state.nextVarId = () => `_el$${state._varCounter++}`;
|
|
1521
|
+
state.nextMemoId = () => `_c$${state._memoCounter++}`;
|
|
1088
1522
|
state.signalNames = /* @__PURE__ */ new Set();
|
|
1089
1523
|
state.importedIdentifiers = /* @__PURE__ */ new Set();
|
|
1090
1524
|
for (const node of path3.node.body) {
|
|
@@ -1140,20 +1574,19 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1140
1574
|
},
|
|
1141
1575
|
exit(path3, state) {
|
|
1142
1576
|
for (const tmpl of state.templates.reverse()) {
|
|
1577
|
+
const tmplCall = t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)]);
|
|
1578
|
+
t.addComment(tmplCall, "leading", " @__PURE__ ");
|
|
1143
1579
|
path3.unshiftContainer(
|
|
1144
1580
|
"body",
|
|
1145
1581
|
t.variableDeclaration("const", [
|
|
1146
|
-
t.variableDeclarator(
|
|
1147
|
-
t.identifier(tmpl.id),
|
|
1148
|
-
t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)])
|
|
1149
|
-
)
|
|
1582
|
+
t.variableDeclarator(t.identifier(tmpl.id), tmplCall)
|
|
1150
1583
|
])
|
|
1151
1584
|
);
|
|
1152
1585
|
}
|
|
1153
1586
|
const fgSpecifiers = [];
|
|
1154
1587
|
if (state.needsTemplate) {
|
|
1155
1588
|
fgSpecifiers.push(
|
|
1156
|
-
t.importSpecifier(t.identifier("_$template"), t.identifier("template"))
|
|
1589
|
+
t.importSpecifier(t.identifier("_$template"), t.identifier("_$template"))
|
|
1157
1590
|
);
|
|
1158
1591
|
}
|
|
1159
1592
|
if (state.needsInsert) {
|
|
@@ -1181,6 +1614,36 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1181
1614
|
t.importSpecifier(t.identifier("_$setProp"), t.identifier("setProp"))
|
|
1182
1615
|
);
|
|
1183
1616
|
}
|
|
1617
|
+
if (state.needsMemo) {
|
|
1618
|
+
fgSpecifiers.push(
|
|
1619
|
+
t.importSpecifier(t.identifier("_$memo"), t.identifier("memo"))
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
if (state.needsSetClass) {
|
|
1623
|
+
fgSpecifiers.push(
|
|
1624
|
+
t.importSpecifier(t.identifier("_$setClass"), t.identifier("setClass"))
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
if (state.needsSetStyle) {
|
|
1628
|
+
fgSpecifiers.push(
|
|
1629
|
+
t.importSpecifier(t.identifier("_$setStyle"), t.identifier("setStyle"))
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
if (state.needsSetAttr) {
|
|
1633
|
+
fgSpecifiers.push(
|
|
1634
|
+
t.importSpecifier(t.identifier("_$setAttr"), t.identifier("setAttr"))
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
if (state.needsSetValue) {
|
|
1638
|
+
fgSpecifiers.push(
|
|
1639
|
+
t.importSpecifier(t.identifier("_$setValue"), t.identifier("setValue"))
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
if (state.needsSetChecked) {
|
|
1643
|
+
fgSpecifiers.push(
|
|
1644
|
+
t.importSpecifier(t.identifier("_$setChecked"), t.identifier("setChecked"))
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1184
1647
|
if (state.needsCreateComponent) {
|
|
1185
1648
|
fgSpecifiers.push(
|
|
1186
1649
|
t.importSpecifier(t.identifier("_$createComponent"), t.identifier("_$createComponent"))
|
|
@@ -1238,47 +1701,36 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1238
1701
|
const eventArray = t.arrayExpression(
|
|
1239
1702
|
[...state.delegatedEvents].map((e) => t.stringLiteral(e))
|
|
1240
1703
|
);
|
|
1241
|
-
|
|
1242
|
-
"
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1704
|
+
const helperFn = t.functionDeclaration(
|
|
1705
|
+
t.identifier("_$delegate$"),
|
|
1706
|
+
[],
|
|
1707
|
+
t.blockStatement([
|
|
1708
|
+
t.ifStatement(
|
|
1709
|
+
t.identifier("_$delegated$"),
|
|
1710
|
+
t.returnStatement()
|
|
1711
|
+
),
|
|
1712
|
+
t.expressionStatement(
|
|
1713
|
+
t.assignmentExpression("=", t.identifier("_$delegated$"), t.booleanLiteral(true))
|
|
1714
|
+
),
|
|
1715
|
+
t.expressionStatement(
|
|
1716
|
+
t.callExpression(t.identifier("_$delegateEvents"), [eventArray])
|
|
1717
|
+
)
|
|
1718
|
+
])
|
|
1246
1719
|
);
|
|
1720
|
+
path3.unshiftContainer("body", [
|
|
1721
|
+
t.variableDeclaration("let", [
|
|
1722
|
+
t.variableDeclarator(t.identifier("_$delegated$"), t.booleanLiteral(false))
|
|
1723
|
+
]),
|
|
1724
|
+
helperFn
|
|
1725
|
+
]);
|
|
1247
1726
|
}
|
|
1248
1727
|
}
|
|
1249
1728
|
},
|
|
1250
1729
|
JSXElement(path3, state) {
|
|
1251
|
-
state
|
|
1252
|
-
state._pendingSetup = [];
|
|
1253
|
-
const transformed = transformElementFineGrained(path3, state);
|
|
1254
|
-
const pending = state._pendingSetup;
|
|
1255
|
-
state._pendingSetup = [];
|
|
1256
|
-
if (pending.length > 0) {
|
|
1257
|
-
let stmtPath = path3;
|
|
1258
|
-
while (stmtPath && !stmtPath.isStatement()) {
|
|
1259
|
-
stmtPath = stmtPath.parentPath;
|
|
1260
|
-
}
|
|
1261
|
-
if (stmtPath && stmtPath.isStatement()) {
|
|
1262
|
-
for (const stmt of pending) {
|
|
1263
|
-
stmtPath.insertBefore(stmt);
|
|
1264
|
-
}
|
|
1265
|
-
path3.replaceWith(transformed);
|
|
1266
|
-
} else {
|
|
1267
|
-
pending.push(t.returnStatement(transformed));
|
|
1268
|
-
path3.replaceWith(
|
|
1269
|
-
t.callExpression(
|
|
1270
|
-
t.arrowFunctionExpression([], t.blockStatement(pending)),
|
|
1271
|
-
[]
|
|
1272
|
-
)
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
} else {
|
|
1276
|
-
path3.replaceWith(transformed);
|
|
1277
|
-
}
|
|
1730
|
+
transformJsxRoot(path3, state, transformElementFineGrained);
|
|
1278
1731
|
},
|
|
1279
1732
|
JSXFragment(path3, state) {
|
|
1280
|
-
|
|
1281
|
-
path3.replaceWith(transformed);
|
|
1733
|
+
transformJsxRoot(path3, state, transformFragmentFineGrained);
|
|
1282
1734
|
}
|
|
1283
1735
|
}
|
|
1284
1736
|
};
|
|
@@ -1417,6 +1869,13 @@ function extractPageConfig(source) {
|
|
|
1417
1869
|
return { mode: "client" };
|
|
1418
1870
|
}
|
|
1419
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
|
+
}
|
|
1420
1879
|
function generateRoutesModule(pagesDir, rootDir) {
|
|
1421
1880
|
const { pages, layouts, apiRoutes } = scanPages(pagesDir);
|
|
1422
1881
|
const imports = [];
|
|
@@ -1433,9 +1892,11 @@ function generateRoutesModule(pagesDir, rootDir) {
|
|
|
1433
1892
|
const relPath = toImportPath(page.filePath, rootDir);
|
|
1434
1893
|
imports.push(`import ${varName} from '${relPath}';`);
|
|
1435
1894
|
let pageConfig = { mode: "client" };
|
|
1895
|
+
let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
|
|
1436
1896
|
try {
|
|
1437
1897
|
const source = fs.readFileSync(page.filePath, "utf-8");
|
|
1438
1898
|
pageConfig = extractPageConfig(source);
|
|
1899
|
+
detected = detectPageExports(source);
|
|
1439
1900
|
} catch {
|
|
1440
1901
|
}
|
|
1441
1902
|
const layoutVar = findLayout(page.routePath, layoutMap);
|
|
@@ -1443,7 +1904,8 @@ function generateRoutesModule(pagesDir, rootDir) {
|
|
|
1443
1904
|
path: page.routePath,
|
|
1444
1905
|
component: varName,
|
|
1445
1906
|
mode: pageConfig.mode || "client",
|
|
1446
|
-
layout: layoutVar || null
|
|
1907
|
+
layout: layoutVar || null,
|
|
1908
|
+
hasLoader: detected.hasLoader
|
|
1447
1909
|
};
|
|
1448
1910
|
routeEntries.push(entry);
|
|
1449
1911
|
});
|
|
@@ -1465,7 +1927,7 @@ function generateRoutesModule(pagesDir, rootDir) {
|
|
|
1465
1927
|
"",
|
|
1466
1928
|
"export const routes = [",
|
|
1467
1929
|
...routeEntries.map(
|
|
1468
|
-
(r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""} },`
|
|
1930
|
+
(r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""}${r.hasLoader ? ", hasLoader: true" : ""} },`
|
|
1469
1931
|
),
|
|
1470
1932
|
"];",
|
|
1471
1933
|
"",
|
|
@@ -1913,7 +2375,12 @@ function whatVitePlugin(options = {}) {
|
|
|
1913
2375
|
// Pages directory (relative to project root)
|
|
1914
2376
|
pages = "src/pages",
|
|
1915
2377
|
// HMR: enabled by default in dev, disabled in production
|
|
1916
|
-
hot = !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
|
|
1917
2384
|
} = options;
|
|
1918
2385
|
let rootDir = "";
|
|
1919
2386
|
let pagesDir = "";
|
|
@@ -1968,6 +2435,13 @@ function whatVitePlugin(options = {}) {
|
|
|
1968
2435
|
const result = transformSync(code, {
|
|
1969
2436
|
filename: id,
|
|
1970
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,
|
|
1971
2445
|
plugins: [
|
|
1972
2446
|
[whatBabelPlugin, { production }]
|
|
1973
2447
|
],
|
|
@@ -2011,15 +2485,43 @@ function whatVitePlugin(options = {}) {
|
|
|
2011
2485
|
return;
|
|
2012
2486
|
},
|
|
2013
2487
|
// Configure for development
|
|
2014
|
-
config(config, { mode }) {
|
|
2488
|
+
config(config, { mode, command }) {
|
|
2489
|
+
const useProdCondition = command === "build" && mode === "production" && prodBundles;
|
|
2015
2490
|
return {
|
|
2491
|
+
...useProdCondition ? { resolve: { conditions: ["production"] } } : {},
|
|
2016
2492
|
esbuild: {
|
|
2017
2493
|
// Preserve JSX so our babel plugin handles it -- don't let esbuild transform it
|
|
2018
2494
|
jsx: "preserve"
|
|
2019
2495
|
},
|
|
2020
2496
|
optimizeDeps: {
|
|
2021
|
-
//
|
|
2022
|
-
|
|
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"]
|
|
2023
2525
|
}
|
|
2024
2526
|
};
|
|
2025
2527
|
}
|