From 8cc1c935efbcf1ddc385f263d23db4123eedf077 Mon Sep 17 00:00:00 2001
From: Mia Kanashi
Date: Fri, 14 Nov 2025 23:09:55 +0200
Subject: [PATCH] feat: implement return init from lifecycle onMount
---
.changeset/eager-pets-grin.md | 5 ++
.sizes.json | 4 +-
.sizes/dom.js | 12 ++--
.../__snapshots__/csr-sanitized.expected.md | 1 +
.../__snapshots__/csr.expected.md | 1 +
.../__snapshots__/dom.expected/template.js | 11 +++
.../__snapshots__/html.expected/template.js | 5 ++
.../template.marko | 6 ++
.../test.ts | 6 ++
.../__snapshots__/.name-cache.json | 10 +++
.../__snapshots__/csr-sanitized.expected.md | 29 ++++++++
.../__snapshots__/csr.expected.md | 38 +++++++++++
.../dom.expected/template.hydrate.js | 17 +++++
.../__snapshots__/dom.expected/template.js | 26 +++++++
.../__snapshots__/html.expected/template.js | 13 ++++
.../resume-sanitized.expected.md | 29 ++++++++
.../__snapshots__/resume.expected.md | 68 +++++++++++++++++++
.../__snapshots__/ssr-sanitized.expected.md | 11 +++
.../__snapshots__/ssr.expected.md | 46 +++++++++++++
.../lifecycle-tag-return-init/template.marko | 13 ++++
.../lifecycle-tag-return-init/test.ts | 5 ++
packages/runtime-tags/src/dom/dom.ts | 18 ++++-
packages/runtime-tags/tags/lifecycle.d.marko | 11 +--
23 files changed, 372 insertions(+), 13 deletions(-)
create mode 100644 .changeset/eager-pets-grin.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr-sanitized.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/dom.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/html.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/template.marko
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/test.ts
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/.name-cache.json
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr-sanitized.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.hydrate.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/html.expected/template.js
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume-sanitized.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr-sanitized.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr.expected.md
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/template.marko
create mode 100644 packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/test.ts
diff --git a/.changeset/eager-pets-grin.md b/.changeset/eager-pets-grin.md
new file mode 100644
index 0000000000..ef9d2122a4
--- /dev/null
+++ b/.changeset/eager-pets-grin.md
@@ -0,0 +1,5 @@
+---
+"@marko/runtime-tags": patch
+---
+
+Add ability for the lifecycle onMount hook to spread values into this object by returning.
diff --git a/.sizes.json b/.sizes.json
index eac3cc0fac..0c33b1fc55 100644
--- a/.sizes.json
+++ b/.sizes.json
@@ -7,8 +7,8 @@
{
"name": "*",
"total": {
- "min": 19644,
- "brotli": 7530
+ "min": 19674,
+ "brotli": 7562
}
},
{
diff --git a/.sizes/dom.js b/.sizes/dom.js
index bce5e42055..b7b446cb43 100644
--- a/.sizes/dom.js
+++ b/.sizes/dom.js
@@ -1,4 +1,4 @@
-// size: 19644 (min) 7530 (brotli)
+// size: 19674 (min) 7562 (brotli)
var empty = [],
rest = Symbol();
function attrTag(attrs) {
@@ -1134,11 +1134,13 @@ function normalizeString(value) {
}
function _lifecycle(scope, index, thisObj) {
let instance = scope[index];
- instance
- ? (Object.assign(instance, thisObj), instance.onUpdate?.())
- : ((scope[index] = thisObj),
- thisObj.onMount?.(),
+ if (instance) (Object.assign(instance, thisObj), instance.onUpdate?.());
+ else {
+ scope[index] = thisObj;
+ let newProps = thisObj.onMount?.();
+ (Object.assign(thisObj, newProps),
($signal(scope, "K" + index).onabort = () => thisObj.onDestroy?.()));
+ }
}
function removeChildNodes(startNode, endNode) {
let stop = endNode.nextSibling,
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr-sanitized.expected.md
new file mode 100644
index 0000000000..32a840a806
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr-sanitized.expected.md
@@ -0,0 +1 @@
+Tried to overwrite existing property "w" in $lifecycle onMount.
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr.expected.md
new file mode 100644
index 0000000000..32a840a806
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/csr.expected.md
@@ -0,0 +1 @@
+Tried to overwrite existing property "w" in $lifecycle onMount.
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/dom.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/dom.expected/template.js
new file mode 100644
index 0000000000..c235305980
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/dom.expected/template.js
@@ -0,0 +1,11 @@
+export const $template = "";
+export const $walks = "";
+import * as _ from "@marko/runtime-tags/debug/dom";
+const $setup__script = _._script("__tests__/template.marko_0", $scope => _._lifecycle($scope, "$lifecycle", {
+ w: 0,
+ onMount: function () {
+ this.w = 1;
+ }
+}));
+export const $setup = $setup__script;
+export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/html.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/html.expected/template.js
new file mode 100644
index 0000000000..7b28ee92e9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/__snapshots__/html.expected/template.js
@@ -0,0 +1,5 @@
+import * as _ from "@marko/runtime-tags/debug/html";
+export default _._template("__tests__/template.marko", input => {
+ const $scope0_id = _._scope_id();
+ _._script($scope0_id, "__tests__/template.marko_0");
+});
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/template.marko b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/template.marko
new file mode 100644
index 0000000000..82dc607aa5
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/template.marko
@@ -0,0 +1,6 @@
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/test.ts b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/test.ts
new file mode 100644
index 0000000000..974bf2f3f8
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-property-overwrite-error/test.ts
@@ -0,0 +1,6 @@
+import { wait } from "../../utils/resolve";
+
+export const steps = [{}, wait(1)];
+
+export const error_runtime = true;
+export const skip_ssr = true;
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/.name-cache.json b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/.name-cache.json
new file mode 100644
index 0000000000..aa121a254d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/.name-cache.json
@@ -0,0 +1,10 @@
+{
+ "vars": {
+ "props": {
+ "$_": "t",
+ "$init": "n",
+ "$$x__script": "o",
+ "$$x": "i"
+ }
+ }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr-sanitized.expected.md
new file mode 100644
index 0000000000..85641a6582
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr-sanitized.expected.md
@@ -0,0 +1,29 @@
+# Render
+```html
+
+
+```
+
+
+# Render
+```js
+container.querySelector("#increment")?.click();
+```
+```html
+
+ {"x":1,"w":1,"y":0,"u":5}
+
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr.expected.md
new file mode 100644
index 0000000000..2e0c529f6b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/csr.expected.md
@@ -0,0 +1,38 @@
+# Render
+```html
+
+
+```
+
+# Mutations
+```
+INSERT div, button
+```
+
+# Render
+```js
+container.querySelector("#increment")?.click();
+```
+```html
+
+ {"x":1,"w":1,"y":0,"u":5}
+
+
+```
+
+# Mutations
+```
+INSERT div/#text
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.hydrate.js b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.hydrate.js
new file mode 100644
index 0000000000..16d2aeb323
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.hydrate.js
@@ -0,0 +1,17 @@
+// size: 251 (min) 156 (brotli)
+const $x__script = _._script("a0", ($scope) => {
+ (_._lifecycle($scope, 2, {
+ x: $scope.b,
+ onMount: function () {
+ return ((this.w = 1), { y: this.x, u: 5 });
+ },
+ onUpdate: function () {
+ document.getElementById("ref").textContent = JSON.stringify(this);
+ },
+ }),
+ _._on($scope.a, "click", function () {
+ $x($scope, $scope.b + 1);
+ }));
+ }),
+ $x = _._let(1, $x__script);
+init();
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.js
new file mode 100644
index 0000000000..de574a04d9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/dom.expected/template.js
@@ -0,0 +1,26 @@
+export const $template = "";
+export const $walks = /* over(1), get, over(1) */"b b";
+import * as _ from "@marko/runtime-tags/debug/dom";
+const $x__script = _._script("__tests__/template.marko_0_x", $scope => {
+ _._lifecycle($scope, "$lifecycle", {
+ x: $scope.x,
+ onMount: function () {
+ this.w = 1;
+ return {
+ y: this.x,
+ u: 5
+ };
+ },
+ onUpdate: function () {
+ document.getElementById("ref").textContent = JSON.stringify(this);
+ }
+ });
+ _._on($scope["#button/0"], "click", function () {
+ $x($scope, $scope.x + 1);
+ });
+});
+const $x = /* @__PURE__ */_._let("x/1", $x__script);
+export function $setup($scope) {
+ $x($scope, 0);
+}
+export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/html.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/html.expected/template.js
new file mode 100644
index 0000000000..fb418ed17e
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/html.expected/template.js
@@ -0,0 +1,13 @@
+import * as _ from "@marko/runtime-tags/debug/html";
+export default _._template("__tests__/template.marko", input => {
+ const $scope0_id = _._scope_id();
+ let x = 0;
+ _._html(`${_._el_resume($scope0_id, "#button/0")}`);
+ _._script($scope0_id, "__tests__/template.marko_0_x");
+ _._scope($scope0_id, {
+ x
+ }, "__tests__/template.marko", 0, {
+ x: "1:6"
+ });
+ _._resume_branch($scope0_id);
+});
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume-sanitized.expected.md
new file mode 100644
index 0000000000..85641a6582
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume-sanitized.expected.md
@@ -0,0 +1,29 @@
+# Render
+```html
+
+
+```
+
+
+# Render
+```js
+container.querySelector("#increment")?.click();
+```
+```html
+
+ {"x":1,"w":1,"y":0,"u":5}
+
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume.expected.md
new file mode 100644
index 0000000000..61db6c5f3a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/resume.expected.md
@@ -0,0 +1,68 @@
+# Render
+```html
+
+
+
+
+
+
+
+
+
+```
+
+
+# Render
+```js
+container.querySelector("#increment")?.click();
+```
+```html
+
+
+
+ {"x":1,"w":1,"y":0,"u":5}
+
+
+
+
+
+
+```
+
+# Mutations
+```
+INSERT html/body/div/#text
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr-sanitized.expected.md
new file mode 100644
index 0000000000..63e5aced7b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr-sanitized.expected.md
@@ -0,0 +1,11 @@
+# Render End
+```html
+
+
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr.expected.md
new file mode 100644
index 0000000000..e77e2fc69c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/__snapshots__/ssr.expected.md
@@ -0,0 +1,46 @@
+# Write
+```html
+
+```
+
+# Render End
+```html
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+INSERT html
+INSERT html/head
+INSERT html/body
+INSERT html/body/div
+INSERT html/body/button
+INSERT html/body/button/#text
+INSERT html/body/#comment
+INSERT html/body/script
+INSERT html/body/script/#text
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/template.marko b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/template.marko
new file mode 100644
index 0000000000..2319f4c97c
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/template.marko
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/test.ts b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/test.ts
new file mode 100644
index 0000000000..d38ace115a
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/lifecycle-tag-return-init/test.ts
@@ -0,0 +1,5 @@
+export const steps = [{}, increment];
+
+function increment(container: Element) {
+ container.querySelector("#increment")?.click();
+}
diff --git a/packages/runtime-tags/src/dom/dom.ts b/packages/runtime-tags/src/dom/dom.ts
index 10472b01bd..3cbdbb4e77 100644
--- a/packages/runtime-tags/src/dom/dom.ts
+++ b/packages/runtime-tags/src/dom/dom.ts
@@ -386,7 +386,7 @@ export function _lifecycle(
scope: Scope,
index: string | number,
thisObj: Record & {
- onMount?: (this: unknown) => void;
+ onMount?: (this: unknown) => Record | void;
onUpdate?: (this: unknown) => void;
onDestroy?: (this: unknown) => void;
},
@@ -397,7 +397,21 @@ export function _lifecycle(
instance.onUpdate?.();
} else {
scope[index] = thisObj;
- thisObj.onMount?.();
+ let snapshot = undefined;
+ if (MARKO_DEBUG) {
+ snapshot = Object.assign({}, thisObj);
+ }
+ const newProps = thisObj.onMount?.();
+ Object.assign(thisObj, newProps);
+ if (MARKO_DEBUG) {
+ for (const [prop, prevVal] of Object.entries(snapshot!)) {
+ const nextVal = thisObj[prop];
+ if (Object.is(prevVal, nextVal)) continue;
+ throw new Error(
+ `Tried to overwrite existing property "${prop}" in ${index} onMount.`,
+ );
+ }
+ }
$signal(scope, AccessorPrefix.LifecycleAbortController + index).onabort =
() => thisObj.onDestroy?.();
}
diff --git a/packages/runtime-tags/tags/lifecycle.d.marko b/packages/runtime-tags/tags/lifecycle.d.marko
index b2f69e6968..9f3de0fe3e 100644
--- a/packages/runtime-tags/tags/lifecycle.d.marko
+++ b/packages/runtime-tags/tags/lifecycle.d.marko
@@ -1,7 +1,10 @@
/** File for types only, not actual implementation **/
-export type Input = T & {
- onMount?(): unknown;
- onUpdate?(): unknown;
- onDestroy?(): unknown;
+export type Input<
+ T extends object,
+ R extends object = {},
+> = T & ThisType & {
+ onMount?: () => R | void;
+ onUpdate?: () => void;
+ onDestroy?: () => void;
};