Skip to content

Commit

Permalink
feat(): support lifeCycle.onMount/onUnmount for control nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
weareoutman committed Aug 29, 2023
1 parent c034587 commit d6ef1b6
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 7 deletions.
2 changes: 2 additions & 0 deletions cypress/e2e/control-nodes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ for (const port of Cypress.env("ports")) {
["Hello", "prefix", "ForEach in ForEach <1>", "suffix", "Toggle"].join(
""
),
"[forEach mount] false 1",
]);

cy.get("@console.error").should("not.be.called");
Expand All @@ -190,6 +191,7 @@ for (const port of Cypress.env("ports")) {
["Hello", "prefix", "ForEach in ForEach <2>", "suffix", "Toggle"].join(
""
),
"[forEach mount] false 1, [forEach unmount] 2, [forEach mount] true 2",
]);
});
});
Expand Down
22 changes: 22 additions & 0 deletions mock-micro-apps/e2e/storyboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ routes:
- name: value
value: 1
- name: constant
- name: messages
value: []
bricks:
- brick: div
children:
Expand All @@ -407,6 +409,23 @@ routes:
textContent: prefix
- brick: :forEach
dataSource: <%= [CTX.value] %>
lifeCycle:
onMount:
action: context.replace
args:
- messages
- |
<%
CTX.messages.concat(`[forEach mount] ${EVENT.detail.rerender} ${CTX.value}`)
%>
onUnmount:
action: context.replace
args:
- messages
- |
<%
CTX.messages.concat(`[forEach unmount] ${CTX.value}`)
%>
children:
- brick: p
properties:
Expand All @@ -423,6 +442,9 @@ routes:
args:
- value
- <% 3 - CTX.value %>
- brick: p
properties:
textContent: <%= CTX.messages.join(", ") %>

# Sub-routes incremental rendering
- path: '${APP.homepage}/sub-routes/:sub'
Expand Down
27 changes: 20 additions & 7 deletions packages/runtime/src/internal/Renderer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,16 @@ describe("renderBrick for control nodes", () => {
portal: true,
},
],
lifeCycle: {
onMount: {
action: "console.info",
args: [":forEach mount", "<% EVENT.detail.rerender %>"],
},
onUnmount: {
action: "console.info",
args: [":forEach unmount"],
},
},
},
],
runtimeContext,
Expand All @@ -1014,9 +1024,10 @@ describe("renderBrick for control nodes", () => {
expect(consoleInfo).not.toBeCalled();
rendererContext.dispatchOnMount();
rendererContext.initializeScrollIntoView();
expect(consoleInfo).toBeCalledTimes(2);
expect(consoleInfo).toBeCalledTimes(3);
expect(consoleInfo).toHaveBeenNthCalledWith(1, "onMount", "mount", "a");
expect(consoleInfo).toHaveBeenNthCalledWith(2, "onMount", "mount", "b");
expect(consoleInfo).toHaveBeenNthCalledWith(3, ":forEach mount", false);

expect(container.innerHTML).toBe("<div>a</div><div>b</div>");
expect(portal.innerHTML).toBe("<p>portal:a</p><p>portal:b</p>");
Expand All @@ -1025,15 +1036,17 @@ describe("renderBrick for control nodes", () => {
expect(container.innerHTML).toBe('<div title="mark">a</div><div>b</div>');

ctxStore.updateValue("list", ["a", "c"], "replace");
expect(consoleInfo).toBeCalledTimes(2);
expect(consoleInfo).toBeCalledTimes(3);
// Wait for `_.debounce()`
await new Promise((resolve) => setTimeout(resolve, 0));

expect(consoleInfo).toBeCalledTimes(6);
expect(consoleInfo).toHaveBeenNthCalledWith(3, "onUnmount", "unmount", "a");
expect(consoleInfo).toHaveBeenNthCalledWith(4, "onUnmount", "unmount", "b");
expect(consoleInfo).toHaveBeenNthCalledWith(5, "onMount", "mount", "a");
expect(consoleInfo).toHaveBeenNthCalledWith(6, "onMount", "mount", "c");
expect(consoleInfo).toBeCalledTimes(9);
expect(consoleInfo).toHaveBeenNthCalledWith(4, ":forEach unmount");
expect(consoleInfo).toHaveBeenNthCalledWith(5, "onUnmount", "unmount", "a");
expect(consoleInfo).toHaveBeenNthCalledWith(6, "onUnmount", "unmount", "b");
expect(consoleInfo).toHaveBeenNthCalledWith(7, "onMount", "mount", "a");
expect(consoleInfo).toHaveBeenNthCalledWith(8, "onMount", "mount", "c");
expect(consoleInfo).toHaveBeenNthCalledWith(9, ":forEach mount", true);

// Note: previous `title="mark"` is removed
expect(container.innerHTML).toBe("<div>a</div><div>c</div>");
Expand Down
25 changes: 25 additions & 0 deletions packages/runtime/src/internal/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { getPreEvaluatedRaw } from "./compute/evaluate.js";
import { RuntimeBrickConfOfTplSymbols } from "./CustomTemplates/constants.js";
import { matchHomepage } from "./matchStoryboard.js";
import type { DataStore, DataStoreType } from "./data/DataStore.js";
import { listenerFactory } from "./bindListeners.js";

export interface RenderOutput {
node?: RenderBrick;
Expand Down Expand Up @@ -413,6 +414,7 @@ export async function renderBrick(
};

const controlledOutput = await renderControlNode(runtimeContext);
const { onMount, onUnmount } = brickConf.lifeCycle ?? {};

const { contextNames, stateNames } = getTracks(dataSource);
if (contextNames || stateNames) {
Expand All @@ -434,13 +436,27 @@ export async function renderBrick(

// Ignore stale renders
if (renderId === currentRenderId) {
if (onUnmount) {
listenerFactory(
onUnmount,
runtimeContext
)(new CustomEvent("unmount"));
}

rendererContext.reRender(
slotId,
keyPath,
controlOutput.node,
returnNode
);

if (onMount) {
listenerFactory(
onMount,
scopedRuntimeContext
)(new CustomEvent("mount", { detail: { rerender: true } }));
}

for (const store of scopedStores) {
store.mountAsyncData();
}
Expand All @@ -464,6 +480,15 @@ export async function renderBrick(
}
}

if (onMount) {
rendererContext.registerArbitraryLifeCycle("onMount", () => {
listenerFactory(
onMount,
runtimeContext
)(new CustomEvent("mount", { detail: { rerender: false } }));
});
}

return controlledOutput;
}

Expand Down

0 comments on commit d6ef1b6

Please sign in to comment.