diff --git a/.changeset/fair-jobs-punch.md b/.changeset/fair-jobs-punch.md
new file mode 100644
index 0000000000..78ed1d4bd7
--- /dev/null
+++ b/.changeset/fair-jobs-punch.md
@@ -0,0 +1,5 @@
+---
+"@marko/runtime-tags": patch
+---
+
+Fix async runtime placeholder duplication and race condition.
diff --git a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr-sanitized.expected.md
index d608f511de..26c7d8fab4 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr-sanitized.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr-sanitized.expected.md
@@ -1,4 +1,4 @@
# Render "End"
```html
-aERROR!efg
+aERROR!def
```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md
index f620a1c409..ff12ca75bc 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md
@@ -3,7 +3,7 @@
# Write
- efgERROR!
+ defERROR!
# Render "End"
@@ -17,7 +17,7 @@
- aERROR!efg
+ aERROR!def
diff --git a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/server.ts b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/server.ts
index a0355bdad1..a3560ad050 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/server.ts
+++ b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/server.ts
@@ -13,15 +13,15 @@ const renderer = () => {
() => {
write("b");
fork(rejectAfter(new Error("ERROR!"), 2), write);
- write("d");
+ write("c");
},
(err) => {
write((err as Error).message);
},
);
- write("e");
- fork(resolveAfter("f", 1), write);
- write("g");
+ write("d");
+ fork(resolveAfter("e", 1), write);
+ write("f");
};
export default createTemplate("", renderer);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/component-markers-placeholder.skip/server.ts b/packages/runtime-tags/src/__tests__/fixtures/component-markers-placeholder.skip/server.ts
index 4e46a19a1e..6dbc2f9fa3 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/component-markers-placeholder.skip/server.ts
+++ b/packages/runtime-tags/src/__tests__/fixtures/component-markers-placeholder.skip/server.ts
@@ -21,7 +21,7 @@ const firstComponent = () => {
write("d");
},
() => {
- write("e...");
+ write("_A_");
},
);
write("e");
@@ -30,18 +30,18 @@ const firstComponent = () => {
};
const secondComponent = () => {
- write("v");
+ write("1");
tryPlaceholder(
() => {
- write("w");
- fork(resolveAfter("x", 2), write);
- write("y");
+ write("2");
+ fork(resolveAfter("3", 2), write);
+ write("4");
},
() => {
- write("z...");
+ write("_B_");
},
);
- write("z");
+ write("5");
};
export default createTemplate("", renderer);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr-sanitized.expected.md
index ed9f2dca39..6af7eb33af 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr-sanitized.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr-sanitized.expected.md
@@ -1,10 +1,4 @@
# Render "End"
```html
-abdfgh
-
- cd
-
+abcdefg
```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md
index aa07adb0ca..ffe40ddbf6 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md
@@ -1,13 +1,13 @@
# Write
- ae...fbd
+ a_A_ebd
# Write
- gh
+ fg
# Write
- cd
+ c
# Render "End"
@@ -21,19 +21,11 @@
- ab
-
- df
+ abcde
- gh
-
- cd
-
+ fg
@@ -55,23 +47,27 @@ inserted #document/html0/head0/style0
inserted #document/html0/head0/style0/#text0
inserted t
inserted #document/html0/body1/#text1
-inserted #document/html0/body1/#comment2
+inserted #comment
inserted #document/html0/body1/#text3
inserted #document/html0/body1/script5
inserted #document/html0/body1/script5/#text0
removed #document/html0/head0/style0 after #document/html0/body1/#text4
inserted #document/html0/head0/style0
inserted #document/html0/body1/#text6
-inserted #document/html0/body1/t7
-inserted #document/html0/body1/t7/#text0
-inserted #document/html0/body1/script8
-inserted #document/html0/body1/script8/#text0
+inserted t
+inserted #document/html0/body1/#text2
+inserted #document/html0/body1/script7
+inserted #document/html0/body1/script7/#text0
+removed #document/html0/body1/#text2 in t
+removed #comment after #document/html0/body1/#text1
+inserted #document/html0/body1/#text2
+removed t after #document/html0/body1/#text6
removed #text after #comment
removed #comment after #comment
-removed #document/html0/body1/#text1 before #document/html0/body1/#comment2
-removed #document/html0/body1/#comment2 before #document/html0/body1/#text3
+removed #document/html0/body1/#text1 before #document/html0/body1/#text2
+removed #document/html0/body1/#text2 before #document/html0/body1/#text3
removed #document/html0/body1/#text3 in t
removed #comment after #document/html0/body1/#text0
-inserted #document/html0/body1/#text1, #document/html0/body1/#comment2, #document/html0/body1/#text3
+inserted #document/html0/body1/#text1, #document/html0/body1/#text2, #document/html0/body1/#text3
removed t after #document/html0/body1/#text4
```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/server.ts b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/server.ts
index a0b47fe338..dfa1b2a631 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/server.ts
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/server.ts
@@ -16,12 +16,12 @@ const renderer = () => {
write("d");
},
() => {
- write("e...");
+ write("_A_");
},
);
- write("f");
- fork(resolveAfter("g", 1), write);
- write("h");
+ write("e");
+ fork(resolveAfter("f", 1), write);
+ write("g");
};
export default createTemplate("", renderer);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr-sanitized.expected.md
index d5df79d24e..ca7712944b 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr-sanitized.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr-sanitized.expected.md
@@ -1,4 +1,4 @@
# Render "End"
```html
-abdef
+abcde
```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr.expected.md
index e5af9491f6..75637958ea 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/__snapshots__/ssr.expected.md
@@ -1,9 +1,9 @@
# Write
- abd
+ abc
# Write
- ef
+ de
# Render "End"
@@ -11,7 +11,7 @@
- abdef
+ abcde
```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/server.ts b/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/server.ts
index f1d12fda86..9cd2f42e89 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/server.ts
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-skipped/server.ts
@@ -14,12 +14,12 @@ const renderer = () => {
write("b");
},
() => {
- write("c...");
+ write("_A_");
},
);
- write("d");
- fork(resolveAfter("e", 1), write);
- write("f");
+ write("c");
+ fork(resolveAfter("d", 1), write);
+ write("e");
};
export default createTemplate("", renderer);
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr-sanitized.expected.md
index 12f34ee5b6..0ab84c1a02 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr-sanitized.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr-sanitized.expected.md
@@ -1,10 +1,4 @@
# Render "End"
```html
-abdeg
-
- fg
-
+abcdefghij
```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md
index 068afdc9b9..3ff0b9658b 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md
@@ -1,17 +1,17 @@
# Write
- ai...jbdh...eg
+ a_B_hbd_A_eg
# Write
- kl
+ ij
# Write
- cdh...
+ c
# Write
- fg
+ f
# Render "End"
@@ -25,20 +25,14 @@
- ab
-
- de
-
- g
+ abcdefgh
+
+ ij
-
- fg
-
@@ -55,54 +49,58 @@ inserted #document/html0/body1/#text0
inserted #comment
inserted #text
inserted #comment
-inserted #text
+inserted #document/html0/body1/#text7
inserted #document/html0/head0/style0
inserted #document/html0/head0/style0/#text0
inserted t
inserted #document/html0/body1/#text1
-inserted #document/html0/body1/#comment2
+inserted #comment
inserted #document/html0/body1/#text3
inserted #comment
inserted #text
inserted #comment
inserted t
inserted #document/html0/body1/#text4
-inserted #document/html0/body1/#comment5
+inserted #comment
inserted #document/html0/body1/#text6
-inserted script
-inserted script/#text0
-removed #document/html0/head0/style0 after #text
+inserted #document/html0/body1/script8
+inserted #document/html0/body1/script8/#text0
+removed #document/html0/head0/style0 after #document/html0/body1/#text7
inserted #document/html0/head0/style0
-inserted #text
+inserted #document/html0/body1/#text9
inserted t
-inserted t/#text0
-inserted t/#comment1
-inserted t/#text2
-inserted t/#comment3
-inserted #document/html0/body1/script7
-inserted #document/html0/body1/script7/#text0
+inserted #document/html0/body1/#text2
+inserted #document/html0/body1/script10
+inserted #document/html0/body1/script10/#text0
+removed #document/html0/body1/#text2 in t
+removed #comment after #document/html0/body1/#text1
+inserted #document/html0/body1/#text2
+removed t after #document/html0/body1/#text9
removed #text after #comment
removed #comment after #comment
-removed #document/html0/body1/#text1 before #document/html0/body1/#comment2
-removed #document/html0/body1/#comment2 before #document/html0/body1/#text3
+removed #document/html0/body1/#text1 before #document/html0/body1/#text2
+removed #document/html0/body1/#text2 before #document/html0/body1/#text3
removed #document/html0/body1/#text3 before #comment
removed #comment before #text
removed #text before #comment
removed #comment in t
removed #comment after #document/html0/body1/#text0
-inserted #document/html0/body1/#text1, #document/html0/body1/#comment2, #document/html0/body1/#text3, #comment, #text, #comment
-removed t after #text
-inserted #document/html0/body1/t8
-inserted #document/html0/body1/t8/#text0
-inserted #document/html0/body1/script9
-inserted #document/html0/body1/script9/#text0
+inserted #document/html0/body1/#text1, #document/html0/body1/#text2, #document/html0/body1/#text3, #comment, #text, #comment
+removed t after #document/html0/body1/#text7
+inserted t
+inserted #document/html0/body1/#text5
+inserted #document/html0/body1/script11
+inserted #document/html0/body1/script11/#text0
+removed #document/html0/body1/#text5 in t
+removed #comment after #document/html0/body1/#text4
+inserted #document/html0/body1/#text5
+removed t after #document/html0/body1/script10
removed #text after #comment
removed #comment after #comment
-removed #text after #comment
-removed t after #comment
-removed script after #comment
-removed #text after #comment
-removed t after #comment
+removed #document/html0/body1/#text4 before #document/html0/body1/#text5
+removed #document/html0/body1/#text5 before #document/html0/body1/#text6
+removed #document/html0/body1/#text6 in t
removed #comment after #document/html0/body1/#text3
-inserted #document/html0/body1/#text4, #document/html0/body1/#comment5, #document/html0/body1/#text6
+inserted #document/html0/body1/#text4, #document/html0/body1/#text5, #document/html0/body1/#text6
+removed t after #document/html0/body1/#text7
```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/server.ts b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/server.ts
index 652081b3b2..7020798b1d 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/server.ts
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/server.ts
@@ -21,17 +21,17 @@ const renderer = () => {
write("g");
},
() => {
- write("h...");
+ write("_A_");
},
);
},
() => {
- write("i...");
+ write("_B_");
},
);
+ write("h");
+ fork(resolveAfter("i", 1), write);
write("j");
- fork(resolveAfter("k", 1), write);
- write("l");
};
export default createTemplate("", renderer);
diff --git a/packages/runtime-tags/src/html/inlined-runtimes.ts b/packages/runtime-tags/src/html/inlined-runtimes.ts
index bd4d3965c4..f02b3019de 100644
--- a/packages/runtime-tags/src/html/inlined-runtimes.ts
+++ b/packages/runtime-tags/src/html/inlined-runtimes.ts
@@ -1,5 +1,5 @@
export const WALKER_RUNTIME_CODE = MARKO_DEBUG
- ? `((runtimeId) =>
+ ? /* js */ `((runtimeId) =>
(self[runtimeId] =
self[runtimeId] ||
((renderId) => {
@@ -36,13 +36,12 @@ export const WALKER_RUNTIME_CODE = MARKO_DEBUG
}),
prefix = renderId.length;
})))`
- : `(e=>self[e]=self[e]||(l=>{let t,d={},f=[],s=document,a=s.createTreeWalker(s,129),r=self[e][l]={i:l=e+l,d:s,l:d,v:f,x(){},w(e){for(;e=a.nextNode();)this.x(r=(r=e.data)&&!r.indexOf(l)&&(d[t=r.slice(x+1)]=e,r[x]),t,e),r>"#"&&f.push(e)}},x=l.length}))`;
+ : `(e=>self[e]=self[e]||(l=>{let t,d={},s=[],f=document,i=f.createTreeWalker(f,129),n=self[e][l]={i:l=e+l,d:f,l:d,v:s,x(){},w(e){for(;e=i.nextNode();)this.x(n=(n=e.data)&&!n.indexOf(l)&&(d[t=n.slice(x+1)]=e,n[x]),t,e),n>"#"&&s.push(e)}},x=l.length}))`;
export const REORDER_RUNTIME_CODE = MARKO_DEBUG
- ? `((runtime) => {
-let insertOne,
+ ? /* js */ `((runtime) => {
+let onNextSibling,
placeholder,
nextSibling,
- previousSibling,
placeholders = {},
replace = (marker, container) => {
marker.replaceWith(...container.childNodes);
@@ -53,21 +52,19 @@ runtime.d.head.append(
);
runtime.j = {};
runtime.x = (op, id, node, start, placeholderCallback) => {
- // "node" and "end" are all closed over and can't be repurposed. "start" is too but only in the new placeholder case
-
if (op == "#") {
(placeholders[id] = placeholder).i++;
} else if (node == nextSibling) {
- insertOne();
+ onNextSibling();
}
if (node.tagName == "T" && (id = node.getAttribute(runtime.i))) {
start = runtime.l["^" + id];
if (start) {
- placeholder = placeholders[id] = {
- i: 0,
- c(end = runtime.l[id] || previousSibling || node) {
+ placeholders[id] = {
+ i: 1,
+ c(end = runtime.l[id] || node) {
while (end.parentNode !== start.parentNode) {
end = end.parentNode;
}
@@ -80,27 +77,23 @@ runtime.x = (op, id, node, start, placeholderCallback) => {
replace(start, node);
},
};
- } else {
- insertOne = () => {
- previousSibling = node.previousSibling;
- replace(runtime.l[id], node);
- if (!--start.i) {
- start.c();
- }
- };
-
- // repurpose "start" to hold this placeholder
- start = placeholder = placeholders[id];
- nextSibling = node.nextElementSibling || insertOne();
}
- // repurpose "op" for callbacks ...carefully
- placeholderCallback = placeholder.c;
- (op = runtime.j[id]) &&
- (placeholder.c = () => placeholderCallback() + op(runtime));
+ nextSibling = node.nextSibling;
+ placeholder = placeholders[id];
+ onNextSibling = () => {
+ start || replace(runtime.l[id], node);
+ if (!--placeholder.i) {
+ placeholder.c();
+ }
+ };
- if (node.attributes.c) placeholder.c();
+ // repurpose "op" for callbacks ...carefully
+ if (op = runtime.j[id]) {
+ placeholderCallback = placeholder.c;
+ placeholder.c = () => placeholderCallback() + op(runtime);
+ }
}
};
})`
- : `(e=>{let i,t,r,l,d={},n=(e,i)=>{e.replaceWith(...i.childNodes),i.remove()};e.d.head.append(e.d.querySelector("style["+e.i+"]")||""),e.j={},e.x=(o,a,c,p,b)=>{"#"==o?(d[a]=t).i++:c==r&&i(),"T"==c.tagName&&(a=c.getAttribute(e.i))&&((p=e.l["^"+a])?t=d[a]={i:0,c(i=e.l[a]||l||c){for(;i.parentNode!==p.parentNode;)i=i.parentNode;for(;i!=r;(r=p.nextSibling).remove());n(p,c)}}:(i=()=>{l=c.previousSibling,n(e.l[a],c),--p.i||p.c()},p=t=d[a],r=c.nextElementSibling||i()),b=t.c,(o=e.j[a])&&(t.c=()=>b()+o(e)),c.attributes.c&&t.c())}})`;
+ : `(e=>{let i,t,r,l={},o=(e,i)=>{e.replaceWith(...i.childNodes),i.remove()};e.d.head.append(e.d.querySelector("style["+e.i+"]")||""),e.j={},e.x=(d,n,a,c,p)=>{"#"==d?(l[n]=t).i++:a==r&&i(),"T"==a.tagName&&(n=a.getAttribute(e.i))&&((c=e.l["^"+n])&&(l[n]={i:1,c(i=e.l[n]||a){for(;i.parentNode!==c.parentNode;)i=i.parentNode;for(;i!=r;(r=c.nextSibling).remove());o(c,a)}}),r=a.nextSibling,t=l[n],i=()=>{c||o(e.l[n],a),--t.i||t.c()},(d=e.j[n])&&(p=t.c,t.c=()=>p()+d(e)))}})`;
diff --git a/packages/runtime-tags/src/html/writer.ts b/packages/runtime-tags/src/html/writer.ts
index 2cc30fac0e..4abd31d6a9 100644
--- a/packages/runtime-tags/src/html/writer.ts
+++ b/packages/runtime-tags/src/html/writer.ts
@@ -610,7 +610,6 @@ export function prepareChunk(chunk: Chunk) {
for (const reorderedChunk of state.writeReorders) {
const { reorderId } = reorderedChunk;
- let isSync = true;
let reorderHTML = "";
let reorderEffects = "";
let reorderScripts = "";
@@ -618,7 +617,9 @@ export function prepareChunk(chunk: Chunk) {
reorderedChunk.reorderId = null;
for (;;) {
+ const { next } = cur;
cur.flushPlaceholder();
+ cur.consumed = true;
reorderHTML += cur.html;
reorderEffects = concatEffects(reorderEffects, cur.effects);
reorderScripts = concatScripts(reorderScripts, cur.scripts);
@@ -629,11 +630,11 @@ export function prepareChunk(chunk: Chunk) {
(cur.reorderId = state.nextReorderId()),
);
cur.html = cur.effects = cur.scripts = "";
- isSync = false;
+ cur.next = null;
}
- if (cur.next) {
- cur = cur.next;
+ if (next) {
+ cur = next;
} else {
break;
}
@@ -668,7 +669,6 @@ export function prepareChunk(chunk: Chunk) {
html +=
"