Skip to content
Merged
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
84 changes: 31 additions & 53 deletions src/runtime/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OwlError } from "../common/owl_error";
import { version } from "../version";
import { Component, ComponentConstructor, Props } from "./component";
import { Component, ComponentConstructor } from "./component";
import { ComponentNode, saveCurrent } from "./component_node";
import { handleError, nodeErrorHandlers } from "./rendering/error_handling";
import { Fiber, MountOptions, RootFiber } from "./rendering/fibers";
Expand All @@ -9,21 +9,20 @@ import { proxy, toRaw } from "./reactivity/proxy";
import { Scheduler } from "./rendering/scheduler";
import { TemplateSet, TemplateSetConfig } from "./template_set";
import { validateTarget } from "./utils";
import { GetProps } from "./props";

// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f

export interface Env {
[key: string]: any;
}
type ComponentInstance<C extends ComponentConstructor> = C extends new (...args: any) => infer T
? T
: never;

interface RootConfig<P, E> {
env?: E;
interface RootConfig<P> {
pluginManager?: PluginManager;
props?: P;
}

export interface AppConfig<E> extends TemplateSetConfig {
env?: E;
export interface AppConfig extends TemplateSetConfig {
name?: string;
pluginManager?: PluginManager;
test?: boolean;
Expand All @@ -47,27 +46,26 @@ declare global {

type MountTarget = HTMLElement | ShadowRoot;

interface Root<P extends Props, E, T extends abstract new (...args: any) => any = any> {
node: ComponentNode<P, E>;
promise: Promise<any>;
mount(target: MountTarget, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>>;
interface Root<T extends ComponentConstructor> {
node: ComponentNode;
promise: Promise<ComponentInstance<T>>;
mount(target: MountTarget, options?: MountOptions): Promise<ComponentInstance<T>>;
destroy(): void;
}

window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, proxy };

export class App<E = any> extends TemplateSet {
export class App extends TemplateSet {
static validateTarget = validateTarget;
static apps = apps;
static version = version;

name: string;
env: E;
scheduler = new Scheduler();
roots: Set<Root<any, any>> = new Set();
roots: Set<Root<any>> = new Set();
pluginManager: PluginManager;

constructor(config: AppConfig<E> = {}) {
constructor(config: AppConfig = {}) {
super(config);
this.name = config.name || "";
apps.add(this);
Expand All @@ -79,31 +77,18 @@ export class App<E = any> extends TemplateSet {
console.info(`Owl is running in 'dev' mode.`);
hasBeenLogged = true;
}
const env = config.env || {};
const descrs = Object.getOwnPropertyDescriptors(env);
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
}

createRoot<Props extends object, SubEnv = any>(
Root: ComponentConstructor<Props, E>,
config: RootConfig<Props, SubEnv> = {}
): Root<Props, SubEnv> {
const props = config.props || ({} as Props);
// hack to make sure the sub root get the sub env if necessary. for owl 3,
// would be nice to rethink the initialization process to make sure that
// we can create a ComponentNode and give it explicitely the env, instead
// of looking it up in the app
const env = this.env;
if (config.env) {
this.env = config.env as any;
}
createRoot<T extends ComponentConstructor>(
Root: T,
config: RootConfig<GetProps<ComponentInstance<T>>> = {}
): Root<T> {
const props = config.props || ({} as any);

const restore = saveCurrent();
const node = this.makeNode(Root, props);
restore();
if (config.env) {
this.env = env;
}

let resolve!: (value: any) => void;
let reject!: (reason?: any) => void;
const promise = new Promise<any>((res, rej) => {
Expand All @@ -128,7 +113,10 @@ export class App<E = any> extends TemplateSet {
return root;
}

makeNode(Component: ComponentConstructor, props: any): ComponentNode {
makeNode<T extends ComponentConstructor>(
Component: T,
props: GetProps<ComponentInstance<T>>
): ComponentNode {
return new ComponentNode(Component, props, this, null, null);
}

Expand Down Expand Up @@ -169,20 +157,20 @@ export class App<E = any> extends TemplateSet {
apps.delete(this);
}

createComponent<P extends Props>(
createComponent<P extends Record<string, any>>(
name: string | null,
isStatic: boolean,
hasSlotsProp: boolean,
hasDynamicPropList: boolean,
propList: string[]
) {
const isDynamic = !isStatic;
let arePropsDifferent: (p1: Object, p2: Object) => boolean;
let arePropsDifferent: (p1: P, p2: P) => boolean;
const hasNoProp = propList.length === 0;
if (hasSlotsProp) {
arePropsDifferent = (_1, _2) => true;
} else if (hasDynamicPropList) {
arePropsDifferent = function (props1: Props, props2: Props) {
arePropsDifferent = function (props1: P, props2: P) {
for (let k in props1) {
if (props1[k] !== props2[k]) {
return true;
Expand All @@ -193,7 +181,7 @@ export class App<E = any> extends TemplateSet {
} else if (hasNoProp) {
arePropsDifferent = (_1: any, _2: any) => false;
} else {
arePropsDifferent = function (props1: Props, props2: Props) {
arePropsDifferent = function (props1: P, props2: P) {
for (let p of propList) {
if (props1[p] !== props2[p]) {
return true;
Expand Down Expand Up @@ -250,20 +238,10 @@ export class App<E = any> extends TemplateSet {
}
}

type ComponentInstance<C extends ComponentConstructor<any, any>> = C extends new (
...args: any
) => infer T
? T
: never;

export async function mount<
T extends ComponentConstructor<any, any>,
P extends object = any,
E = any
>(
C: T & ComponentConstructor<P, E>,
export async function mount<T extends ComponentConstructor>(
C: T,
target: MountTarget,
config: AppConfig<E> & RootConfig<P, E> & MountOptions = {}
config: AppConfig & RootConfig<GetProps<ComponentInstance<T>>> & MountOptions = {}
): Promise<ComponentInstance<T>> {
const app = new App(config);
const root = app.createRoot(C, config);
Expand Down
14 changes: 4 additions & 10 deletions src/runtime/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@ import type { ComponentNode } from "./component_node";
// Component Class
// -----------------------------------------------------------------------------

export type Props = { [key: string]: any };

interface StaticComponentProperties {
template: string;
defaultProps?: any;
components?: { [componentName: string]: ComponentConstructor };
}

export interface ComponentConstructor<P extends Props = any, E = any>
extends StaticComponentProperties {
new (env: E, node: ComponentNode): Component<P, E>;
export interface ComponentConstructor extends StaticComponentProperties {
new (node: ComponentNode): Component;
}

export class Component<Props = any, Env = any> {
export class Component {
static template: string = "";
static defaultProps?: any;

__props: Props = {} as any; // TODO: remove. it's just to keep types for now
env: Env;
__owl__: ComponentNode;

constructor(env: Env, node: ComponentNode) {
this.env = env;
constructor(node: ComponentNode) {
this.__owl__ = node;
}

Expand Down
23 changes: 10 additions & 13 deletions src/runtime/component_node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OwlError } from "../common/owl_error";
import type { App, Env } from "./app";
import type { App } from "./app";
import { BDom, VNode } from "./blockdom";
import { Component, ComponentConstructor, Props } from "./component";
import { Component, ComponentConstructor } from "./component";
import { PluginManager } from "./plugins";
import {
Atom,
Expand Down Expand Up @@ -53,21 +53,20 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)

type LifecycleHook = Function;

export class ComponentNode<P extends Props = any, E = any> implements VNode<ComponentNode<P, E>> {
export class ComponentNode implements VNode<ComponentNode> {
el?: HTMLElement | Text | undefined;
app: App;
fiber: Fiber | null = null;
component: Component<P, E>;
component: Component;
bdom: BDom | null = null;
status: STATUS = STATUS.NEW;
forceNextRender: boolean = false;
parentKey: string | null;
name: string; // TODO: remove
props: P;
props: Record<string, any>;

renderFn: Function;
parent: ComponentNode | null;
childEnv: Env;
children: { [key: string]: ComponentNode } = Object.create(null);
refs: any = {};

Expand All @@ -83,8 +82,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
pluginManager: PluginManager;

constructor(
C: ComponentConstructor<P, E>,
props: P,
C: ComponentConstructor,
props: Record<string, any>,
app: App,
parent: ComponentNode | null,
parentKey: string | null
Expand All @@ -107,11 +106,9 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
applyDefaultProps(props, defaultProps);
}
this.props = props;
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
const previousComputation = getCurrentComputation();
setComputation(this.signalComputation);
this.component = new C(env, this);
this.component = new C(this);
const ctx = Object.assign(Object.create(this.component), { this: this.component });
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
this.component.setup();
Expand Down Expand Up @@ -239,7 +236,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.status = STATUS.DESTROYED;
}

async updateAndRender(props: P, parentFiber: Fiber) {
async updateAndRender(props: Record<string, any>, parentFiber: Fiber) {
props = Object.assign({}, props);
// update
const fiber = makeChildFiber(this, parentFiber);
Expand Down Expand Up @@ -330,7 +327,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.bdom!.moveBeforeDOMNode(node, parent);
}

moveBeforeVNode(other: ComponentNode<P, E> | null, afterNode: Node | null) {
moveBeforeVNode(other: ComponentNode | null, afterNode: Node | null) {
this.bdom!.moveBeforeVNode(other ? other.bdom : null, afterNode);
}

Expand Down
34 changes: 0 additions & 34 deletions src/runtime/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Env } from "./app";
import { getCurrent } from "./component_node";
import { onMounted, onPatched, onWillDestroy, onWillUnmount } from "./lifecycle_hooks";
import { PluginConstructor, PluginManager } from "./plugins";
Expand All @@ -24,39 +23,6 @@ export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el:
};
}

// -----------------------------------------------------------------------------
// useEnv and useSubEnv
// -----------------------------------------------------------------------------

/**
* This hook is useful as a building block for some customized hooks, that may
* need a reference to the env of the component calling them.
*/
export function useEnv<E extends Env>(): E {
return getCurrent().component.env as any;
}

function extendEnv(currentEnv: Object, extension: Object): Object {
const env = Object.create(currentEnv);
const descrs = Object.getOwnPropertyDescriptors(extension);
return Object.freeze(Object.defineProperties(env, descrs));
}

/**
* This hook is a simple way to let components use a sub environment. Note that
* like for all hooks, it is important that this is only called in the
* constructor method.
*/
export function useSubEnv(envExtension: Env) {
const node = getCurrent();
node.component.env = extendEnv(node.component.env as any, envExtension);
useChildSubEnv(envExtension);
}

export function useChildSubEnv(envExtension: Env) {
const node = getCurrent();
node.childEnv = extendEnv(node.childEnv, envExtension);
}
// -----------------------------------------------------------------------------
// useEffect
// -----------------------------------------------------------------------------
Expand Down
12 changes: 2 additions & 10 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,14 @@ export { Component } from "./component";
export type { ComponentConstructor } from "./component";
export { useComponent } from "./component_node";
export { props } from "./props";
export type { PropsValidation } from "./props";
export type { GetProps, PropsValidation } from "./props";
export { status } from "./status";
export { proxy, markRaw, toRaw } from "./reactivity/proxy";
export { untrack } from "./reactivity/computations";
export { signal } from "./reactivity/signal";
export { derived } from "./reactivity/derived";
export { effect } from "./reactivity/effect";
export {
useEffect,
useEnv,
useListener,
useRef,
useChildSubEnv,
useSubEnv,
usePlugins,
} from "./hooks";
export { useEffect, useListener, useRef, usePlugins } from "./hooks";
export { batched, EventBus, htmlEscape, whenReady, markup } from "./utils";
export {
onWillStart,
Expand Down
Loading