@barefootjs/mojolicious 0.33.4 → 0.34.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/adapter/emit-context.d.ts +11 -1
- package/dist/adapter/emit-context.d.ts.map +1 -1
- package/dist/adapter/expr/emitters.d.ts +11 -1
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +33 -23
- package/dist/adapter/mojo-adapter.d.ts +14 -0
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +37 -25
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +197 -135
- package/lib/BarefootJS/Backend/Mojo.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
- package/package.json +5 -5
- package/src/adapter/emit-context.ts +12 -1
- package/src/adapter/expr/emitters.ts +33 -0
- package/src/adapter/mojo-adapter.ts +37 -43
- package/src/conformance-pins.ts +11 -0
- package/src/render-divergences.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/mojolicious",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
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,7 +52,7 @@
|
|
|
52
52
|
"directory": "packages/adapter-mojolicious"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@barefootjs/shared": "0.
|
|
55
|
+
"@barefootjs/shared": "0.34.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@barefootjs/jsx": ">=0.2.0",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
73
|
-
"@barefootjs/jsx": "0.
|
|
74
|
-
"@barefootjs/vite": "0.
|
|
75
|
-
"@barefootjs/client": "0.
|
|
73
|
+
"@barefootjs/jsx": "0.34.0",
|
|
74
|
+
"@barefootjs/vite": "0.34.0",
|
|
75
|
+
"@barefootjs/client": "0.34.0",
|
|
76
76
|
"vite": "^6.0.0"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* rather than re-exposing the whole adapter.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx'
|
|
21
|
+
import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher } from '@barefootjs/jsx'
|
|
22
22
|
|
|
23
23
|
export interface MojoEmitContext {
|
|
24
24
|
/**
|
|
@@ -27,6 +27,17 @@ export interface MojoEmitContext {
|
|
|
27
27
|
*/
|
|
28
28
|
readonly _searchParamsLocals: Set<string>
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Registered lowering-plugin matchers (#2057), bound to this component's
|
|
32
|
+
* metadata at init. Read by `MojoTopLevelEmitter`'s `lowering` seam (#2843)
|
|
33
|
+
* so a registered call — the built-in `queryHref`, or any userland plugin —
|
|
34
|
+
* is recognised no matter where it sits in an expression tree (a ternary
|
|
35
|
+
* branch, a template-literal interpolation, …), not only when it's the call
|
|
36
|
+
* the adapter's own top-level conversion entry point
|
|
37
|
+
* (`convertExpressionToPerl`) is asked to lower directly.
|
|
38
|
+
*/
|
|
39
|
+
readonly _loweringMatchers: readonly LoweringMatcher[]
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* Inline a module-scope pure string-literal const by name as the resolved
|
|
32
43
|
* literal value, or null when the name is not such a const.
|
|
@@ -27,6 +27,10 @@ import { groupBinaryOperand,
|
|
|
27
27
|
matchSearchParamsMethodCall,
|
|
28
28
|
sortComparatorFromArrow,
|
|
29
29
|
asCallbackMethodCall,
|
|
30
|
+
type LoweringEmitter,
|
|
31
|
+
type LoweringNode,
|
|
32
|
+
queryHrefArgs,
|
|
33
|
+
isValidHelperId,
|
|
30
34
|
} from '@barefootjs/jsx'
|
|
31
35
|
|
|
32
36
|
import type { MojoEmitContext } from '../emit-context.ts'
|
|
@@ -323,6 +327,35 @@ export class MojoTopLevelEmitter implements ParsedExprEmitter {
|
|
|
323
327
|
this.ctx = ctx
|
|
324
328
|
}
|
|
325
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Registered-lowering seam (#2843): `emitParsedExpr`'s shared `call` case
|
|
332
|
+
* tries every matcher here BEFORE `call()` itself, so a registered call
|
|
333
|
+
* (the built-in `queryHref`, or any userland plugin) is recognised no
|
|
334
|
+
* matter where it sits in the tree. `render` is what used to live inline
|
|
335
|
+
* in `MojoAdapter.convertExpressionToPerl` before the object-literal
|
|
336
|
+
* support-gate refusal (`checkSupport`'s `call` arm, now itself
|
|
337
|
+
* registry-aware) made the pre-gate special case unnecessary.
|
|
338
|
+
*/
|
|
339
|
+
get lowering(): LoweringEmitter {
|
|
340
|
+
return {
|
|
341
|
+
matchers: this.ctx._loweringMatchers,
|
|
342
|
+
render: (node: LoweringNode, emit: (e: ParsedExpr) => string): string | null => {
|
|
343
|
+
// `query` guard-list — `queryHref`-shaped.
|
|
344
|
+
if (node.kind === 'guard-list' && node.helper === 'query') {
|
|
345
|
+
const qArgs = queryHrefArgs(node, emit)
|
|
346
|
+
return `bf->query(${qArgs.join(', ')})`
|
|
347
|
+
}
|
|
348
|
+
// Generic `helper-call` (#2069) — a userland `LoweringPlugin`'s
|
|
349
|
+
// single runtime-helper invocation; `bf-><helper>(args…)` mirrors
|
|
350
|
+
// the `query` helper's own naming convention.
|
|
351
|
+
if (node.kind === 'helper-call' && isValidHelperId(node.helper)) {
|
|
352
|
+
return `bf->${node.helper}(${node.args.map(emit).join(', ')})`
|
|
353
|
+
}
|
|
354
|
+
return null
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
326
359
|
identifier(name: string): string {
|
|
327
360
|
// `undefined` / `null` nested inside a larger expression tree
|
|
328
361
|
// (#1897, pagination's `props.isActive ? 'page' : undefined`) — the
|
|
@@ -56,8 +56,6 @@ import {
|
|
|
56
56
|
lookupStaticRecordLiteral,
|
|
57
57
|
searchParamsLocalNames,
|
|
58
58
|
prepareLoweringMatchers,
|
|
59
|
-
queryHrefArgs,
|
|
60
|
-
isValidHelperId,
|
|
61
59
|
sortComparatorFromArrow,
|
|
62
60
|
isLowerableLoopDestructure,
|
|
63
61
|
isDangerousInnerHtmlAttr,
|
|
@@ -67,6 +65,7 @@ import {
|
|
|
67
65
|
resolveStaticLoopSource,
|
|
68
66
|
derivesScopeFromSlot,
|
|
69
67
|
BindingScope,
|
|
68
|
+
buildImportAliasMap,
|
|
70
69
|
} from '@barefootjs/jsx'
|
|
71
70
|
import { isAriaBooleanAttr, isBooleanResultExpr } from './boolean-result.ts'
|
|
72
71
|
import type { ParsedExpr, LoweringMatcher } from '@barefootjs/jsx'
|
|
@@ -256,6 +255,18 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
256
255
|
*/
|
|
257
256
|
private nullableOptionalProps: Set<string> = new Set()
|
|
258
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Local alias -> declared/exported name for imported components (#2822,
|
|
260
|
+
* the SSR-side counterpart of #2777's client-JS registry-key fix). A
|
|
261
|
+
* child referenced under an import alias (`import { Foo as Bar }`,
|
|
262
|
+
* `<Bar/>`) must build its cross-template call against the child's own
|
|
263
|
+
* declared name (`Foo`, what `foo.tsx` registers its Mojo partial as) —
|
|
264
|
+
* never the caller-local binding. Built once per compile from
|
|
265
|
+
* `ir.metadata.imports` via the shared `buildImportAliasMap`
|
|
266
|
+
* (`@barefootjs/jsx`) and read by `toTemplateName`.
|
|
267
|
+
*/
|
|
268
|
+
private importAliases: Map<string, string> = new Map()
|
|
269
|
+
|
|
259
270
|
constructor(options: MojoAdapterOptions = {}) {
|
|
260
271
|
super()
|
|
261
272
|
this.options = {
|
|
@@ -285,6 +296,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
285
296
|
this._searchParamsLocals = searchParamsLocalNames(ir.metadata)
|
|
286
297
|
this._loweringMatchers = prepareLoweringMatchers(ir.metadata)
|
|
287
298
|
this.localConstants = ir.metadata.localConstants ?? []
|
|
299
|
+
this.importAliases = buildImportAliasMap(ir.metadata.imports ?? [])
|
|
288
300
|
this.scope = BindingScope.EMPTY
|
|
289
301
|
this.errors = []
|
|
290
302
|
this.childrenCaptureCounter = 0
|
|
@@ -390,7 +402,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
390
402
|
*/
|
|
391
403
|
parseUndefinedAlternateTernary(
|
|
392
404
|
expr: string,
|
|
393
|
-
): { condition: string; consequent: string } | null {
|
|
405
|
+
): { condition: string; consequent: string; testParsed: ParsedExpr; consequentParsed: ParsedExpr } | null {
|
|
394
406
|
const parsed = parseExpression(expr.trim())
|
|
395
407
|
if (parsed?.kind !== 'conditional') return null
|
|
396
408
|
const alt = parsed.alternate
|
|
@@ -398,13 +410,16 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
398
410
|
(alt.kind === 'identifier' && (alt.name === 'undefined' || alt.name === 'null')) ||
|
|
399
411
|
(alt.kind === 'literal' && (alt.value === null || alt.value === undefined))
|
|
400
412
|
if (!isUndef) return null
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
413
|
+
// `condition`/`consequent` are DEBUG text only (`exprToString` renders an
|
|
414
|
+
// unsupported nested shape like an object literal as non-reparseable
|
|
415
|
+
// `[UNSUPPORTED: …]` text) — callers that need to re-lower the
|
|
416
|
+
// sub-expression must use `testParsed`/`consequentParsed` (the actual
|
|
417
|
+
// parsed trees) as `preParsed`, not re-parse these strings.
|
|
405
418
|
return {
|
|
406
419
|
condition: exprToString(parsed.test),
|
|
407
420
|
consequent: exprToString(parsed.consequent),
|
|
421
|
+
testParsed: parsed.test,
|
|
422
|
+
consequentParsed: parsed.consequent,
|
|
408
423
|
}
|
|
409
424
|
}
|
|
410
425
|
|
|
@@ -1257,8 +1272,12 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1257
1272
|
private presenceVarCounter = 0
|
|
1258
1273
|
|
|
1259
1274
|
private toTemplateName(componentName: string): string {
|
|
1275
|
+
// Resolve an import alias (`import { Foo as Bar }`, `<Bar/>`) back to
|
|
1276
|
+
// the child's own declared name BEFORE snake-casing (#2822) — `Bar`
|
|
1277
|
+
// has no `foo.tsx`-registered partial; only `Foo` does.
|
|
1278
|
+
const declaredName = this.importAliases.get(componentName) ?? componentName
|
|
1260
1279
|
// Convert PascalCase to snake_case for Mojo template naming
|
|
1261
|
-
return
|
|
1280
|
+
return declaredName
|
|
1262
1281
|
.replace(/([A-Z])/g, '_$1')
|
|
1263
1282
|
.toLowerCase()
|
|
1264
1283
|
.replace(/^_/, '')
|
|
@@ -1441,8 +1460,8 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1441
1460
|
{
|
|
1442
1461
|
const m = this.parseUndefinedAlternateTernary(value.expr)
|
|
1443
1462
|
if (m) {
|
|
1444
|
-
const cond = this.convertExpressionToPerl(m.
|
|
1445
|
-
const val = this.convertExpressionToPerl(m.
|
|
1463
|
+
const cond = this.convertExpressionToPerl('', m.testParsed)
|
|
1464
|
+
const val = this.convertExpressionToPerl('', m.consequentParsed)
|
|
1446
1465
|
return `<% if (${cond}) { %>${name}="<%= ${val} %>"<% } %>`
|
|
1447
1466
|
}
|
|
1448
1467
|
}
|
|
@@ -1764,7 +1783,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1764
1783
|
const hasTaggedTemplate = /[A-Za-z_$][\w$]*\s*`/.test(probe)
|
|
1765
1784
|
if (!startsAsObjectLiteral && !hasTaggedTemplate) return false
|
|
1766
1785
|
const parsed = parseExpression(expr.trim())
|
|
1767
|
-
const support = isSupported(parsed)
|
|
1786
|
+
const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers })
|
|
1768
1787
|
if (parsed.kind !== 'unsupported' && support.supported) return false
|
|
1769
1788
|
// Surface the `isSupported` reason so the diagnostic is as
|
|
1770
1789
|
// actionable as the Go adapter's BF101 — keeps cross-adapter
|
|
@@ -1794,6 +1813,7 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1794
1813
|
private get emitCtx(): MojoEmitContext {
|
|
1795
1814
|
return {
|
|
1796
1815
|
_searchParamsLocals: this._searchParamsLocals,
|
|
1816
|
+
_loweringMatchers: this._loweringMatchers,
|
|
1797
1817
|
resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
|
|
1798
1818
|
resolveLiteralConst: (name) => this.resolveLiteralConst(name),
|
|
1799
1819
|
resolveStaticRecordLiteral: (o, k) => this.resolveStaticRecordLiteral(o, k),
|
|
@@ -1850,42 +1870,16 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
|
|
|
1850
1870
|
parsed = parseExpression(trimmed)
|
|
1851
1871
|
}
|
|
1852
1872
|
|
|
1853
|
-
// Registered call lowerings (#2057) — including the built-in `queryHref`
|
|
1854
|
-
// plugin (#2042), which lowers `queryHref(base, { … })` to a neutral
|
|
1855
|
-
// `guard-list` on the `query` helper → `bf->query(base, <triples>)`.
|
|
1856
|
-
// Recognised before the support gate because the object-literal arg is
|
|
1857
|
-
// otherwise `unsupported` (BF101). The `bf->query` helper includes a pair iff
|
|
1858
|
-
// its guard is truthy AND its value is a non-empty string (the client's
|
|
1859
|
-
// `if (value)`): a plain `key: v` passes guard `1`, a conditional
|
|
1860
|
-
// `key: cond ? v : undefined` passes the lowered cond. Only the `query`
|
|
1861
|
-
// helper renders to `bf->query`; another guard-list helper must not be
|
|
1862
|
-
// silently mis-rendered as a query.
|
|
1863
|
-
if (parsed.kind === 'call') {
|
|
1864
|
-
for (const matcher of this._loweringMatchers) {
|
|
1865
|
-
const node = matcher(parsed.callee, parsed.args)
|
|
1866
|
-
if (node?.kind === 'guard-list' && node.helper === 'query') {
|
|
1867
|
-
const argsGo = queryHrefArgs(node, n => this.renderParsedExprToPerl(n))
|
|
1868
|
-
return `bf->query(${argsGo.join(', ')})`
|
|
1869
|
-
}
|
|
1870
|
-
// Generic `helper-call` (#2069) — the neutral vocabulary's escape
|
|
1871
|
-
// hatch for a userland `LoweringPlugin` that lowers to a single
|
|
1872
|
-
// runtime-helper invocation. `bf-><helper>(args…)` mirrors the
|
|
1873
|
-
// `query` helper's own naming convention exactly: the framework
|
|
1874
|
-
// renders the call, the plugin author registers `<helper>` as a
|
|
1875
|
-
// method on their own Mojolicious `bf` helper object — same
|
|
1876
|
-
// contract as `bf->query` itself, just not built in.
|
|
1877
|
-
if (node?.kind === 'helper-call' && isValidHelperId(node.helper)) {
|
|
1878
|
-
const argsX = node.args.map(a => this.renderParsedExprToPerl(a))
|
|
1879
|
-
return `bf->${node.helper}(${argsX.join(', ')})`
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
1873
|
// `pos` distinguishes a derived-seed RHS (an assignment, checked via
|
|
1885
1874
|
// `isSupportedValue`) from every other, genuinely rendered call site —
|
|
1886
1875
|
// the rendered gate would otherwise re-refuse a tree the seed plan
|
|
1887
1876
|
// already classified `derived` at value position (#2696 review).
|
|
1888
|
-
|
|
1877
|
+
// Registered call lowerings (#2057) — including the built-in `queryHref`
|
|
1878
|
+
// plugin — are consulted by this gate no matter where the call sits in
|
|
1879
|
+
// the tree (`MojoTopLevelEmitter`'s `lowering` seam does the actual
|
|
1880
|
+
// rendering below, via `emitParsedExpr`'s shared `call` case).
|
|
1881
|
+
const supportOpts = { loweringMatchers: this._loweringMatchers }
|
|
1882
|
+
const support = pos === 'value' ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts)
|
|
1889
1883
|
if (!support.supported) {
|
|
1890
1884
|
this.errors.push({
|
|
1891
1885
|
code: 'BF101',
|
package/src/conformance-pins.ts
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
import type { ConformancePins } from '@barefootjs/jsx'
|
|
11
11
|
|
|
12
12
|
export const conformancePins: ConformancePins = {
|
|
13
|
+
// #2843: graduated — a registered lowering call inside a ternary
|
|
14
|
+
// attribute branch (or any nested value position) is now recognised via
|
|
15
|
+
// `MojoTopLevelEmitter`'s `lowering` seam + the registry-aware support
|
|
16
|
+
// gate, matching the direct-call attribute path exactly.
|
|
13
17
|
'filter-typeof-predicate': [{ code: 'BF021', severity: 'error' }],
|
|
14
18
|
'map-array-builder-body': [{ code: 'BF021', severity: 'error' }],
|
|
15
19
|
'map-array-builder-escaping': [{ code: 'BF021', severity: 'error' }],
|
|
@@ -56,4 +60,11 @@ export const conformancePins: ConformancePins = {
|
|
|
56
60
|
// `rich-prop-client-read` above.
|
|
57
61
|
'jsx-element-prop-ternary': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2667' }],
|
|
58
62
|
'jsx-element-prop-array': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2667' }],
|
|
63
|
+
// #2771: a reactive primitive invoked through a namespace import
|
|
64
|
+
// (`import * as bf from '@barefootjs/client'`, `bf.createSignal(...)`)
|
|
65
|
+
// that the analyzer's checker-less fast path cannot recognize refuses
|
|
66
|
+
// loudly (BF013) instead of silently dropping the declaration — fired
|
|
67
|
+
// in the shared analyzer pass ahead of any adapter's `adapter.generate()`,
|
|
68
|
+
// so all nine adapters (including Hono) pin this identically.
|
|
69
|
+
'namespace-import-primitive': [{ code: 'BF013', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2771' }],
|
|
59
70
|
}
|
|
@@ -28,4 +28,6 @@ import type { RenderDivergences } from '@barefootjs/jsx'
|
|
|
28
28
|
export const renderDivergences: RenderDivergences = {
|
|
29
29
|
'children-passthrough-renamed':
|
|
30
30
|
'A renamed `children` destructure emits the local alias as a template variable; the stash defines only `children`, so the Perl render dies (#2788).',
|
|
31
|
+
'aliased-loop-source':
|
|
32
|
+
'A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) dies inside `Mojo::Template::process` on real Mojolicious — the seeded loop data is keyed by the signal\'s real name (`items`), and the alias hop is never resolved when deciding what to seed under `items__alias`. This is the SSR-side twin of #2778 (fixed for the CSR client-JS template in the same PR that added this fixture) — that fix only touches client-JS emission, not SSR data-seeding. Tracked at https://github.com/piconic-ai/barefootjs/issues/2813; graduate by resolving the alias hop at SSR-seeding time using the same `resolveAliasOrigin`/`resolveGetterAliases` mechanism #2778 introduced, rather than a third alias-hop walker.',
|
|
31
33
|
}
|