Skip to content
Open
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
67 changes: 34 additions & 33 deletions src/runtime/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PluginConstructor, PluginManager, startPlugins } from "./plugin_manager
import { GetProps } from "./props";
import { proxy, toRaw } from "./reactivity/proxy";
import { nodeErrorHandlers } from "./rendering/error_handling";
import { Fiber, MountOptions, RootFiber } from "./rendering/fibers";
import { Fiber, MountFiber, MountOptions, RootFiber } from "./rendering/fibers";
import { Scheduler } from "./rendering/scheduler";
import { Resource } from "./resource";
import { TemplateSet, TemplateSetConfig } from "./template_set";
Expand Down Expand Up @@ -45,7 +45,7 @@ declare global {
}
}

type MountTarget = HTMLElement | ShadowRoot;
import type { MountTarget } from "./blockdom";

interface Root<T extends ComponentConstructor> {
node: ComponentNode;
Expand Down Expand Up @@ -111,12 +111,42 @@ export class App extends TemplateSet {
const root = {
node: node!,
promise,
mount: (target: HTMLElement | ShadowRoot, options?: MountOptions) => {
mount: (target: MountTarget, options?: MountOptions) => {
if (error) {
return promise;
}
App.validateTarget(target);
this.mountNode(node, target, resolve, reject, options);

// Set up error handler and onMounted callback
let handlers = nodeErrorHandlers.get(node);
if (!handlers) {
handlers = [];
nodeErrorHandlers.set(node, handlers);
}
handlers.unshift((e, finalize) => {
const finalError = finalize();
reject(finalError);
});
node.mounted.push(() => {
resolve(node.component);
handlers!.shift();
});

const fiber = new MountFiber(node, target, options);
this.scheduler.addFiber(fiber);
if (node.willStart.length) {
node.initiateRender(fiber);
} else {
node.fiber = fiber;
if (node.mounted.length) {
fiber.root!.mounted.push(fiber);
}
try {
fiber.render();
} catch (e) {
reject(e);
}
}
return promise;
},
destroy: () => {
Expand All @@ -129,35 +159,6 @@ export class App extends TemplateSet {
return root;
}

private mountNode(
node: ComponentNode,
target: HTMLElement | ShadowRoot,
resolve: (c: any) => void,
reject: (e: any) => void,
options?: MountOptions
) {
// Manually add the last resort error handler on the node
let handlers = nodeErrorHandlers.get(node);
if (!handlers) {
handlers = [];
nodeErrorHandlers.set(node, handlers);
}

handlers.unshift((e, finalize) => {
const finalError = finalize();
reject(finalError);
});

// manually set a onMounted callback.
// that way, we are independant from the current node.
node.mounted.push(() => {
resolve(node.component);
handlers!.shift();
});

node.mountComponent(target, options);
}

destroy() {
for (let root of this.roots) {
root.destroy();
Expand Down
8 changes: 5 additions & 3 deletions src/runtime/blockdom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export { text, comment } from "./text";
export { html } from "./html";
export { createCatcher } from "./event_catcher";

export type MountTarget = HTMLElement | ShadowRoot;

export interface VNode<T = any> {
mount(parent: HTMLElement, afterNode: Node | null): void;
moveBeforeDOMNode(node: Node | null, parent?: HTMLElement): void;
mount(parent: MountTarget, afterNode: Node | null): void;
moveBeforeDOMNode(node: Node | null, parent?: MountTarget): void;
moveBeforeVNode(other: T | null, afterNode: Node | null): void;
patch(other: T, withBeforeRemove: boolean): void;
beforeRemove(): void;
Expand All @@ -25,7 +27,7 @@ export interface VNode<T = any> {

export type BDom = VNode<any>;

export function mount(vnode: VNode, fixture: HTMLElement, afterNode: Node | null = null) {
export function mount(vnode: VNode, fixture: MountTarget, afterNode: Node | null = null) {
vnode.mount(fixture, afterNode);
}

Expand Down
13 changes: 1 addition & 12 deletions src/runtime/component_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
setComputation,
} from "./reactivity/computations";
import { fibersInError, handleError } from "./rendering/error_handling";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./rendering/fibers";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber } from "./rendering/fibers";
import { STATUS } from "./status";

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -85,17 +85,6 @@ export class ComponentNode implements VNode<ComponentNode> {
contextStack.length = 0; // clear context stack
}

mountComponent(target: any, options?: MountOptions) {
const fiber = new MountFiber(this, target, options);
this.app.scheduler.addFiber(fiber);
let prev = getCurrentComputation();
this.initiateRender(fiber);
// only useful if the component is a root, and a willstart function just
// crashed synchonously. In that case, it is possible that the previous
// computation has not been properly restored
setComputation(prev);
}

async initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber;
if (this.mounted.length) {
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/rendering/fibers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OwlError } from "../../common/owl_error";
import { BDom, mount } from "../blockdom";
import { BDom, mount, type MountTarget } from "../blockdom";
import type { ComponentNode } from "../component_node";
import { getCurrentComputation, removeSources, setComputation } from "../reactivity/computations";
import { STATUS } from "../status";
Expand Down Expand Up @@ -235,10 +235,10 @@ export interface MountOptions {
}

export class MountFiber extends RootFiber {
target: HTMLElement;
target: MountTarget;
position: Position;

constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) {
constructor(node: ComponentNode, target: MountTarget, options: MountOptions = {}) {
super(node, null);
this.target = target;
this.position = options.position || "last-child";
Expand Down
4 changes: 2 additions & 2 deletions tests/app/sub_root.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ test("destroy a subroot while another component is mounted in main app", async (

const app = new App();
const comp = await app.createRoot(SomeComponent).mount(fixture);
expect(fixture.innerHTML).toBe("a<div></div>");
await nextTick();
// With the sync fast path, the sub-root's child C (no willStart) renders
// synchronously during onMounted, so it's already in the DOM.
expect(fixture.innerHTML).toBe("a<div>c</div>");
comp.state.flag = true;
await nextTick();
Expand Down
60 changes: 24 additions & 36 deletions tests/components/error_handling.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { App, Component, mount, onWillDestroy, props, types } from "../../src";
import { OwlError } from "../../src/common/owl_error";
import {
onError,
onMounted,
Expand Down Expand Up @@ -319,17 +318,14 @@ describe("errors and promises", () => {
}
}

const app = new App();
let error: OwlError;
const mountProm = app
.createRoot(Root)
.mount(fixture)
.catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"[Owl] Unhandled error. Destroying the root component"
);
await mountProm;
expect(error!).toBeDefined();
let error: any;
try {
await mount(Root, fixture);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("[Owl] Unhandled error. Destroying the root component");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toHaveBeenCalledTimes(0);
expect(mockConsoleWarn).toHaveBeenCalledTimes(0);
Expand All @@ -345,18 +341,14 @@ describe("errors and promises", () => {
}
}

const app = new App({ test: true });
let error: OwlError;
const mountProm = app
.createRoot(Root)
.mount(fixture)
.catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"[Owl] Unhandled error. Destroying the root component"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause.stack).toContain("error_handling.test.ts");
let error: any;
try {
await mount(Root, fixture, { test: true });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.cause.stack).toContain("error_handling.test.ts");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toHaveBeenCalledTimes(0);
expect(mockConsoleWarn).toHaveBeenCalledTimes(0);
Expand All @@ -373,18 +365,14 @@ describe("errors and promises", () => {
}
}

const app = new App({ test: true });
let error: OwlError;
const mountProm = app
.createRoot(Root)
.mount(fixture)
.catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"[Owl] Unhandled error. Destroying the root component"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause.message).toBe(`boom in onWillStart`);
let error: any;
try {
await mount(Root, fixture, { test: true });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.cause.message).toBe("boom in onWillStart");
});

test("an error in willPatch call will reject the render promise", async () => {
Expand Down
Loading
Loading