fix: multiple dynamic closures server render

This commit is contained in:
Ryan Turnquist 2025-12-20 00:42:08 -08:00
parent 694fcd84c3
commit 907c87ca06
14 changed files with 608 additions and 1 deletions

View File

@ -0,0 +1,5 @@
---
"@marko/runtime-tags": patch
---
Fix multiple dynamic closures losing scope in server render

View File

@ -0,0 +1,28 @@
{
"vars": {
"props": {
"$_": "t",
"$init": "o",
"$resolveAfter": "r",
"$$await_content__value": "_",
"$$await_content__$params": "c",
"$$await_content": "e",
"$$try_content__await": "n",
"$$try_content__clickCount": "m",
"$$clickCount_closure": "s",
"$$clickCount__script": "a",
"$$clickCount": "i",
"$$clickCount__closure": "l",
"$$try_content__await_promise": "u",
"$resolveAfterNext": "p",
"$$if_content__a": "d",
"$$if_content__b": "g",
"$$a__OR__b__script": "f",
"$$a__OR__b": "k",
"$$a__closure": "b",
"$$a": "x",
"$$b__closure": "h",
"$$b": "j"
}
}
}

View File

@ -0,0 +1,55 @@
# Render
```html
<button />
<div>
0
</div>
<div>
0
</div>
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<div>
1
</div>
<div>
1
</div>
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<div>
2
</div>
<div>
2
</div>
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<div>
3
</div>
<div>
3
</div>
```

View File

@ -0,0 +1,87 @@
# Render
```html
<button />
<!---->
<div>
0
</div>
<div>
0
</div>
<!---->
<!---->
```
# Mutations
```
INSERT button, #comment0, div0, div1, #comment1, #comment2
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<!---->
<div>
1
</div>
<div>
1
</div>
<!---->
<!---->
```
# Mutations
```
UPDATE div1/#text "0" => "1"
UPDATE div0/#text "0" => "1"
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<!---->
<div>
2
</div>
<div>
2
</div>
<!---->
<!---->
```
# Mutations
```
UPDATE div1/#text "1" => "2"
UPDATE div0/#text "1" => "2"
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<!---->
<div>
3
</div>
<div>
3
</div>
<!---->
<!---->
```
# Mutations
```
UPDATE div1/#text "2" => "3"
UPDATE div0/#text "2" => "3"
```

View File

@ -0,0 +1,27 @@
// size: 307 (min) 164 (brotli)
_._enable_catch();
const $if_content__a = _._closure_get(
2,
($scope) => _._text($scope.a, $scope._._.c),
($scope) => $scope._._,
),
$if_content__b = _._closure_get(
3,
($scope) => _._text($scope.b, $scope._._.d),
($scope) => $scope._._,
),
$a__OR__b__script = _._script("a0", ($scope) =>
_._on($scope.a, "click", function () {
($a($scope, $scope.c + 1), $b($scope, $scope.d + 1));
}),
),
$a__OR__b = _._or(4, $a__OR__b__script),
$a__closure = _._closure($if_content__a),
$a = _._let(2, ($scope) => {
($a__OR__b($scope), $a__closure($scope));
}),
$b__closure = _._closure($if_content__b),
$b = _._let(3, ($scope) => {
($a__OR__b($scope), $b__closure($scope));
});
init();

View File

@ -0,0 +1,34 @@
export const $template = "<button></button><!><!>";
export const $walks = /* get, over(1), replace, over(2) */" b%c";
import * as _ from "@marko/runtime-tags/debug/dom";
_._enable_catch();
const $if_content__a = /* @__PURE__ */_._closure_get("a", $scope => _._text($scope["#text/0"], $scope._._.a), $scope => $scope._._);
const $if_content__b = /* @__PURE__ */_._closure_get("b", $scope => _._text($scope["#text/1"], $scope._._.b), $scope => $scope._._);
const $if_content__setup = $scope => {
$if_content__a($scope);
$if_content__b($scope);
};
const $try_content__if = /* @__PURE__ */_._if("#text/0", "<div> </div><div> </div>", /* next(1), get, out(1), next(1), get, out(1) */"D lD l", $if_content__setup);
const $try_content__setup = $scope => $try_content__if($scope, true ? 0 : 1);
const $a__OR__b__script = _._script("__tests__/template.marko_0_a_b", $scope => _._on($scope["#button/0"], "click", function () {
$a($scope, $scope.a + 1);
$b($scope, $scope.b + 1);
}));
const $a__OR__b = /* @__PURE__ */_._or(4, $a__OR__b__script);
const $a__closure = /* @__PURE__ */_._closure($if_content__a);
const $a = /* @__PURE__ */_._let("a/2", $scope => {
$a__OR__b($scope);
$a__closure($scope);
});
const $b__closure = /* @__PURE__ */_._closure($if_content__b);
const $b = /* @__PURE__ */_._let("b/3", $scope => {
$a__OR__b($scope);
$b__closure($scope);
});
const $try = /* @__PURE__ */_._try("#text/1", "<!><!><!>", /* over(1), replace, over(2) */"b%c", $try_content__setup);
export function $setup($scope) {
$a($scope, 0);
$b($scope, 0);
$try($scope, {});
}
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);

