what-compiler 0.8.3 → 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/babel-plugin.js
CHANGED
|
@@ -61,9 +61,56 @@ var SIGNAL_CREATORS = /* @__PURE__ */ new Set([
|
|
|
61
61
|
"useQuery",
|
|
62
62
|
"useInfiniteQuery"
|
|
63
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
|
+
}
|
|
64
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
|
+
}
|
|
65
111
|
function parseEventModifiers(name) {
|
|
66
|
-
const
|
|
112
|
+
const delimiter = name.includes("|") ? "|" : "__";
|
|
113
|
+
const parts = name.split(delimiter);
|
|
67
114
|
const eventName = parts[0];
|
|
68
115
|
const modifiers = parts.slice(1).filter((m) => EVENT_MODIFIERS.has(m));
|
|
69
116
|
return { eventName, modifiers };
|
|
@@ -193,22 +240,20 @@ function whatBabelPlugin({ types: t }) {
|
|
|
193
240
|
}
|
|
194
241
|
let scope = path.scope;
|
|
195
242
|
while (scope) {
|
|
196
|
-
for (const
|
|
243
|
+
for (const binding of Object.values(scope.bindings)) {
|
|
197
244
|
if (binding.path.isVariableDeclarator()) {
|
|
198
245
|
extractFromDeclarator(binding.path.node);
|
|
199
246
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
}
|
|
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);
|
|
212
257
|
}
|
|
213
258
|
}
|
|
214
259
|
}
|
|
@@ -276,7 +321,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
276
321
|
return isPotentiallyReactive(expr.callee, signalNames, importedIds) || expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
|
|
277
322
|
}
|
|
278
323
|
if (t.isIdentifier(expr)) {
|
|
279
|
-
return isSignalIdentifier(expr.name, signalNames);
|
|
324
|
+
return isSignalIdentifier(expr.name, signalNames) || importedIds && importedIds.has(expr.name);
|
|
280
325
|
}
|
|
281
326
|
if (t.isMemberExpression(expr)) {
|
|
282
327
|
return isPotentiallyReactive(expr.object, signalNames, importedIds);
|
|
@@ -306,6 +351,93 @@ function whatBabelPlugin({ types: t }) {
|
|
|
306
351
|
}
|
|
307
352
|
return false;
|
|
308
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
|
+
}
|
|
309
441
|
function isStaticChild(child) {
|
|
310
442
|
if (t.isJSXText(child)) return true;
|
|
311
443
|
if (t.isJSXExpressionContainer(child)) return false;
|
|
@@ -329,7 +461,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
329
461
|
}
|
|
330
462
|
function extractStaticHTML(node) {
|
|
331
463
|
if (t.isJSXText(node)) {
|
|
332
|
-
const text = node.value
|
|
464
|
+
const text = normalizeJsxText(node.value);
|
|
333
465
|
return text ? escapeHTML(text) : "";
|
|
334
466
|
}
|
|
335
467
|
if (t.isJSXExpressionContainer(node)) {
|
|
@@ -369,7 +501,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
369
501
|
html += ">";
|
|
370
502
|
for (const child of node.children) {
|
|
371
503
|
if (t.isJSXText(child)) {
|
|
372
|
-
const text = child.value
|
|
504
|
+
const text = normalizeJsxText(child.value);
|
|
373
505
|
if (text) html += escapeHTML(text);
|
|
374
506
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
375
507
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -396,15 +528,15 @@ function whatBabelPlugin({ types: t }) {
|
|
|
396
528
|
const { node } = path;
|
|
397
529
|
const openingElement = node.openingElement;
|
|
398
530
|
const tagName = openingElement.name.name;
|
|
399
|
-
if (isComponent(tagName)) {
|
|
400
|
-
return transformComponentFineGrained(path, state);
|
|
401
|
-
}
|
|
402
531
|
if (tagName === "For") {
|
|
403
532
|
return transformForFineGrained(path, state);
|
|
404
533
|
}
|
|
405
534
|
if (tagName === "Show") {
|
|
406
535
|
return transformShowFineGrained(path, state);
|
|
407
536
|
}
|
|
537
|
+
if (isComponent(tagName)) {
|
|
538
|
+
return transformComponentFineGrained(path, state);
|
|
539
|
+
}
|
|
408
540
|
const attributes = openingElement.attributes;
|
|
409
541
|
const children = node.children;
|
|
410
542
|
const allChildrenStatic = children.every(isStaticChild);
|
|
@@ -469,7 +601,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
469
601
|
const transformedChildren = [];
|
|
470
602
|
for (const child of children) {
|
|
471
603
|
if (t.isJSXText(child)) {
|
|
472
|
-
const text = child.value
|
|
604
|
+
const text = normalizeJsxText(child.value);
|
|
473
605
|
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
474
606
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
475
607
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -526,7 +658,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
526
658
|
);
|
|
527
659
|
continue;
|
|
528
660
|
}
|
|
529
|
-
if (attrName.startsWith("on") && !attrName.includes("|")) {
|
|
661
|
+
if (attrName.startsWith("on") && !attrName.includes("|") && !hasEventModifiers(attrName, state)) {
|
|
530
662
|
const event = attrName.slice(2).toLowerCase();
|
|
531
663
|
const handler = getAttributeValue(attr.value);
|
|
532
664
|
if (DELEGATED_EVENTS.has(event)) {
|
|
@@ -539,7 +671,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
539
671
|
"=",
|
|
540
672
|
t.memberExpression(
|
|
541
673
|
t.identifier(elId),
|
|
542
|
-
t.identifier(
|
|
674
|
+
t.identifier(`$$${event}`)
|
|
543
675
|
),
|
|
544
676
|
handler
|
|
545
677
|
)
|
|
@@ -557,7 +689,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
557
689
|
}
|
|
558
690
|
continue;
|
|
559
691
|
}
|
|
560
|
-
if (attrName.startsWith("on") && attrName.includes("|")) {
|
|
692
|
+
if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
|
|
561
693
|
const { eventName, modifiers } = parseEventModifiers(attrName);
|
|
562
694
|
const handler = getAttributeValue(attr.value);
|
|
563
695
|
const wrappedHandler = createEventHandler(handler, modifiers);
|
|
@@ -657,8 +789,9 @@ function whatBabelPlugin({ types: t }) {
|
|
|
657
789
|
const domName = normalizeAttrName(attrName);
|
|
658
790
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
659
791
|
state.needsEffect = true;
|
|
792
|
+
const valueExpr = t.isIdentifier(expr) && (isSignalIdentifier(expr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(expr.name)) ? t.callExpression(expr, []) : expr;
|
|
660
793
|
const effectCall = t.callExpression(t.identifier("_$effect"), [
|
|
661
|
-
t.arrowFunctionExpression([], buildSetPropCall(domName,
|
|
794
|
+
t.arrowFunctionExpression([], buildSetPropCall(domName, valueExpr))
|
|
662
795
|
]);
|
|
663
796
|
if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
664
797
|
t.addComment(
|
|
@@ -680,7 +813,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
680
813
|
let childIndex = 0;
|
|
681
814
|
for (const child of children) {
|
|
682
815
|
if (t.isJSXText(child)) {
|
|
683
|
-
const text = child.value
|
|
816
|
+
const text = normalizeJsxText(child.value);
|
|
684
817
|
if (text) childIndex++;
|
|
685
818
|
continue;
|
|
686
819
|
}
|
|
@@ -709,22 +842,31 @@ function whatBabelPlugin({ types: t }) {
|
|
|
709
842
|
const entriesNeedingRef = entries.filter(
|
|
710
843
|
(e) => e.type === "expression" || e.type === "component" || e.type === "static" && e.hasAnythingDynamic
|
|
711
844
|
);
|
|
712
|
-
const
|
|
713
|
-
const needsPreCapture = entriesNeedingRef.length >= 2 && hasDynamicInsert;
|
|
845
|
+
const needsPreCapture = entriesNeedingRef.length >= 2;
|
|
714
846
|
const markerVars = /* @__PURE__ */ new Map();
|
|
715
847
|
if (needsPreCapture) {
|
|
848
|
+
let prevVar = null;
|
|
849
|
+
let prevIndex = 0;
|
|
716
850
|
for (const entry of entriesNeedingRef) {
|
|
717
|
-
const
|
|
851
|
+
const idx = entry.childIndex;
|
|
718
852
|
const markerVar = state.nextVarId();
|
|
719
|
-
markerVars.set(
|
|
853
|
+
markerVars.set(idx, markerVar);
|
|
854
|
+
let init;
|
|
855
|
+
if (prevVar === null) {
|
|
856
|
+
init = buildChildAccess(elId, idx);
|
|
857
|
+
} else {
|
|
858
|
+
init = t.identifier(prevVar);
|
|
859
|
+
for (let i = prevIndex; i < idx; i++) {
|
|
860
|
+
init = t.memberExpression(init, t.identifier("nextSibling"));
|
|
861
|
+
}
|
|
862
|
+
}
|
|
720
863
|
statements.push(
|
|
721
864
|
t.variableDeclaration("const", [
|
|
722
|
-
t.variableDeclarator(
|
|
723
|
-
t.identifier(markerVar),
|
|
724
|
-
buildChildAccess(elId, entry.childIndex)
|
|
725
|
-
)
|
|
865
|
+
t.variableDeclarator(t.identifier(markerVar), init)
|
|
726
866
|
])
|
|
727
867
|
);
|
|
868
|
+
prevVar = markerVar;
|
|
869
|
+
prevIndex = idx;
|
|
728
870
|
}
|
|
729
871
|
}
|
|
730
872
|
function getMarker(idx) {
|
|
@@ -735,9 +877,41 @@ function whatBabelPlugin({ types: t }) {
|
|
|
735
877
|
}
|
|
736
878
|
for (const entry of entries) {
|
|
737
879
|
if (entry.type === "expression") {
|
|
738
|
-
|
|
880
|
+
let expr = entry.child.expression;
|
|
739
881
|
const marker = getMarker(entry.childIndex);
|
|
740
882
|
state.needsInsert = true;
|
|
883
|
+
const mapResult = tryLowerMapToMapArray(expr, state);
|
|
884
|
+
if (mapResult) {
|
|
885
|
+
state.needsMapArray = true;
|
|
886
|
+
const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
|
|
887
|
+
const isArrowAlready = t.isArrowFunctionExpression(mapResult);
|
|
888
|
+
const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
|
|
889
|
+
statements.push(
|
|
890
|
+
t.expressionStatement(
|
|
891
|
+
t.callExpression(t.identifier("_$insert"), [
|
|
892
|
+
t.identifier(elId),
|
|
893
|
+
insertArg,
|
|
894
|
+
marker
|
|
895
|
+
])
|
|
896
|
+
)
|
|
897
|
+
);
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
|
|
901
|
+
if (isMapArrayCall) {
|
|
902
|
+
state.needsMapArray = true;
|
|
903
|
+
if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
|
|
904
|
+
statements.push(
|
|
905
|
+
t.expressionStatement(
|
|
906
|
+
t.callExpression(t.identifier("_$insert"), [
|
|
907
|
+
t.identifier(elId),
|
|
908
|
+
expr,
|
|
909
|
+
marker
|
|
910
|
+
])
|
|
911
|
+
)
|
|
912
|
+
);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
741
915
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
742
916
|
const insertCall = t.callExpression(t.identifier("_$insert"), [
|
|
743
917
|
t.identifier(elId),
|
|
@@ -946,7 +1120,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
946
1120
|
}
|
|
947
1121
|
continue;
|
|
948
1122
|
}
|
|
949
|
-
if (attrName.startsWith("on") && attrName.includes("|")) {
|
|
1123
|
+
if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
|
|
950
1124
|
const { eventName, modifiers } = parseEventModifiers(attrName);
|
|
951
1125
|
const handler = getAttributeValue(attr.value);
|
|
952
1126
|
const wrappedHandler = createEventHandler(handler, modifiers);
|
|
@@ -964,7 +1138,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
964
1138
|
const transformedChildren = [];
|
|
965
1139
|
for (const child of children) {
|
|
966
1140
|
if (t.isJSXText(child)) {
|
|
967
|
-
const text = child.value
|
|
1141
|
+
const text = normalizeJsxText(child.value);
|
|
968
1142
|
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
969
1143
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
970
1144
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -998,10 +1172,24 @@ function whatBabelPlugin({ types: t }) {
|
|
|
998
1172
|
const { node } = path;
|
|
999
1173
|
const attributes = node.openingElement.attributes;
|
|
1000
1174
|
const children = node.children;
|
|
1175
|
+
if (true) {
|
|
1176
|
+
const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
|
|
1177
|
+
if (!_forInfoWarned.has(fileName)) {
|
|
1178
|
+
_forInfoWarned.add(fileName);
|
|
1179
|
+
const loc = node.loc;
|
|
1180
|
+
const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
|
|
1181
|
+
console.info(
|
|
1182
|
+
`[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).`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1001
1186
|
let eachExpr = null;
|
|
1187
|
+
let keyExpr = null;
|
|
1002
1188
|
for (const attr of attributes) {
|
|
1003
|
-
if (t.isJSXAttribute(attr)
|
|
1004
|
-
|
|
1189
|
+
if (t.isJSXAttribute(attr)) {
|
|
1190
|
+
const name = getAttrName(attr);
|
|
1191
|
+
if (name === "each") eachExpr = getAttributeValue(attr.value);
|
|
1192
|
+
else if (name === "key") keyExpr = getAttributeValue(attr.value);
|
|
1005
1193
|
}
|
|
1006
1194
|
}
|
|
1007
1195
|
if (!eachExpr) {
|
|
@@ -1022,11 +1210,78 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1022
1210
|
return transformElementAsH(path, state);
|
|
1023
1211
|
}
|
|
1024
1212
|
state.needsMapArray = true;
|
|
1025
|
-
|
|
1213
|
+
const args = [eachExpr, renderFn];
|
|
1214
|
+
if (keyExpr) {
|
|
1215
|
+
args.push(t.objectExpression([
|
|
1216
|
+
t.objectProperty(t.identifier("key"), keyExpr)
|
|
1217
|
+
]));
|
|
1218
|
+
}
|
|
1219
|
+
return t.callExpression(t.identifier("_$mapArray"), args);
|
|
1026
1220
|
}
|
|
1027
1221
|
function transformShowFineGrained(path, state) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1222
|
+
const { node } = path;
|
|
1223
|
+
const attributes = node.openingElement.attributes;
|
|
1224
|
+
const children = node.children;
|
|
1225
|
+
let whenExpr = null;
|
|
1226
|
+
let fallbackExpr = null;
|
|
1227
|
+
for (const attr of attributes) {
|
|
1228
|
+
if (t.isJSXAttribute(attr)) {
|
|
1229
|
+
const name = getAttrName(attr);
|
|
1230
|
+
if (name === "when") whenExpr = getAttributeValue(attr.value);
|
|
1231
|
+
else if (name === "fallback") fallbackExpr = getAttributeValue(attr.value);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (!whenExpr) {
|
|
1235
|
+
throw path.buildCodeFrameError(
|
|
1236
|
+
'<Show> requires a "when" prop. Example: <Show when={isOpen} fallback={null}>...</Show>'
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
let contentExpr = null;
|
|
1240
|
+
for (const child of children) {
|
|
1241
|
+
if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
|
|
1242
|
+
contentExpr = child.expression;
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (!contentExpr) {
|
|
1247
|
+
const transformedChildren = [];
|
|
1248
|
+
for (const child of children) {
|
|
1249
|
+
if (t.isJSXText(child)) {
|
|
1250
|
+
const text = normalizeJsxText(child.value);
|
|
1251
|
+
if (text) transformedChildren.push(t.stringLiteral(text));
|
|
1252
|
+
} else if (t.isJSXElement(child)) {
|
|
1253
|
+
transformedChildren.push(transformElementFineGrained({ node: child }, state));
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
if (transformedChildren.length === 1) {
|
|
1257
|
+
contentExpr = transformedChildren[0];
|
|
1258
|
+
} else if (transformedChildren.length > 1) {
|
|
1259
|
+
contentExpr = t.arrayExpression(transformedChildren);
|
|
1260
|
+
} else {
|
|
1261
|
+
contentExpr = t.nullLiteral();
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
let condition;
|
|
1265
|
+
if (t.isCallExpression(whenExpr)) {
|
|
1266
|
+
condition = whenExpr;
|
|
1267
|
+
} else if (t.isArrowFunctionExpression(whenExpr) && t.isExpression(whenExpr.body)) {
|
|
1268
|
+
condition = whenExpr.body;
|
|
1269
|
+
} else if (t.isIdentifier(whenExpr) && (state.signalNames && isSignalIdentifier(whenExpr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(whenExpr.name))) {
|
|
1270
|
+
condition = t.callExpression(whenExpr, []);
|
|
1271
|
+
} else {
|
|
1272
|
+
condition = whenExpr;
|
|
1273
|
+
}
|
|
1274
|
+
const vId = path.scope ? path.scope.generateUidIdentifier("v") : t.identifier("_v");
|
|
1275
|
+
const consequent = t.isFunction(contentExpr) ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
|
|
1276
|
+
const alternate = fallbackExpr || t.nullLiteral();
|
|
1277
|
+
return t.arrowFunctionExpression([], t.blockStatement([
|
|
1278
|
+
t.variableDeclaration("const", [
|
|
1279
|
+
t.variableDeclarator(vId, condition)
|
|
1280
|
+
]),
|
|
1281
|
+
t.returnStatement(
|
|
1282
|
+
t.conditionalExpression(t.cloneNode(vId), consequent, alternate)
|
|
1283
|
+
)
|
|
1284
|
+
]));
|
|
1030
1285
|
}
|
|
1031
1286
|
function transformFragmentFineGrained(path, state) {
|
|
1032
1287
|
const { node } = path;
|
|
@@ -1034,7 +1289,7 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1034
1289
|
const transformed = [];
|
|
1035
1290
|
for (const child of children) {
|
|
1036
1291
|
if (t.isJSXText(child)) {
|
|
1037
|
-
const text = child.value
|
|
1292
|
+
const text = normalizeJsxText(child.value);
|
|
1038
1293
|
if (text) transformed.push(t.stringLiteral(text));
|
|
1039
1294
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
1040
1295
|
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
@@ -1244,20 +1499,31 @@ function whatBabelPlugin({ types: t }) {
|
|
|
1244
1499
|
}
|
|
1245
1500
|
},
|
|
1246
1501
|
JSXElement(path, state) {
|
|
1247
|
-
|
|
1502
|
+
const scope = path.scope;
|
|
1503
|
+
let cache = state._signalNamesCache;
|
|
1504
|
+
if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
|
|
1505
|
+
let names = cache.get(scope);
|
|
1506
|
+
if (!names) {
|
|
1507
|
+
names = collectSignalNamesFromScope(path);
|
|
1508
|
+
cache.set(scope, names);
|
|
1509
|
+
}
|
|
1510
|
+
state.signalNames = names;
|
|
1248
1511
|
state._pendingSetup = [];
|
|
1249
1512
|
const transformed = transformElementFineGrained(path, state);
|
|
1250
1513
|
const pending = state._pendingSetup;
|
|
1251
1514
|
state._pendingSetup = [];
|
|
1252
1515
|
if (pending.length > 0) {
|
|
1253
1516
|
let stmtPath = path;
|
|
1517
|
+
let crossedFunctionBoundary = false;
|
|
1254
1518
|
while (stmtPath && !stmtPath.isStatement()) {
|
|
1519
|
+
if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
|
|
1520
|
+
crossedFunctionBoundary = true;
|
|
1521
|
+
}
|
|
1255
1522
|
stmtPath = stmtPath.parentPath;
|
|
1256
1523
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1524
|
+
const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
|
|
1525
|
+
if (inStatementList && !crossedFunctionBoundary) {
|
|
1526
|
+
stmtPath.insertBefore(pending);
|
|
1261
1527
|
path.replaceWith(transformed);
|
|
1262
1528
|
} else {
|
|
1263
1529
|
pending.push(t.returnStatement(transformed));
|