@barefootjs/jsx 0.4.0 → 0.5.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/adapters/interface.d.ts +20 -0
- package/dist/adapters/interface.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +36 -19
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/import-map.d.ts +56 -0
- package/dist/import-map.d.ts.map +1 -0
- package/dist/import-map.js +18 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +154 -162
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/scanner/js-scanner.d.ts +10 -0
- package/dist/scanner/js-scanner.d.ts.map +1 -1
- package/dist/scanner/js-scanner.js +5 -0
- package/package.json +7 -3
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +134 -8
- package/src/__tests__/child-components-in-map.test.ts +76 -0
- package/src/__tests__/import-map.test.ts +75 -0
- package/src/__tests__/ir-sort-comparator.test.ts +212 -9
- package/src/__tests__/token-contains-ident.test.ts +27 -0
- package/src/__tests__/unsupported-expression.test.ts +42 -13
- package/src/adapters/interface.ts +20 -0
- package/src/expression-parser.ts +265 -50
- package/src/import-map.ts +72 -0
- package/src/index.ts +5 -1
- package/src/ir-to-client-js/stringify/static-array-child-init.ts +8 -4
- package/src/ir-to-client-js/utils.ts +29 -115
- package/src/scanner/js-scanner.ts +16 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAClG,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,16 @@ export interface JsToken {
|
|
|
39
39
|
* about brace depth, not classification).
|
|
40
40
|
*/
|
|
41
41
|
export declare function iterateJsTokens(text: string, start?: number, end?: number): Generator<JsToken>;
|
|
42
|
+
export declare function isTriviaKind(kind: ts.SyntaxKind): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether `kind` is an identifier-like token — a plain identifier or any
|
|
45
|
+
* reserved/contextual keyword. Keywords lex to their own token kinds but
|
|
46
|
+
* still match the `[A-Za-z_$][\w$]*` shape the previous hand-rolled
|
|
47
|
+
* scanners treated as candidate identifier tokens, so consumers that want
|
|
48
|
+
* "every bare word in code context" (e.g. `tokenContainsIdent`) include
|
|
49
|
+
* them and compare the slice text themselves.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isIdentifierLikeToken(kind: ts.SyntaxKind): boolean;
|
|
42
52
|
type Replacement = string | ((substring: string, ...args: any[]) => string);
|
|
43
53
|
/**
|
|
44
54
|
* Apply `re` / `replacement` to `code`, but only in expression-context
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"js-scanner.d.ts","sourceRoot":"","sources":["../../src/scanner/js-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,MAAM,WAAW,OAAO;IACtB,+FAA+F;IAC/F,IAAI,EAAE,EAAE,CAAC,UAAU,CAAA;IACnB,wGAAwG;IACxG,GAAG,EAAE,MAAM,CAAA;IACX,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;;;;GAUG;AACH,wBAAiB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,KAAK,SAAI,EACT,GAAG,GAAE,MAAoB,GACxB,SAAS,CAAC,OAAO,CAAC,CA+DpB;
|
|
1
|
+
{"version":3,"file":"js-scanner.d.ts","sourceRoot":"","sources":["../../src/scanner/js-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,MAAM,WAAW,OAAO;IACtB,+FAA+F;IAC/F,IAAI,EAAE,EAAE,CAAC,UAAU,CAAA;IACnB,wGAAwG;IACxG,GAAG,EAAE,MAAM,CAAA;IACX,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;;;;GAUG;AACH,wBAAiB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,KAAK,SAAI,EACT,GAAG,GAAE,MAAoB,GACxB,SAAS,CAAC,OAAO,CAAC,CA+DpB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CASzD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAKlE;AAkBD,KAAK,WAAW,GAAG,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,CAAA;AAE3E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,WAAW,GACvB,MAAM,CAuCR;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAuBxE;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAiF1E"}
|
|
@@ -62,6 +62,9 @@ function canRegexStartHere(prev) {
|
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
function isIdentifierLikeToken(kind) {
|
|
66
|
+
return kind === ts.SyntaxKind.Identifier || kind >= ts.SyntaxKind.FirstKeyword && kind <= ts.SyntaxKind.LastKeyword;
|
|
67
|
+
}
|
|
65
68
|
function isOpaqueContentKind(kind) {
|
|
66
69
|
return kind === ts.SyntaxKind.StringLiteral || kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral || kind === ts.SyntaxKind.RegularExpressionLiteral || kind === ts.SyntaxKind.SingleLineCommentTrivia || kind === ts.SyntaxKind.MultiLineCommentTrivia;
|
|
67
70
|
}
|
|
@@ -175,6 +178,8 @@ function findTopLevelTemplateLiterals(code) {
|
|
|
175
178
|
export {
|
|
176
179
|
replaceInExprContexts,
|
|
177
180
|
iterateJsTokens,
|
|
181
|
+
isTriviaKind,
|
|
182
|
+
isIdentifierLikeToken,
|
|
178
183
|
findTopLevelTemplateLiterals,
|
|
179
184
|
findInterpolationEnd
|
|
180
185
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"types": "./dist/scanner/js-scanner.d.ts",
|
|
15
15
|
"import": "./dist/scanner/js-scanner.js"
|
|
16
16
|
},
|
|
17
|
+
"./import-map": {
|
|
18
|
+
"types": "./dist/import-map.d.ts",
|
|
19
|
+
"import": "./dist/import-map.js"
|
|
20
|
+
},
|
|
17
21
|
"./jsx-runtime": {
|
|
18
22
|
"types": "./src/jsx-runtime/index.d.ts"
|
|
19
23
|
},
|
|
@@ -27,7 +31,7 @@
|
|
|
27
31
|
],
|
|
28
32
|
"scripts": {
|
|
29
33
|
"build": "bun run build:js && bun run build:types",
|
|
30
|
-
"build:js": "bun build ./src/index.ts ./src/scanner/js-scanner.ts --root ./src --outdir ./dist --format esm --external typescript --external @barefootjs/shared --external @barefootjs/client",
|
|
34
|
+
"build:js": "bun build ./src/index.ts ./src/scanner/js-scanner.ts ./src/import-map.ts --root ./src --outdir ./dist --format esm --external typescript --external @barefootjs/shared --external @barefootjs/client",
|
|
31
35
|
"build:types": "tsgo --emitDeclarationOnly --outDir ./dist",
|
|
32
36
|
"test": "bun test && bun run typecheck:tests",
|
|
33
37
|
"typecheck:tests": "tsgo --noEmit -p __tests__/tsconfig.json",
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
"directory": "packages/jsx"
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
52
|
-
"@barefootjs/shared": "0.
|
|
56
|
+
"@barefootjs/shared": "0.5.0"
|
|
53
57
|
},
|
|
54
58
|
"peerDependencies": {
|
|
55
59
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -474,7 +474,133 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-
|
|
|
474
474
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
475
475
|
`;
|
|
476
476
|
|
|
477
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
477
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L69 — ✅ Multi-key: sort by price, break ties by name 1`] = `
|
|
478
|
+
"import { $, $t, createComponent, createEffect, createSignal, hydrate, initChild, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
479
|
+
|
|
480
|
+
export function initTodoItem(__scope, _p = {}) {
|
|
481
|
+
if (!__scope) return
|
|
482
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
483
|
+
|
|
484
|
+
const [_s0] = $t(__scope, 's0')
|
|
485
|
+
|
|
486
|
+
createEffect(() => {
|
|
487
|
+
const __val = String(_p.todo)
|
|
488
|
+
if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
hydrate('TodoItem__b3c36eee', { init: initTodoItem, template: (_p) => \`<li bf="s1"><!--bf:s0-->\${String(_p.todo)}<!--/--></li>\` })
|
|
494
|
+
export function TodoItem(_p, __bfKey) { return createComponent('TodoItem__b3c36eee', _p, __bfKey) }
|
|
495
|
+
export function initItem(__scope, _p = {}) {
|
|
496
|
+
if (!__scope) return
|
|
497
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
498
|
+
|
|
499
|
+
const [_s0] = $t(__scope, 's0')
|
|
500
|
+
|
|
501
|
+
createEffect(() => {
|
|
502
|
+
const __val = String(_p.item)
|
|
503
|
+
if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
hydrate('Item__b3c36eee', { init: initItem, template: (_p) => \`<li bf="s1"><!--bf:s0-->\${String(_p.item)}<!--/--></li>\` })
|
|
509
|
+
export function Item(_p, __bfKey) { return createComponent('Item__b3c36eee', _p, __bfKey) }
|
|
510
|
+
function initDashboard() {}
|
|
511
|
+
|
|
512
|
+
hydrate('Dashboard__b3c36eee', { init: initDashboard, template: (_p) => \`<div>D</div>\` })
|
|
513
|
+
export function Dashboard(_p, __bfKey) { return createComponent('Dashboard__b3c36eee', _p, __bfKey) }
|
|
514
|
+
export function initExample(__scope, _p = {}) {
|
|
515
|
+
if (!__scope) return
|
|
516
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
517
|
+
|
|
518
|
+
const children = _p.children
|
|
519
|
+
const [count, setCount] = createSignal(0)
|
|
520
|
+
const [isLoggedIn] = createSignal(false)
|
|
521
|
+
const [todos] = createSignal([])
|
|
522
|
+
const [items] = createSignal([])
|
|
523
|
+
const [filter] = createSignal('all')
|
|
524
|
+
const [accepted] = createSignal(false)
|
|
525
|
+
const [text, setText] = createSignal('')
|
|
526
|
+
|
|
527
|
+
const [_s1] = $(__scope, 's1')
|
|
528
|
+
|
|
529
|
+
mapArray(() => items().toSorted((a, b) => a.price - b.price || a.name.localeCompare(b.name)), _s1, (item) => String(item.id), (item, __idx, __existing) => {
|
|
530
|
+
if (__existing) { initChild('Item__b3c36eee', __existing, { get item() { return item() } }); return __existing }
|
|
531
|
+
return createComponent('Item__b3c36eee', { get item() { return item() } }, item().id)
|
|
532
|
+
}, 'l0')
|
|
533
|
+
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).toSorted((a, b) => a.price - b.price || a.name.localeCompare(b.name)).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
|
|
537
|
+
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
538
|
+
`;
|
|
539
|
+
|
|
540
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L74 — ✅ Relational ternary 1`] = `
|
|
541
|
+
"import { $, $t, createComponent, createEffect, createSignal, hydrate, initChild, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
542
|
+
|
|
543
|
+
export function initTodoItem(__scope, _p = {}) {
|
|
544
|
+
if (!__scope) return
|
|
545
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
546
|
+
|
|
547
|
+
const [_s0] = $t(__scope, 's0')
|
|
548
|
+
|
|
549
|
+
createEffect(() => {
|
|
550
|
+
const __val = String(_p.todo)
|
|
551
|
+
if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
hydrate('TodoItem__b3c36eee', { init: initTodoItem, template: (_p) => \`<li bf="s1"><!--bf:s0-->\${String(_p.todo)}<!--/--></li>\` })
|
|
557
|
+
export function TodoItem(_p, __bfKey) { return createComponent('TodoItem__b3c36eee', _p, __bfKey) }
|
|
558
|
+
export function initItem(__scope, _p = {}) {
|
|
559
|
+
if (!__scope) return
|
|
560
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
561
|
+
|
|
562
|
+
const [_s0] = $t(__scope, 's0')
|
|
563
|
+
|
|
564
|
+
createEffect(() => {
|
|
565
|
+
const __val = String(_p.item)
|
|
566
|
+
if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
hydrate('Item__b3c36eee', { init: initItem, template: (_p) => \`<li bf="s1"><!--bf:s0-->\${String(_p.item)}<!--/--></li>\` })
|
|
572
|
+
export function Item(_p, __bfKey) { return createComponent('Item__b3c36eee', _p, __bfKey) }
|
|
573
|
+
function initDashboard() {}
|
|
574
|
+
|
|
575
|
+
hydrate('Dashboard__b3c36eee', { init: initDashboard, template: (_p) => \`<div>D</div>\` })
|
|
576
|
+
export function Dashboard(_p, __bfKey) { return createComponent('Dashboard__b3c36eee', _p, __bfKey) }
|
|
577
|
+
export function initExample(__scope, _p = {}) {
|
|
578
|
+
if (!__scope) return
|
|
579
|
+
const __scopeId = __scope.getAttribute('bf-s')
|
|
580
|
+
|
|
581
|
+
const children = _p.children
|
|
582
|
+
const [count, setCount] = createSignal(0)
|
|
583
|
+
const [isLoggedIn] = createSignal(false)
|
|
584
|
+
const [todos] = createSignal([])
|
|
585
|
+
const [items] = createSignal([])
|
|
586
|
+
const [filter] = createSignal('all')
|
|
587
|
+
const [accepted] = createSignal(false)
|
|
588
|
+
const [text, setText] = createSignal('')
|
|
589
|
+
|
|
590
|
+
const [_s1] = $(__scope, 's1')
|
|
591
|
+
|
|
592
|
+
mapArray(() => items().toSorted((a, b) => a.price > b.price ? 1 : -1), _s1, (item) => String(item.id), (item, __idx, __existing) => {
|
|
593
|
+
if (__existing) { initChild('Item__b3c36eee', __existing, { get item() { return item() } }); return __existing }
|
|
594
|
+
return createComponent('Item__b3c36eee', { get item() { return item() } }, item().id)
|
|
595
|
+
}, 'l0')
|
|
596
|
+
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).toSorted((a, b) => a.price > b.price ? 1 : -1).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
|
|
600
|
+
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
601
|
+
`;
|
|
602
|
+
|
|
603
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L85 — (no label) 1`] = `
|
|
478
604
|
"import { $, $t, createComponent, createEffect, createSignal, hydrate } from '@barefootjs/client/runtime'
|
|
479
605
|
|
|
480
606
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -536,7 +662,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div><button bf="s0"
|
|
|
536
662
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
537
663
|
`;
|
|
538
664
|
|
|
539
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
665
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L131 — ❌ BF101 on Go/Mojo; works on Hono 1`] = `
|
|
540
666
|
"import { $t, createComponent, createEffect, createSignal, hydrate } from '@barefootjs/client/runtime'
|
|
541
667
|
|
|
542
668
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -599,7 +725,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
599
725
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
600
726
|
`;
|
|
601
727
|
|
|
602
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
728
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L134 — ✅ Use /* @client */ 1`] = `
|
|
603
729
|
"import { $t, createComponent, createEffect, createSignal, hydrate, updateClientMarker } from '@barefootjs/client/runtime'
|
|
604
730
|
|
|
605
731
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -660,7 +786,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-
|
|
|
660
786
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
661
787
|
`;
|
|
662
788
|
|
|
663
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
789
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L151 — ❌ BF101 on Go/Mojo; works on Hono 1`] = `
|
|
664
790
|
"import { $t, createComponent, createEffect, createSignal, hydrate } from '@barefootjs/client/runtime'
|
|
665
791
|
|
|
666
792
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -723,7 +849,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
723
849
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
724
850
|
`;
|
|
725
851
|
|
|
726
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
852
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L154 — ✅ Use arrow functions for adapter portability 1`] = `
|
|
727
853
|
"import { $t, createComponent, createEffect, createSignal, hydrate } from '@barefootjs/client/runtime'
|
|
728
854
|
|
|
729
855
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -786,7 +912,7 @@ hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf:
|
|
|
786
912
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
787
913
|
`;
|
|
788
914
|
|
|
789
|
-
exports[`docs/core/rendering/jsx-compatibility.md doc-examples
|
|
915
|
+
exports[`docs/core/rendering/jsx-compatibility.md doc-examples L168 — ✅ Use /* @client */ 1`] = `
|
|
790
916
|
"import { $, $t, createComponent, createEffect, createSignal, hydrate, initChild, mapArray, renderChild } from '@barefootjs/client/runtime'
|
|
791
917
|
|
|
792
918
|
export function initTodoItem(__scope, _p = {}) {
|
|
@@ -838,14 +964,14 @@ export function initExample(__scope, _p = {}) {
|
|
|
838
964
|
|
|
839
965
|
const [_s1] = $(__scope, 's1')
|
|
840
966
|
|
|
841
|
-
mapArray(() => items().sort((a, b) => a.name > b.name ? 1 : -1), _s1, (item) => String(item.id), (item, __idx, __existing) => {
|
|
967
|
+
mapArray(() => items().sort((a, b) => { const an = a.name; return an > b.name ? 1 : -1 }), _s1, (item) => String(item.id), (item, __idx, __existing) => {
|
|
842
968
|
if (__existing) { initChild('Item__b3c36eee', __existing, { get item() { return item() } }); return __existing }
|
|
843
969
|
return createComponent('Item__b3c36eee', { get item() { return item() } }, item().id)
|
|
844
970
|
}, 'l0')
|
|
845
971
|
|
|
846
972
|
}
|
|
847
973
|
|
|
848
|
-
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).sort((a, b) => a.name > b.name ? 1 : -1).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
|
|
974
|
+
hydrate('Example', { init: initExample, template: (_p) => \`<div bf="s1"><!--bf-loop:l0-->\${([]).sort((a, b) => { const an = a.name; return an > b.name ? 1 : -1 }).map((item) => \`\${renderChild('Item__b3c36eee', {item: item}, item.id)}\`).join('')}<!--bf-/loop:l0--></div>\` })
|
|
849
975
|
export function Example(_p, __bfKey) { return createComponent('Example', _p, __bfKey) }"
|
|
850
976
|
`;
|
|
851
977
|
|
|
@@ -729,4 +729,80 @@ describe('child components inside .map() (#344)', () => {
|
|
|
729
729
|
expect(content).toContain('children[__idx]')
|
|
730
730
|
expect(content).not.toContain('children[__idx + ')
|
|
731
731
|
})
|
|
732
|
+
|
|
733
|
+
test('nested .map() with multiple inner components emits unique __compEl bindings (#1664)', () => {
|
|
734
|
+
const source = `
|
|
735
|
+
'use client'
|
|
736
|
+
|
|
737
|
+
export function Picker() {
|
|
738
|
+
const GROUPS = [
|
|
739
|
+
{ id: 'a', items: [{ id: 'x', label: 'X' }] },
|
|
740
|
+
]
|
|
741
|
+
return (
|
|
742
|
+
<div>
|
|
743
|
+
{GROUPS.map(group => (
|
|
744
|
+
<div key={group.id}>
|
|
745
|
+
{group.items.map(it => (
|
|
746
|
+
<div key={it.id}>
|
|
747
|
+
<SelectItem value={it.id}>{it.label}</SelectItem>
|
|
748
|
+
<SelectIcon name={it.id} />
|
|
749
|
+
</div>
|
|
750
|
+
))}
|
|
751
|
+
</div>
|
|
752
|
+
))}
|
|
753
|
+
</div>
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
`
|
|
757
|
+
const result = compileJSX(source, 'Picker.tsx', { adapter })
|
|
758
|
+
expect(result.errors).toHaveLength(0)
|
|
759
|
+
|
|
760
|
+
const clientJs = result.files.find(f => f.type === 'clientJs')
|
|
761
|
+
expect(clientJs).toBeDefined()
|
|
762
|
+
const content = clientJs!.content
|
|
763
|
+
|
|
764
|
+
// Both inner-loop components must be initialised.
|
|
765
|
+
expect(content).toContain("initChild('SelectItem'")
|
|
766
|
+
expect(content).toContain("initChild('SelectIcon'")
|
|
767
|
+
|
|
768
|
+
// No re-declaration of `__compEl` in the shared inner-forEach scope:
|
|
769
|
+
// each comp must use a uniquely-suffixed binding.
|
|
770
|
+
expect(content).toContain('__compEl0')
|
|
771
|
+
expect(content).toContain('__compEl1')
|
|
772
|
+
|
|
773
|
+
// The bug threw "Identifier '__compEl' has already been declared" — the
|
|
774
|
+
// unsuffixed binding must not appear when multiple comps share a scope.
|
|
775
|
+
expect(content).not.toContain('const __compEl =')
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
test('nested .map() with a single inner component keeps the plain __compEl binding (#1664)', () => {
|
|
779
|
+
const source = `
|
|
780
|
+
'use client'
|
|
781
|
+
|
|
782
|
+
export function Picker() {
|
|
783
|
+
const GROUPS = [
|
|
784
|
+
{ id: 'a', items: [{ id: 'x', label: 'X' }] },
|
|
785
|
+
]
|
|
786
|
+
return (
|
|
787
|
+
<div>
|
|
788
|
+
{GROUPS.map(group => (
|
|
789
|
+
<div key={group.id}>
|
|
790
|
+
{group.items.map(it => (
|
|
791
|
+
<SelectItem key={it.id} value={it.id}>{it.label}</SelectItem>
|
|
792
|
+
))}
|
|
793
|
+
</div>
|
|
794
|
+
))}
|
|
795
|
+
</div>
|
|
796
|
+
)
|
|
797
|
+
}
|
|
798
|
+
`
|
|
799
|
+
const result = compileJSX(source, 'Picker.tsx', { adapter })
|
|
800
|
+
expect(result.errors).toHaveLength(0)
|
|
801
|
+
|
|
802
|
+
const content = result.files.find(f => f.type === 'clientJs')!.content
|
|
803
|
+
expect(content).toContain("initChild('SelectItem'")
|
|
804
|
+
// Single comp keeps the unsuffixed name.
|
|
805
|
+
expect(content).toContain('const __compEl =')
|
|
806
|
+
expect(content).not.toContain('__compEl0')
|
|
807
|
+
})
|
|
732
808
|
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderImportMapHtml tests
|
|
3
|
+
*
|
|
4
|
+
* The shared importmap-snippet renderer turns a parsed `barefoot-externals.json`
|
|
5
|
+
* into the `<script type="importmap">` (+ `<link rel="modulepreload">`) HTML that
|
|
6
|
+
* `bf build` emits as `barefoot-importmap.html` for template-string adapters
|
|
7
|
+
* (issue #1644). This is the single source of truth for that snippet.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, test, expect } from 'bun:test'
|
|
10
|
+
import { renderImportMapHtml } from '../import-map'
|
|
11
|
+
|
|
12
|
+
function parseImportMap(html: string): Record<string, string> {
|
|
13
|
+
const match = html.match(/<script type="importmap">(.*?)<\/script>/s)
|
|
14
|
+
if (!match) throw new Error(`no importmap in: ${html}`)
|
|
15
|
+
// Decode the < escape the renderer applies before parsing.
|
|
16
|
+
return JSON.parse(match[1]).imports
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('renderImportMapHtml', () => {
|
|
20
|
+
test('emits the manifest importmap imports verbatim', () => {
|
|
21
|
+
const html = renderImportMapHtml({
|
|
22
|
+
importmap: {
|
|
23
|
+
imports: {
|
|
24
|
+
'@barefootjs/client': '/components/barefoot.js',
|
|
25
|
+
'@barefootjs/client/runtime': '/components/barefoot.js',
|
|
26
|
+
zod: 'https://esm.sh/zod@4.4.3',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
preloads: [],
|
|
30
|
+
})
|
|
31
|
+
expect(parseImportMap(html)).toEqual({
|
|
32
|
+
'@barefootjs/client': '/components/barefoot.js',
|
|
33
|
+
'@barefootjs/client/runtime': '/components/barefoot.js',
|
|
34
|
+
zod: 'https://esm.sh/zod@4.4.3',
|
|
35
|
+
})
|
|
36
|
+
expect(html).not.toContain('modulepreload')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('emits modulepreload links with crossorigin for manifest preloads (#1648)', () => {
|
|
40
|
+
const html = renderImportMapHtml({
|
|
41
|
+
importmap: { imports: {} },
|
|
42
|
+
preloads: ['/components/form.js', 'https://esm.sh/zod@4.4.3'],
|
|
43
|
+
})
|
|
44
|
+
expect(html).toContain('<link rel="modulepreload" href="/components/form.js" crossorigin>')
|
|
45
|
+
expect(html).toContain('<link rel="modulepreload" href="https://esm.sh/zod@4.4.3" crossorigin>')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('reads defensively from a partial manifest', () => {
|
|
49
|
+
expect(parseImportMap(renderImportMapHtml({}))).toEqual({})
|
|
50
|
+
expect(renderImportMapHtml({})).not.toContain('modulepreload')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('ends with a trailing newline (template-include friendly)', () => {
|
|
54
|
+
expect(renderImportMapHtml({ importmap: { imports: {} } }).endsWith('\n')).toBe(true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('escapes < in the importmap JSON so a URL cannot break out of the script', () => {
|
|
58
|
+
const html = renderImportMapHtml({
|
|
59
|
+
importmap: { imports: { evil: 'https://x/</script><script>alert(1)</script>' } },
|
|
60
|
+
})
|
|
61
|
+
// The literal closing tag must not appear before the importmap's own.
|
|
62
|
+
const importmapClose = html.indexOf('</script>')
|
|
63
|
+
expect(html.slice(0, importmapClose)).not.toContain('</script>')
|
|
64
|
+
// But the value still round-trips through JSON.parse.
|
|
65
|
+
expect(parseImportMap(html).evil).toBe('https://x/</script><script>alert(1)</script>')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('escapes double quotes and angle brackets in preload hrefs', () => {
|
|
69
|
+
const html = renderImportMapHtml({
|
|
70
|
+
preloads: ['/components/"onerror=alert(1).js'],
|
|
71
|
+
})
|
|
72
|
+
expect(html).not.toContain('"onerror=alert(1)')
|
|
73
|
+
expect(html).toContain('"onerror=alert(1)')
|
|
74
|
+
})
|
|
75
|
+
})
|