From f058983081e28f7740319055af7655caf9e0d6d2 Mon Sep 17 00:00:00 2001
From: dpiercey
Date: Wed, 1 Jan 2025 07:58:48 -0700
Subject: [PATCH 1/2] feat: add await tag ssr
---
.changeset/few-shirts-raise.md | 5 +
.../__snapshots__/dom.expected/template.js | 26 +++
.../__snapshots__/html.expected/template.js | 34 ++++
.../__snapshots__/ssr-sanitized.expected.md | 9 +
.../await-tag/__snapshots__/ssr.expected.md | 76 +++++++++
.../fixtures/await-tag/template.marko | 22 +++
.../src/__tests__/fixtures/await-tag/test.ts | 7 +
.../runtime-tags/src/translator/core/await.ts | 159 ++++++++++++++++++
.../runtime-tags/src/translator/core/index.ts | 2 +
.../src/translator/util/references.ts | 2 +-
packages/runtime-tags/tag-types/await.d.marko | 8 +
11 files changed, 349 insertions(+), 1 deletion(-)
create mode 100644 .changeset/few-shirts-raise.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/dom.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/html.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr-sanitized.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/await-tag/template.marko
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/await-tag/test.ts
create mode 100644 packages/runtime-tags/src/translator/core/await.ts
create mode 100644 packages/runtime-tags/tag-types/await.d.marko
diff --git a/.changeset/few-shirts-raise.md b/.changeset/few-shirts-raise.md
new file mode 100644
index 0000000000..d105fe310b
--- /dev/null
+++ b/.changeset/few-shirts-raise.md
@@ -0,0 +1,5 @@
+---
+"@marko/runtime-tags": patch
+---
+
+Add await tag ssr.
diff --git a/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/dom.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/dom.expected/template.js
new file mode 100644
index 0000000000..0c57e9bc3e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/dom.expected/template.js
@@ -0,0 +1,26 @@
+export const _template_ = "Inc
";
+export const _walks_ = /* next(1), get, out(1) */"D l";
+import { resolveAfter } from "../../utils/resolve";
+import * as _$ from "@marko/runtime-tags/debug/dom";
+const _value$await_content3 = /* @__PURE__ */_$.value("value", (_scope, value) => _$.data(_scope["#text/0"], value));
+const _params_4$await_content = /* @__PURE__ */_$.value("_params_4", (_scope, _params_4) => _value$await_content3(_scope, _params_4[0]));
+const _count$await_content3 = /* @__PURE__ */_$.closure("count", (_scope, count) => _$.data(_scope["#text/1"], count));
+const _await_content3 = _$.register("__tests__/template.marko_3_renderer", /* @__PURE__ */_$.createRenderer("Got: ", /* over(1), replace, over(2), replace */"b%c%", void 0, () => [_count$await_content3], () => _params_4$await_content));
+const _value$await_content2 = /* @__PURE__ */_$.value("value", (_scope, value) => _$.data(_scope["#text/0"], value));
+const _params_3$await_content = /* @__PURE__ */_$.value("_params_3", (_scope, _params_3) => _value$await_content2(_scope, _params_3[0]));
+const _count$await_content2 = /* @__PURE__ */_$.closure("count", (_scope, count) => _$.data(_scope["#text/1"], count));
+const _await_content2 = _$.register("__tests__/template.marko_2_renderer", /* @__PURE__ */_$.createRenderer("Got: ", /* over(1), replace, over(2), replace */"b%c%", void 0, () => [_count$await_content2], () => _params_3$await_content));
+const _value$await_content = /* @__PURE__ */_$.value("value", (_scope, value) => _$.data(_scope["#text/0"], value));
+const _params_2$await_content = /* @__PURE__ */_$.value("_params_2", (_scope, _params_2) => _value$await_content(_scope, _params_2[0]));
+const _count$await_content = /* @__PURE__ */_$.closure("count", (_scope, count) => _$.data(_scope["#text/1"], count));
+const _await_content = _$.register("__tests__/template.marko_1_renderer", /* @__PURE__ */_$.createRenderer("Got: ", /* over(1), replace, over(2), replace */"b%c%", void 0, () => [_count$await_content], () => _params_2$await_content));
+const _count_effect = _$.effect("__tests__/template.marko_0_count", (_scope, {
+ count
+}) => _$.on(_scope["#button/0"], "click", function () {
+ _count(_scope, count + 1), count;
+}));
+const _count = /* @__PURE__ */_$.state("count", (_scope, count) => _count_effect(_scope), () => _$.intersections([_count$await_content, _count$await_content2, _count$await_content3]));
+export function _setup_(_scope) {
+ _count(_scope, 0);
+}
+export default /* @__PURE__ */_$.createTemplate("__tests__/template.marko", _template_, _walks_, _setup_);
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/html.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/html.expected/template.js
new file mode 100644
index 0000000000..0ee138faeb
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/html.expected/template.js
@@ -0,0 +1,34 @@
+import { resolveAfter } from "../../utils/resolve";
+import * as _$ from "@marko/runtime-tags/debug/html";
+const _renderer = /* @__PURE__ */_$.createRenderer((input, _tagVar) => {
+ const _scope0_id = _$.nextScopeId();
+ const count = 0;
+ _$.write("");
+ _$.fork(Promise.resolve("a"), value => {
+ const _scope1_id = _$.nextScopeId();
+ _$.write(`Got: ${_$.escapeXML(value)} ${_$.escapeXML(count)}${_$.markResumeNode(_scope1_id, "#text/1")}`);
+ _$.writeScope(_scope1_id, {
+ "_": _$.ensureScopeWithId(_scope0_id)
+ });
+ });
+ _$.fork(resolveAfter("b", 2), value => {
+ const _scope2_id = _$.nextScopeId();
+ _$.write(`Got: ${_$.escapeXML(value)} ${_$.escapeXML(count)}${_$.markResumeNode(_scope2_id, "#text/1")}`);
+ _$.writeScope(_scope2_id, {
+ "_": _$.ensureScopeWithId(_scope0_id)
+ });
+ });
+ _$.fork(resolveAfter("c", 1), value => {
+ const _scope3_id = _$.nextScopeId();
+ _$.write(`Got: ${_$.escapeXML(value)} ${_$.escapeXML(count)}${_$.markResumeNode(_scope3_id, "#text/1")}`);
+ _$.writeScope(_scope3_id, {
+ "_": _$.ensureScopeWithId(_scope0_id)
+ });
+ });
+ _$.write(`Inc ${_$.markResumeNode(_scope0_id, "#button/0")}
`);
+ _$.writeEffect(_scope0_id, "__tests__/template.marko_0_count");
+ _$.writeScope(_scope0_id, {
+ "count": count
+ });
+});
+export default /* @__PURE__ */_$.createTemplate("__tests__/template.marko", _renderer);
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr-sanitized.expected.md
new file mode 100644
index 0000000000..737b6f378d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr-sanitized.expected.md
@@ -0,0 +1,9 @@
+# Render "End"
+```html
+
+ Got: a 0Got: b 0Got: c 0
+
+ Inc
+
+
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr.expected.md
new file mode 100644
index 0000000000..c4729dbe04
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/await-tag/__snapshots__/ssr.expected.md
@@ -0,0 +1,76 @@
+# Write
+ Got: a 0
+
+
+# Write
+
+
+
+# Write
+ Got: b 0Got: c 0Inc
+
+
+# Render "End"
+```html
+
+
+
+
+ Got: a
+
+ 0
+
+
+
+ Got: b
+
+ 0
+
+ Got: c
+
+ 0
+
+
+ Inc
+
+
+
+
+
+
+```
+
+# Mutations
+```
+inserted #document/html0
+inserted #document/html0/head0
+inserted #document/html0/body1
+inserted #document/html0/body1/div0
+inserted #document/html0/body1/div0/#text0
+inserted #document/html0/body1/div0/#comment1
+inserted #document/html0/body1/div0/#text2
+inserted #document/html0/body1/div0/#comment3
+inserted #document/html0/body1/div0/script4
+inserted #document/html0/body1/div0/script4/#text0
+inserted #document/html0/body1/div0/script5
+inserted #document/html0/body1/div0/script5/#text0
+inserted #document/html0/body1/div0/#text6
+inserted #document/html0/body1/div0/#comment7
+inserted #document/html0/body1/div0/#text8
+inserted #document/html0/body1/div0/#comment9
+inserted #document/html0/body1/div0/#text10
+inserted #document/html0/body1/div0/#comment11
+inserted #document/html0/body1/div0/#text12
+inserted #document/html0/body1/div0/#comment13
+inserted #document/html0/body1/div0/button14
+inserted #document/html0/body1/div0/button14/#text0
+inserted #document/html0/body1/div0/#comment15
+inserted #document/html0/body1/script1
+inserted #document/html0/body1/script1/#text0
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/await-tag/template.marko b/packages/runtime-tags/src/__tests__/fixtures/await-tag/template.marko
new file mode 100644
index 0000000000..cc846f16d6
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/await-tag/template.marko
@@ -0,0 +1,22 @@
+import { resolveAfter } from "../../utils/resolve";
+
+
+
+
+ Got: ${value} ${count}
+
+
+
+ Got: ${value} ${count}
+
+
+
+ Got: ${value} ${count}
+
+
+
+ Inc
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/await-tag/test.ts b/packages/runtime-tags/src/__tests__/fixtures/await-tag/test.ts
new file mode 100644
index 0000000000..ab029d1de9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/await-tag/test.ts
@@ -0,0 +1,7 @@
+export const skip_csr = true;
+
+export const steps = [{}, click, click, click];
+
+function click(container: Element) {
+ container.querySelector("button")!.click();
+}
diff --git a/packages/runtime-tags/src/translator/core/await.ts b/packages/runtime-tags/src/translator/core/await.ts
new file mode 100644
index 0000000000..66f30588e2
--- /dev/null
+++ b/packages/runtime-tags/src/translator/core/await.ts
@@ -0,0 +1,159 @@
+import { types as t } from "@marko/compiler";
+import {
+ assertNoArgs,
+ assertNoAttributeTags,
+ assertNoVar,
+ type Tag,
+} from "@marko/compiler/babel-utils";
+
+import { assertNoSpreadAttrs } from "../util/assert";
+import evaluate from "../util/evaluate";
+import { isStatefulReferences } from "../util/is-stateful";
+import { BindingType, trackParamsReferences } from "../util/references";
+import { callRuntime } from "../util/runtime";
+import runtimeInfo from "../util/runtime-info";
+import {
+ checkStatefulClosures,
+ getOrCreateSection,
+ getSectionForBody,
+ setSectionParentIsOwner,
+} from "../util/sections";
+import {
+ setForceResumeScope,
+ setSubscriberBuilder,
+ writeHTMLResumeStatements,
+} from "../util/signals";
+import { toFirstExpressionOrBlock } from "../util/to-first-expression-or-block";
+import { translateByTarget } from "../util/visitors";
+import * as writer from "../util/writer";
+
+export default {
+ analyze(tag: t.NodePath) {
+ assertNoVar(tag);
+ assertNoArgs(tag);
+ assertNoSpreadAttrs(tag);
+ assertNoAttributeTags(tag);
+ const { node } = tag;
+ const [valueAttr] = node.attributes;
+
+ if (!valueAttr) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError("The `await` tag requires a value.");
+ }
+
+ if (
+ node.attributes.length > 1 ||
+ !t.isMarkoAttribute(valueAttr) ||
+ valueAttr.name !== "value"
+ ) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "The `await` tag only supports the `value` attribute.",
+ );
+ }
+
+ if (!node.body.body.length) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError("The `await` tag requires body content.");
+ }
+
+ if (
+ node.body.params.length &&
+ (node.body.params.length > 1 || t.isSpreadElement(node.body.params[0]))
+ ) {
+ throw tag
+ .get("name")
+ .buildCodeFrameError(
+ "The `await` tag only supports a single parameter.",
+ );
+ }
+
+ getOrCreateSection(tag);
+ trackParamsReferences(
+ tag.get("body"),
+ BindingType.derived,
+ undefined,
+ evaluate(valueAttr.value),
+ );
+ },
+ translate: translateByTarget({
+ html: {
+ enter(tag) {
+ const tagBody = tag.get("body");
+ const bodySection = getSectionForBody(tagBody);
+
+ if (!bodySection) {
+ tag.remove();
+ return;
+ }
+
+ setSectionParentIsOwner(bodySection, true);
+ writer.flushBefore(tag);
+ },
+ exit(tag) {
+ const { node } = tag;
+ const [valueAttr] = node.attributes;
+ const tagBody = tag.get("body");
+ const bodySection = getSectionForBody(tagBody)!;
+
+ if (
+ isStatefulReferences(valueAttr.extra?.referencedBindings) ||
+ checkStatefulClosures(bodySection, true)
+ ) {
+ setForceResumeScope(bodySection);
+ }
+
+ writer.flushInto(tag);
+ // TODO: this is a hack to get around the fact that we don't have a way to
+ // know if a scope requires dynamic subscriptions
+ setSubscriberBuilder(tag, (() => {}) as any);
+ writeHTMLResumeStatements(tagBody);
+
+ tag
+ .replaceWith(
+ t.expressionStatement(
+ callRuntime(
+ "fork",
+ valueAttr.value,
+ t.arrowFunctionExpression(
+ node.body.params,
+ toFirstExpressionOrBlock(node.body.body),
+ ),
+ ),
+ ),
+ )[0]
+ .skip();
+ },
+ },
+ dom: {
+ enter(tag) {
+ const tagBody = tag.get("body");
+ const bodySection = getSectionForBody(tagBody);
+
+ if (!bodySection) {
+ tag.remove();
+ return;
+ }
+
+ setSectionParentIsOwner(bodySection, true);
+ // TODO: this is a hack to get around the fact that we don't have a way to
+ // know if a scope requires dynamic subscriptions
+ setSubscriberBuilder(tag, (signal) => signal);
+ },
+ exit(tag) {
+ tag.remove();
+ },
+ },
+ }),
+ attributes: {},
+ autocomplete: [
+ {
+ description: "Use to consume asynchronous an data.",
+ descriptionMoreURL: "https://markojs.com/docs/core-tags/#await",
+ },
+ ],
+ types: runtimeInfo.name + "/tag-types/await.d.marko",
+} as Tag;
diff --git a/packages/runtime-tags/src/translator/core/index.ts b/packages/runtime-tags/src/translator/core/index.ts
index 311e63e4f7..0b460482fb 100644
--- a/packages/runtime-tags/src/translator/core/index.ts
+++ b/packages/runtime-tags/src/translator/core/index.ts
@@ -1,5 +1,6 @@
import runtimeInfo from "../util/runtime-info";
import AttrsTag from "./attrs";
+import AwaitTag from "./await";
import ClientTag from "./client";
import ConstTag from "./const";
import DebugTag from "./debug";
@@ -25,6 +26,7 @@ import StyleTag from "./style";
export default {
taglibId: runtimeInfo.taglibId,
"": AttrsTag,
+ "": AwaitTag,
"": ClientTag,
"": ConstTag,
"": DebugTag,
diff --git a/packages/runtime-tags/src/translator/util/references.ts b/packages/runtime-tags/src/translator/util/references.ts
index edba3ee8e0..d3fdfa3c39 100644
--- a/packages/runtime-tags/src/translator/util/references.ts
+++ b/packages/runtime-tags/src/translator/util/references.ts
@@ -191,7 +191,7 @@ export function trackParamsReferences(
body.scope,
section,
paramsBinding,
- undefined,
+ upstreamExpression,
i + "",
);
}
diff --git a/packages/runtime-tags/tag-types/await.d.marko b/packages/runtime-tags/tag-types/await.d.marko
new file mode 100644
index 0000000000..db3e2dc7c2
--- /dev/null
+++ b/packages/runtime-tags/tag-types/await.d.marko
@@ -0,0 +1,8 @@
+/** File for types only, not actual implementation **/
+
+export interface Input {
+ value: T;
+ content: Marko.Body<[Awaited]>
+}
+
+return=input.value
From 7b2b38ed6568ba1b5d1f48cbac52ce97df9a7138 Mon Sep 17 00:00:00 2001
From: dpiercey
Date: Thu, 9 Jan 2025 10:14:00 -0700
Subject: [PATCH 2/2] feat: add basic ssr try tag
---
.changeset/eight-poems-promise.md | 5 +
.../__snapshots__/html.expected/template.js | 2 +-
.../__snapshots__/resume.expected.md | 48 --
.../__snapshots__/ssr.expected.md | 26 +-
.../catch-single-reject-async/server.ts | 15 +-
.../catch-single-success-async/server.ts | 14 +-
.../catch-single-success-sync/server.ts | 14 +-
.../catch-single-throw-sync/server.ts | 14 +-
.../server.ts | 26 +-
.../__snapshots__/html.expected/template.js | 4 +-
.../__snapshots__/resume.expected.md | 9 -
.../__snapshots__/ssr.expected.md | 19 +-
.../__snapshots__/html.expected/template.js | 2 +-
.../__snapshots__/resume.expected.md | 21 +-
.../__snapshots__/ssr.expected.md | 38 +-
.../__snapshots__/html.expected/template.js | 16 +-
.../for-tag/__snapshots__/resume.expected.md | 249 +------
.../for-tag/__snapshots__/ssr.expected.md | 693 +++---------------
.../fixtures/placeholder-single/server.ts | 15 +-
.../fixtures/placeholder-skipped/server.ts | 15 +-
.../fixtures/placeholders-nested/server.ts | 28 +-
.../__snapshots__/html.expected/template.js | 21 +
.../__snapshots__/ssr-sanitized.expected.md | 4 +
.../__snapshots__/ssr.expected.md | 30 +
.../try-single-throw-sync/template.marko | 8 +
.../fixtures/try-single-throw-sync/test.ts | 1 +
packages/runtime-tags/src/html.ts | 3 +-
.../runtime-tags/src/html/inlined-runtimes.ts | 6 +-
packages/runtime-tags/src/html/writer.ts | 36 +-
.../src/translator/core/define.ts | 2 +
.../runtime-tags/src/translator/core/index.ts | 2 +
.../runtime-tags/src/translator/core/try.ts | 112 +++
.../src/translator/util/signals.ts | 1 -
packages/runtime-tags/tag-types/try.d.marko | 7 +
34 files changed, 465 insertions(+), 1041 deletions(-)
create mode 100644 .changeset/eight-poems-promise.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/try-single-throw-sync/__snapshots__/html.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/try-single-throw-sync/__snapshots__/ssr-sanitized.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/try-single-throw-sync/__snapshots__/ssr.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/try-single-throw-sync/template.marko
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/try-single-throw-sync/test.ts
create mode 100644 packages/runtime-tags/src/translator/core/try.ts
create mode 100644 packages/runtime-tags/tag-types/try.d.marko
diff --git a/.changeset/eight-poems-promise.md b/.changeset/eight-poems-promise.md
new file mode 100644
index 0000000000..afe77fb5b8
--- /dev/null
+++ b/.changeset/eight-poems-promise.md
@@ -0,0 +1,5 @@
+---
+"@marko/runtime-tags": patch
+---
+
+Add basic ssr try tag.
diff --git a/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js
index 1fdf2bfaf9..9c8b6e196f 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js
+++ b/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/html.expected/template.js
@@ -5,7 +5,7 @@ const _renderer = /* @__PURE__ */_$.createRenderer((input, _tagVar) => {
const _scope1_ = new Map();
_$.forOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], (num, _index) => {
const _scope1_id = _$.nextScopeId();
- _$.write(`${_$.escapeXML(num)}${_$.markResumeNode(_scope1_id, "#text/1")} ${_$.markResumeNode(_scope1_id, "#button/0")}`);
+ _$.write(`${_$.escapeXML(num)} ${_$.markResumeNode(_scope1_id, "#button/0")}`);
_$.writeEffect(_scope1_id, "__tests__/template.marko_1_num");
_$.writeScope(_scope1_id, {
"num": num,
diff --git a/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md b/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md
index 328cd53609..b58bef977b 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/resume.expected.md
@@ -5,62 +5,50 @@
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
+ 1 2 3 4 5 6 7 8 9 10 11 12
# Render "End"
@@ -9,62 +9,50 @@
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/for-tag-siblings/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/for-tag-siblings/__snapshots__/ssr.expected.md
index f4f98f0f0c..91cd3d34d4 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/for-tag-siblings/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/for-tag-siblings/__snapshots__/ssr.expected.md
@@ -1,5 +1,5 @@
# Write
-
+
# Render "End"
@@ -10,35 +10,26 @@
-