@barefootjs/mojolicious 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/adapter/index.js
CHANGED
|
@@ -400,8 +400,10 @@ ${whenTrue}
|
|
|
400
400
|
const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
|
|
401
401
|
const prevInLoop = this.inLoop;
|
|
402
402
|
this.inLoop = true;
|
|
403
|
-
const
|
|
403
|
+
const renderedChildren = this.renderChildren(loop.children);
|
|
404
404
|
this.inLoop = prevInLoop;
|
|
405
|
+
const children = loop.bodyIsItemConditional && loop.key ? `<%== bf->comment("loop-i:" . ${this.convertExpressionToPerl(loop.key)}) %>
|
|
406
|
+
${renderedChildren}` : renderedChildren;
|
|
405
407
|
const lines = [];
|
|
406
408
|
lines.push(`<%== bf->comment("loop:${loop.markerId}") %>`);
|
|
407
409
|
if (sortedHoist && loop.sortComparator) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/build.js
CHANGED
|
@@ -400,8 +400,10 @@ ${whenTrue}
|
|
|
400
400
|
const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
|
|
401
401
|
const prevInLoop = this.inLoop;
|
|
402
402
|
this.inLoop = true;
|
|
403
|
-
const
|
|
403
|
+
const renderedChildren = this.renderChildren(loop.children);
|
|
404
404
|
this.inLoop = prevInLoop;
|
|
405
|
+
const children = loop.bodyIsItemConditional && loop.key ? `<%== bf->comment("loop-i:" . ${this.convertExpressionToPerl(loop.key)}) %>
|
|
406
|
+
${renderedChildren}` : renderedChildren;
|
|
405
407
|
const lines = [];
|
|
406
408
|
lines.push(`<%== bf->comment("loop:${loop.markerId}") %>`);
|
|
407
409
|
if (sortedHoist && loop.sortComparator) {
|
package/dist/index.js
CHANGED
|
@@ -400,8 +400,10 @@ ${whenTrue}
|
|
|
400
400
|
const indexVar = loop.iterationShape === "keys" ? `$${param}` : loop.index ? `$${loop.index}` : "$_i";
|
|
401
401
|
const prevInLoop = this.inLoop;
|
|
402
402
|
this.inLoop = true;
|
|
403
|
-
const
|
|
403
|
+
const renderedChildren = this.renderChildren(loop.children);
|
|
404
404
|
this.inLoop = prevInLoop;
|
|
405
|
+
const children = loop.bodyIsItemConditional && loop.key ? `<%== bf->comment("loop-i:" . ${this.convertExpressionToPerl(loop.key)}) %>
|
|
406
|
+
${renderedChildren}` : renderedChildren;
|
|
405
407
|
const lines = [];
|
|
406
408
|
lines.push(`<%== bf->comment("loop:${loop.markerId}") %>`);
|
|
407
409
|
if (sortedHoist && loop.sortComparator) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/mojolicious",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Mojolicious EP template adapter for BarefootJS - generates .html.ep files from IR",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
"directory": "packages/adapter-mojolicious"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@barefootjs/shared": "0.5.
|
|
55
|
+
"@barefootjs/shared": "0.5.1"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@barefootjs/jsx": ">=0.2.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
62
|
-
"@barefootjs/jsx": "0.5.
|
|
62
|
+
"@barefootjs/jsx": "0.5.1",
|
|
63
63
|
"typescript": "^5.0.0"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -69,6 +69,19 @@ runAdapterConformanceTests({
|
|
|
69
69
|
'toggle-shared',
|
|
70
70
|
'reactive-props',
|
|
71
71
|
'props-reactivity-comparison',
|
|
72
|
+
// #1665 whole-item loop conditional. The Mojo adapter correctly emits the
|
|
73
|
+
// per-item `<!--bf-loop-i:KEY-->` anchor, `data-key`, and the conditional
|
|
74
|
+
// markers (verified by template-structure tests), but the fixture's
|
|
75
|
+
// `sel() === t.id` string comparison lowers to Perl numeric `==`
|
|
76
|
+
// (`"b" == "a"` → `0 == 0` → true), so the perl-executed render renders
|
|
77
|
+
// every item's true branch instead of only the matching one. Selecting
|
|
78
|
+
// `eq` vs `==` from operand types is a separate pre-existing Mojo
|
|
79
|
+
// limitation (same family as the skipped `logical-or-jsx` /
|
|
80
|
+
// `nullish-coalescing-jsx` map shapes); the anchored SSR shape itself is
|
|
81
|
+
// covered cross-adapter by Hono + CSR conformance and the runtime
|
|
82
|
+
// hydration tests. Remove this skip once the Mojo limitation is fixed:
|
|
83
|
+
// https://github.com/piconic-ai/barefootjs/issues/1672
|
|
84
|
+
'loop-item-conditional',
|
|
72
85
|
],
|
|
73
86
|
// Per-fixture build-time contracts for shapes the Mojo adapter
|
|
74
87
|
// intentionally refuses to lower. Owned by this adapter test file
|
|
@@ -958,3 +971,165 @@ describe('MojoAdapter - #1448 Tier A/B fixture-driven lowering pins', () => {
|
|
|
958
971
|
})
|
|
959
972
|
}
|
|
960
973
|
})
|
|
974
|
+
|
|
975
|
+
// =============================================================================
|
|
976
|
+
// #1448 — `/* @client */` escape hatch for STILL-UNSUPPORTED methods
|
|
977
|
+
// =============================================================================
|
|
978
|
+
//
|
|
979
|
+
// Mojo sibling of the Go block: #1448 documents `/* @client */` as the
|
|
980
|
+
// universal workaround for any Array/String method the template
|
|
981
|
+
// adapters can't lower. This pins that contract for the Mojo adapter —
|
|
982
|
+
// wrapping the unsupported expression in the directive must clear the
|
|
983
|
+
// BF021/BF101 build error the bare form raises and emit a client-only
|
|
984
|
+
// placeholder so the Mojo SSR pass renders valid `.html.ep` that the
|
|
985
|
+
// client runtime fills at hydration.
|
|
986
|
+
//
|
|
987
|
+
// Same silent-footgun caveat as Go: the unsupported *string* methods
|
|
988
|
+
// raise NO build diagnostic — bare `.startsWith` / `.repeat` / … lower
|
|
989
|
+
// to a Perl hash-deref-and-call (`$name->{startsWith}('a')`) that
|
|
990
|
+
// passes the adapter gate, then dies at render with
|
|
991
|
+
// `Can't use string (...) as a HASH ref while "strict refs"`.
|
|
992
|
+
// `/* @client */` is the only escape hatch, so these tests pin it.
|
|
993
|
+
describe('MojoAdapter - #1448 @client escape hatch (unsupported methods)', () => {
|
|
994
|
+
function emit(expr: string, client: boolean) {
|
|
995
|
+
const marker = client ? '/* @client */ ' : ''
|
|
996
|
+
const adapter = new MojoAdapter()
|
|
997
|
+
const ir = compileToIR(`
|
|
998
|
+
"use client"
|
|
999
|
+
import { createSignal } from "@barefootjs/client"
|
|
1000
|
+
export function C() {
|
|
1001
|
+
const [items, setItems] = createSignal<{ name: string; n: number; tags: string[] }[]>([])
|
|
1002
|
+
const [name, setName] = createSignal("x")
|
|
1003
|
+
return <div>{${marker}${expr}}</div>
|
|
1004
|
+
}
|
|
1005
|
+
`, adapter)
|
|
1006
|
+
const template = adapter.generate(ir).template ?? ''
|
|
1007
|
+
return { errors: adapter.errors ?? [], template }
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function emitLoop(chain: string, client: boolean) {
|
|
1011
|
+
const marker = client ? '/* @client */ ' : ''
|
|
1012
|
+
const adapter = new MojoAdapter()
|
|
1013
|
+
const ir = compileToIR(`
|
|
1014
|
+
"use client"
|
|
1015
|
+
import { createSignal } from "@barefootjs/client"
|
|
1016
|
+
export function C() {
|
|
1017
|
+
const [items, setItems] = createSignal<{ name: string; n: number }[]>([])
|
|
1018
|
+
const myCmp = (a: { n: number }, b: { n: number }) => a.n - b.n
|
|
1019
|
+
return <ul>{${marker}${chain}}</ul>
|
|
1020
|
+
}
|
|
1021
|
+
`, adapter)
|
|
1022
|
+
const template = adapter.generate(ir).template ?? ''
|
|
1023
|
+
return { errors: adapter.errors ?? [], template }
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Tier C array methods — bare form raises BF101 at build time.
|
|
1027
|
+
const unsupportedArray: Array<[string, string]> = [
|
|
1028
|
+
['reduce', `items().reduce((a, b) => a + b.n, 0)`],
|
|
1029
|
+
['flatMap', `items().flatMap(i => i.tags)`],
|
|
1030
|
+
['flat', `items().flat()`],
|
|
1031
|
+
]
|
|
1032
|
+
for (const [name, expr] of unsupportedArray) {
|
|
1033
|
+
test(`array .${name}: bare raises BF101, @client clears it + emits client placeholder`, () => {
|
|
1034
|
+
const bare = emit(expr, false)
|
|
1035
|
+
expect(bare.errors.some(e => e.code === 'BF101')).toBe(true)
|
|
1036
|
+
|
|
1037
|
+
const guarded = emit(expr, true)
|
|
1038
|
+
expect(guarded.errors).toEqual([])
|
|
1039
|
+
// Client-only text slot → `<%== bf->comment("client:sN") %>`.
|
|
1040
|
+
expect(guarded.template).toMatch(/bf->comment\("client:s\d+"\)/)
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Tier B/C string methods — bare form emits an INVALID Perl
|
|
1045
|
+
// hash-deref-and-call with NO build error (the silent footgun).
|
|
1046
|
+
const unsupportedString: Array<[string, string, string]> = [
|
|
1047
|
+
['split', `name().split(",")`, '->{split}'],
|
|
1048
|
+
['startsWith', `name().startsWith("a")`, '->{startsWith}'],
|
|
1049
|
+
['endsWith', `name().endsWith("z")`, '->{endsWith}'],
|
|
1050
|
+
['replace', `name().replace("a", "b")`, '->{replace}'],
|
|
1051
|
+
['repeat', `name().repeat(3)`, '->{repeat}'],
|
|
1052
|
+
['padStart', `name().padStart(5, "0")`, '->{padStart}'],
|
|
1053
|
+
['padEnd', `name().padEnd(5, "0")`, '->{padEnd}'],
|
|
1054
|
+
['charAt', `name().charAt(0)`, '->{charAt}'],
|
|
1055
|
+
]
|
|
1056
|
+
for (const [name, expr, badEmit] of unsupportedString) {
|
|
1057
|
+
test(`string .${name}: bare emits invalid Perl deref, @client emits client placeholder`, () => {
|
|
1058
|
+
const bare = emit(expr, false)
|
|
1059
|
+
// Documents the footgun: no BF101 guard, invalid template emitted.
|
|
1060
|
+
expect(bare.errors.filter(e => e.code === 'BF101')).toEqual([])
|
|
1061
|
+
expect(bare.template).toContain(badEmit)
|
|
1062
|
+
|
|
1063
|
+
const guarded = emit(expr, true)
|
|
1064
|
+
expect(guarded.errors).toEqual([])
|
|
1065
|
+
expect(guarded.template).toMatch(/bf->comment\("client:s\d+"\)/)
|
|
1066
|
+
expect(guarded.template).not.toContain(badEmit)
|
|
1067
|
+
})
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Tier B `.sort` / `.toSorted` follow-ups still refused with BF021.
|
|
1071
|
+
// The Mojo client-only loop placeholder is an empty element (the
|
|
1072
|
+
// client runtime repopulates it via the `bf-s` scope marker), so the
|
|
1073
|
+
// contract here is: no errors + the comparator never lowers + no
|
|
1074
|
+
// rendered `<li>` survives.
|
|
1075
|
+
const unsupportedSort: Array<[string, string]> = [
|
|
1076
|
+
['function-reference comparator', `items().toSorted(myCmp).map(x => <li key={x.name}>{x.name}</li>)`],
|
|
1077
|
+
['localeCompare locale/options arg', `items().toSorted((a, b) => a.name.localeCompare(b.name, "ja", { numeric: true })).map(x => <li key={x.name}>{x.name}</li>)`],
|
|
1078
|
+
]
|
|
1079
|
+
for (const [label, chain] of unsupportedSort) {
|
|
1080
|
+
test(`sort follow-up (${label}): bare raises BF021, @client clears it`, () => {
|
|
1081
|
+
const bare = compileJSX(`
|
|
1082
|
+
"use client"
|
|
1083
|
+
import { createSignal } from "@barefootjs/client"
|
|
1084
|
+
export function C() {
|
|
1085
|
+
const [items, setItems] = createSignal<{ name: string; n: number }[]>([])
|
|
1086
|
+
const myCmp = (a: { n: number }, b: { n: number }) => a.n - b.n
|
|
1087
|
+
return <ul>{${chain}}</ul>
|
|
1088
|
+
}
|
|
1089
|
+
`.trimStart(), 'test.tsx', { adapter: new MojoAdapter() })
|
|
1090
|
+
expect(bare.errors?.some(e => e.code === 'BF021')).toBe(true)
|
|
1091
|
+
|
|
1092
|
+
const guarded = emitLoop(chain, true)
|
|
1093
|
+
expect(guarded.errors).toEqual([])
|
|
1094
|
+
// Empty client-only loop placeholder — no item rows emitted SSR.
|
|
1095
|
+
expect(guarded.template).not.toContain('<li')
|
|
1096
|
+
expect(guarded.template).not.toContain('localeCompare')
|
|
1097
|
+
})
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// End-to-end proof via perl + Mojolicious: the bare unsupported form
|
|
1101
|
+
// crashes Mojo template execution, while the `@client` form renders a
|
|
1102
|
+
// `<!--bf-client:sN-->` placeholder. Skipped on hosts without
|
|
1103
|
+
// Mojolicious installed.
|
|
1104
|
+
test('e2e: bare string method crashes perl render, @client renders placeholder', async () => {
|
|
1105
|
+
const guarded = `
|
|
1106
|
+
"use client"
|
|
1107
|
+
import { createSignal } from "@barefootjs/client"
|
|
1108
|
+
export function C() {
|
|
1109
|
+
const [name, setName] = createSignal("hello")
|
|
1110
|
+
return <div>{/* @client */ name().repeat(3)}</div>
|
|
1111
|
+
}
|
|
1112
|
+
`
|
|
1113
|
+
const bareSrc = guarded.replace('/* @client */ ', '')
|
|
1114
|
+
try {
|
|
1115
|
+
const html = await renderMojoComponent({
|
|
1116
|
+
source: guarded.trimStart(),
|
|
1117
|
+
adapter: new MojoAdapter(),
|
|
1118
|
+
})
|
|
1119
|
+
expect(html).toContain('<!--bf-client:s0-->')
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
if (err instanceof PerlNotAvailableError) {
|
|
1122
|
+
console.log('Skipping #1448 @client e2e: perl/Mojolicious not found')
|
|
1123
|
+
return
|
|
1124
|
+
}
|
|
1125
|
+
throw err
|
|
1126
|
+
}
|
|
1127
|
+
// Bare form must fail Mojo template execution (no @client guard).
|
|
1128
|
+
await expect(
|
|
1129
|
+
renderMojoComponent({
|
|
1130
|
+
source: bareSrc.trimStart(),
|
|
1131
|
+
adapter: new MojoAdapter(),
|
|
1132
|
+
}),
|
|
1133
|
+
).rejects.toThrow(/HASH ref/)
|
|
1134
|
+
})
|
|
1135
|
+
})
|
|
@@ -598,9 +598,19 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
598
598
|
: loop.index ? `$${loop.index}` : '$_i'
|
|
599
599
|
const prevInLoop = this.inLoop
|
|
600
600
|
this.inLoop = true
|
|
601
|
-
const
|
|
601
|
+
const renderedChildren = this.renderChildren(loop.children)
|
|
602
602
|
this.inLoop = prevInLoop
|
|
603
603
|
|
|
604
|
+
// Whole-item conditional (#1665): prepend an always-present
|
|
605
|
+
// `<!--bf-loop-i:KEY-->` anchor before each item's (possibly empty)
|
|
606
|
+
// conditional content so the client's `mapArrayAnchored` can hydrate
|
|
607
|
+
// every SSR-rendered item by its anchor. `bf->comment` prepends `bf-`,
|
|
608
|
+
// so `"loop-i:" . KEY` yields `<!--bf-loop-i:KEY-->`.
|
|
609
|
+
const children =
|
|
610
|
+
loop.bodyIsItemConditional && loop.key
|
|
611
|
+
? `<%== bf->comment("loop-i:" . ${this.convertExpressionToPerl(loop.key)}) %>\n${renderedChildren}`
|
|
612
|
+
: renderedChildren
|
|
613
|
+
|
|
604
614
|
const lines: string[] = []
|
|
605
615
|
// Scoped per-call-site marker so sibling `.map()`s under the same parent
|
|
606
616
|
// each get their own reconciliation range (#1087).
|