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 @@ + + + +Increment 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; };