Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 43 additions & 26 deletions src/runtime/component_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OwlError } from "../common/owl_error";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
import { STATUS } from "./status";
import { batched, Callback } from "./utils";
import { batched, Callback, possiblySync } from "./utils";

let currentNode: ComponentNode | null = null;

Expand Down Expand Up @@ -128,21 +128,29 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.initiateRender(fiber);
}

async initiateRender(fiber: Fiber | MountFiber) {
initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber;
if (this.mounted.length) {
fiber.root!.mounted.push(fiber);
}
const component = this.component;
try {
await Promise.all(this.willStart.map((f) => f.call(component)));
} catch (e) {
this.app.handleError({ node: this, error: e });
return;
}
if (this.status === STATUS.NEW && this.fiber === fiber) {
fiber.render();
}
return possiblySync(
() => {
const willStartResults = this.willStart.map((f) => f.call(component));
if (willStartResults.some((r) => typeof r?.then === "function")) {
return Promise.all(willStartResults);
}
return;
},
() => {
if (this.status === STATUS.NEW && this.fiber === fiber) {
fiber.render();
}
},
(e: any) => {
this.app.handleError({ node: this, error: e });
}
);
}

async render(deep: boolean) {
Expand Down Expand Up @@ -238,7 +246,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.status = STATUS.DESTROYED;
}

async updateAndRender(props: P, parentFiber: Fiber) {
updateAndRender(props: P, parentFiber: Fiber) {
this.nextProps = props;
props = Object.assign({}, props);
// update
Expand All @@ -258,20 +266,29 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
}
currentNode = null;
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
if (fiber !== this.fiber) {
return;
}
component.props = props;
fiber.render();
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
}
return possiblySync(
() => {
const willUpdateProps = this.willUpdateProps.map((f) => f.call(component, props));
if (willUpdateProps.some((p) => typeof p?.then === "function")) {
return Promise.all(willUpdateProps);
}
return;
},
() => {
if (fiber !== this.fiber) {
return;
}
component.props = props;
fiber.render();
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
}
}
);
}

