@barefootjs/jsx 0.33.3 → 0.33.6
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/child-scope.d.ts +13 -0
- package/dist/adapters/child-scope.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1392 -1117
- package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
- package/dist/ir-to-client-js/child-components.d.ts.map +1 -1
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/component-scope.d.ts +20 -0
- package/dist/ir-to-client-js/component-scope.d.ts.map +1 -1
- package/dist/ir-to-client-js/compute-inlinability.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +2 -4
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts +4 -3
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts +35 -9
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts +10 -0
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +49 -2
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/index.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts +12 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +5 -2
- package/dist/prop-rewrite.d.ts.map +1 -1
- package/dist/props-binding.d.ts +49 -11
- package/dist/props-binding.d.ts.map +1 -1
- package/dist/types.d.ts +19 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +15 -15
- package/src/__tests__/aliased-import-child-registry-2777.test.ts +112 -0
- package/src/__tests__/date-lowering.test.ts +18 -4
- package/src/__tests__/fragment-body-loop-key.test.ts +95 -0
- package/src/__tests__/inner-loop-mathml-namespace.test.ts +1 -1
- package/src/__tests__/inner-loop-svg-namespace.test.ts +4 -4
- package/src/__tests__/issue-2724-prop-alias-loop-not-static.test.ts +355 -0
- package/src/__tests__/issue-2737-props-object-alias-template.test.ts +86 -0
- package/src/__tests__/issue-2756-input-skeleton-bails.test.ts +123 -0
- package/src/__tests__/issue-2778-getter-alias-csr-template.test.ts +113 -0
- package/src/__tests__/issue-2806-rest-bag-template-rewrite.test.ts +136 -0
- package/src/__tests__/issue-2835-props-type-member-name-collision.test.ts +259 -0
- package/src/__tests__/loop-item-root-scope.test.ts +7 -3
- package/src/__tests__/markup-prop-brand.test.ts +165 -26
- package/src/__tests__/multi-root-loop-body.test.ts +7 -3
- package/src/__tests__/namespace-import-primitive.test.ts +244 -0
- package/src/__tests__/preamble-declarations.test.ts +42 -0
- package/src/__tests__/rewrite-destructured-props.test.ts +53 -0
- package/src/__tests__/static-loop-csr-materialize.test.ts +2 -2
- package/src/adapters/child-scope.ts +23 -0
- package/src/analyzer.ts +130 -0
- package/src/errors.ts +14 -1
- package/src/index.ts +7 -0
- package/src/ir-to-client-js/build-references.ts +7 -0
- package/src/ir-to-client-js/child-components.ts +8 -1
- package/src/ir-to-client-js/collect-elements.ts +10 -38
- package/src/ir-to-client-js/component-scope.ts +60 -0
- package/src/ir-to-client-js/compute-inlinability.ts +46 -11
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +19 -1
- package/src/ir-to-client-js/control-flow/plan/loop.ts +2 -4
- package/src/ir-to-client-js/control-flow/shared.ts +12 -5
- package/src/ir-to-client-js/control-flow/stringify/component-loop.ts +7 -0
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +8 -2
- package/src/ir-to-client-js/control-flow.ts +12 -0
- package/src/ir-to-client-js/csr-substitute.ts +82 -16
- package/src/ir-to-client-js/emit-reactive.ts +29 -13
- package/src/ir-to-client-js/emit-registration.ts +1 -1
- package/src/ir-to-client-js/html-template.ts +194 -82
- package/src/ir-to-client-js/imports.ts +4 -0
- package/src/ir-to-client-js/index.ts +34 -23
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +7 -2
- package/src/ir-to-client-js/rewrite-props-object.ts +14 -0
- package/src/jsx-to-ir.ts +318 -54
- package/src/prop-rewrite.ts +23 -5
- package/src/props-binding.ts +74 -18
- package/src/types.ts +19 -5
|
@@ -15,6 +15,7 @@ import { datePlugin, DATE_METHODS } from '../date-lowering.ts'
|
|
|
15
15
|
import { toLocaleDatePlugin, foldedArgToClientJs } from '../to-locale-date-lowering.ts'
|
|
16
16
|
import { tsNodeToParsedExpr } from '../expression-parser.ts'
|
|
17
17
|
import type { LoweringMatcher } from '../lowering-registry.ts'
|
|
18
|
+
import { rewriteBarePropRefs } from '../prop-rewrite.ts'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Profile mode (#1690, SR3/SR4): the id appended to a DOM-binding effect so the
|
|
@@ -127,28 +128,43 @@ export function emitAttrUpdate(target: string, attrName: string, expression: str
|
|
|
127
128
|
/**
|
|
128
129
|
* Rewrite destructured prop names in an expression to `(props.xxx ?? default)`.
|
|
129
130
|
* Only applies when the component uses destructured props (not props.xxx style).
|
|
131
|
+
*
|
|
132
|
+
* Delegates the actual walk to `rewriteBarePropRefs` (`prop-rewrite.ts`) — the
|
|
133
|
+
* one AST walk in the compiler that already distinguishes a VALUE reference
|
|
134
|
+
* from an object-literal key, a property-access name, a shorthand property,
|
|
135
|
+
* or a name shadowed by an inner binding (`items.map((tag) => tag.x)`).
|
|
136
|
+
* This function used to run its own `\btag\b`-style regex over the raw
|
|
137
|
+
* expression text with no such distinction, so `queryHref(base, { tag: tag })`
|
|
138
|
+
* rewrote the KEY too (`{ _p.tag: _p.tag }`, a syntax error) — #2741. Only
|
|
139
|
+
* the default-value `(_p.x ?? <default>)` wrapping is specific to this call
|
|
140
|
+
* site, supplied via `replacementFor` rather than a second copy of the walk.
|
|
130
141
|
*/
|
|
131
142
|
export function rewriteDestructuredPropsInExpr(expr: string, ctx: ClientJsContext): string {
|
|
132
143
|
if (ctx.propsObjectName) return expr
|
|
133
144
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
const propNames = new Set<string>()
|
|
146
|
+
const propAliases = new Map<string, string>()
|
|
147
|
+
const propsByName = new Map<string, ClientJsContext['propsParams'][number]>()
|
|
137
148
|
for (const prop of ctx.propsParams) {
|
|
138
149
|
if (prop.name === 'children') continue
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
propNames.add(prop.name)
|
|
151
|
+
propsByName.set(prop.name, prop)
|
|
142
152
|
// `_p` is always keyed by the caller-facing name (#2524 CSR half).
|
|
143
|
-
|
|
144
|
-
const defaultVal = prop.defaultValue
|
|
145
|
-
const replacement = defaultVal
|
|
146
|
-
? `(${PROPS_PARAM}.${callerKey} ?? ${prop.defaultContainsArrow ? `(${defaultVal})` : defaultVal})`
|
|
147
|
-
: `${PROPS_PARAM}.${callerKey}`
|
|
148
|
-
result = result.replace(new RegExp(`(?<![-.])\\b${prop.name}\\b`, 'g'), replacement)
|
|
153
|
+
if (prop.sourceName && prop.sourceName !== prop.name) propAliases.set(prop.name, prop.sourceName)
|
|
149
154
|
}
|
|
155
|
+
if (propNames.size === 0) return expr
|
|
150
156
|
|
|
151
|
-
|
|
157
|
+
const replacementFor = (localName: string, callerKey: string): string => {
|
|
158
|
+
const defaultVal = propsByName.get(localName)?.defaultValue
|
|
159
|
+
if (!defaultVal) return `${PROPS_PARAM}.${callerKey}`
|
|
160
|
+
const defaultContainsArrow = propsByName.get(localName)?.defaultContainsArrow
|
|
161
|
+
return `(${PROPS_PARAM}.${callerKey} ?? ${defaultContainsArrow ? `(${defaultVal})` : defaultVal})`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Parenthesized so an object-literal or arrow-function expression parses
|
|
165
|
+
// standalone — same convention `applyScopedPropRefRewrite` itself uses.
|
|
166
|
+
const sourceFile = ts.createSourceFile('__bf_reactive_expr.ts', `(${expr}\n)`, ts.ScriptTarget.Latest, true)
|
|
167
|
+
return rewriteBarePropRefs(expr, sourceFile, propNames, undefined, propAliases, replacementFor) ?? expr
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
/**
|
|
@@ -223,7 +223,7 @@ export function emitRegistrationAndHydration(
|
|
|
223
223
|
// (#2651) — see `generateCsrTemplate`'s identical derivation below
|
|
224
224
|
// for why this is reused as-is rather than re-derived.
|
|
225
225
|
const markupSlotIds = new Set(ctx.dynamicElements.map(e => e.slotId))
|
|
226
|
-
const templateHtml = irToComponentTemplate(_ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName, markupSlotIds)
|
|
226
|
+
const templateHtml = irToComponentTemplate(_ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName, markupSlotIds, ctx.restPropsName)
|
|
227
227
|
if (templateHtml) {
|
|
228
228
|
defParts.push(buildTemplateDefPart(ctx, templateHtml))
|
|
229
229
|
}
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
* IR → HTML template string generation and validation.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { AttrValue, FlatMapCallback, IRAttribute, IRExpression, IRNode, IRProp, MapCallbackPreamble } from '../types.ts'
|
|
5
|
+
import type { AttrValue, FlatMapCallback, IRAttribute, IRElement, IRExpression, IRNode, IRProp, MapCallbackPreamble } from '../types.ts'
|
|
6
6
|
import { isBooleanAttr } from '../html-constants.ts'
|
|
7
7
|
import { toHtmlAttrName, attrValueToString, quotePropName, PROPS_PARAM, DATA_BF_PH, keyAttrName, loopStartMarker, loopEndMarker, loopItemMarker, freeIdsFromRefs, setIntersects, wrapExprWithLoopParams } from './utils.ts'
|
|
8
8
|
import type { LoopParamSpec } from './utils.ts'
|
|
9
9
|
import { nameForRegistryRef } from './component-scope.ts'
|
|
10
10
|
import { assertNever } from './walker.ts'
|
|
11
|
-
import { buildSignalMemoEnv, csrSubstitute,
|
|
11
|
+
import { buildSignalMemoEnv, csrSubstitute, type CsrEnv } from './csr-substitute.ts'
|
|
12
|
+
import { rewritePropsObjectRef } from './rewrite-props-object.ts'
|
|
12
13
|
import type { ClientJsContext } from './types.ts'
|
|
13
|
-
import { BF_PARENT_SCOPE_PLACEHOLDER, BF_SCOPE, escapeHtml } from '@barefootjs/shared'
|
|
14
|
+
import { BF_PARENT_SCOPE_PLACEHOLDER, BF_SCOPE, classifyDOMProp, escapeHtml } from '@barefootjs/shared'
|
|
14
15
|
import { buildLoopChainExpr } from '../loop-chain.ts'
|
|
15
|
-
import {
|
|
16
|
+
import { renderChildScopeArgs } from '../adapters/child-scope.ts'
|
|
16
17
|
import { BindingScope } from '../scope/binding-scope.ts'
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -547,6 +548,25 @@ export interface MergeContext {
|
|
|
547
548
|
honorClientOnly: boolean
|
|
548
549
|
}
|
|
549
550
|
|
|
551
|
+
/**
|
|
552
|
+
* The client-template twin of `IRElement.keyAttr`'s SSR-side decision
|
|
553
|
+
* (`jsx-to-ir.ts`'s `extractLoopKey`/`applyLoopKeyAttr`) — #2763. A raw
|
|
554
|
+
* `key={}` JSX attribute survives on `.attrs` regardless of whether the
|
|
555
|
+
* loop/relay actually recognized it as a row key (a fragment-bodied loop
|
|
556
|
+
* row before #2763's fix, or heterogeneous keys across conditional
|
|
557
|
+
* branches) — baking `data-key` from the mere presence of that attribute
|
|
558
|
+
* would disagree with every SSR adapter, which all gate on `keyAttr`
|
|
559
|
+
* instead. Returns the resolved attribute name only when `node.keyAttr` is
|
|
560
|
+
* actually set; `null` otherwise, so the caller drops the raw `key`
|
|
561
|
+
* attribute entirely rather than emitting a `data-key` no SSR adapter
|
|
562
|
+
* agrees with. `keyAttr.name` and `keyAttrName(loopDepth)` are already the
|
|
563
|
+
* same value (both derive from `@barefootjs/shared`'s `keyAttrName`, see
|
|
564
|
+
* `IRElement.keyAttr`'s docstring) — this only decides WHETHER to use it.
|
|
565
|
+
*/
|
|
566
|
+
function resolvedKeyAttrName(node: IRElement, loopDepth: number): string | null {
|
|
567
|
+
return node.keyAttr ? keyAttrName(loopDepth) : null
|
|
568
|
+
}
|
|
569
|
+
|
|
550
570
|
/** Return true if this attribute should participate in the merge object. */
|
|
551
571
|
function isMergeableAttr(a: IRAttribute, ctx: MergeContext): boolean {
|
|
552
572
|
if (ctx.honorClientOnly && a.clientOnly) return false
|
|
@@ -639,10 +659,11 @@ function buildSpreadAttrsMergeCall(args: {
|
|
|
639
659
|
* are wrapped with `__bfSlot(EXPR, <branchSlotsVar>)` so the runtime can
|
|
640
660
|
* splice live `Node` returns into the parsed fragment instead of
|
|
641
661
|
* stringifying them via the template literal (#1213).
|
|
642
|
-
* Component nodes
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
662
|
+
* Component nodes always pass their `slotId` to `renderChild` (via
|
|
663
|
+
* `renderChildScopeArgs`) — a loop-item-root component (its own scope,
|
|
664
|
+
* identified by `data-key`) keeps `bf-h`/`bf-m` slot identity without
|
|
665
|
+
* DERIVING its `bf-s` scope id from the parent slot (#2833); a component
|
|
666
|
+
* nested below the row root derives from it as usual (#2444). See
|
|
646
667
|
* `IRComponent.loopItemRoot` / `./adapters/child-scope.ts`.
|
|
647
668
|
*/
|
|
648
669
|
/**
|
|
@@ -876,10 +897,11 @@ export function irToHtmlTemplate(node: IRNode, restSpreadNames?: ReadonlySet<str
|
|
|
876
897
|
// others are already represented inside the merge object.
|
|
877
898
|
return idx === firstMergeableIdx ? mergeCall! : ''
|
|
878
899
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
900
|
+
if (a.name === 'key') {
|
|
901
|
+
const attrName = resolvedKeyAttrName(node, loopDepth)
|
|
902
|
+
return attrName ? renderTemplateAttrPart(a, attrName, wrapExpr, restSpreadNames) : ''
|
|
903
|
+
}
|
|
904
|
+
return renderTemplateAttrPart(a, toHtmlAttrName(a.name), wrapExpr, restSpreadNames)
|
|
883
905
|
})
|
|
884
906
|
.filter(Boolean)
|
|
885
907
|
|
|
@@ -1059,12 +1081,11 @@ export function irToHtmlTemplate(node: IRNode, restSpreadNames?: ReadonlySet<str
|
|
|
1059
1081
|
const keyArg = keyProp ? `, ${attrValueToString(keyProp.value) ?? 'undefined'}` : ''
|
|
1060
1082
|
// Pass slotId as suffix so $c() can find the child component by slot
|
|
1061
1083
|
// after branch swap. A loop-item-root component owns a separate scope
|
|
1062
|
-
// per iteration (identified by `data-key`)
|
|
1063
|
-
//
|
|
1064
|
-
// (#
|
|
1065
|
-
//
|
|
1066
|
-
|
|
1067
|
-
return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ', undefined' : '')}${slotArg})}`
|
|
1084
|
+
// per iteration (identified by `data-key`) rather than deriving from
|
|
1085
|
+
// the slot — `renderChildScopeArgs` still passes its slotId with the
|
|
1086
|
+
// `loopItemRoot` flag (#2833) so it keeps `bf-h`/`bf-m` slot identity,
|
|
1087
|
+
// matching the Hono reference (#2444).
|
|
1088
|
+
return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${renderChildScopeArgs(node, keyArg)})}`
|
|
1068
1089
|
}
|
|
1069
1090
|
|
|
1070
1091
|
case 'loop': {
|
|
@@ -1189,7 +1210,9 @@ export function buildLoopSkeletonTemplate(node: IRNode, safe: LoopSkeletonSafeSl
|
|
|
1189
1210
|
if (a.name === '...') return null
|
|
1190
1211
|
if (a.name === 'dangerouslySetInnerHTML') return null
|
|
1191
1212
|
if (a.name === 'key') {
|
|
1192
|
-
|
|
1213
|
+
if (resolvedKeyAttrName(node, 0)) {
|
|
1214
|
+
attrParts.push(`${keyAttrName(0)}=""`)
|
|
1215
|
+
}
|
|
1193
1216
|
continue
|
|
1194
1217
|
}
|
|
1195
1218
|
const v = a.value
|
|
@@ -1208,6 +1231,21 @@ export function buildLoopSkeletonTemplate(node: IRNode, safe: LoopSkeletonSafeSl
|
|
|
1208
1231
|
case 'template': {
|
|
1209
1232
|
const attrKey = node.slotId ? `${node.slotId}::${a.name}` : null
|
|
1210
1233
|
if (!attrKey || !safe.reactiveAttrKeys.has(attrKey)) return null
|
|
1234
|
+
// #2756 (input sub-mechanism): "omit and let the effect's eager
|
|
1235
|
+
// first run fill it in" only holds for attribute-reflected
|
|
1236
|
+
// binds — the effect calls `setAttribute`, so a freshly-cloned
|
|
1237
|
+
// row ends up byte-identical to an SSR-baked/hydrated one. A
|
|
1238
|
+
// property-only bind (`value`/`checked`, `classifyDOMProp`)
|
|
1239
|
+
// never does: `emitAttrUpdate`'s value/boolean-attr branches
|
|
1240
|
+
// deliberately write only the DOM PROPERTY (#2716), never the
|
|
1241
|
+
// attribute. SSR still bakes the attribute (`clientOnly` is
|
|
1242
|
+
// false here — `clientOnly` binds, e.g. textarea/select's
|
|
1243
|
+
// `value`, take neither path since SSR omits their attribute
|
|
1244
|
+
// too, so omitting stays correct for them). Refuse the whole
|
|
1245
|
+
// skeleton fast path instead of just this attribute; the
|
|
1246
|
+
// caller falls back to `irToHtmlTemplate`, which already bakes
|
|
1247
|
+
// this attribute correctly.
|
|
1248
|
+
if (!a.clientOnly && classifyDOMProp(a.name).kind === 'property') return null
|
|
1211
1249
|
// Covered by a loop-child createEffect — omit from the skeleton
|
|
1212
1250
|
// entirely; the effect's eager first run fills it in.
|
|
1213
1251
|
break
|
|
@@ -1485,9 +1523,11 @@ export function irToPlaceholderTemplate(node: IRNode, restSpreadNames?: Readonly
|
|
|
1485
1523
|
// composite-row twin of it, so a row it builds must carry the
|
|
1486
1524
|
// same attributes a hydration-reused row does (#2756).
|
|
1487
1525
|
if (a.clientOnly) return ''
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1526
|
+
if (a.name === 'key') {
|
|
1527
|
+
const attrName = resolvedKeyAttrName(node, loopDepth)
|
|
1528
|
+
return attrName ? renderTemplateAttrPart(a, attrName, wrapExpr, restSpreadNames) : ''
|
|
1529
|
+
}
|
|
1530
|
+
const attrName = a.name === '...' ? '...' : toHtmlAttrName(a.name)
|
|
1491
1531
|
return renderTemplateAttrPart(a, attrName, wrapExpr, restSpreadNames)
|
|
1492
1532
|
})
|
|
1493
1533
|
.filter(Boolean)
|
|
@@ -1604,34 +1644,52 @@ export function irToPlaceholderTemplate(node: IRNode, restSpreadNames?: Readonly
|
|
|
1604
1644
|
* - IRConditional → ternary expression
|
|
1605
1645
|
* - Single child → single expression; multiple → array literal
|
|
1606
1646
|
*/
|
|
1607
|
-
export function irChildrenToJsExpr(children: IRNode[]): string {
|
|
1647
|
+
export function irChildrenToJsExpr(children: IRNode[], brandMarkup = false): string {
|
|
1608
1648
|
// Flatten fragments and filter empty text nodes
|
|
1609
|
-
const exprs = children.flatMap(c => irNodeToJsExprs(c)).filter(Boolean)
|
|
1649
|
+
const exprs = children.flatMap(c => irNodeToJsExprs(c, brandMarkup)).filter(Boolean)
|
|
1610
1650
|
if (exprs.length === 0) return "''"
|
|
1611
1651
|
if (exprs.length === 1) return exprs[0]
|
|
1612
1652
|
return `[${exprs.join(', ')}]`
|
|
1613
1653
|
}
|
|
1614
1654
|
|
|
1615
1655
|
/**
|
|
1616
|
-
*
|
|
1617
|
-
* `
|
|
1618
|
-
* `
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
* `
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
* `
|
|
1656
|
+
* Build the getter-body expression for a `jsx-children` prop value —
|
|
1657
|
+
* `collect-elements.ts`'s `buildComponentPropsExpr` and this file's
|
|
1658
|
+
* `irNodeToJsExprs` 'component' case both hand a child component this exact
|
|
1659
|
+
* value through `get <propName>() { return <expr> }`. The ONE place that
|
|
1660
|
+
* decides whether (and how) to brand it with `bfMarkup()` (#2651, #2702).
|
|
1661
|
+
*
|
|
1662
|
+
* `children` itself is never branded — its consumer is a bare
|
|
1663
|
+
* `{children}` passthrough interpolation with no unwrap call, so a branded
|
|
1664
|
+
* object there would stringify to `[object Object]`.
|
|
1665
|
+
*
|
|
1666
|
+
* Otherwise, brand every `'element'` LEAF (`irNodeToJsExprs(..., true)`),
|
|
1667
|
+
* not the whole assembled value: `escapeTextOrNode`/`escapeTextOrMarkup`
|
|
1668
|
+
* (`@barefootjs/client/runtime`) unwrap a `bfMarkup()`-branded STRING, so a
|
|
1669
|
+
* lone element (`header={<strong/>}`), a fragment wrapping one (`header={<>
|
|
1670
|
+
* <strong/></>}`), and a conditional/nested-conditional between elements
|
|
1671
|
+
* (`header={<>{cond ? <a/> : <b/>}</>}`, #2702) all reduce, after flattening,
|
|
1672
|
+
* to exactly ONE branded expression — brand-at-the-leaf makes this sound
|
|
1673
|
+
* even when a branch is plain text or an arbitrary expression
|
|
1674
|
+
* (`cond ? <a/> : label()`): `irNodeToJsExprs`'s 'text'/'expression' cases
|
|
1675
|
+
* never brand, so `label()` still reaches `escapeTextOrNode` unescaped-HTML-free
|
|
1676
|
+
* and gets escaped normally, while the `<a/>` branch is trusted.
|
|
1677
|
+
*
|
|
1678
|
+
* A multi-child fragment (`header={<>text<strong/></>}`) flattens to MORE
|
|
1679
|
+
* than one expression — an array, per `irChildrenToJsExpr`'s own contract —
|
|
1680
|
+
* and neither runtime unwrapper has an array contract, so that shape stays
|
|
1681
|
+
* exactly as it rendered before this function existed: unbranded, via the
|
|
1682
|
+
* plain (unbranded) `irChildrenToJsExpr` call below. This is a pre-existing,
|
|
1683
|
+
* separate gap (tracked apart from #2702), not something this function
|
|
1684
|
+
* silently "fixes" by guessing at a new consumer contract.
|
|
1629
1685
|
*/
|
|
1630
|
-
function
|
|
1631
|
-
|
|
1686
|
+
export function jsxChildrenPropGetterExpr(children: IRNode[], propName: string): string {
|
|
1687
|
+
if (propName === 'children') return irChildrenToJsExpr(children)
|
|
1688
|
+
const brandedExprs = children.flatMap(c => irNodeToJsExprs(c, true)).filter(Boolean)
|
|
1689
|
+
return brandedExprs.length === 1 ? brandedExprs[0] : irChildrenToJsExpr(children)
|
|
1632
1690
|
}
|
|
1633
1691
|
|
|
1634
|
-
function irNodeToJsExprs(node: IRNode): string[] {
|
|
1692
|
+
function irNodeToJsExprs(node: IRNode, brandMarkup = false): string[] {
|
|
1635
1693
|
switch (node.type) {
|
|
1636
1694
|
case 'component': {
|
|
1637
1695
|
const propsEntries: string[] = node.props
|
|
@@ -1642,17 +1700,10 @@ function irNodeToJsExprs(node: IRNode): string[] {
|
|
|
1642
1700
|
}
|
|
1643
1701
|
switch (p.value.kind) {
|
|
1644
1702
|
case 'jsx-children': {
|
|
1645
|
-
// Brand (#2651)
|
|
1646
|
-
//
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
// `children`-exclusion argument (bare-passthrough consumer,
|
|
1650
|
-
// no unwrap call).
|
|
1651
|
-
const jsxExpr = irChildrenToJsExpr(p.value.children)
|
|
1652
|
-
const wrapped = (p.name !== 'children' && isSingleElementJsxChildren(p.value.children))
|
|
1653
|
-
? `bfMarkup(${jsxExpr})`
|
|
1654
|
-
: jsxExpr
|
|
1655
|
-
return `get ${quotePropName(p.name)}() { return ${wrapped} }`
|
|
1703
|
+
// Brand (#2651/#2702) via the shared door — see
|
|
1704
|
+
// `jsxChildrenPropGetterExpr`'s docstring.
|
|
1705
|
+
const jsxExpr = jsxChildrenPropGetterExpr(p.value.children, p.name)
|
|
1706
|
+
return `get ${quotePropName(p.name)}() { return ${jsxExpr} }`
|
|
1656
1707
|
}
|
|
1657
1708
|
case 'literal':
|
|
1658
1709
|
return `get ${quotePropName(p.name)}() { return ${JSON.stringify(p.value.value)} }`
|
|
@@ -1683,14 +1734,16 @@ function irNodeToJsExprs(node: IRNode): string[] {
|
|
|
1683
1734
|
if (!node.value.trim()) return []
|
|
1684
1735
|
return [JSON.stringify(node.value)]
|
|
1685
1736
|
|
|
1686
|
-
case 'element':
|
|
1687
|
-
|
|
1737
|
+
case 'element': {
|
|
1738
|
+
const html = `\`${irToHtmlTemplate(node)}\``
|
|
1739
|
+
return [brandMarkup ? `bfMarkup(${html})` : html]
|
|
1740
|
+
}
|
|
1688
1741
|
|
|
1689
1742
|
case 'fragment':
|
|
1690
|
-
return node.children.flatMap(c => irNodeToJsExprs(c))
|
|
1743
|
+
return node.children.flatMap(c => irNodeToJsExprs(c, brandMarkup))
|
|
1691
1744
|
|
|
1692
1745
|
case 'conditional':
|
|
1693
|
-
return [`${node.condition} ? ${irChildrenToJsExpr([node.whenTrue])} : ${irChildrenToJsExpr([node.whenFalse])}`]
|
|
1746
|
+
return [`${node.condition} ? ${irChildrenToJsExpr([node.whenTrue], brandMarkup)} : ${irChildrenToJsExpr([node.whenFalse], brandMarkup)}`]
|
|
1694
1747
|
|
|
1695
1748
|
// The kinds below previously fell through to `default: return []` and so
|
|
1696
1749
|
// were silently dropped when used as a component child. The explicit
|
|
@@ -1747,6 +1800,21 @@ export interface TemplateOptions {
|
|
|
1747
1800
|
inlinableConstants?: Map<string, string>
|
|
1748
1801
|
restSpreadNames?: ReadonlySet<string>
|
|
1749
1802
|
propsObjectName?: string | null
|
|
1803
|
+
/**
|
|
1804
|
+
* The rest-bag binding's source name (`ctx.restPropsName` — `'rest'` in
|
|
1805
|
+
* `{ children, ...rest }`), threaded alongside `propsObjectName` into
|
|
1806
|
+
* `rewritePropsObjectRef` so a component that reads the rest binding
|
|
1807
|
+
* directly in its own body (`{rest.header}`, not just spread onto an
|
|
1808
|
+
* element via `applyRestAttrs`) resolves to `_p` here too (#2806).
|
|
1809
|
+
* Not folded into `restSpreadNames`: that set is spread-filter
|
|
1810
|
+
* membership keyed by raw expression text (`isFilteredSpread`,
|
|
1811
|
+
* `collect-elements.ts`'s exclude-key resolution) and must keep seeing
|
|
1812
|
+
* the untouched name; this field is the separate "identifier to rewrite"
|
|
1813
|
+
* input `rewritePropsObjectRef` already accepts for the init body
|
|
1814
|
+
* (`generate-init.ts`) — the four template builders below were the only
|
|
1815
|
+
* callers still passing `null` for it.
|
|
1816
|
+
*/
|
|
1817
|
+
restPropsName?: string | null
|
|
1750
1818
|
/**
|
|
1751
1819
|
* Names that exist only in the init-body scope (or were demoted to unsafe
|
|
1752
1820
|
* during chained-ref resolution). The CSR template runs at module scope
|
|
@@ -1838,13 +1906,14 @@ export function irToComponentTemplate(
|
|
|
1838
1906
|
inlinableConstants?: Map<string, string>,
|
|
1839
1907
|
restSpreadNames?: ReadonlySet<string>,
|
|
1840
1908
|
propsObjectName?: string | null,
|
|
1841
|
-
markupSlotIds?: ReadonlySet<string
|
|
1909
|
+
markupSlotIds?: ReadonlySet<string>,
|
|
1910
|
+
restPropsName?: string | null
|
|
1842
1911
|
): string {
|
|
1843
|
-
return irToComponentTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, loopDepth: -1, markupSlotIds })
|
|
1912
|
+
return irToComponentTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, loopDepth: -1, markupSlotIds, restPropsName })
|
|
1844
1913
|
}
|
|
1845
1914
|
|
|
1846
1915
|
function irToComponentTemplateWithOpts(node: IRNode, opts: TemplateOptions): string {
|
|
1847
|
-
const { inlinableConstants, restSpreadNames, propsObjectName, loopDepth = 0 } = opts
|
|
1916
|
+
const { inlinableConstants, restSpreadNames, propsObjectName, restPropsName, loopDepth = 0 } = opts
|
|
1848
1917
|
// `recurse` preserves `inHoistedChildren` for structural pass-through
|
|
1849
1918
|
// (fragment / conditional); `childrenRecurse` clears it so element
|
|
1850
1919
|
// descendants don't re-emit the placeholder. (#1320)
|
|
@@ -1865,15 +1934,31 @@ function irToComponentTemplateWithOpts(node: IRNode, opts: TemplateOptions): str
|
|
|
1865
1934
|
}
|
|
1866
1935
|
}
|
|
1867
1936
|
|
|
1868
|
-
// Normalize source-level props object access (e.g., props.xxx → _p.xxx)
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1937
|
+
// Normalize source-level props object access (e.g., props.xxx → _p.xxx).
|
|
1938
|
+
// Restore FIRST: `rewritePropsObjectRef` parses `result` as real
|
|
1939
|
+
// TypeScript, and a still-protected string/template-literal span would
|
|
1940
|
+
// parse as an opaque placeholder token instead of the source text it
|
|
1941
|
+
// stands for (harmless for the placeholder itself, but it must not be
|
|
1942
|
+
// mistaken for unparsed leftover input by a caller reading `result`
|
|
1943
|
+
// before this point). Regex-based `\b${propsObjectName}\.` (removed
|
|
1944
|
+
// here) required the name to be immediately followed by `.`, so a
|
|
1945
|
+
// parenthesised receiver — `(props).label`, produced by this same
|
|
1946
|
+
// function's own constant-inlining step just above when the inlined
|
|
1947
|
+
// value is itself `props` (a local alias of the whole props object) —
|
|
1948
|
+
// silently kept the pre-rewrite name instead of `_p`.
|
|
1949
|
+
//
|
|
1950
|
+
// The gate stays keyed on `propsObjectName` for the destructured-prop
|
|
1951
|
+
// case (unconditionally rewriting here would also change static-path
|
|
1952
|
+
// output for components with neither a props object nor a rest binding
|
|
1953
|
+
// — a separate alignment, not this fix) but widens to fire when only
|
|
1954
|
+
// a rest binding exists (`{ children, ...rest }` with no whole-props
|
|
1955
|
+
// param), so `rest.header` resolves to `_p.header` here the same way
|
|
1956
|
+
// it already does in the init body (`generate-init.ts`, #2723) instead
|
|
1957
|
+
// of reaching the template as a bare, undeclared `rest` (#2806).
|
|
1958
|
+
const restored = restore(result)
|
|
1959
|
+
return (propsObjectName && propsObjectName !== PROPS_PARAM) || restPropsName
|
|
1960
|
+
? rewritePropsObjectRef(restored, propsObjectName ?? null, restPropsName ?? null, { enclosingScope: opts.scope })
|
|
1961
|
+
: restored
|
|
1877
1962
|
}
|
|
1878
1963
|
|
|
1879
1964
|
switch (node.type) {
|
|
@@ -2250,8 +2335,11 @@ export function generateCsrTemplate(
|
|
|
2250
2335
|
// Build the substitution env once per component. Signals + memos come
|
|
2251
2336
|
// from `buildSignalMemoEnv`; inlinable constants layer in here so
|
|
2252
2337
|
// each call to `transformExpr` is a pure AST projection over the
|
|
2253
|
-
// shared env (no per-call Map cloning).
|
|
2254
|
-
|
|
2338
|
+
// shared env (no per-call Map cloning). `ctx.localConstants` lets
|
|
2339
|
+
// `buildSignalMemoEnv` also resolve a bare getter-alias chain
|
|
2340
|
+
// (`const items__alias = items`, #2778) to the same call-kind entry
|
|
2341
|
+
// its origin has.
|
|
2342
|
+
const base = buildSignalMemoEnv(ctx.signals, ctx.memos, propsObjectName ?? null, ctx.localConstants)
|
|
2255
2343
|
const csrEnv: CsrEnv = { substitutions: new Map(base.substitutions), propsObjectName: base.propsObjectName }
|
|
2256
2344
|
if (inlinableConstants) {
|
|
2257
2345
|
for (const [name, value] of inlinableConstants) {
|
|
@@ -2268,7 +2356,7 @@ export function generateCsrTemplate(
|
|
|
2268
2356
|
// by construction (`collectElements`'s `expression` visitor only pushes
|
|
2269
2357
|
// here). Reused as-is, not re-derived.
|
|
2270
2358
|
const markupSlotIds = new Set(ctx.dynamicElements.map(e => e.slotId))
|
|
2271
|
-
return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, unsafeLocalNames: effectiveUnsafeLocalNames, deferredChildSlots, loopDepth: -1, markupSlotIds })
|
|
2359
|
+
return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, unsafeLocalNames: effectiveUnsafeLocalNames, deferredChildSlots, loopDepth: -1, markupSlotIds, restPropsName: ctx.restPropsName })
|
|
2272
2360
|
}
|
|
2273
2361
|
|
|
2274
2362
|
/**
|
|
@@ -2345,7 +2433,7 @@ function buildCsrEnvForCtx(
|
|
|
2345
2433
|
inlinableConstants: Map<string, string> | undefined,
|
|
2346
2434
|
propsObjectName?: string | null,
|
|
2347
2435
|
): CsrEnv {
|
|
2348
|
-
const base = buildSignalMemoEnv(ctx.signals, ctx.memos, propsObjectName ?? null)
|
|
2436
|
+
const base = buildSignalMemoEnv(ctx.signals, ctx.memos, propsObjectName ?? null, ctx.localConstants)
|
|
2349
2437
|
const csrEnv: CsrEnv = { substitutions: new Map(base.substitutions), propsObjectName: base.propsObjectName }
|
|
2350
2438
|
if (inlinableConstants) {
|
|
2351
2439
|
for (const [name, value] of inlinableConstants) {
|
|
@@ -2463,7 +2551,7 @@ export function computeDeferredChildSlots(
|
|
|
2463
2551
|
}
|
|
2464
2552
|
|
|
2465
2553
|
function generateCsrTemplateWithOpts(node: IRNode, opts: TemplateOptions): string {
|
|
2466
|
-
const { restSpreadNames, propsObjectName, csrEnv, unsafeLocalNames, loopDepth = 0 } = opts
|
|
2554
|
+
const { restSpreadNames, propsObjectName, restPropsName, csrEnv, unsafeLocalNames, loopDepth = 0 } = opts
|
|
2467
2555
|
const env: CsrEnv = csrEnv ?? { substitutions: new Map(), propsObjectName: propsObjectName ?? null }
|
|
2468
2556
|
const transformExpr = (expr: string, templateExpr?: string): string => {
|
|
2469
2557
|
// Single AST substitution pass: replaces signal getter calls
|
|
@@ -2492,11 +2580,16 @@ function generateCsrTemplateWithOpts(node: IRNode, opts: TemplateOptions): strin
|
|
|
2492
2580
|
if (unsafeLocalNames && unsafeLocalNames.size > 0 && setIntersects(freeIdentifiers, unsafeLocalNames)) {
|
|
2493
2581
|
return UNSAFE_TEMPLATE_EXPR
|
|
2494
2582
|
}
|
|
2495
|
-
// Final emit-form: rewrite `propsName.X → _p.X
|
|
2496
|
-
//
|
|
2583
|
+
// Final emit-form: rewrite `propsName.X → _p.X` (and a direct
|
|
2584
|
+
// rest-bag read, `restName.X → _p.X` — the rest binding IS `_p` at
|
|
2585
|
+
// runtime, same as the init body's `rewritePropsObjectRef` call and
|
|
2586
|
+
// `applyRestAttrs`'s exclude-key filtering both already assume; a
|
|
2587
|
+
// computed "props minus consumed keys" object here would be a second,
|
|
2588
|
+
// divergent implementation of that same decision, #2806). Deferred
|
|
2589
|
+
// until after the unsafe-name check because the check used raw form
|
|
2497
2590
|
// (consistent with `populateCsrInlinable`'s `isInlinableInTemplate`
|
|
2498
2591
|
// gate — #1138).
|
|
2499
|
-
return
|
|
2592
|
+
return rewritePropsObjectRef(rewritten, propsObjectName ?? null, restPropsName ?? null, { enclosingScope: opts.scope })
|
|
2500
2593
|
}
|
|
2501
2594
|
|
|
2502
2595
|
// `recurse` preserves `inHoistedChildren` for structural pass-through
|
|
@@ -2550,9 +2643,26 @@ function generateCsrTemplateWithOpts(node: IRNode, opts: TemplateOptions): strin
|
|
|
2550
2643
|
if (mergeCtx.isFilteredSpread(v)) return ''
|
|
2551
2644
|
return `\${spreadAttrs(${transformExpr(v.expr, v.templateExpr)})}`
|
|
2552
2645
|
}
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2646
|
+
if (a.name === 'key') {
|
|
2647
|
+
const keyName = resolvedKeyAttrName(node, loopDepth)
|
|
2648
|
+
if (!keyName) return ''
|
|
2649
|
+
switch (v.kind) {
|
|
2650
|
+
case 'boolean-attr':
|
|
2651
|
+
return keyName
|
|
2652
|
+
case 'literal':
|
|
2653
|
+
return `${keyName}="${escapeHtml(v.value)}"`
|
|
2654
|
+
case 'expression':
|
|
2655
|
+
return templateAttrExpr(keyName, transformExpr(v.expr, v.templateExpr), v.presenceOrUndefined)
|
|
2656
|
+
case 'template': {
|
|
2657
|
+
const valueStr = attrValueToString(v, { useTemplate: true })
|
|
2658
|
+
return valueStr ? templateAttrExpr(keyName, transformExpr(valueStr)) : ''
|
|
2659
|
+
}
|
|
2660
|
+
case 'boolean-shorthand':
|
|
2661
|
+
case 'jsx-children':
|
|
2662
|
+
return ''
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
const attrName = toHtmlAttrName(a.name)
|
|
2556
2666
|
switch (v.kind) {
|
|
2557
2667
|
case 'boolean-attr':
|
|
2558
2668
|
return attrName
|
|
@@ -2731,8 +2841,10 @@ function generateCsrTemplateWithOpts(node: IRNode, opts: TemplateOptions): strin
|
|
|
2731
2841
|
const propsExpr = propsEntries.length > 0 ? `{${propsEntries.join(', ')}}` : '{}'
|
|
2732
2842
|
const keyProp = node.props.find(p => p.name === 'key')
|
|
2733
2843
|
const keyArg = keyProp ? `, ${transformKeyValue(keyProp.value, transformExpr)}` : ''
|
|
2734
|
-
|
|
2735
|
-
|
|
2844
|
+
// See `renderChildScopeArgs`'s docstring — a loop item root still gets
|
|
2845
|
+
// its slotId, plus the `loopItemRoot` flag (#2833), rather than no
|
|
2846
|
+
// slot argument at all.
|
|
2847
|
+
return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${renderChildScopeArgs(node, keyArg)})}`
|
|
2736
2848
|
}
|
|
2737
2849
|
|
|
2738
2850
|
case 'loop': {
|
|
@@ -2800,7 +2912,7 @@ function generateCsrTemplateWithOpts(node: IRNode, opts: TemplateOptions): strin
|
|
|
2800
2912
|
// its own props context via recurseInLoopBody.
|
|
2801
2913
|
const body = renderPreamble(node.flatMapCallback, {
|
|
2802
2914
|
textVariant: 'template',
|
|
2803
|
-
transformJs: (t) =>
|
|
2915
|
+
transformJs: (t) => rewritePropsObjectRef(t, propsObjectName ?? null, restPropsName ?? null, { enclosingScope: childScope }),
|
|
2804
2916
|
// Leaf `key` stripped — see the irToHtmlTemplate site above.
|
|
2805
2917
|
renderLeaf: (ir) => recurseInLoopBody(stripLeafKeyAttr(ir)),
|
|
2806
2918
|
})
|
|
@@ -2808,7 +2920,7 @@ function generateCsrTemplateWithOpts(node: IRNode, opts: TemplateOptions): strin
|
|
|
2808
2920
|
} else if (node.preamble) {
|
|
2809
2921
|
// Stage 3 / D4 — template-variant js text with the props rewrite
|
|
2810
2922
|
// applied per segment; JSX leaves render via the loop-body recursion.
|
|
2811
|
-
const preamble = renderPreamble(node.preamble, { textVariant: 'template', transformJs: (t) =>
|
|
2923
|
+
const preamble = renderPreamble(node.preamble, { textVariant: 'template', transformJs: (t) => rewritePropsObjectRef(t, propsObjectName ?? null, restPropsName ?? null, { enclosingScope: childScope }), renderLeaf: (ir) => recurseInLoopBody(ir) })
|
|
2812
2924
|
mapExpr = `\${${iterArrayExpr}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`
|
|
2813
2925
|
} else {
|
|
2814
2926
|
mapExpr = `\${${iterArrayExpr}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`
|
|
@@ -23,6 +23,10 @@ export const RUNTIME_IMPORT_CANDIDATES = [
|
|
|
23
23
|
// falling through to the global store. The clone-root counterpart of the
|
|
24
24
|
// mount point `createComponent` consumes for component-root rows.
|
|
25
25
|
'mountRowRoot',
|
|
26
|
+
// Ambient parent-scope override for a static loop's materialize `forEach`
|
|
27
|
+
// (#2833) — that codepath runs during `init`, after the parent's own
|
|
28
|
+
// template evaluation (and its `_parentScopeId`) has already unwound.
|
|
29
|
+
'withParentScope',
|
|
26
30
|
'createPortal',
|
|
27
31
|
'provideContext', 'createContext', 'useContext',
|
|
28
32
|
'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'escapeTextOrNode',
|
|
@@ -18,7 +18,7 @@ import { canGenerateStaticTemplate, irToComponentTemplate, generateCsrTemplate }
|
|
|
18
18
|
import { PROPS_PARAM } from './utils.ts'
|
|
19
19
|
import { buildInlinableConstants, csrInlinableConstantsFromCtx } from './emit-registration.ts'
|
|
20
20
|
import { buildEnvFromCtx } from './compute-inlinability.ts'
|
|
21
|
-
import { nameForRegistryRef } from './component-scope.ts'
|
|
21
|
+
import { nameForRegistryRef, buildImportAliasMap, setActiveImportAliases } from './component-scope.ts'
|
|
22
22
|
import { resolveRestSpreadNames } from './prop-handling.ts'
|
|
23
23
|
import { IMPORT_PLACEHOLDER, RUNTIME_MODULE, detectUsedImports, collectExternalImports } from './imports.ts'
|
|
24
24
|
import { isInlinableInTemplate } from '../relocate.ts'
|
|
@@ -85,32 +85,43 @@ export function generateClientJsWithSourceMap(
|
|
|
85
85
|
adapterCapabilities?: AdapterCapabilities,
|
|
86
86
|
profile?: boolean,
|
|
87
87
|
): ClientJsResult {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// #2777 — installed for the whole body (not just via `compiler.ts`'s
|
|
89
|
+
// `setActiveComponentScope`) because this function is also called
|
|
90
|
+
// directly, bypassing `compiler.ts` entirely: the CSR-conformance
|
|
91
|
+
// harness (`csr-render.ts`) and `analyzeClientNeeds` both do. Cleared
|
|
92
|
+
// in `finally` so a thrown error never leaks the alias map into an
|
|
93
|
+
// unrelated later compile.
|
|
94
|
+
setActiveImportAliases(buildImportAliasMap(ir.metadata.imports))
|
|
95
|
+
try {
|
|
96
|
+
const ctx = createContext(ir, scope, adapterCapabilities, profile)
|
|
97
|
+
const siblingOffsets = computeLoopSiblingOffsets(ir.root)
|
|
98
|
+
collectElements(ir.root, ctx, siblingOffsets)
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
// Both `generateTemplateOnlyMount` and `generateInitFunction` run inline
|
|
101
|
+
// analysis passes (buildInlinableConstants → computeInlinability) that
|
|
102
|
+
// can add to `ctx.warnings` — most notably the BF060/BF061 stage-violation
|
|
103
|
+
// diagnostics. Flush warnings to `ir.errors` AFTER those passes so the
|
|
104
|
+
// diagnostics survive to the caller.
|
|
105
|
+
if (!needsClientJs(ctx)) {
|
|
106
|
+
const code = generateTemplateOnlyMount(ir, ctx)
|
|
107
|
+
ir.errors.push(...ctx.warnings)
|
|
108
|
+
return { code }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const code = generateInitFunction(ir, ctx, siblingComponents, localImportPrefixes)
|
|
99
112
|
ir.errors.push(...ctx.warnings)
|
|
100
|
-
return { code }
|
|
101
|
-
}
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
if (options?.sourceMaps && code) {
|
|
115
|
+
const fileName = options.generatedFileName ?? `${ir.metadata.componentName}.client.js`
|
|
116
|
+
const sourceMap = buildSourceMapFromIR(code, ir, fileName)
|
|
117
|
+
const codeWithUrl = code + `\n//# sourceMappingURL=${fileName}.map`
|
|
118
|
+
return { code: codeWithUrl, sourceMap }
|
|
119
|
+
}
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const codeWithUrl = code + `\n//# sourceMappingURL=${fileName}.map`
|
|
110
|
-
return { code: codeWithUrl, sourceMap }
|
|
121
|
+
return { code }
|
|
122
|
+
} finally {
|
|
123
|
+
setActiveImportAliases(null)
|
|
111
124
|
}
|
|
112
|
-
|
|
113
|
-
return { code }
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
/**
|
|
@@ -275,7 +286,7 @@ function generateTemplateOnlyMount(ir: ComponentIR, ctx: ClientJsContext): strin
|
|
|
275
286
|
// matches `emitRegistrationAndHydration`'s derivation byte-for-byte
|
|
276
287
|
// rather than special-casing "no markup slots here" (#2651).
|
|
277
288
|
const markupSlotIds = new Set(ctx.dynamicElements.map(e => e.slotId))
|
|
278
|
-
templateHtml = irToComponentTemplate(ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName, markupSlotIds)
|
|
289
|
+
templateHtml = irToComponentTemplate(ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName, markupSlotIds, ctx.restPropsName)
|
|
279
290
|
}
|
|
280
291
|
|
|
281
292
|
// CSR fallback: when static template generation fails (e.g., components with
|