roqa 0.0.3 → 0.0.5
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/CHANGELOG.md +20 -0
- package/package.json +76 -76
- package/src/compiler/transforms/inline-get.js +159 -48
- package/src/runtime/for-block.js +31 -20
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.5] - 2026-03-31
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fixed a compiler bug where cleanup-captured `bind()` calls inside `<For>` and `<Show>` blocks could be fully inlined to ref assignments while leaving dangling `_cleanup_N()` calls in generated cleanup functions
|
|
13
|
+
- The inliner now tracks cleanup variable names for removable `bind()` subscriptions and removes stale cleanup calls when their underlying subscription has been optimized away
|
|
14
|
+
- Cleanup properties are omitted entirely when all generated cleanup calls were eliminated during inlining
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Restored the js benchmark example to the faster per-row selection strategy so compiled output uses direct row refs instead of per-row subscriptions to a shared selection cell
|
|
19
|
+
|
|
20
|
+
## [0.0.4] - 2026-01-10
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed `set()` calls not notifying subscribers when using cleanup-captured `bind()` calls inside `<For>` and `<Show>` blocks
|
|
25
|
+
- The `findBindCallbacks` function in the inliner now correctly detects `bind()` calls in variable declarations (`const _cleanup_N = bind(...)`) in addition to expression statements
|
|
26
|
+
- This ensures the effect loop is generated for cells with non-inlined bind callbacks
|
|
27
|
+
|
|
8
28
|
## [0.0.3] - 2026-01-10
|
|
9
29
|
|
|
10
30
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
2
|
+
"name": "roqa",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "Roqa is a reactive UI framework",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"UI",
|
|
7
|
+
"framework",
|
|
8
|
+
"roqa",
|
|
9
|
+
"roqajs",
|
|
10
|
+
"web components",
|
|
11
|
+
"custom elements",
|
|
12
|
+
"jsx",
|
|
13
|
+
"compiler"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://roqa.dev",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/roqajs/roqa/issues"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Hawk Ticehurst",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/roqajs/roqa.git",
|
|
25
|
+
"directory": "packages/roqa"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"types",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./types/index.d.ts",
|
|
38
|
+
"import": "./src/runtime/index.js",
|
|
39
|
+
"browser": "./src/runtime/index.js",
|
|
40
|
+
"default": "./src/runtime/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json",
|
|
43
|
+
"./compiler": {
|
|
44
|
+
"types": "./types/compiler.d.ts",
|
|
45
|
+
"import": "./src/compiler/index.js",
|
|
46
|
+
"default": "./src/compiler/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./jsx-runtime": {
|
|
49
|
+
"types": "./src/jsx-runtime.d.ts",
|
|
50
|
+
"import": "./src/jsx-runtime.js",
|
|
51
|
+
"default": "./src/jsx-runtime.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test:unit": "vitest run --project unit",
|
|
58
|
+
"test:browser": "vitest run --project browser",
|
|
59
|
+
"test:coverage": "vitest run --coverage"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@babel/generator": "catalog:default",
|
|
63
|
+
"@babel/parser": "catalog:default",
|
|
64
|
+
"@babel/traverse": "catalog:default",
|
|
65
|
+
"@babel/types": "catalog:default",
|
|
66
|
+
"magic-string": "catalog:default"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@vitest/browser": "^3.0.0",
|
|
70
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
71
|
+
"playwright": "^1.49.0",
|
|
72
|
+
"vitest": "^3.0.0"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=20.0.0"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -458,66 +458,90 @@ function findBlockInfo(ast, code) {
|
|
|
458
458
|
* Returns a map from cell code -> array of callback info
|
|
459
459
|
*/
|
|
460
460
|
function findBindCallbacks(ast, code) {
|
|
461
|
-
// Map: cellCode -> [{ callbackBody, elementVars, refNum, paramName, statementStart, statementEnd, hasClosureVars }]
|
|
461
|
+
// Map: cellCode -> [{ callbackBody, elementVars, refNum, paramName, statementStart, statementEnd, hasClosureVars, cleanupVarName }]
|
|
462
462
|
const bindCallbacks = new Map();
|
|
463
463
|
// Track ref numbers per cell
|
|
464
464
|
const refCounters = new Map();
|
|
465
465
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
/**
|
|
467
|
+
* Process a bind() call expression and add to bindCallbacks
|
|
468
|
+
* @param {object} bindExpr - The bind() CallExpression node
|
|
469
|
+
* @param {number} stmtStart - Start position of containing statement
|
|
470
|
+
* @param {number} stmtEnd - End position of containing statement
|
|
471
|
+
*/
|
|
472
|
+
function processBindCall(bindExpr, stmtStart, stmtEnd, cleanupVarName = null) {
|
|
473
|
+
const cellArg = bindExpr.arguments[0];
|
|
474
|
+
const callbackArg = bindExpr.arguments[1];
|
|
475
|
+
if (!cellArg || !callbackArg) return;
|
|
470
476
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
477
|
+
// Get callback info
|
|
478
|
+
if (
|
|
479
|
+
callbackArg.type !== "ArrowFunctionExpression" &&
|
|
480
|
+
callbackArg.type !== "FunctionExpression"
|
|
481
|
+
) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
474
484
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
callbackArg.type !== "ArrowFunctionExpression" &&
|
|
478
|
-
callbackArg.type !== "FunctionExpression"
|
|
479
|
-
) {
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
485
|
+
const cellCode = code.slice(cellArg.start, cellArg.end);
|
|
486
|
+
const paramName = callbackArg.params[0]?.name || "v";
|
|
482
487
|
|
|
483
|
-
|
|
484
|
-
|
|
488
|
+
// Get the callback body
|
|
489
|
+
const body = callbackArg.body;
|
|
490
|
+
let bodyCode;
|
|
491
|
+
if (body.type === "BlockStatement") {
|
|
492
|
+
// Extract statements from block, removing braces
|
|
493
|
+
bodyCode = code.slice(body.start + 1, body.end - 1).trim();
|
|
494
|
+
} else {
|
|
495
|
+
// Expression body
|
|
496
|
+
bodyCode = code.slice(body.start, body.end);
|
|
497
|
+
}
|
|
485
498
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
// Extract statements from block, removing braces
|
|
491
|
-
bodyCode = code.slice(body.start + 1, body.end - 1).trim();
|
|
492
|
-
} else {
|
|
493
|
-
// Expression body
|
|
494
|
-
bodyCode = code.slice(body.start, body.end);
|
|
495
|
-
}
|
|
499
|
+
// Find element variables used in the callback (e.g., p_1_text, tr_1)
|
|
500
|
+
// Also detect closure variables that would prevent inlining
|
|
501
|
+
const { elementVars, closureVars } = findElementVariables(body, code, paramName, cellCode);
|
|
502
|
+
const hasClosureVars = closureVars.size > 0;
|
|
496
503
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
504
|
+
// Get or create ref number for this cell
|
|
505
|
+
const currentRef = refCounters.get(cellCode) || 0;
|
|
506
|
+
const refNum = currentRef + 1;
|
|
507
|
+
refCounters.set(cellCode, refNum);
|
|
501
508
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
509
|
+
// Store bind callback info
|
|
510
|
+
if (!bindCallbacks.has(cellCode)) {
|
|
511
|
+
bindCallbacks.set(cellCode, []);
|
|
512
|
+
}
|
|
506
513
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
514
|
+
bindCallbacks.get(cellCode).push({
|
|
515
|
+
callbackBody: bodyCode,
|
|
516
|
+
elementVars,
|
|
517
|
+
refNum,
|
|
518
|
+
paramName,
|
|
519
|
+
statementStart: stmtStart,
|
|
520
|
+
statementEnd: stmtEnd,
|
|
521
|
+
hasClosureVars,
|
|
522
|
+
cleanupVarName,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
511
525
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
526
|
+
traverse(ast, {
|
|
527
|
+
// Handle: bind(cell, callback);
|
|
528
|
+
ExpressionStatement(path) {
|
|
529
|
+
const expr = path.node.expression;
|
|
530
|
+
if (!isBindCall(expr)) return;
|
|
531
|
+
processBindCall(expr, path.node.start, path.node.end);
|
|
532
|
+
},
|
|
533
|
+
// Handle: const _cleanup_N = bind(cell, callback);
|
|
534
|
+
VariableDeclaration(path) {
|
|
535
|
+
for (const decl of path.node.declarations) {
|
|
536
|
+
if (decl.init && isBindCall(decl.init)) {
|
|
537
|
+
processBindCall(
|
|
538
|
+
decl.init,
|
|
539
|
+
path.node.start,
|
|
540
|
+
path.node.end,
|
|
541
|
+
decl.id.type === "Identifier" ? decl.id.name : null,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
521
545
|
},
|
|
522
546
|
noScope: true,
|
|
523
547
|
});
|
|
@@ -525,6 +549,35 @@ function findBindCallbacks(ast, code) {
|
|
|
525
549
|
return bindCallbacks;
|
|
526
550
|
}
|
|
527
551
|
|
|
552
|
+
function isCleanupCallStatement(node, cleanupVarNames) {
|
|
553
|
+
return (
|
|
554
|
+
node?.type === "ExpressionStatement" &&
|
|
555
|
+
node.expression?.type === "CallExpression" &&
|
|
556
|
+
node.expression.callee?.type === "Identifier" &&
|
|
557
|
+
cleanupVarNames.has(node.expression.callee.name) &&
|
|
558
|
+
node.expression.arguments.length === 0
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function removeObjectProperty(s, code, propertyNode) {
|
|
563
|
+
let start = propertyNode.start;
|
|
564
|
+
let end = propertyNode.end;
|
|
565
|
+
|
|
566
|
+
let left = start - 1;
|
|
567
|
+
while (left >= 0 && /\s/.test(code[left])) left--;
|
|
568
|
+
if (left >= 0 && code[left] === ",") {
|
|
569
|
+
start = left;
|
|
570
|
+
} else {
|
|
571
|
+
let right = end;
|
|
572
|
+
while (right < code.length && /\s/.test(code[right])) right++;
|
|
573
|
+
if (right < code.length && code[right] === ",") {
|
|
574
|
+
end = right + 1;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
s.remove(start, end);
|
|
579
|
+
}
|
|
580
|
+
|
|
528
581
|
/**
|
|
529
582
|
* Find element variables used in a callback body
|
|
530
583
|
* Looks for element.property = ... patterns
|
|
@@ -722,6 +775,7 @@ export function inlineGetCalls(code, filename) {
|
|
|
722
775
|
|
|
723
776
|
// Track bind statements to remove
|
|
724
777
|
const bindStatementsToRemove = [];
|
|
778
|
+
const inlinedCleanupVars = new Set();
|
|
725
779
|
|
|
726
780
|
// Track roqa imports for removal
|
|
727
781
|
const importsToRemove = [];
|
|
@@ -1090,6 +1144,9 @@ export function inlineGetCalls(code, filename) {
|
|
|
1090
1144
|
|
|
1091
1145
|
// Replace bind() call with ref assignment(s)
|
|
1092
1146
|
if (refAssignment) {
|
|
1147
|
+
if (callback.cleanupVarName) {
|
|
1148
|
+
inlinedCleanupVars.add(callback.cleanupVarName);
|
|
1149
|
+
}
|
|
1093
1150
|
s.overwrite(start, end, refAssignment);
|
|
1094
1151
|
} else {
|
|
1095
1152
|
// No element vars found - bind() can't be fully inlined
|
|
@@ -1099,6 +1156,60 @@ export function inlineGetCalls(code, filename) {
|
|
|
1099
1156
|
}
|
|
1100
1157
|
}
|
|
1101
1158
|
|
|
1159
|
+
// Remove cleanup calls for bind() subscriptions that were fully inlined away.
|
|
1160
|
+
// If a cleanup block only contains now-removed cleanup calls, drop the cleanup
|
|
1161
|
+
// property entirely so runtime list items don't carry no-op cleanup functions.
|
|
1162
|
+
if (inlinedCleanupVars.size > 0) {
|
|
1163
|
+
const cleanupStatementsToRemove = [];
|
|
1164
|
+
const cleanupPropertiesToRemove = [];
|
|
1165
|
+
|
|
1166
|
+
traverse(ast, {
|
|
1167
|
+
ObjectProperty(path) {
|
|
1168
|
+
const node = path.node;
|
|
1169
|
+
const key = node.key;
|
|
1170
|
+
const isCleanupProperty =
|
|
1171
|
+
(key?.type === "Identifier" && key.name === "cleanup") ||
|
|
1172
|
+
(key?.type === "StringLiteral" && key.value === "cleanup");
|
|
1173
|
+
if (!isCleanupProperty) return;
|
|
1174
|
+
|
|
1175
|
+
const fn = node.value;
|
|
1176
|
+
if (
|
|
1177
|
+
!fn ||
|
|
1178
|
+
(fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression") ||
|
|
1179
|
+
fn.body?.type !== "BlockStatement"
|
|
1180
|
+
) {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const statements = fn.body.body;
|
|
1185
|
+
if (statements.length === 0) return;
|
|
1186
|
+
|
|
1187
|
+
const removableStatements = statements.filter((stmt) =>
|
|
1188
|
+
isCleanupCallStatement(stmt, inlinedCleanupVars),
|
|
1189
|
+
);
|
|
1190
|
+
if (removableStatements.length === 0) return;
|
|
1191
|
+
|
|
1192
|
+
if (removableStatements.length === statements.length) {
|
|
1193
|
+
cleanupPropertiesToRemove.push(node);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
cleanupStatementsToRemove.push(...removableStatements);
|
|
1198
|
+
},
|
|
1199
|
+
noScope: true,
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
cleanupStatementsToRemove.sort((a, b) => b.start - a.start);
|
|
1203
|
+
for (const stmt of cleanupStatementsToRemove) {
|
|
1204
|
+
s.remove(stmt.start, stmt.end);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
cleanupPropertiesToRemove.sort((a, b) => b.start - a.start);
|
|
1208
|
+
for (const property of cleanupPropertiesToRemove) {
|
|
1209
|
+
removeObjectProperty(s, code, property);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1102
1213
|
// Remove imports that are no longer needed
|
|
1103
1214
|
// Collect all imports to remove first
|
|
1104
1215
|
// Only remove bind import if ALL bind calls were fully inlined (had element vars and no closure vars)
|
package/src/runtime/for-block.js
CHANGED
|
@@ -66,10 +66,13 @@ function lisAlgorithm(arr) {
|
|
|
66
66
|
* @param {*} value - The data item
|
|
67
67
|
* @param {number} index - Array index
|
|
68
68
|
* @param {Function} renderFn - (anchor, value, index) => { start, end } or just appends nodes
|
|
69
|
+
* @param {Object} forState - The for loop state (to track cleanup count)
|
|
69
70
|
*/
|
|
70
|
-
function createItem(anchor, value, index, renderFn) {
|
|
71
|
+
function createItem(anchor, value, index, renderFn, forState) {
|
|
71
72
|
// renderFn should return { start, end } nodes for the item
|
|
72
73
|
const item = renderFn(anchor, value, index);
|
|
74
|
+
// Track if this item has cleanup for fast-path optimization
|
|
75
|
+
if (item.cleanup) forState.cleanupCount++;
|
|
73
76
|
return {
|
|
74
77
|
s: item, // state: { start, end } - the DOM range for this item
|
|
75
78
|
v: value,
|
|
@@ -101,14 +104,18 @@ function moveItem(item, anchor) {
|
|
|
101
104
|
|
|
102
105
|
/**
|
|
103
106
|
* Destroy an item's DOM nodes and run cleanup if present
|
|
107
|
+
* @param {Object} forState - The for loop state (to track cleanup count)
|
|
104
108
|
*/
|
|
105
|
-
function destroyItem(item) {
|
|
109
|
+
function destroyItem(item, forState) {
|
|
106
110
|
const state = item.s;
|
|
107
111
|
let node = state.start;
|
|
108
112
|
const end = state.end;
|
|
109
113
|
|
|
110
114
|
// Run cleanup function if the render provided one
|
|
111
|
-
if (state.cleanup)
|
|
115
|
+
if (state.cleanup) {
|
|
116
|
+
state.cleanup();
|
|
117
|
+
forState.cleanupCount--;
|
|
118
|
+
}
|
|
112
119
|
|
|
113
120
|
while (node !== null) {
|
|
114
121
|
const next = node.nextSibling;
|
|
@@ -122,11 +129,14 @@ function destroyItem(item) {
|
|
|
122
129
|
* Fast path: clear all items when going from non-empty to empty
|
|
123
130
|
*/
|
|
124
131
|
function reconcileFastClear(anchor, forState, array) {
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
// Only run cleanup loop if there are items with cleanup functions
|
|
133
|
+
if (forState.cleanupCount > 0) {
|
|
134
|
+
const items = forState.items;
|
|
135
|
+
for (let i = 0; i < items.length; i++) {
|
|
136
|
+
const state = items[i].s;
|
|
137
|
+
if (state.cleanup) state.cleanup();
|
|
138
|
+
}
|
|
139
|
+
forState.cleanupCount = 0;
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
const parent_node = anchor.parentNode;
|
|
@@ -161,7 +171,7 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
161
171
|
// Empty -> non-empty: create all
|
|
162
172
|
if (aLen === 0) {
|
|
163
173
|
for (; j < bLen; j++) {
|
|
164
|
-
bItems[j] = createItem(anchor, b[j], j, renderFn);
|
|
174
|
+
bItems[j] = createItem(anchor, b[j], j, renderFn, forState);
|
|
165
175
|
}
|
|
166
176
|
forState.array = b;
|
|
167
177
|
forState.items = bItems;
|
|
@@ -205,14 +215,14 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
205
215
|
while (j <= bEnd) {
|
|
206
216
|
bVal = b[j];
|
|
207
217
|
target = j >= aLen ? anchor : aItems[j].s.start;
|
|
208
|
-
bItems[j] = createItem(target, bVal, j, renderFn);
|
|
218
|
+
bItems[j] = createItem(target, bVal, j, renderFn, forState);
|
|
209
219
|
j++;
|
|
210
220
|
}
|
|
211
221
|
}
|
|
212
222
|
} else if (j > bEnd) {
|
|
213
223
|
// Only removals
|
|
214
224
|
while (j <= aEnd) {
|
|
215
|
-
destroyItem(aItems[j++]);
|
|
225
|
+
destroyItem(aItems[j++], forState);
|
|
216
226
|
}
|
|
217
227
|
} else {
|
|
218
228
|
// General case: need full reconciliation
|
|
@@ -237,7 +247,7 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
237
247
|
sources[j - bStart] = i + 1;
|
|
238
248
|
if (fastPathRemoval) {
|
|
239
249
|
fastPathRemoval = false;
|
|
240
|
-
while (aStart < i) destroyItem(aItems[aStart++]);
|
|
250
|
+
while (aStart < i) destroyItem(aItems[aStart++], forState);
|
|
241
251
|
}
|
|
242
252
|
if (pos > j) moved = true;
|
|
243
253
|
else pos = j;
|
|
@@ -246,9 +256,9 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
246
256
|
break;
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
|
-
if (!fastPathRemoval && j > bEnd) destroyItem(aItems[i]);
|
|
259
|
+
if (!fastPathRemoval && j > bEnd) destroyItem(aItems[i], forState);
|
|
250
260
|
} else if (!fastPathRemoval) {
|
|
251
|
-
destroyItem(aItems[i]);
|
|
261
|
+
destroyItem(aItems[i], forState);
|
|
252
262
|
}
|
|
253
263
|
}
|
|
254
264
|
} else {
|
|
@@ -262,7 +272,7 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
262
272
|
if (j !== undefined) {
|
|
263
273
|
if (fastPathRemoval) {
|
|
264
274
|
fastPathRemoval = false;
|
|
265
|
-
while (i > aStart) destroyItem(aItems[aStart++]);
|
|
275
|
+
while (i > aStart) destroyItem(aItems[aStart++], forState);
|
|
266
276
|
}
|
|
267
277
|
sources[j - bStart] = i + 1;
|
|
268
278
|
if (pos > j) moved = true;
|
|
@@ -270,10 +280,10 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
270
280
|
bItems[j] = aItems[i];
|
|
271
281
|
++patched;
|
|
272
282
|
} else if (!fastPathRemoval) {
|
|
273
|
-
destroyItem(aItems[i]);
|
|
283
|
+
destroyItem(aItems[i], forState);
|
|
274
284
|
}
|
|
275
285
|
} else if (!fastPathRemoval) {
|
|
276
|
-
destroyItem(aItems[i]);
|
|
286
|
+
destroyItem(aItems[i], forState);
|
|
277
287
|
}
|
|
278
288
|
}
|
|
279
289
|
}
|
|
@@ -295,7 +305,7 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
295
305
|
|
|
296
306
|
if (sources[i] === 0) {
|
|
297
307
|
bVal = b[pos];
|
|
298
|
-
bItems[pos] = createItem(target, bVal, pos, renderFn);
|
|
308
|
+
bItems[pos] = createItem(target, bVal, pos, renderFn, forState);
|
|
299
309
|
} else if (j < 0 || i !== seq[j]) {
|
|
300
310
|
moveItem(bItems[pos], target);
|
|
301
311
|
} else {
|
|
@@ -309,7 +319,7 @@ function reconcileByRef(anchor, forState, b, renderFn) {
|
|
|
309
319
|
bVal = b[pos];
|
|
310
320
|
const nextPos = pos + 1;
|
|
311
321
|
target = nextPos < bLen ? bItems[nextPos].s.start : anchor;
|
|
312
|
-
bItems[pos] = createItem(target, bVal, pos, renderFn);
|
|
322
|
+
bItems[pos] = createItem(target, bVal, pos, renderFn, forState);
|
|
313
323
|
}
|
|
314
324
|
}
|
|
315
325
|
}
|
|
@@ -339,6 +349,7 @@ export function forBlock(container, sourceCell, renderFn) {
|
|
|
339
349
|
const forState = {
|
|
340
350
|
array: [],
|
|
341
351
|
items: [],
|
|
352
|
+
cleanupCount: 0, // Track items with cleanup for fast-path optimization
|
|
342
353
|
};
|
|
343
354
|
|
|
344
355
|
const doUpdate = () => {
|
|
@@ -363,7 +374,7 @@ export function forBlock(container, sourceCell, renderFn) {
|
|
|
363
374
|
// Destroy all current items
|
|
364
375
|
const items = forState.items;
|
|
365
376
|
for (let i = 0; i < items.length; i++) {
|
|
366
|
-
destroyItem(items[i]);
|
|
377
|
+
destroyItem(items[i], forState);
|
|
367
378
|
}
|
|
368
379
|
forState.array = [];
|
|
369
380
|
forState.items = [];
|