what-compiler 0.8.4 → 0.10.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 +314 -48
- 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 +354 -52
- 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 +354 -52
- 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 +514 -65
- package/src/file-router.js +104 -2
- package/src/vite-plugin.js +28 -2
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);
|
|
@@ -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)) {
|
|
@@ -530,7 +662,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
530
662
|
);
|
|
531
663
|
continue;
|
|
532
664
|
}
|
|
533
|
-
if (attrName.startsWith("on") && !attrName.includes("|")) {
|
|
665
|
+
if (attrName.startsWith("on") && !attrName.includes("|") && !hasEventModifiers(attrName, state)) {
|
|
534
666
|
const event = attrName.slice(2).toLowerCase();
|
|
535
667
|
const handler = getAttributeValue(attr.value);
|
|
536
668
|
if (DELEGATED_EVENTS.has(event)) {
|
|
@@ -543,7 +675,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
543
675
|
"=",
|
|
544
676
|
t.memberExpression(
|
|
545
677
|
t.identifier(elId),
|
|
546
|
-
t.identifier(
|
|
678
|
+
t.identifier(`$$${event}`)
|
|
547
679
|
),
|
|
548
680
|
handler
|
|
549
681
|
)
|
|
@@ -561,7 +693,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
561
693
|
}
|
|
562
694
|
continue;
|
|
563
695
|
}
|
|
564
|
-
if (attrName.startsWith("on") && attrName.includes("|")) {
|
|
696
|
+
if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
|
|
565
697
|
const { eventName, modifiers } = parseEventModifiers(attrName);
|
|
566
698
|
const handler = getAttributeValue(attr.value);
|
|
567
699
|
const wrappedHandler = createEventHandler(handler, modifiers);
|
|
@@ -661,8 +793,9 @@ function whatBabelPlugin({ types: t }) {
|
|
|
661
793
|
const domName = normalizeAttrName(attrName);
|
|
662
794
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
663
795
|
state.needsEffect = true;
|
|
796
|
+
const valueExpr = t.isIdentifier(expr) && (isSignalIdentifier(expr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(expr.name)) ? t.callExpression(expr, []) : expr;
|
|
664
797
|
const effectCall = t.callExpression(t.identifier("_$effect"), [
|
|
665
|
-
t.arrowFunctionExpression([], buildSetPropCall(domName,
|
|
798
|
+
t.arrowFunctionExpression([], buildSetPropCall(domName, valueExpr))
|
|
666
799
|
]);
|
|
667
800
|
if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
668
801
|
t.addComment(
|
|
@@ -684,7 +817,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
684
817
|
let childIndex = 0;
|
|
685
818
|
for (const child of children) {
|
|
686
819
|
if (t.isJSXText(child)) {
|
|
687
|
-
const text = child.value
|
|
820
|
+
const text = normalizeJsxText(child.value);
|
|
688
821
|
if (text) childIndex++;
|
|
689
822
|
continue;
|
|
690
823
|
}
|
|
@@ -713,22 +846,31 @@ function whatBabelPlugin({ types: t }) {
|
|
|
713
846
|
const entriesNeedingRef = entries.filter(
|
|
714
847
|
(e) => e.type === "expression" || e.type === "component" || e.type === "static" && e.hasAnythingDynamic
|
|
715
848
|
);
|
|
716
|
-
const
|
|
717
|
-
const needsPreCapture = entriesNeedingRef.length >= 2 && hasDynamicInsert;
|
|
849
|
+
const needsPreCapture = entriesNeedingRef.length >= 2;
|
|
718
850
|
const markerVars = /* @__PURE__ */ new Map();
|
|
719
851
|
if (needsPreCapture) {
|
|
852
|
+
let prevVar = null;
|
|
853
|
+
let prevIndex = 0;
|
|
720
854
|
for (const entry of entriesNeedingRef) {
|
|
721
|
-
const
|
|
855
|
+
const idx = entry.childIndex;
|
|
722
856
|
const markerVar = state.nextVarId();
|
|
723
|
-
markerVars.set(
|
|
857
|
+
markerVars.set(idx, markerVar);
|
|
858
|
+
let init;
|
|
859
|
+
if (prevVar === null) {
|
|
860
|
+
init = buildChildAccess(elId, idx);
|
|
861
|
+
} else {
|
|
862
|
+
init = t.identifier(prevVar);
|
|
863
|
+
for (let i = prevIndex; i < idx; i++) {
|
|
864
|
+
init = t.memberExpression(init, t.identifier("nextSibling"));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
724
867
|
statements.push(
|
|
725
868
|
t.variableDeclaration("const", [
|
|
726
|
-
t.variableDeclarator(
|
|
727
|
-
t.identifier(markerVar),
|
|
728
|
-
buildChildAccess(elId, entry.childIndex)
|
|
729
|
-
)
|
|
869
|
+
t.variableDeclarator(t.identifier(markerVar), init)
|
|
730
870
|
])
|
|
731
871
|
);
|
|
872
|
+
prevVar = markerVar;
|
|
873
|
+
prevIndex = idx;
|
|
732
874
|
}
|
|
733
875
|
}
|
|
734
876
|
function getMarker(idx) {
|
|
@@ -739,9 +881,41 @@ function whatBabelPlugin({ types: t }) {
|
|
|
739
881
|
}
|
|
740
882
|
for (const entry of entries) {
|
|
741
883
|
if (entry.type === "expression") {
|
|
742
|
-
|
|
884
|
+
let expr = entry.child.expression;
|
|
743
885
|
const marker = getMarker(entry.childIndex);
|
|
744
886
|
state.needsInsert = true;
|
|
887
|
+
const mapResult = tryLowerMapToMapArray(expr, state);
|
|
888
|
+
if (mapResult) {
|
|
889
|
+
state.needsMapArray = true;
|
|
890
|
+
const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
|
|
891
|
+
const isArrowAlready = t.isArrowFunctionExpression(mapResult);
|
|
892
|
+
const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
|
|
893
|
+
statements.push(
|
|
894
|
+
t.expressionStatement(
|
|
895
|
+
t.callExpression(t.identifier("_$insert"), [
|
|
896
|
+
t.identifier(elId),
|
|
897
|
+
insertArg,
|
|
898
|
+
marker
|
|
899
|
+
])
|
|
900
|
+
)
|
|
901
|
+
);
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
|
|
905
|
+
if (isMapArrayCall) {
|
|
906
|
+
state.needsMapArray = true;
|
|
907
|
+
if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
|
|
908
|
+
statements.push(
|
|
909
|
+
t.expressionStatement(
|
|
910
|
+
t.callExpression(t.identifier("_$insert"), [
|
|
911
|
+
t.identifier(elId),
|
|
912
|
+
expr,
|
|
913
|
+
marker
|
|
914
|
+
])
|
|
915
|
+
)
|
|
916
|
+
);
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
745
919
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
746
920
|
const insertCall = t.callExpression(t.identifier("_$insert"), [
|
|
747
921
|
t.identifier(elId),
|
|
@@ -950,7 +1124,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
950
1124
|
}
|
|
951
1125
|
continue;
|
|
952
1126
|
}
|
|
953
|
-
if (attrName.startsWith("on") && attrName.includes("|")) {
|
|
1127
|
+
if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
|
|
954
1128
|
const { eventName, modifiers } = parseEventModifiers(attrName);
|
|
955
1129
|
const handler = getAttributeValue(attr.value);
|
|
956
1130
|
const wrappedHandler = createEventHandler(handler, modifiers);
|
|
@@ -968,7 +1142,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
968
1142
|
const transformedChildren = [];
|
|
969
1143
|
for (const child of children) {
|
|
970
1144
|
if (t.isJSXText(child)) {
|
|
971
|
-
const text = child.value
|
|
1145
|
+
const text = normalizeJsxText(child.value);
|
|
972
1146
|
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
973
1147
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
974
1148
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -1002,10 +1176,24 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1002
1176
|
const { node } = path3;
|
|
1003
1177
|
const attributes = node.openingElement.attributes;
|
|
1004
1178
|
const children = node.children;
|
|
1179
|
+
if (true) {
|
|
1180
|
+
const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
|
|
1181
|
+
if (!_forInfoWarned.has(fileName)) {
|
|
1182
|
+
_forInfoWarned.add(fileName);
|
|
1183
|
+
const loc = node.loc;
|
|
1184
|
+
const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
|
|
1185
|
+
console.info(
|
|
1186
|
+
`[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).`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1005
1190
|
let eachExpr = null;
|
|
1191
|
+
let keyExpr = null;
|
|
1006
1192
|
for (const attr of attributes) {
|
|
1007
|
-
if (t.isJSXAttribute(attr)
|
|
1008
|
-
|
|
1193
|
+
if (t.isJSXAttribute(attr)) {
|
|
1194
|
+
const name = getAttrName(attr);
|
|
1195
|
+
if (name === "each") eachExpr = getAttributeValue(attr.value);
|
|
1196
|
+
else if (name === "key") keyExpr = getAttributeValue(attr.value);
|
|
1009
1197
|
}
|
|
1010
1198
|
}
|
|
1011
1199
|
if (!eachExpr) {
|
|
@@ -1026,11 +1214,78 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1026
1214
|
return transformElementAsH(path3, state);
|
|
1027
1215
|
}
|
|
1028
1216
|
state.needsMapArray = true;
|
|
1029
|
-
|
|
1217
|
+
const args = [eachExpr, renderFn];
|
|
1218
|
+
if (keyExpr) {
|
|
1219
|
+
args.push(t.objectExpression([
|
|
1220
|
+
t.objectProperty(t.identifier("key"), keyExpr)
|
|
1221
|
+
]));
|
|
1222
|
+
}
|
|
1223
|
+
return t.callExpression(t.identifier("_$mapArray"), args);
|
|
1030
1224
|
}
|
|
1031
1225
|
function transformShowFineGrained(path3, state) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1226
|
+
const { node } = path3;
|
|
1227
|
+
const attributes = node.openingElement.attributes;
|
|
1228
|
+
const children = node.children;
|
|
1229
|
+
let whenExpr = null;
|
|
1230
|
+
let fallbackExpr = null;
|
|
1231
|
+
for (const attr of attributes) {
|
|
1232
|
+
if (t.isJSXAttribute(attr)) {
|
|
1233
|
+
const name = getAttrName(attr);
|
|
1234
|
+
if (name === "when") whenExpr = getAttributeValue(attr.value);
|
|
1235
|
+
else if (name === "fallback") fallbackExpr = getAttributeValue(attr.value);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (!whenExpr) {
|
|
1239
|
+
throw path3.buildCodeFrameError(
|
|
1240
|
+
'<Show> requires a "when" prop. Example: <Show when={isOpen} fallback={null}>...</Show>'
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
let contentExpr = null;
|
|
1244
|
+
for (const child of children) {
|
|
1245
|
+
if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
|
|
1246
|
+
contentExpr = child.expression;
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (!contentExpr) {
|
|
1251
|
+
const transformedChildren = [];
|
|
1252
|
+
for (const child of children) {
|
|
1253
|
+
if (t.isJSXText(child)) {
|
|
1254
|
+
const text = normalizeJsxText(child.value);
|
|
1255
|
+
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
1256
|
+
} else if (t.isJSXElement(child)) {
|
|
1257
|
+
transformedChildren.push(transformElementFineGrained({ node: child }, state));
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (transformedChildren.length === 1) {
|
|
1261
|
+
contentExpr = transformedChildren[0];
|
|
1262
|
+
} else if (transformedChildren.length > 1) {
|
|
1263
|
+
contentExpr = t.arrayExpression(transformedChildren);
|
|
1264
|
+
} else {
|
|
1265
|
+
contentExpr = t.nullLiteral();
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
let condition;
|
|
1269
|
+
if (t.isCallExpression(whenExpr)) {
|
|
1270
|
+
condition = whenExpr;
|
|
1271
|
+
} else if (t.isArrowFunctionExpression(whenExpr) && t.isExpression(whenExpr.body)) {
|
|
1272
|
+
condition = whenExpr.body;
|
|
1273
|
+
} else if (t.isIdentifier(whenExpr) && (state.signalNames && isSignalIdentifier(whenExpr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(whenExpr.name))) {
|
|
1274
|
+
condition = t.callExpression(whenExpr, []);
|
|
1275
|
+
} else {
|
|
1276
|
+
condition = whenExpr;
|
|
1277
|
+
}
|
|
1278
|
+
const vId = path3.scope ? path3.scope.generateUidIdentifier("v") : t.identifier("_v");
|
|
1279
|
+
const consequent = t.isFunction(contentExpr) ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
|
|
1280
|
+
const alternate = fallbackExpr || t.nullLiteral();
|
|
1281
|
+
return t.arrowFunctionExpression([], t.blockStatement([
|
|
1282
|
+
t.variableDeclaration("const", [
|
|
1283
|
+
t.variableDeclarator(vId, condition)
|
|
1284
|
+
]),
|
|
1285
|
+
t.returnStatement(
|
|
1286
|
+
t.conditionalExpression(t.cloneNode(vId), consequent, alternate)
|
|
1287
|
+
)
|
|
1288
|
+
]));
|
|
1034
1289
|
}
|
|
1035
1290
|
function transformFragmentFineGrained(path3, state) {
|
|
1036
1291
|
const { node } = path3;
|
|
@@ -1038,7 +1293,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1038
1293
|
const transformed = [];
|
|
1039
1294
|
for (const child of children) {
|
|
1040
1295
|
if (t.isJSXText(child)) {
|
|
1041
|
-
const text = child.value
|
|
1296
|
+
const text = normalizeJsxText(child.value);
|
|
1042
1297
|
if (text) transformed.push(t.stringLiteral(text));
|
|
1043
1298
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
1044
1299
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -1248,20 +1503,31 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1248
1503
|
}
|
|
1249
1504
|
},
|
|
1250
1505
|
JSXElement(path3, state) {
|
|
1251
|
-
|
|
1506
|
+
const scope = path3.scope;
|
|
1507
|
+
let cache = state._signalNamesCache;
|
|
1508
|
+
if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
|
|
1509
|
+
let names = cache.get(scope);
|
|
1510
|
+
if (!names) {
|
|
1511
|
+
names = collectSignalNamesFromScope(path3);
|
|
1512
|
+
cache.set(scope, names);
|
|
1513
|
+
}
|
|
1514
|
+
state.signalNames = names;
|
|
1252
1515
|
state._pendingSetup = [];
|
|
1253
1516
|
const transformed = transformElementFineGrained(path3, state);
|
|
1254
1517
|
const pending = state._pendingSetup;
|
|
1255
1518
|
state._pendingSetup = [];
|
|
1256
1519
|
if (pending.length > 0) {
|
|
1257
1520
|
let stmtPath = path3;
|
|
1521
|
+
let crossedFunctionBoundary = false;
|
|
1258
1522
|
while (stmtPath && !stmtPath.isStatement()) {
|
|
1523
|
+
if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
|
|
1524
|
+
crossedFunctionBoundary = true;
|
|
1525
|
+
}
|
|
1259
1526
|
stmtPath = stmtPath.parentPath;
|
|
1260
1527
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1528
|
+
const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
|
|
1529
|
+
if (inStatementList && !crossedFunctionBoundary) {
|
|
1530
|
+
stmtPath.insertBefore(pending);
|
|
1265
1531
|
path3.replaceWith(transformed);
|
|
1266
1532
|
} else {
|
|
1267
1533
|
pending.push(t.returnStatement(transformed));
|
|
@@ -1417,6 +1683,13 @@ function extractPageConfig(source) {
|
|
|
1417
1683
|
return { mode: "client" };
|
|
1418
1684
|
}
|
|
1419
1685
|
}
|
|
1686
|
+
function detectPageExports(source) {
|
|
1687
|
+
return {
|
|
1688
|
+
hasLoader: /export\s+(?:async\s+)?(?:const|let|var|function)\s+loader\b/.test(source),
|
|
1689
|
+
hasGetStaticPaths: /export\s+(?:async\s+)?(?:const|let|var|function)\s+getStaticPaths\b/.test(source),
|
|
1690
|
+
hasPageConfig: /export\s+const\s+page\b/.test(source)
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1420
1693
|
function generateRoutesModule(pagesDir, rootDir) {
|
|
1421
1694
|
const { pages, layouts, apiRoutes } = scanPages(pagesDir);
|
|
1422
1695
|
const imports = [];
|
|
@@ -1433,9 +1706,11 @@ function generateRoutesModule(pagesDir, rootDir) {
|
|
|
1433
1706
|
const relPath = toImportPath(page.filePath, rootDir);
|
|
1434
1707
|
imports.push(`import ${varName} from '${relPath}';`);
|
|
1435
1708
|
let pageConfig = { mode: "client" };
|
|
1709
|
+
let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
|
|
1436
1710
|
try {
|
|
1437
1711
|
const source = fs.readFileSync(page.filePath, "utf-8");
|
|
1438
1712
|
pageConfig = extractPageConfig(source);
|
|
1713
|
+
detected = detectPageExports(source);
|
|
1439
1714
|
} catch {
|
|
1440
1715
|
}
|
|
1441
1716
|
const layoutVar = findLayout(page.routePath, layoutMap);
|
|
@@ -1443,7 +1718,8 @@ function generateRoutesModule(pagesDir, rootDir) {
|
|
|
1443
1718
|
path: page.routePath,
|
|
1444
1719
|
component: varName,
|
|
1445
1720
|
mode: pageConfig.mode || "client",
|
|
1446
|
-
layout: layoutVar || null
|
|
1721
|
+
layout: layoutVar || null,
|
|
1722
|
+
hasLoader: detected.hasLoader
|
|
1447
1723
|
};
|
|
1448
1724
|
routeEntries.push(entry);
|
|
1449
1725
|
});
|
|
@@ -1465,7 +1741,7 @@ function generateRoutesModule(pagesDir, rootDir) {
|
|
|
1465
1741
|
"",
|
|
1466
1742
|
"export const routes = [",
|
|
1467
1743
|
...routeEntries.map(
|
|
1468
|
-
(r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""} },`
|
|
1744
|
+
(r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""}${r.hasLoader ? ", hasLoader: true" : ""} },`
|
|
1469
1745
|
),
|
|
1470
1746
|
"];",
|
|
1471
1747
|
"",
|
|
@@ -2018,8 +2294,34 @@ function whatVitePlugin(options = {}) {
|
|
|
2018
2294
|
jsx: "preserve"
|
|
2019
2295
|
},
|
|
2020
2296
|
optimizeDeps: {
|
|
2021
|
-
//
|
|
2022
|
-
|
|
2297
|
+
// Exclude framework packages from Vite's dependency pre-bundling.
|
|
2298
|
+
//
|
|
2299
|
+
// Bug class this prevents — "dual module instance":
|
|
2300
|
+
// The compiler emits `import { ... } from 'what-framework/render'`
|
|
2301
|
+
// (a subpath resolved to the source file). Meanwhile user code
|
|
2302
|
+
// imports `'what-framework'` (the package entry). If Vite
|
|
2303
|
+
// pre-bundles `'what-framework'` into an esbuild chunk under
|
|
2304
|
+
// node_modules/.vite, those two import paths resolve to two
|
|
2305
|
+
// *different* module instances. Module-scoped state — the
|
|
2306
|
+
// `componentStack` used by createComponent, effect ownership,
|
|
2307
|
+
// the signal subscriber registry — is duplicated, so a signal
|
|
2308
|
+
// created in user code never notifies effects created via the
|
|
2309
|
+
// compiler-emitted path, and `getCurrentComponent()` returns
|
|
2310
|
+
// undefined inside components mounted through compiler output.
|
|
2311
|
+
//
|
|
2312
|
+
// Why `exclude` is the right knob:
|
|
2313
|
+
// `include` would force pre-bundling of the package entry, which
|
|
2314
|
+
// does not resolve the subpath import the compiler emits — so the
|
|
2315
|
+
// split persists. Using `exclude` tells Vite to skip the optimizer
|
|
2316
|
+
// for these packages and serve them via the normal module graph,
|
|
2317
|
+
// where both the package entry and the `/render` subpath share
|
|
2318
|
+
// a single ESM module record.
|
|
2319
|
+
//
|
|
2320
|
+
// Regression symptom if this is removed:
|
|
2321
|
+
// Components mount but lifecycle hooks (onMount, onCleanup) and
|
|
2322
|
+
// shared store state silently no-op; effects don't re-run on
|
|
2323
|
+
// signal writes from user code; SSR/CSR hydration mismatches.
|
|
2324
|
+
exclude: ["what-framework", "what-core", "what-compiler", "what-router"]
|
|
2023
2325
|
}
|
|
2024
2326
|
};
|
|
2025
2327
|
}
|