/**
Expand Down
21 changes: 21 additions & 0 deletions src/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,24 @@ export class Markup extends String {}
export function markup(value: any) {
return new Markup(value);
}

export function possiblySync(computation: Function, onSuccess: Function, onError?: Function) {
try {
let result;
if (onError) {
try {
result = computation();
} catch (e) {
return onError(e);
}
} else {
result = computation();
}
if (typeof result?.then === "function") {
return result.then(onSuccess, onError);
}
return onSuccess(result);
} catch (e) {
return Promise.reject(e);
}
}
2 changes: 1 addition & 1 deletion tests/app/__snapshots__/app.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately
}"
`;

exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 2`] = `
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down
43 changes: 34 additions & 9 deletions tests/app/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useLogLifecycle,
makeDeferred,
nextMicroTick,
steps,
} from "../helpers";

let fixture: HTMLElement;
Expand Down Expand Up @@ -123,23 +124,47 @@ describe("app", () => {

const app = new App(A);
const comp = await app.mount(fixture);
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged();
expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:setup",
"A:willStart",
"A:willRender",
"A:rendered",
"A:mounted",
]
`);

comp.state.value = true;
await nextTick();
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged();
expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
]
`);

// rerender to force the instantiation of a new B component (and cancelling the first)
comp.render();
await nextMicroTick();
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged();
expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
]
`);

app.destroy();
expect([
"A:willUnmount",
"B:willDestroy",
"A:willDestroy",
"B:willDestroy", // make sure the 2 B instances have been destroyed synchronously
]).toBeLogged();
expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willUnmount",
"B:willDestroy",
"A:willDestroy",
"B:willDestroy",
]
`);
});
});
38 changes: 19 additions & 19 deletions tests/components/__snapshots__/concurrency.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ exports[`changing state before first render does not trigger a render (with pare
}"
`;

exports[`changing state before first render does not trigger a render (with parent) 2`] = `
exports[`changing state before first render does not trigger a render (with parent) 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -254,7 +254,7 @@ exports[`components are not destroyed between animation frame 1`] = `
}"
`;

exports[`components are not destroyed between animation frame 2`] = `
exports[`components are not destroyed between animation frame 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand All @@ -268,7 +268,7 @@ exports[`components are not destroyed between animation frame 2`] = `
}"
`;

exports[`components are not destroyed between animation frame 3`] = `
exports[`components are not destroyed between animation frame 5`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -748,7 +748,7 @@ exports[`concurrent renderings scenario 10 2`] = `
}"
`;

exports[`concurrent renderings scenario 10 3`] = `
exports[`concurrent renderings scenario 10 4`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -993,7 +993,7 @@ exports[`concurrent renderings scenario 16 3`] = `
}"
`;

exports[`concurrent renderings scenario 16 4`] = `
exports[`concurrent renderings scenario 16 6`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1024,7 +1024,7 @@ exports[`creating two async components, scenario 1 1`] = `
}"
`;

exports[`creating two async components, scenario 1 2`] = `
exports[`creating two async components, scenario 1 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand All @@ -1038,7 +1038,7 @@ exports[`creating two async components, scenario 1 2`] = `
}"
`;

exports[`creating two async components, scenario 1 3`] = `
exports[`creating two async components, scenario 1 5`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1085,7 +1085,7 @@ exports[`creating two async components, scenario 2 2`] = `
}"
`;

exports[`creating two async components, scenario 2 3`] = `
exports[`creating two async components, scenario 2 5`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1133,7 +1133,7 @@ exports[`creating two async components, scenario 3 (patching in the same frame)
}"
`;

exports[`creating two async components, scenario 3 (patching in the same frame) 3`] = `
exports[`creating two async components, scenario 3 (patching in the same frame) 5`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1308,7 +1308,7 @@ exports[`delayed render does not go through when t-component value changed 2`] =
}"
`;

exports[`delayed render does not go through when t-component value changed 3`] = `
exports[`delayed render does not go through when t-component value changed 4`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1617,7 +1617,7 @@ exports[`destroyed component causes other soon to be destroyed component to rere
}"
`;

exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 2`] = `
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand All @@ -1628,7 +1628,7 @@ exports[`destroyed component causes other soon to be destroyed component to rere
}"
`;

exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 3`] = `
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 4`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1656,7 +1656,7 @@ exports[`destroying/recreating a subcomponent, other scenario 1`] = `
}"
`;

exports[`destroying/recreating a subcomponent, other scenario 2`] = `
exports[`destroying/recreating a subcomponent, other scenario 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1685,7 +1685,7 @@ exports[`destroying/recreating a subwidget with different props (if start is not
}"
`;

exports[`destroying/recreating a subwidget with different props (if start is not over) 2`] = `
exports[`destroying/recreating a subwidget with different props (if start is not over) 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1787,7 +1787,7 @@ exports[`rendering component again in next microtick 1`] = `
}"
`;

exports[`rendering component again in next microtick 2`] = `
exports[`rendering component again in next microtick 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down Expand Up @@ -1861,10 +1861,10 @@ exports[`renderings, destruction, patch, stuff, ... yet another variation 3`] =
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;

let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`);
let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`);

return function template(ctx, node, key = \\"\\") {
const b2 = text(\`D\`);
const b2 = text(\`C\`);
let hdlr1 = [ctx['increment'], ctx];
let txt1 = ctx['state'].val;
const b3 = block3([hdlr1, txt1]);
Expand All @@ -1878,10 +1878,10 @@ exports[`renderings, destruction, patch, stuff, ... yet another variation 4`] =
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;

let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`);
let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`);

return function template(ctx, node, key = \\"\\") {
const b2 = text(\`C\`);
const b2 = text(\`D\`);
let hdlr1 = [ctx['increment'], ctx];
let txt1 = ctx['state'].val;
const b3 = block3([hdlr1, txt1]);
Expand Down
2 changes: 1 addition & 1 deletion tests/components/__snapshots__/error_handling.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ exports[`can catch errors an error in onWillDestroy, variation 1`] = `
}"
`;

exports[`can catch errors an error in onWillDestroy, variation 2`] = `
exports[`can catch errors an error in onWillDestroy, variation 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
Expand Down
Loading