View File

@ -0,0 +1,35 @@
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/template.marko", input => {
const $scope0_id = _._scope_id();
const $a__closures = new Set();
const $b__closures = new Set();
let a = 0;
let b = 0;
_._html(`<button></button>${_._el_resume($scope0_id, "#button/0")}`);
_._try($scope0_id, "#text/1", _._content_resume("__tests__/template.marko_1_content", () => {
const $scope1_id = _._scope_id();
if (true) {
const $scope2_id = _._scope_id();
_._html(`<div>${_._escape(a)}${_._el_resume($scope2_id, "#text/0")}</div><div>${_._escape(b)}${_._el_resume($scope2_id, "#text/1")}</div>`);
_._subscribe($b__closures, _._subscribe($a__closures, _._scope($scope2_id, {
_: _._scope_with_id($scope1_id),
"ClosureSignalIndex:a": 0,
"ClosureSignalIndex:b": 0
}, "__tests__/template.marko", "8:4")));
}
_._scope($scope1_id, {
_: _._scope_with_id($scope0_id)
}, "__tests__/template.marko", "7:2");
}, $scope0_id), {});
_._script($scope0_id, "__tests__/template.marko_0_a_b");
_._scope($scope0_id, {
a,
b,
"ClosureScopes:a": $a__closures,
"ClosureScopes:b": $b__closures
}, "__tests__/template.marko", 0, {
a: "1:6",
b: "2:6"
});
_._resume_branch($scope0_id);
});

View File

@ -0,0 +1,55 @@
# Render
```html
<button />
<div>
0
</div>
<div>
0
</div>
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<div>
1
</div>
<div>
1
</div>
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<div>
2
</div>
<div>
2
</div>
```
# Render
```js
container.querySelector("button").click();
```
```html
<button />
<div>
3
</div>
<div>
3
</div>
```

View File

@ -0,0 +1,191 @@
# Render
```html
<html>
<head />
<body>
<button />
<!--M_*1 #button/0-->
<!--M_[-->
<div>
0
<!--M_*3 #text/0-->
</div>
<div>
0
<!--M_*3 #text/1-->
</div>
<!--M_]1 #text/1 2-->
<script>
WALKER_RUNTIME("M")("_");
M._.r = [_ =&gt; (_.c = [0, _.a = {
a: 0,
b: 0,
"ClosureScopes:a": _.d = new Set,
"ClosureScopes:b": _.f = new Set
}, _.b = {
_: _.a,
"#BranchAccessor": "#text/1"
}, _.e = {
_: _.b,
"ClosureSignalIndex:a": 0,
"ClosureSignalIndex:b": 0
}], (_.d).add(_.e), (_.f).add(_.e), _.c),
"__tests__/template.marko_0_a_b 1"
];
M._.w()
</script>
</body>
</html>
```
# Mutations
```
INSERT html/body/#text
```
# Render
```js
container.querySelector("button").click();
```
```html
<html>
<head />
<body>
<button />
<!--M_*1 #button/0-->
<!--M_[-->
<div>
1
<!--M_*3 #text/0-->
</div>
<div>
1
<!--M_*3 #text/1-->
</div>
<!--M_]1 #text/1 2-->
<script>
WALKER_RUNTIME("M")("_");
M._.r = [_ =&gt; (_.c = [0, _.a = {
a: 0,
b: 0,
"ClosureScopes:a": _.d = new Set,
"ClosureScopes:b": _.f = new Set
}, _.b = {
_: _.a,
"#BranchAccessor": "#text/1"
}, _.e = {
_: _.b,
"ClosureSignalIndex:a": 0,
"ClosureSignalIndex:b": 0
}], (_.d).add(_.e), (_.f).add(_.e), _.c),
"__tests__/template.marko_0_a_b 1"
];
M._.w()
</script>
</body>
</html>
```
# Mutations
```
UPDATE html/body/div1/#text "0" => "1"
UPDATE html/body/div0/#text "0" => "1"
```
# Render
```js
container.querySelector("button").click();
```
```html
<html>
<head />
<body>
<button />
<!--M_*1 #button/0-->
<!--M_[-->
<div>
2
<!--M_*3 #text/0-->
</div>
<div>
2
<!--M_*3 #text/1-->
</div>
<!--M_]1 #text/1 2-->
<script>
WALKER_RUNTIME("M")("_");
M._.r = [_ =&gt; (_.c = [0, _.a = {
a: 0,
b: 0,
"ClosureScopes:a": _.d = new Set,
"ClosureScopes:b": _.f = new Set
}, _.b = {
_: _.a,
"#BranchAccessor": "#text/1"
}, _.e = {
_: _.b,
"ClosureSignalIndex:a": 0,
"ClosureSignalIndex:b": 0
}], (_.d).add(_.e), (_.f).add(_.e), _.c),
"__tests__/template.marko_0_a_b 1"
];
M._.w()
</script>
</body>
</html>
```
# Mutations
```
UPDATE html/body/div1/#text "1" => "2"
UPDATE html/body/div0/#text "1" => "2"
```
# Render
```js
container.querySelector("button").click();
```
```html
<html>
<head />
<body>
<button />
<!--M_*1 #button/0-->
<!--M_[-->
<div>
3
<!--M_*3 #text/0-->
</div>
<div>
3
<!--M_*3 #text/1-->
</div>
<!--M_]1 #text/1 2-->
<script>
WALKER_RUNTIME("M")("_");
M._.r = [_ =&gt; (_.c = [0, _.a = {
a: 0,
b: 0,
"ClosureScopes:a": _.d = new Set,
"ClosureScopes:b": _.f = new Set
}, _.b = {
_: _.a,
"#BranchAccessor": "#text/1"
}, _.e = {
_: _.b,
"ClosureSignalIndex:a": 0,
"ClosureSignalIndex:b": 0
}], (_.d).add(_.e), (_.f).add(_.e), _.c),
"__tests__/template.marko_0_a_b 1"
];
M._.w()
</script>
</body>
</html>
```
# Mutations
```
UPDATE html/body/div1/#text "2" => "3"
UPDATE html/body/div0/#text "2" => "3"
```

