From db1ba3673a103ebb813b522032734ff5a96f01a0 Mon Sep 17 00:00:00 2001
From: Luke LaValva
Date: Mon, 4 Mar 2024 08:41:18 -0800
Subject: [PATCH] fix: hydration in for loops (#2134)
* fix: hydration in for loops
---------
Co-authored-by: Michael Rawlings
---
.sizes.json | 48 +--
packages/runtime-tags/src/dom/control-flow.ts | 5 +-
.../__snapshots__/html.expected/template.js | 13 +-
.../resume-sanitized.expected.md | 195 +++++++++
.../__snapshots__/resume.expected.md | 369 ++++++++++++++++++
.../__snapshots__/ssr-sanitized.expected.md | 39 ++
.../__snapshots__/ssr.expected.md | 134 +++++++
.../fixtures/basic-nested-scope-for/test.ts | 2 -
packages/translator-tags/src/core/for.ts | 14 +-
9 files changed, 783 insertions(+), 36 deletions(-)
create mode 100644 packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume-sanitized.expected.md
create mode 100644 packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md
create mode 100644 packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr-sanitized.expected.md
create mode 100644 packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr.expected.md
diff --git a/.sizes.json b/.sizes.json
index c9664d79e..c1c3c345b 100644
--- a/.sizes.json
+++ b/.sizes.json
@@ -7,9 +7,9 @@
{
"name": "*",
"total": {
- "min": 12644,
- "gzip": 5391,
- "brotli": 4935
+ "min": 12664,
+ "gzip": 5394,
+ "brotli": 4922
}
},
{
@@ -21,12 +21,12 @@
},
"runtime": {
"min": 3821,
- "gzip": 1808,
+ "gzip": 1800,
"brotli": 1614
},
"total": {
"min": 4172,
- "gzip": 2083,
+ "gzip": 2075,
"brotli": 1852
}
},
@@ -34,17 +34,17 @@
"name": "counter 💧",
"user": {
"min": 204,
- "gzip": 180,
+ "gzip": 182,
"brotli": 157
},
"runtime": {
"min": 2683,
- "gzip": 1362,
+ "gzip": 1370,
"brotli": 1222
},
"total": {
"min": 2887,
- "gzip": 1542,
+ "gzip": 1552,
"brotli": 1379
}
},
@@ -52,36 +52,36 @@
"name": "comments",
"user": {
"min": 1182,
- "gzip": 698,
- "brotli": 639
+ "gzip": 703,
+ "brotli": 637
},
"runtime": {
- "min": 7343,
- "gzip": 3386,
- "brotli": 3083
+ "min": 7363,
+ "gzip": 3405,
+ "brotli": 3091
},
"total": {
- "min": 8525,
- "gzip": 4084,
- "brotli": 3722
+ "min": 8545,
+ "gzip": 4108,
+ "brotli": 3728
}
},
{
"name": "comments 💧",
"user": {
"min": 949,
- "gzip": 586,
- "brotli": 543
+ "gzip": 588,
+ "brotli": 544
},
"runtime": {
- "min": 7867,
- "gzip": 3596,
- "brotli": 3269
+ "min": 7887,
+ "gzip": 3612,
+ "brotli": 3284
},
"total": {
- "min": 8816,
- "gzip": 4182,
- "brotli": 3812
+ "min": 8836,
+ "gzip": 4200,
+ "brotli": 3828
}
}
]
diff --git a/packages/runtime-tags/src/dom/control-flow.ts b/packages/runtime-tags/src/dom/control-flow.ts
index 57e31ae0d..3fd68e69f 100644
--- a/packages/runtime-tags/src/dom/control-flow.ts
+++ b/packages/runtime-tags/src/dom/control-flow.ts
@@ -315,7 +315,10 @@ export function inLoopScope(
) {
const loopScopeAccessor = loopNodeAccessor + AccessorChar.LoopScopeArray;
return (scope: Scope, clean?: boolean | 1) => {
- const loopScopes = scope[loopScopeAccessor] ?? [];
+ const loopScopes =
+ scope[loopScopeAccessor] ??
+ scope[loopNodeAccessor + AccessorChar.LoopScopeMap]?.values() ??
+ [];
for (const scope of loopScopes) {
signal(scope, clean);
}
diff --git a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js
index db8d41ccd..d8ab80148 100644
--- a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js
+++ b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js
@@ -1,18 +1,25 @@
-import { attr as _attr, escapeXML as _escapeXML, markResumeNode as _markResumeNode, write as _write, serializedScope as _serializedScope, writeEffect as _writeEffect, writeScope as _writeScope, nextScopeId as _nextScopeId, maybeFlush as _maybeFlush, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-tags/debug/html";
+import { attr as _attr, escapeXML as _escapeXML, markResumeNode as _markResumeNode, markResumeControlSingleNodeEnd as _markResumeControlSingleNodeEnd, write as _write, serializedScope as _serializedScope, writeEffect as _writeEffect, writeScope as _writeScope, nextScopeId as _nextScopeId, maybeFlush as _maybeFlush, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-tags/debug/html";
const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar) => {
const _scope0_id = _nextScopeId();
const selected = 0;
+ const _forScopeIds = [],
+ _scope1_ = new Map();
+ let _i2 = 0;
for (const num of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) {
const _scope1_id = _nextScopeId();
+ let _i = _i2++;
+ _forScopeIds.push(_scope1_id);
_write(`${_markResumeNode(_scope1_id, "#button/0")}`);
_writeEffect(_scope1_id, "packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/template.marko_1_num");
- _writeScope(_scope1_id, {
+ _writeScope(_scope1_id, (_s => (_scope1_.set(_i, _s), _s))({
"num": num,
"_": _serializedScope(_scope0_id)
- });
+ }));
_maybeFlush();
}
+ _write(`${_markResumeControlSingleNodeEnd(_scope0_id, "#text/0", _forScopeIds)}`);
_writeScope(_scope0_id, {
+ "#text/0(": _scope1_.size ? _scope1_ : undefined,
"selected": selected
});
});
diff --git a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume-sanitized.expected.md b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume-sanitized.expected.md
new file mode 100644
index 000000000..2434b359c
--- /dev/null
+++ b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume-sanitized.expected.md
@@ -0,0 +1,195 @@
+# Render {}
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+# Render
+c => click(c, 2)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+# Render
+c => click(c, 3)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+# Render
+c => click(c, 5)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md
new file mode 100644
index 000000000..281862701
--- /dev/null
+++ b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md
@@ -0,0 +1,369 @@
+# Render {}
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+
+```
+
+
+# Render
+c => click(c, 2)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button2: attr(data-selected) null => ""
+#document/html0/body1/button2: attr(data-multiple) null => ""
+#document/html0/body1/button6: attr(data-multiple) null => ""
+#document/html0/body1/button10: attr(data-multiple) null => ""
+#document/html0/body1/button14: attr(data-multiple) null => ""
+#document/html0/body1/button18: attr(data-multiple) null => ""
+#document/html0/body1/button22: attr(data-multiple) null => ""
+```
+
+
+# Render
+c => click(c, 3)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button2: attr(data-selected) "" => null
+#document/html0/body1/button2: attr(data-multiple) "" => null
+#document/html0/body1/button4: attr(data-selected) null => ""
+#document/html0/body1/button4: attr(data-multiple) null => ""
+#document/html0/body1/button6: attr(data-multiple) "" => null
+#document/html0/body1/button10: attr(data-multiple) "" => ""
+#document/html0/body1/button14: attr(data-multiple) "" => null
+#document/html0/body1/button16: attr(data-multiple) null => ""
+#document/html0/body1/button18: attr(data-multiple) "" => null
+#document/html0/body1/button22: attr(data-multiple) "" => ""
+```
+
+
+# Render
+c => click(c, 5)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button4: attr(data-selected) "" => null
+#document/html0/body1/button4: attr(data-multiple) "" => null
+#document/html0/body1/button8: attr(data-selected) null => ""
+#document/html0/body1/button8: attr(data-multiple) null => ""
+#document/html0/body1/button10: attr(data-multiple) "" => null
+#document/html0/body1/button16: attr(data-multiple) "" => null
+#document/html0/body1/button18: attr(data-multiple) null => ""
+#document/html0/body1/button22: attr(data-multiple) "" => null
+```
\ No newline at end of file
diff --git a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr-sanitized.expected.md b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr-sanitized.expected.md
new file mode 100644
index 000000000..bbf01da2c
--- /dev/null
+++ b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr-sanitized.expected.md
@@ -0,0 +1,39 @@
+# Render "End"
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr.expected.md b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr.expected.md
new file mode 100644
index 000000000..43edc5a86
--- /dev/null
+++ b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/ssr.expected.md
@@ -0,0 +1,134 @@
+# Write
+
+
+
+# Render "End"
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+inserted #document/html0
+inserted #document/html0/head0
+inserted #document/html0/body1
+inserted #document/html0/body1/button0
+inserted #document/html0/body1/button0/#text0
+inserted #document/html0/body1/button0/#comment1
+inserted #document/html0/body1/#comment1
+inserted #document/html0/body1/button2
+inserted #document/html0/body1/button2/#text0
+inserted #document/html0/body1/button2/#comment1
+inserted #document/html0/body1/#comment3
+inserted #document/html0/body1/button4
+inserted #document/html0/body1/button4/#text0
+inserted #document/html0/body1/button4/#comment1
+inserted #document/html0/body1/#comment5
+inserted #document/html0/body1/button6
+inserted #document/html0/body1/button6/#text0
+inserted #document/html0/body1/button6/#comment1
+inserted #document/html0/body1/#comment7
+inserted #document/html0/body1/button8
+inserted #document/html0/body1/button8/#text0
+inserted #document/html0/body1/button8/#comment1
+inserted #document/html0/body1/#comment9
+inserted #document/html0/body1/button10
+inserted #document/html0/body1/button10/#text0
+inserted #document/html0/body1/button10/#comment1
+inserted #document/html0/body1/#comment11
+inserted #document/html0/body1/button12
+inserted #document/html0/body1/button12/#text0
+inserted #document/html0/body1/button12/#comment1
+inserted #document/html0/body1/#comment13
+inserted #document/html0/body1/button14
+inserted #document/html0/body1/button14/#text0
+inserted #document/html0/body1/button14/#comment1
+inserted #document/html0/body1/#comment15
+inserted #document/html0/body1/button16
+inserted #document/html0/body1/button16/#text0
+inserted #document/html0/body1/button16/#comment1
+inserted #document/html0/body1/#comment17
+inserted #document/html0/body1/button18
+inserted #document/html0/body1/button18/#text0
+inserted #document/html0/body1/button18/#comment1
+inserted #document/html0/body1/#comment19
+inserted #document/html0/body1/button20
+inserted #document/html0/body1/button20/#text0
+inserted #document/html0/body1/button20/#comment1
+inserted #document/html0/body1/#comment21
+inserted #document/html0/body1/button22
+inserted #document/html0/body1/button22/#text0
+inserted #document/html0/body1/button22/#comment1
+inserted #document/html0/body1/#comment23
+inserted #document/html0/body1/#comment24
+inserted #document/html0/body1/script25
+inserted #document/html0/body1/script25/#text0
+```
\ No newline at end of file
diff --git a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/test.ts b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/test.ts
index d2e0a5a59..ea79a7a2f 100644
--- a/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/test.ts
+++ b/packages/translator-tags/src/__tests__/fixtures/basic-nested-scope-for/test.ts
@@ -12,5 +12,3 @@ function click(container: Element, number: number) {
const button = buttons.find((b) => b.textContent === "" + number)!;
button.click();
}
-
-export const skip_ssr = true;
diff --git a/packages/translator-tags/src/core/for.ts b/packages/translator-tags/src/core/for.ts
index a7cbe1cf2..62bfcbaf9 100644
--- a/packages/translator-tags/src/core/for.ts
+++ b/packages/translator-tags/src/core/for.ts
@@ -5,7 +5,7 @@ import {
getTagDef,
} from "@marko/babel-utils";
import { types as t } from "@marko/compiler";
-import { WalkCode } from "@marko/runtime-tags/common/types";
+import { AccessorChar, WalkCode } from "@marko/runtime-tags/common/types";
import { isOutputHTML } from "../util/marko-config";
import analyzeAttributeTags from "../util/nested-attribute-tags";
import { mergeReferences } from "../util/references";
@@ -282,7 +282,7 @@ const translateHTML = {
let byParams: t.Expression[];
let keyExpression: t.Expression | undefined = t.identifier("NOO");
- if (isStateful) {
+ if (isStateful || bodySection.closures) {
setRegisterScopeBuilder(tag, (scope: t.Expression) => {
const tempScopeIdentifier =
currentProgramPath.scope.generateUidIdentifier("s");
@@ -363,7 +363,7 @@ const translateHTML = {
valParam = tempValParam;
}
- if (indexParam || isStateful) {
+ if (indexParam || isStateful || bodySection.closures) {
indexParam ??= currentProgramPath.scope.generateUidIdentifier("i");
const indexName = tag.scope.generateUidIdentifierBasedOnNode(
indexParam,
@@ -421,7 +421,7 @@ const translateHTML = {
const stepName = tag.scope.generateUidIdentifier("step");
const fromName = tag.scope.generateUidIdentifier("from");
- if (indexParam || isStateful) {
+ if (indexParam || isStateful || bodySection.closures) {
indexParam ??= currentProgramPath.scope.generateUidIdentifier("i");
keyExpression = indexParam as t.Identifier;
block.body.unshift(
@@ -466,7 +466,7 @@ const translateHTML = {
);
}
- if (isStateful) {
+ if (isStateful || bodySection.closures) {
const forScopeIdsIdentifier =
tag.scope.generateUidIdentifier("forScopeIds");
const forScopesIdentifier = getScopeIdentifier(bodySection);
@@ -511,7 +511,9 @@ const translateHTML = {
)}`;
}
getSerializedScopeProperties(tagSection).set(
- t.stringLiteral(getScopeAccessorLiteral(reserve!).value + "("),
+ t.stringLiteral(
+ getScopeAccessorLiteral(reserve!).value + AccessorChar.LoopScopeMap,
+ ),
t.conditionalExpression(
t.memberExpression(forScopesIdentifier, t.identifier("size")),
forScopesIdentifier,