@barefootjs/jsx 0.33.0 → 0.33.2
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/analyzer.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +14 -0
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +268 -79
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +8 -0
- package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +12 -0
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +1 -0
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/element-refs.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-registration.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +7 -7
- 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/phases/provider-and-child-inits.d.ts.map +1 -1
- package/dist/ir-to-client-js/prop-handling.d.ts +33 -0
- package/dist/ir-to-client-js/prop-handling.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +5 -0
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/rewrite-props-object.d.ts +36 -8
- package/dist/ir-to-client-js/rewrite-props-object.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +21 -0
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +23 -6
- package/src/__tests__/binding-scope-ratchet.test.ts +5 -1
- package/src/__tests__/child-components-in-map.test.ts +11 -3
- package/src/__tests__/client-js-generation.test.ts +48 -1
- package/src/__tests__/inline-jsx-callback.test.ts +55 -0
- package/src/__tests__/ir-jsx-props.test.ts +148 -0
- package/src/__tests__/issue-2705-branch-inner-loop-container.test.ts +91 -0
- package/src/__tests__/issue-2723-prop-alias-reactivity.test.ts +124 -0
- package/src/__tests__/markup-prop-brand.test.ts +49 -0
- package/src/__tests__/nested-loop-conditional.test.ts +20 -11
- package/src/__tests__/return-through-local-var.test.ts +269 -0
- package/src/__tests__/rewrite-props-object.test.ts +41 -4
- package/src/analyzer.ts +71 -0
- package/src/errors.ts +17 -1
- package/src/expression-parser.ts +26 -0
- package/src/index.ts +1 -1
- package/src/ir-to-client-js/collect-elements.ts +49 -45
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +19 -0
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +22 -3
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +5 -2
- package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +12 -0
- package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +9 -0
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +7 -1
- package/src/ir-to-client-js/element-refs.ts +8 -0
- package/src/ir-to-client-js/emit-reactive.ts +91 -23
- package/src/ir-to-client-js/emit-registration.ts +26 -7
- package/src/ir-to-client-js/generate-init.ts +1 -1
- package/src/ir-to-client-js/html-template.ts +8 -8
- package/src/ir-to-client-js/imports.ts +5 -0
- package/src/ir-to-client-js/index.ts +10 -4
- package/src/ir-to-client-js/phases/provider-and-child-inits.ts +5 -1
- package/src/ir-to-client-js/prop-handling.ts +94 -0
- package/src/ir-to-client-js/reactivity.ts +59 -0
- package/src/ir-to-client-js/rewrite-props-object.ts +50 -10
- package/src/ir-to-client-js/types.ts +21 -0
- package/src/jsx-to-ir.ts +204 -9
- package/src/types.ts +14 -0
|
@@ -28,6 +28,57 @@ function bindingIdArg(ctx: ClientJsContext, slotId: string | undefined): string
|
|
|
28
28
|
return `, ${JSON.stringify(`${ctx.componentName}#binding:${slotId}`)}`
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Generate statements that write a `value` HTML ATTRIBUTE the developer
|
|
33
|
+
* wrote directly on an element (`<div value={x}>`, a loop row's `<li
|
|
34
|
+
* value={x}>`, …) — SSR renders that same attribute, so hydration keeping
|
|
35
|
+
* it in sync is exactly the contract. Gated at runtime to elements that
|
|
36
|
+
* ALREADY expose a native `.value` IDL property (`'value' in target` —
|
|
37
|
+
* form controls, but also e.g. `<li value>`; the same duck-type check
|
|
38
|
+
* `applyRestAttrs` uses, deliberately not a tag-name allowlist), because
|
|
39
|
+
* for those `setAttribute('value', x)` only sets the INITIAL HTML
|
|
40
|
+
* attribute and the live property is required after user interaction; any
|
|
41
|
+
* other element falls back to a plain attribute write, which still matches
|
|
42
|
+
* what SSR rendered there. Writing the live property unconditionally would
|
|
43
|
+
* plant an expando SSR never had — a hydrated/SSR DOM-state divergence and a
|
|
44
|
+
* hazard for anything that duck-types form controls via `'value' in el`
|
|
45
|
+
* (#2716).
|
|
46
|
+
*
|
|
47
|
+
* NOT for the child-component-root `value` MIRROR (`emitReactivePropBindings`
|
|
48
|
+
* / `emitReactiveChildProps` reflecting a named prop onto a child's root
|
|
49
|
+
* element) — that mechanism has no SSR-rendered counterpart at all
|
|
50
|
+
* regardless of prop name, so an attribute fallback there would itself
|
|
51
|
+
* plant a fresh SSR/hydrate divergence; see `emitChildValueMirrorStatements`.
|
|
52
|
+
*/
|
|
53
|
+
function emitValueUpdateStatements(target: string, expression: string): string[] {
|
|
54
|
+
return [
|
|
55
|
+
`const __val = String(${expression})`,
|
|
56
|
+
`if ('value' in ${target}) { if (${target}.value !== __val) ${target}.value = __val } else { ${target}.setAttribute('value', __val) }`,
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* `value`-prop write for the CHILD-ROOT MIRROR mechanism
|
|
62
|
+
* (`emitReactivePropBindings` / `emitReactiveChildProps`, both reactively
|
|
63
|
+
* reflect a parent-passed NAMED PROP onto a child component's root DOM
|
|
64
|
+
* element). Unlike a developer-authored `value=` attribute
|
|
65
|
+
* (`emitValueUpdateStatements`), this mirror has NO SSR-rendered
|
|
66
|
+
* counterpart at all — SSR never puts a `value` attribute on a child's
|
|
67
|
+
* root just because the parent passed a `value` prop. So a root WITHOUT a
|
|
68
|
+
* native `.value` property gets NOTHING written — not even an attribute
|
|
69
|
+
* (confirmed against the oracle's structural-HTML comparison, #2716: an
|
|
70
|
+
* attribute fallback here reintroduced a fresh SSR/hydrate divergence one
|
|
71
|
+
* layer down from the IDL-property expando this replaces). A root that
|
|
72
|
+
* already exposes `.value` (`'value' in target`, same duck-type gate as
|
|
73
|
+
* the direct-attribute case) still gets the live controlled-value
|
|
74
|
+
* property.
|
|
75
|
+
*/
|
|
76
|
+
function emitChildValueMirrorStatements(target: string, expression: string): string[] {
|
|
77
|
+
return [
|
|
78
|
+
`if ('value' in ${target}) { const __val = String(${expression}); if (${target}.value !== __val) ${target}.value = __val }`,
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
31
82
|
/**
|
|
32
83
|
* Generate JS statements to update a DOM attribute reactively.
|
|
33
84
|
* Centralizes the attribute-type dispatch (value, class, boolean, presence, generic)
|
|
@@ -55,10 +106,7 @@ export function emitAttrUpdate(target: string, attrName: string, expression: str
|
|
|
55
106
|
]
|
|
56
107
|
}
|
|
57
108
|
if (htmlName === 'value') {
|
|
58
|
-
return
|
|
59
|
-
`const __val = String(${expression})`,
|
|
60
|
-
`if (${target}.value !== __val) ${target}.value = __val`,
|
|
61
|
-
]
|
|
109
|
+
return emitValueUpdateStatements(target, expression)
|
|
62
110
|
}
|
|
63
111
|
if (isBooleanAttr(htmlName)) {
|
|
64
112
|
return [`${target}.${htmlName} = !!(${expression})`]
|
|
@@ -462,37 +510,43 @@ export function emitReactivePropBindings(lines: string[], ctx: ClientJsContext):
|
|
|
462
510
|
}
|
|
463
511
|
|
|
464
512
|
for (const [slotId, props] of propsBySlot) {
|
|
465
|
-
|
|
466
|
-
|
|
513
|
+
// The component's own `comment: true` root child IS `__scope` — no
|
|
514
|
+
// `$c` ref was declared for it (element-refs.ts), so reference
|
|
515
|
+
// `__scope` directly instead of a `_sN` var that doesn't exist
|
|
516
|
+
// (#2649, see `ClientJsContext.commentScopeRootSlotId`'s docstring).
|
|
517
|
+
const ref = slotId === ctx.commentScopeRootSlotId ? '__scope' : `_${varSlotId(slotId)}`
|
|
518
|
+
lines.push(` if (${ref}) {`)
|
|
467
519
|
for (const prop of props) {
|
|
468
520
|
const value = `${prop.expression}()`
|
|
469
521
|
if (prop.propName === 'selected') {
|
|
470
522
|
if (prop.componentName === 'TabsContent') {
|
|
471
|
-
lines.push(`
|
|
523
|
+
lines.push(` ${ref}.setAttribute('data-state', ${value} ? 'active' : 'inactive')`)
|
|
472
524
|
lines.push(` if (${value}) {`)
|
|
473
|
-
lines.push(`
|
|
525
|
+
lines.push(` ${ref}.classList.remove('hidden')`)
|
|
474
526
|
lines.push(` } else {`)
|
|
475
|
-
lines.push(`
|
|
527
|
+
lines.push(` ${ref}.classList.add('hidden')`)
|
|
476
528
|
lines.push(` }`)
|
|
477
529
|
} else {
|
|
478
530
|
// Update data-state and aria-selected attributes.
|
|
479
531
|
// Visual styling is driven by CSS data-[state=active/inactive]: selectors.
|
|
480
|
-
lines.push(`
|
|
481
|
-
lines.push(`
|
|
482
|
-
lines.push(`
|
|
532
|
+
lines.push(` ${ref}.setAttribute('aria-selected', String(${value}))`)
|
|
533
|
+
lines.push(` ${ref}.setAttribute('data-state', ${value} ? 'active' : 'inactive')`)
|
|
534
|
+
lines.push(` ${ref}.setAttribute('tabindex', ${value} ? '0' : '-1')`)
|
|
483
535
|
}
|
|
484
|
-
// Use DOM property assignment for value and boolean attrs
|
|
485
|
-
//
|
|
486
|
-
//
|
|
536
|
+
// Use DOM property assignment for value and boolean attrs, but only
|
|
537
|
+
// on genuine form controls — see `emitChildValueMirrorStatements`'s
|
|
538
|
+
// docstring (#2716). `ref` here is a named prop's MIRROR target
|
|
539
|
+
// element (a child component's arbitrary root), not necessarily a
|
|
540
|
+
// form control, and this mirror has no SSR-rendered counterpart to
|
|
541
|
+
// fall back to.
|
|
487
542
|
// Boolean attrs (disabled, checked, etc.) treat any attribute presence as
|
|
488
543
|
// truthy, so setAttribute('disabled', 'false') still disables the element.
|
|
489
544
|
} else if (prop.propName === 'value') {
|
|
490
|
-
lines.push(`
|
|
491
|
-
lines.push(` if (_${v}.value !== __val) _${v}.value = __val`)
|
|
545
|
+
for (const stmt of emitChildValueMirrorStatements(ref, value)) lines.push(` ${stmt}`)
|
|
492
546
|
} else if (isBooleanAttr(prop.propName)) {
|
|
493
|
-
lines.push(`
|
|
547
|
+
lines.push(` ${ref}.${prop.propName} = !!(${value})`)
|
|
494
548
|
} else {
|
|
495
|
-
lines.push(`
|
|
549
|
+
lines.push(` ${ref}.setAttribute('${prop.propName}', String(${value}))`)
|
|
496
550
|
}
|
|
497
551
|
}
|
|
498
552
|
lines.push(` }`)
|
|
@@ -520,13 +574,27 @@ export function emitReactiveChildProps(lines: string[], ctx: ClientJsContext): v
|
|
|
520
574
|
|
|
521
575
|
for (const [, props] of propsByComponent) {
|
|
522
576
|
const first = props[0]
|
|
577
|
+
// The component's own `comment: true` root child IS `__scope` — query
|
|
578
|
+
// it directly rather than through `$c`, which cannot tell "I already
|
|
579
|
+
// am this slot" apart from a coincidentally-matching descendant
|
|
580
|
+
// (#2649, see `ClientJsContext.commentScopeRootSlotId`'s docstring).
|
|
581
|
+
const isCommentRoot = first.slotId !== null && first.slotId === ctx.commentScopeRootSlotId
|
|
523
582
|
const varSuffix = first.slotId ? varSlotId(first.slotId).replace(/-/g, '_') : first.componentName
|
|
524
|
-
const varName = `__${first.componentName}_${varSuffix}El`
|
|
525
|
-
|
|
526
|
-
|
|
583
|
+
const varName = isCommentRoot ? '__scope' : `__${first.componentName}_${varSuffix}El`
|
|
584
|
+
if (!isCommentRoot) {
|
|
585
|
+
const selectorArg = first.slotId ? first.slotId : first.componentName
|
|
586
|
+
lines.push(` const [${varName}] = $c(__scope, '${selectorArg}')`)
|
|
587
|
+
}
|
|
527
588
|
lines.push(` if (${varName}) {`)
|
|
528
589
|
for (const prop of props) {
|
|
529
|
-
|
|
590
|
+
// `value` is the CHILD-ROOT MIRROR case, not a developer-authored
|
|
591
|
+
// attribute — route it through the no-SSR-fallback helper instead
|
|
592
|
+
// of `emitAttrUpdate`'s generic (attribute-fallback) dispatch;
|
|
593
|
+
// see `emitChildValueMirrorStatements`'s docstring (#2716).
|
|
594
|
+
const stmts = toHtmlAttrName(prop.attrName) === 'value'
|
|
595
|
+
? emitChildValueMirrorStatements(varName, prop.expression)
|
|
596
|
+
: emitAttrUpdate(varName, prop.attrName, prop.expression, prop)
|
|
597
|
+
for (const stmt of stmts) {
|
|
530
598
|
lines.push(` ${stmt}`)
|
|
531
599
|
}
|
|
532
600
|
}
|
|
@@ -10,6 +10,7 @@ import { PROPS_PARAM } from './utils.ts'
|
|
|
10
10
|
import { computeInlinability, toLegacyInlinability } from './compute-inlinability.ts'
|
|
11
11
|
import { canGenerateStaticTemplate, irToComponentTemplate, generateCsrTemplate, createStringProtector } from './html-template.ts'
|
|
12
12
|
import { nameForRegistryRef } from './component-scope.ts'
|
|
13
|
+
import { resolveRestSpreadNames } from './prop-handling.ts'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Resolve chained references within a constants map.
|
|
@@ -191,14 +192,29 @@ export function emitRegistrationAndHydration(
|
|
|
191
192
|
const propNamesForStaticCheck = new Set(ctx.propsParams.map((p) => p.name))
|
|
192
193
|
const { inlinableConstants, unsafeLocalNames } = inlinability ?? buildInlinableConstants(ctx, graph, _ir.root)
|
|
193
194
|
|
|
194
|
-
// Build rest spread names: these are rest/props spreads handled by
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
// Build rest spread names: these are rest/props spreads handled by
|
|
196
|
+
// applyRestAttrs, not spreadAttrs — #2723: includes any `const x__alias
|
|
197
|
+
// = x` hop onto the rest/props binding (see `resolveRestSpreadNames`'s
|
|
198
|
+
// docstring in prop-handling.ts).
|
|
199
|
+
const restSpreadNames = resolveRestSpreadNames(ctx)
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
// Two distinct shapes share the `comment: true` (proxy-scoped) def flag,
|
|
202
|
+
// but need OPPOSITE runtime treatment of the def's own scope id
|
|
203
|
+
// (component.ts's `materializeComponent`, #2722):
|
|
204
|
+
// - `root.type === 'fragment'`: a genuine fragment root. Its rendered
|
|
205
|
+
// markup carries NO scope id of its own (SSR moves it into the
|
|
206
|
+
// wrapping `<!--bf-scope:-->` comment, `wrapWithScopeComment` in
|
|
207
|
+
// hono-adapter.ts) — CSR mount must generate one just the same, or
|
|
208
|
+
// every nested `renderChild()` call loses the parent-prefixed naming
|
|
209
|
+
// `_parentScopeId` provides and falls back to a random per-child id
|
|
210
|
+
// (#1627's fallback), diverging from SSR/hydrate.
|
|
211
|
+
// - `root.type === 'component'`: the render-prop / "root is a single
|
|
212
|
+
// child call" case (#2649). The child's OWN markup already carries
|
|
213
|
+
// ITS OWN real scope id — the wrapping comment marks a scope with no
|
|
214
|
+
// DOM presence of its own, and `materializeComponent` must leave
|
|
215
|
+
// `scopeId` null so it doesn't stamp over (or duplicate) the child's.
|
|
216
|
+
const isFragmentRoot = _ir.root.type === 'fragment' && !!(_ir.root as IRFragment).needsScopeComment
|
|
217
|
+
const isCommentScope = isFragmentRoot || _ir.root.type === 'component'
|
|
202
218
|
|
|
203
219
|
// Build ComponentDef object for hydrate()
|
|
204
220
|
const defParts: string[] = [`init: init${name}`]
|
|
@@ -232,6 +248,9 @@ export function emitRegistrationAndHydration(
|
|
|
232
248
|
if (isCommentScope) {
|
|
233
249
|
defParts.push('comment: true')
|
|
234
250
|
}
|
|
251
|
+
if (isFragmentRoot) {
|
|
252
|
+
defParts.push('fragmentRoot: true')
|
|
253
|
+
}
|
|
235
254
|
|
|
236
255
|
const registryKey = nameForRegistryRef(name)
|
|
237
256
|
// When the registry key was file-scoped (`Name__<8hex>`, for a
|
|
@@ -97,7 +97,7 @@ export function generateInitFunction(
|
|
|
97
97
|
// into the analyzer / IR construction stage and introduce a
|
|
98
98
|
// `PropRewritten<T>` brand type so missing the rewrite becomes a
|
|
99
99
|
// compile-time error. ---
|
|
100
|
-
let generatedCode = rewritePropsObjectRef(lines.join('\n'), ctx.propsObjectName)
|
|
100
|
+
let generatedCode = rewritePropsObjectRef(lines.join('\n'), ctx.propsObjectName, ctx.restPropsName)
|
|
101
101
|
generatedCode += '\n' + hydrateLine
|
|
102
102
|
|
|
103
103
|
// Substitute module-level declarations BEFORE import detection: a
|
|
@@ -400,7 +400,7 @@ function renderTemplateAttrPart(
|
|
|
400
400
|
attr: IRAttribute,
|
|
401
401
|
attrName: string,
|
|
402
402
|
wrap: (expr: string) => string,
|
|
403
|
-
restSpreadNames?:
|
|
403
|
+
restSpreadNames?: ReadonlySet<string>,
|
|
404
404
|
): string {
|
|
405
405
|
const v = attr.value
|
|
406
406
|
switch (v.kind) {
|
|
@@ -667,7 +667,7 @@ function stripLeafKeyAttr(ir: IRNode): IRNode {
|
|
|
667
667
|
*/
|
|
668
668
|
export function renderFlatMapClientBody(
|
|
669
669
|
cb: Pick<FlatMapCallback, 'segments'>,
|
|
670
|
-
restSpreadNames?:
|
|
670
|
+
restSpreadNames?: ReadonlySet<string>,
|
|
671
671
|
): string {
|
|
672
672
|
return renderPreamble(cb, {
|
|
673
673
|
textVariant: 'client',
|
|
@@ -699,7 +699,7 @@ export function flatMapCallbackHasKeyedLeaf(cb: Pick<FlatMapCallback, 'segments'
|
|
|
699
699
|
*/
|
|
700
700
|
export function renderFlatMapProjectionClientBody(
|
|
701
701
|
inner: Extract<IRNode, { type: 'loop' }>,
|
|
702
|
-
restSpreadNames?:
|
|
702
|
+
restSpreadNames?: ReadonlySet<string>,
|
|
703
703
|
): string {
|
|
704
704
|
const chained = applyLoopChain(inner)
|
|
705
705
|
const params = inner.index ? `(${inner.param}, ${inner.index})` : `(${inner.param})`
|
|
@@ -747,7 +747,7 @@ function escapeLeafTextExpressions(ir: IRNode): IRNode {
|
|
|
747
747
|
// docstring (`ir-to-client-js/utils.ts`) for why this stays outside #2482's
|
|
748
748
|
// migration (it's the client-JS-emitter twin of the Go adapter's
|
|
749
749
|
// `loopBindingStack`).
|
|
750
|
-
export function irToHtmlTemplate(node: IRNode, restSpreadNames?:
|
|
750
|
+
export function irToHtmlTemplate(node: IRNode, restSpreadNames?: ReadonlySet<string>, loopDepth = 0, loopParams?: ReadonlyArray<string | LoopParamSpec>, branchSlotsVar?: string, inHoistedChildren = false): string {
|
|
751
751
|
const recurse = (n: IRNode): string => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, inHoistedChildren)
|
|
752
752
|
const wrapExpr = (expr: string) => wrapExprWithLoopParams(expr, loopParams)
|
|
753
753
|
const wrapInterpolation = (expr: string): string => branchSlotsVar
|
|
@@ -1373,7 +1373,7 @@ function walkSkeletonPathChildren(
|
|
|
1373
1373
|
* elements (`<div data-bf-ph="sN"></div>`) instead of renderChild() calls.
|
|
1374
1374
|
* The placeholders are replaced with real createComponent() elements at runtime.
|
|
1375
1375
|
*/
|
|
1376
|
-
export function irToPlaceholderTemplate(node: IRNode, restSpreadNames?:
|
|
1376
|
+
export function irToPlaceholderTemplate(node: IRNode, restSpreadNames?: ReadonlySet<string>, loopDepth = 0, loopParams?: ReadonlyArray<string | LoopParamSpec>): string {
|
|
1377
1377
|
const recurse = (n: IRNode): string => irToPlaceholderTemplate(n, restSpreadNames, loopDepth, loopParams)
|
|
1378
1378
|
const wrapExpr = (expr: string) => wrapExprWithLoopParams(expr, loopParams)
|
|
1379
1379
|
|
|
@@ -1637,7 +1637,7 @@ function isSingleRootElement(html: string): boolean {
|
|
|
1637
1637
|
*/
|
|
1638
1638
|
export interface TemplateOptions {
|
|
1639
1639
|
inlinableConstants?: Map<string, string>
|
|
1640
|
-
restSpreadNames?:
|
|
1640
|
+
restSpreadNames?: ReadonlySet<string>
|
|
1641
1641
|
propsObjectName?: string | null
|
|
1642
1642
|
/**
|
|
1643
1643
|
* Names that exist only in the init-body scope (or were demoted to unsafe
|
|
@@ -1728,7 +1728,7 @@ export interface TemplateOptions {
|
|
|
1728
1728
|
export function irToComponentTemplate(
|
|
1729
1729
|
node: IRNode,
|
|
1730
1730
|
inlinableConstants?: Map<string, string>,
|
|
1731
|
-
restSpreadNames?:
|
|
1731
|
+
restSpreadNames?: ReadonlySet<string>,
|
|
1732
1732
|
propsObjectName?: string | null,
|
|
1733
1733
|
markupSlotIds?: ReadonlySet<string>
|
|
1734
1734
|
): string {
|
|
@@ -2133,7 +2133,7 @@ export function generateCsrTemplate(
|
|
|
2133
2133
|
node: IRNode,
|
|
2134
2134
|
inlinableConstants: Map<string, string> | undefined,
|
|
2135
2135
|
ctx: ClientJsContext,
|
|
2136
|
-
restSpreadNames?:
|
|
2136
|
+
restSpreadNames?: ReadonlySet<string>,
|
|
2137
2137
|
propsObjectName?: string | null,
|
|
2138
2138
|
unsafeLocalNames?: Set<string>,
|
|
2139
2139
|
deferredChildSlots?: ReadonlySet<string>,
|
|
@@ -11,6 +11,11 @@ import { identifierCallPattern } from '../identifier-pattern.ts'
|
|
|
11
11
|
export const RUNTIME_IMPORT_CANDIDATES = [
|
|
12
12
|
'createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount',
|
|
13
13
|
'hydrate', 'insert', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'mapArrayLazy', 'patchLeaf', 'createDisposableEffect',
|
|
14
|
+
// Resolves the real DOM container for a loop nested inside a loop-row
|
|
15
|
+
// conditional's branch when the conditional's wrapper element carries no
|
|
16
|
+
// `bf="<slot>"` marker of its own (#2705) — see `findCondContainer`'s
|
|
17
|
+
// docstring (runtime/insert.ts) for why the marker collector can't see it.
|
|
18
|
+
'findCondContainer',
|
|
14
19
|
'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild',
|
|
15
20
|
// Connects a template-clone loop row before the body's tail runs, so a child
|
|
16
21
|
// that inits inside it resolves context against real ancestors rather than
|
|
@@ -19,6 +19,7 @@ import { PROPS_PARAM } from './utils.ts'
|
|
|
19
19
|
import { buildInlinableConstants, csrInlinableConstantsFromCtx } from './emit-registration.ts'
|
|
20
20
|
import { buildEnvFromCtx } from './compute-inlinability.ts'
|
|
21
21
|
import { nameForRegistryRef } from './component-scope.ts'
|
|
22
|
+
import { resolveRestSpreadNames } from './prop-handling.ts'
|
|
22
23
|
import { IMPORT_PLACEHOLDER, RUNTIME_MODULE, detectUsedImports, collectExternalImports } from './imports.ts'
|
|
23
24
|
import { isInlinableInTemplate } from '../relocate.ts'
|
|
24
25
|
import { buildSourceMapFromIR, type SourceMapV3 } from './source-map.ts'
|
|
@@ -185,6 +186,10 @@ function createContext(
|
|
|
185
186
|
refElements: [],
|
|
186
187
|
childInits: [],
|
|
187
188
|
deferredChildSlots: new Set(),
|
|
189
|
+
// Mirrors `emit-registration.ts`'s `isCommentScope` component-root
|
|
190
|
+
// disjunct (not the fragment disjunct — that case is disambiguated at
|
|
191
|
+
// runtime by `commentScopeRegistry`, not by this slot-id shortcut).
|
|
192
|
+
commentScopeRootSlotId: ir.root.type === 'component' ? ir.root.slotId : null,
|
|
188
193
|
reactiveProps: [],
|
|
189
194
|
reactiveChildProps: [],
|
|
190
195
|
reactiveAttrs: [],
|
|
@@ -251,10 +256,11 @@ function generateTemplateOnlyMount(ir: ComponentIR, ctx: ClientJsContext): strin
|
|
|
251
256
|
const graph = buildReferencesGraph(ctx, ir.root)
|
|
252
257
|
const { inlinableConstants, unsafeLocalNames } = buildInlinableConstants(ctx, graph, ir.root)
|
|
253
258
|
|
|
254
|
-
// Build rest spread names: these are rest/props spreads handled by
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
259
|
+
// Build rest spread names: these are rest/props spreads handled by
|
|
260
|
+
// applyRestAttrs, not spreadAttrs — #2723: includes any `const x__alias
|
|
261
|
+
// = x` hop onto the rest/props binding (see `resolveRestSpreadNames`'s
|
|
262
|
+
// docstring in prop-handling.ts).
|
|
263
|
+
const restSpreadNames = resolveRestSpreadNames(ctx)
|
|
258
264
|
|
|
259
265
|
let templateHtml: string | undefined
|
|
260
266
|
|
|
@@ -39,7 +39,11 @@ export function emitProviderAndChildInits(lines: string[], ctx: ClientJsContext)
|
|
|
39
39
|
lines.push(` upsertChild(__scope, '${registryName}', '${child.slotId}', ${child.propsExpr})`)
|
|
40
40
|
continue
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
// The component's own `comment: true` root child IS `__scope` (no
|
|
43
|
+
// separate DOM node exists for it to be looked up at) — see
|
|
44
|
+
// `ClientJsContext.commentScopeRootSlotId`'s docstring (#2649).
|
|
45
|
+
const isCommentRoot = child.slotId !== null && child.slotId === ctx.commentScopeRootSlotId
|
|
46
|
+
const scopeRef = !child.slotId || isCommentRoot ? '__scope' : `_${varSlotId(child.slotId)}`
|
|
43
47
|
lines.push(` initChild('${registryName}', ${scopeRef}, ${child.propsExpr})`)
|
|
44
48
|
}
|
|
45
49
|
}
|
|
@@ -6,6 +6,100 @@ import type { ParamInfo, SignalInfo } from '../types.ts'
|
|
|
6
6
|
import type { ClientJsContext } from './types.ts'
|
|
7
7
|
import type { BindingScope } from '../scope/binding-scope.ts'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Which of the component's two "forwards the caller's leftover props"
|
|
11
|
+
* bindings `name` ultimately names — `'rest'` for `ctx.restPropsName`
|
|
12
|
+
* (the destructured `...rest` binding), `'props'` for `ctx.propsObjectName`
|
|
13
|
+
* (a whole undestructured `(props)` parameter spread whole), or `null` when
|
|
14
|
+
* `name` is neither, walking through any bare `const x__alias = <name>`
|
|
15
|
+
* alias chain to get there (#2723's `alias-props` mutation aliases every
|
|
16
|
+
* destructured binding, the rest parameter included, e.g.
|
|
17
|
+
* `const props__alias = props`).
|
|
18
|
+
*
|
|
19
|
+
* A `{...spread}` attribute is recognised as "forwards the caller's
|
|
20
|
+
* leftover props" (routed to the `applyRestAttrs` runtime helper /
|
|
21
|
+
* excluded from the SSR template's `spreadAttrs({...})` merge, and — only
|
|
22
|
+
* for the `'rest'` case — given the destructured prop names to exclude
|
|
23
|
+
* from what it forwards) by comparing its source expression against
|
|
24
|
+
* exactly `ctx.restPropsName` / `ctx.propsObjectName`. Without this
|
|
25
|
+
* resolver an alias hop makes that comparison fail even though the spread
|
|
26
|
+
* still forwards the SAME object — `collect-elements.ts` then never
|
|
27
|
+
* registers the rest-attrs application at all, and `html-template.ts`'s
|
|
28
|
+
* merge path stops filtering the spread out, folding it into a
|
|
29
|
+
* `spreadAttrs({...})` call keyed by the alias name instead of the
|
|
30
|
+
* runtime-visible one.
|
|
31
|
+
*
|
|
32
|
+
* Walks the chain hop by hop (not a precomputed set) so a multi-hop alias
|
|
33
|
+
* (`const p2 = props; const p3 = p2`) resolves through every link; `visited`
|
|
34
|
+
* guards a constant cycle (`const a = b; const b = a`) the same way
|
|
35
|
+
* `free-refs.ts`'s `resolveConstantInitializerRefs` does. A constant whose
|
|
36
|
+
* value isn't a bare identifier already on the chain (e.g. a real computed
|
|
37
|
+
* object) stops the walk, so a genuinely different spread expression is
|
|
38
|
+
* never mistaken for the rest object.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveRestSpreadOrigin(ctx: ClientJsContext, name: string): 'rest' | 'props' | null {
|
|
41
|
+
const byName = localConstantValues(ctx)
|
|
42
|
+
const visited = new Set<string>()
|
|
43
|
+
let current: string | undefined = name.trim()
|
|
44
|
+
while (current !== undefined && !visited.has(current)) {
|
|
45
|
+
if (ctx.restPropsName && current === ctx.restPropsName) return 'rest'
|
|
46
|
+
if (ctx.propsObjectName && current === ctx.propsObjectName) return 'props'
|
|
47
|
+
visited.add(current)
|
|
48
|
+
current = byName.get(current)?.trim()
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `ctx.localConstants` indexed by name, memoized per `ctx`.
|
|
55
|
+
*
|
|
56
|
+
* A `Map` rather than the `.find(` this file's other two constant lookups
|
|
57
|
+
* use, deliberately: those two are SHADOW-GUARDED lookups that
|
|
58
|
+
* `binding-scope-ratchet.test.ts` deliberately counts, and that ledger is
|
|
59
|
+
* shrink-only and at its floor. Resolving an alias chain hop-by-hop would
|
|
60
|
+
* have added a third counted use — and a hot one, since the walk queries
|
|
61
|
+
* once per hop — so it indexes instead, which is both outside the ledger's
|
|
62
|
+
* concern and cheaper than a linear scan per hop.
|
|
63
|
+
*/
|
|
64
|
+
const _localConstantValuesCache: WeakMap<ClientJsContext, ReadonlyMap<string, string | undefined>> = new WeakMap()
|
|
65
|
+
|
|
66
|
+
function localConstantValues(ctx: ClientJsContext): ReadonlyMap<string, string | undefined> {
|
|
67
|
+
const cached = _localConstantValuesCache.get(ctx)
|
|
68
|
+
if (cached) return cached
|
|
69
|
+
const byName = new Map<string, string | undefined>()
|
|
70
|
+
for (const constant of ctx.localConstants) {
|
|
71
|
+
if (!byName.has(constant.name)) byName.set(constant.name, constant.value)
|
|
72
|
+
}
|
|
73
|
+
_localConstantValuesCache.set(ctx, byName)
|
|
74
|
+
return byName
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Every name that resolves (via `resolveRestSpreadOrigin`) to either of the
|
|
79
|
+
* component's "forwards the caller's leftover props" bindings — used where
|
|
80
|
+
* callers need SET membership (`restSpreadNames?.has(...)` in
|
|
81
|
+
* `html-template.ts`) rather than a per-name resolution. Memoized per
|
|
82
|
+
* `ctx` (`WeakMap`, mirroring `free-refs.ts`'s `_bindingMapCache`) since
|
|
83
|
+
* some callers build this once per component and query it while walking
|
|
84
|
+
* the whole tree.
|
|
85
|
+
*/
|
|
86
|
+
const _restSpreadNamesCache: WeakMap<ClientJsContext, ReadonlySet<string>> = new WeakMap()
|
|
87
|
+
|
|
88
|
+
export function resolveRestSpreadNames(ctx: ClientJsContext): ReadonlySet<string> {
|
|
89
|
+
const cached = _restSpreadNamesCache.get(ctx)
|
|
90
|
+
if (cached) return cached
|
|
91
|
+
|
|
92
|
+
const names = new Set<string>()
|
|
93
|
+
if (ctx.restPropsName) names.add(ctx.restPropsName)
|
|
94
|
+
if (ctx.propsObjectName) names.add(ctx.propsObjectName)
|
|
95
|
+
for (const constant of ctx.localConstants) {
|
|
96
|
+
if (resolveRestSpreadOrigin(ctx, constant.name) !== null) names.add(constant.name)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_restSpreadNamesCache.set(ctx, names)
|
|
100
|
+
return names
|
|
101
|
+
}
|
|
102
|
+
|
|
9
103
|
/**
|
|
10
104
|
* Expand dynamic prop value by resolving local constants.
|
|
11
105
|
*
|
|
@@ -174,11 +174,30 @@ export function decideWrapForChildProp(
|
|
|
174
174
|
* The signal-getter and memo regexes (`\b<name>\s*\(`) still run against
|
|
175
175
|
* the raw string — those are call-shape patterns, not bare-identifier
|
|
176
176
|
* checks, and are outside the scope of #1267.
|
|
177
|
+
*
|
|
178
|
+
* A reference that only reaches a signal/memo/prop through an intervening
|
|
179
|
+
* `const x__alias = x` hop is walked via `ctx.localConstants` below (#2723)
|
|
180
|
+
* — see that block's own comment for why this lives here rather than on
|
|
181
|
+
* `BindingScope` or a new tracking structure.
|
|
177
182
|
*/
|
|
178
183
|
export function needsEffectWrapper(
|
|
179
184
|
expr: string,
|
|
180
185
|
ctx: ClientJsContext,
|
|
181
186
|
freeIdentifiers?: ReadonlySet<string>,
|
|
187
|
+
): boolean {
|
|
188
|
+
return needsEffectWrapperCore(expr, ctx, freeIdentifiers, new Set())
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* `needsEffectWrapper`'s actual body, with a `visitedConstants` accumulator
|
|
193
|
+
* threaded through the local-constant recursion below so a cycle
|
|
194
|
+
* (`const a = b; const b = a`) terminates instead of looping forever.
|
|
195
|
+
*/
|
|
196
|
+
function needsEffectWrapperCore(
|
|
197
|
+
expr: string,
|
|
198
|
+
ctx: ClientJsContext,
|
|
199
|
+
freeIdentifiers: ReadonlySet<string> | undefined,
|
|
200
|
+
visitedConstants: Set<string>,
|
|
182
201
|
): boolean {
|
|
183
202
|
for (const signal of ctx.signals) {
|
|
184
203
|
if (identifierCallPattern(signal.getter).test(expr)) {
|
|
@@ -209,6 +228,46 @@ export function needsEffectWrapper(
|
|
|
209
228
|
if (propsAccess.test(expr)) return true
|
|
210
229
|
}
|
|
211
230
|
|
|
231
|
+
// #2723: a bare `const x__alias = x` hop between a destructured prop and
|
|
232
|
+
// its use site breaks every check above — `expr` (or its precomputed
|
|
233
|
+
// `freeIdentifiers`) names the ALIAS, never the prop it stands for, so
|
|
234
|
+
// none of the direct prop/signal/memo checks fire even though the value
|
|
235
|
+
// is exactly as reactive as `x` itself. Phase 1's `isReactiveExpression`
|
|
236
|
+
// already sees through this (`isPropsReference` / `isSignalOrMemoReference`
|
|
237
|
+
// in jsx-to-ir.ts recursively walk `ctx.patterns.constants`, the
|
|
238
|
+
// `TransformContext` twin of this function's `ctx.localConstants`) —
|
|
239
|
+
// this is that SAME constant-chain walk, ported onto Phase 2's string
|
|
240
|
+
// expression so its independent wrap decision agrees with Phase 1's
|
|
241
|
+
// `hasReactiveAttributes` slotId decision instead of silently
|
|
242
|
+
// disagreeing on whether the attribute gets a `createEffect` at all.
|
|
243
|
+
//
|
|
244
|
+
// This is NOT loop/callback binding resolution — `BindingScope` (#2482)
|
|
245
|
+
// answers "what name is this loop row's own item/index/destructure
|
|
246
|
+
// binding," a question with no bearing on a component-body `const`
|
|
247
|
+
// aliasing a prop — so it does not belong there. It also isn't a new ad
|
|
248
|
+
// hoc tracking structure: `ctx.localConstants` is the existing per-
|
|
249
|
+
// component constant list this file already reads (see
|
|
250
|
+
// `expandConstantForReactivity` above), walked here with a `visited`
|
|
251
|
+
// guard for the same reason `free-refs.ts`'s `resolveConstantInitializerRefs`
|
|
252
|
+
// carries one — a constant cycle must terminate, not loop forever.
|
|
253
|
+
//
|
|
254
|
+
// Constants whose initializer contains an arrow/function expression are
|
|
255
|
+
// skipped, mirroring `resolveConstantInitializerRefs`'s `containsArrow`
|
|
256
|
+
// skip: refs inside a function body run when (and if) the function is
|
|
257
|
+
// invoked, not merely because something reads the bare function value.
|
|
258
|
+
for (const constant of ctx.localConstants) {
|
|
259
|
+
if (visitedConstants.has(constant.name)) continue
|
|
260
|
+
if (constant.value === undefined || constant.containsArrow) continue
|
|
261
|
+
const referenced = freeIdentifiers
|
|
262
|
+
? freeIdentifiers.has(constant.name)
|
|
263
|
+
: tokenContainsIdent(expr, constant.name)
|
|
264
|
+
if (!referenced) continue
|
|
265
|
+
visitedConstants.add(constant.name)
|
|
266
|
+
if (needsEffectWrapperCore(constant.value, ctx, constant.freeIdentifiers, visitedConstants)) {
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
212
271
|
return false
|
|
213
272
|
}
|
|
214
273
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AST-based rename of the source-level props object name (e.g. `props
|
|
3
|
-
*
|
|
4
|
-
* `_p` across the joined
|
|
2
|
+
* AST-based rename of the source-level props object name(s) (e.g. `props`,
|
|
3
|
+
* a user-supplied destructure name, or a destructured rest binding like
|
|
4
|
+
* `...rest`) → the generated parameter name `_p` across the joined
|
|
5
|
+
* init-body string.
|
|
5
6
|
*
|
|
6
7
|
* Replaces the pre-C2 regex hack `\\b<propsObjectName>\\b` which silently
|
|
7
8
|
* matched contexts that should NOT have been rewritten:
|
|
@@ -27,15 +28,54 @@ import { PROPS_PARAM } from './utils.ts'
|
|
|
27
28
|
import { identifierPattern } from '../identifier-pattern.ts'
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
* Rename every value-position reference to `propsObjectName`
|
|
31
|
-
* to `
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Rename every value-position reference to `propsObjectName` — and,
|
|
32
|
+
* independently, to `restPropsName` (#2723) — in `code` to `_p`.
|
|
33
|
+
*
|
|
34
|
+
* `restPropsName` matters even in destructured-prop mode (where
|
|
35
|
+
* `propsObjectName` is null and the analyzer already pre-rewrites bare
|
|
36
|
+
* NAMED-prop refs into `templateXxx` fields, per the historical
|
|
37
|
+
* `propsObjectName ?? 'props'` fallback this replaces): a destructured
|
|
38
|
+
* rest binding (`const { className, ...rest } = props` /
|
|
39
|
+
* `function F({ className, ...rest })`) is not itself a "named prop," so
|
|
40
|
+
* nothing pre-rewrites a bare reference to it. Such a reference reaches
|
|
41
|
+
* this pass whenever the init body needs `rest`'s OWN VALUE rather than
|
|
42
|
+
* just recognising a `{...rest}` spread by name — e.g. a `const
|
|
43
|
+
* rest__alias = rest` hop (#2723's `alias-props` mutation aliases the
|
|
44
|
+
* rest parameter along with every named one). A rest binding named
|
|
45
|
+
* anything but "props" (`...rest`, `...leftover`) left such a reference
|
|
46
|
+
* dangling as a `ReferenceError`, so the analyzer's actual
|
|
47
|
+
* `restPropsName` is passed in to remove that guesswork.
|
|
48
|
+
*
|
|
49
|
+
* The `propsObjectName ?? 'props'` fallback is KEPT alongside it, not
|
|
50
|
+
* replaced by it. The two do different jobs, and reading the fallback as
|
|
51
|
+
* merely a lucky guess at the rest binding's name regressed the
|
|
52
|
+
* doc-example `StatementExample`: a component that destructures its
|
|
53
|
+
* parameters can still write `props.itemId` inside a handler body, and
|
|
54
|
+
* `propsObjectName` is null for exactly that shape — so dropping the
|
|
55
|
+
* fallback left `props.itemId` in the emitted init, where no `props`
|
|
56
|
+
* binding exists to satisfy it.
|
|
57
|
+
*
|
|
58
|
+
* Each candidate name is rewritten independently and skipped when null,
|
|
59
|
+
* already `_p`, or a duplicate of one already processed (a component
|
|
60
|
+
* whose props param IS its own rest destructure target, if that shape
|
|
61
|
+
* ever arises, would otherwise walk the AST twice for the same name).
|
|
34
62
|
*/
|
|
35
|
-
export function rewritePropsObjectRef(
|
|
36
|
-
|
|
37
|
-
|
|
63
|
+
export function rewritePropsObjectRef(
|
|
64
|
+
code: string,
|
|
65
|
+
propsObjectName: string | null,
|
|
66
|
+
restPropsName: string | null = null,
|
|
67
|
+
): string {
|
|
68
|
+
let result = code
|
|
69
|
+
const seen = new Set<string>()
|
|
70
|
+
for (const srcPropsName of [propsObjectName ?? 'props', restPropsName]) {
|
|
71
|
+
if (srcPropsName === null || srcPropsName === PROPS_PARAM || seen.has(srcPropsName)) continue
|
|
72
|
+
seen.add(srcPropsName)
|
|
73
|
+
result = rewriteOneName(result, srcPropsName)
|
|
74
|
+
}
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
38
77
|
|
|
78
|
+
function rewriteOneName(code: string, srcPropsName: string): string {
|
|
39
79
|
// Quick exit when the name doesn't appear at all.
|
|
40
80
|
if (!identifierPattern(srcPropsName).test(code)) return code
|
|
41
81
|
|
|
@@ -102,6 +102,27 @@ export interface ClientJsContext {
|
|
|
102
102
|
* registration-template emit so both agree on which children defer.
|
|
103
103
|
*/
|
|
104
104
|
deferredChildSlots: Set<string>
|
|
105
|
+
/**
|
|
106
|
+
* Slot id of the component's own root, when the ENTIRE render is a single
|
|
107
|
+
* child component call (`ir.root.type === 'component'` — the IR shape is
|
|
108
|
+
* the sole source of truth; `emit-registration.ts`'s `isCommentScope`
|
|
109
|
+
* derives the def's `comment: true` from the same shape). Such a child
|
|
110
|
+
* never gets its own DOM node: `materializeComponent`/`renderChild` leave
|
|
111
|
+
* `bf-s` unset and the child's markup becomes `__scope` itself (#2649).
|
|
112
|
+
* A generic `$c(__scope, slotId)` lookup for this one slot is therefore
|
|
113
|
+
* not just redundant but actively ambiguous — a genuine grandchild
|
|
114
|
+
* nested inside it can derive a `bf-s` whose suffix collides with this
|
|
115
|
+
* same slot id (see `component.ts`'s `_parentScopeId` push), and
|
|
116
|
+
* `$cSingle` cannot tell "I already am this slot" apart from "a
|
|
117
|
+
* coincidentally-matching descendant is this slot" from the DOM alone.
|
|
118
|
+
* Emission sites that would otherwise query this slot via `$c`
|
|
119
|
+
* (`element-refs.ts`, `provider-and-child-inits.ts`,
|
|
120
|
+
* `emit-reactive.ts`) use `__scope` directly instead, sidestepping the
|
|
121
|
+
* ambiguity entirely rather than trying to make the query precise
|
|
122
|
+
* enough to resolve it. `null` for a component whose root is a regular
|
|
123
|
+
* element/fragment (the overwhelmingly common case).
|
|
124
|
+
*/
|
|
125
|
+
commentScopeRootSlotId: string | null
|
|
105
126
|
reactiveProps: ReactiveComponentProp[]
|
|
106
127
|
reactiveChildProps: ReactiveChildProp[]
|
|
107
128
|
reactiveAttrs: ReactiveAttribute[]
|