From 1a54f7952f325fc384130fba9c2bc830b594d12c Mon Sep 17 00:00:00 2001 From: Hadi Al Marzooq <79160189+HadiAlMarzooq@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:40:04 +0300 Subject: [PATCH 1/3] refactor(forLoop): optimize for loop code generation by storing collection in a variable --- src/lib/codegenerator/generator.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index 2390137f..a83f3270 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -618,7 +618,8 @@ const generateForLoopCode = function (templateObject, parent) { ctx.renderCode.push(` let eff${forStartCounter} = () => { - forloops[${forStartCounter}](${cast(result[2], ':for')}, elms, created[${forStartCounter}]) + const collection = ${cast(result[2], ':for')} + forloops[${forStartCounter}](collection, elms, created[${forStartCounter}]) } component[Symbol.for('effects')].push(eff${forStartCounter}) From 9498b224153482be4a089579ebb3540c8b2a8387 Mon Sep 17 00:00:00 2001 From: Hadi Al Marzooq <79160189+HadiAlMarzooq@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:40:18 +0300 Subject: [PATCH 2/3] test: add verification for reactivity in for-loop with empty array handling --- src/lib/codegenerator/generator.test.js | 57 ++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/src/lib/codegenerator/generator.test.js b/src/lib/codegenerator/generator.test.js index e6a04bb3..115aed9c 100644 --- a/src/lib/codegenerator/generator.test.js +++ b/src/lib/codegenerator/generator.test.js @@ -3284,7 +3284,8 @@ test('Generate code for a template with a simple for-loop on an Element', (asser } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3439,7 +3440,8 @@ test('Generate code for a template with a simple for-loop on an Element, Using d } let eff1 = () => { - forloops[1](component.$appState.list, elms, created[1]) + const collection = component.$appState.list + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3594,7 +3596,8 @@ test('Generate code for a template with a simple for-loop on an Element, Using d } let eff1 = () => { - forloops[1](component.content.data, elms, created[1]) + const collection = component.content.data + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3747,7 +3750,8 @@ test('Generate code for a template with a simple for-loop on an Element with a c } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -3900,7 +3904,8 @@ test('Generate code for a template with a simple for-loop on an Element with a k } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -4090,7 +4095,8 @@ test('Generate code for a template with a simple for-loop on a Component with a } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -4254,7 +4260,8 @@ test('Generate code for a template with a simple for-loop on an Element with an } let eff1 = () => { - forloops[1](component.items, elms, created[1]) + const collection = component.items + forloops[1](collection, elms, created[1]) } component[Symbol.for('effects')].push(eff1) @@ -4307,6 +4314,42 @@ test('Generate code for a template with a simple for-loop on an Element with an assert.end() }) +test('Generate code for for-loop with empty array that gets populated - verifies reactivity fix', (assert) => { + const templateObject = { + children: [ + { + [Symbol.for('componentType')]: 'Element', + children: [ + { + [Symbol.for('componentType')]: 'Element', + ':for': 'item in $items', + }, + ], + }, + ], + } + + const generated = generator.call(scope, templateObject) + const renderCode = generated.render.toString() + + assert.ok( + renderCode.includes('const collection = component.items'), + 'Effect should store array in variable to ensure reactive tracking when array changes from empty to populated' + ) + + assert.ok( + renderCode.includes('effect(eff1') && renderCode.includes("'items'"), + 'Effect should be registered with items key for tracking' + ) + + assert.ok( + renderCode.includes('forloops[1](collection, elms, created[1])'), + 'Forloop should use the collection variable to ensure reactive property access is tracked' + ) + + assert.end() +}) + test('Generate code for a template with double $$ (i.e. referencing a Blits plugin)', (assert) => { const templateObject = { children: [ From a16384c222a8fad68c464cfbdef0e7e43108c70c Mon Sep 17 00:00:00 2001 From: Hadi Al Marzooq <79160189+HadiAlMarzooq@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:21:53 +0300 Subject: [PATCH 3/3] fix(generator): enhance effect tracking in for loop by accessing underlying properties --- src/lib/codegenerator/generator.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js index a83f3270..ae922794 100644 --- a/src/lib/codegenerator/generator.js +++ b/src/lib/codegenerator/generator.js @@ -612,19 +612,27 @@ const generateForLoopCode = function (templateObject, parent) { effectKey = effectKey.match(/[^.]+$/)[0] } - // get the reference to range from and to - const effectKeysRegex = /\$([^,} ]+)/g - const effectKeys = [...range.matchAll(effectKeysRegex)].map((match) => `'${match[1]}'`) + const collectionExpr = result[2].trim() + const propName = collectionExpr.replace(/^\$/, '').split('.')[0] ctx.renderCode.push(` let eff${forStartCounter} = () => { + // Access underlying prop/state if this is a direct property to ensure tracking + if (component[Symbol.for('propKeys')] && component[Symbol.for('propKeys')].indexOf('${propName}') !== -1) { + void component[Symbol.for('props')]['${propName}'] + } else if (component[Symbol.for('stateKeys')] && component[Symbol.for('stateKeys')].indexOf('${propName}') !== -1) { + void component[Symbol.for('state')]['${propName}'] + } + const collection = ${cast(result[2], ':for')} forloops[${forStartCounter}](collection, elms, created[${forStartCounter}]) } component[Symbol.for('effects')].push(eff${forStartCounter}) - effect(eff${forStartCounter}, ['${effectKey}', ${effectKeys.join(',')}]) + // Use null as currentKey to track all property accesses during effect execution. + // This ensures computed property dependencies are tracked when accessing underlying reactive properties. + effect(eff${forStartCounter}, null) `) ctx.cleanupCode.push(`