Skip to content

Commit 47f232d

Browse files
matej21claude
andcommitted
fix(repeater): use factory pattern for getSelection to survive Vite DCE
Vite dep optimizer sets moduleSideEffects: false for non-entry modules, causing Rolldown to strip module-level side effects like property assignments on exported objects. Wrap the getSelection assignment in a factory function (createBlockRepeaterWithSelection) called at module level — the factory return value is assigned to an exported binding, so Rolldown must preserve the entire function body including the render/form collection. Also move render/form calls out of mockItems.map callback (which is never called by NPI's children pattern) into the getSelection body. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d38bf4f commit 47f232d

1 file changed

Lines changed: 22 additions & 16 deletions

File tree

packages/bindx-repeater/src/components/BlockRepeater.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,16 @@ export function BlockRepeater<
201201
return <>{children(items, methods)}</>
202202
}
203203

204-
// Static method for selection extraction
205-
const blockRepeaterWithSelection = BlockRepeater as typeof BlockRepeater & SelectionProvider & { [BINDX_COMPONENT]: true }
204+
// Wrap BlockRepeater with selection extraction inside a factory function
205+
// so the property assignments are not module-level side effects
206+
// (Vite dep optimizer marks module-level side effects for DCE).
207+
function createBlockRepeaterWithSelection() {
208+
const component = BlockRepeater as typeof BlockRepeater & SelectionProvider & { [BINDX_COMPONENT]: true }
206209

207-
blockRepeaterWithSelection.getSelection = (
208-
props: BlockRepeaterProps<unknown>,
209-
collectNested: (children: ReactNode) => SelectionMeta,
210-
): SelectionFieldMeta | null => {
210+
component.getSelection = (
211+
props: BlockRepeaterProps<unknown>,
212+
collectNested: (children: ReactNode) => SelectionMeta,
213+
): SelectionFieldMeta | null => {
211214
// Check if the field is a collector proxy with a scope reference (collection phase).
212215
// When present, we merge the collected selection directly into the scope tree,
213216
// which correctly handles deeply nested relations (e.g., page.blocks.items).
@@ -218,8 +221,6 @@ blockRepeaterWithSelection.getSelection = (
218221
const scope = new SelectionScope()
219222
const collectorEntity = createCollectorProxy<unknown>(scope)
220223

221-
const blockJsx: ReactNode[] = []
222-
223224
const mockItems: BlockRepeaterItems<unknown> = {
224225
map: (fn) => {
225226
fn(collectorEntity, {
@@ -232,13 +233,6 @@ blockRepeaterWithSelection.getSelection = (
232233
blockType: null,
233234
block: undefined,
234235
})
235-
// Call block render/form functions inside the map callback so the
236-
// collector proxy records field accesses. This code must execute inside
237-
// a callback passed through props.children() to survive Rolldown DCE.
238-
for (const blockDef of Object.values(props.blocks) as BlockDefinition[]) {
239-
if (blockDef.render) blockJsx.push(blockDef.render(collectorEntity as EntityAccessor<object>))
240-
if (blockDef.form) blockJsx.push(blockDef.form(collectorEntity as EntityAccessor<object>))
241-
}
242236
return []
243237
},
244238
length: 0,
@@ -251,6 +245,14 @@ blockRepeaterWithSelection.getSelection = (
251245
}
252246

253247
const syntheticChildren = props.children(mockItems, mockMethods)
248+
249+
// Call block render/form functions so the collector proxy records field accesses
250+
const blockJsx: ReactNode[] = []
251+
for (const blockDef of Object.values(props.blocks) as BlockDefinition[]) {
252+
if (blockDef.render) blockJsx.push(blockDef.render(collectorEntity as EntityAccessor<object>))
253+
if (blockDef.form) blockJsx.push(blockDef.form(collectorEntity as EntityAccessor<object>))
254+
}
255+
254256
const jsxSelection = collectNested([syntheticChildren, ...blockJsx])
255257

256258
const nestedSelection = scope.toSelectionMeta()
@@ -295,6 +297,10 @@ blockRepeaterWithSelection.getSelection = (
295297
}
296298
}
297299

298-
blockRepeaterWithSelection[BINDX_COMPONENT] = true
300+
component[BINDX_COMPONENT] = true
301+
return component
302+
}
303+
304+
const blockRepeaterWithSelection = createBlockRepeaterWithSelection()
299305

300306
export { blockRepeaterWithSelection as BlockRepeaterWithMeta }

0 commit comments

Comments
 (0)