View File

@ -0,0 +1,10 @@
# Render End
```html
<button />
<div>
0
</div>
<div>
0
</div>
```

View File

@ -0,0 +1,63 @@
# Write
```html
<button></button><!--M_*1 #button/0--><!--M_[--><div>0<!--M_*3 #text/0--></div><div>0<!--M_*3 #text/1--></div><!--M_]1 #text/1 2--><script>WALKER_RUNTIME("M")("_");M._.r=[_=>(_.c=[0,_.a={a:0,b:0,"ClosureScopes:a":_.d=new Set,"ClosureScopes:b":_.f=new Set},_.b={_:_.a,"#BranchAccessor":"#text/1"},_.e={_:_.b,"ClosureSignalIndex:a":0,"ClosureSignalIndex:b":0}],(_.d).add(_.e),(_.f).add(_.e),_.c),"__tests__/template.marko_0_a_b 1"];M._.w()</script>
```
# Render End
```html
<html>
<head />
<body>
<button />
<!--M_*1 #button/0-->
<!--M_[-->
<div>
0
<!--M_*3 #text/0-->
</div>
<div>
0
<!--M_*3 #text/1-->
</div>
<!--M_]1 #text/1 2-->
<script>
WALKER_RUNTIME("M")("_");
M._.r = [_ =&gt; (_.c = [0, _.a = {
a: 0,
b: 0,
"ClosureScopes:a": _.d = new Set,
"ClosureScopes:b": _.f = new Set
}, _.b = {
_: _.a,
"#BranchAccessor": "#text/1"
}, _.e = {
_: _.b,
"ClosureSignalIndex:a": 0,
"ClosureSignalIndex:b": 0
}], (_.d).add(_.e), (_.f).add(_.e), _.c),
"__tests__/template.marko_0_a_b 1"
];
M._.w()
</script>
</body>
</html>
```
# Mutations
```
INSERT html
INSERT html/head
INSERT html/body
INSERT html/body/button
INSERT html/body/#comment0
INSERT html/body/#comment1
INSERT html/body/div0
INSERT html/body/div0/#text
INSERT html/body/div0/#comment
INSERT html/body/div1
INSERT html/body/div1/#text
INSERT html/body/div1/#comment
INSERT html/body/#comment2
INSERT html/body/script
INSERT html/body/script/#text
```

View File

@ -0,0 +1,12 @@
<let/a = 0/>
<let/b = 0/>
<button onClick() {
a++;
b++;
}/>
<try>
<if=true>
<div>${a}</div>
<div>${b}</div>
</if>
</try>

View File

@ -0,0 +1,5 @@
export const steps = [{}, click, click, click];
function click(container: Element) {
container.querySelector("button")!.click();
}

View File

@ -1532,5 +1532,5 @@ export function _subscribe(
scope: ScopeInternals,
) {
$chunk.boundary.state.serializer.writeCall(scope, subscribers, "add");
referenceScope(scope);
return referenceScope(scope);
}