Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
c74c66a
up
Goaman Sep 26, 2025
8d12bf1
comment
Goaman Sep 26, 2025
adb9d40
up
Goaman Sep 26, 2025
2258066
up
Goaman Sep 26, 2025
836fdad
up
Goaman Sep 26, 2025
e31d195
up
Goaman Sep 26, 2025
4385969
up
Goaman Sep 26, 2025
0c1f9f3
fix for dropdown
Goaman Sep 28, 2025
ed9db6e
withoutReactivity
Goaman Sep 29, 2025
6f2600b
md
Goaman Sep 29, 2025
38574a1
no reactivity in setup
Goaman Sep 29, 2025
1ddf2ce
up
Goaman Oct 1, 2025
a309ec4
up
Goaman Oct 1, 2025
d0cdc48
up
Goaman Oct 2, 2025
00f8b21
up
Goaman Oct 6, 2025
5e1adf0
up
Goaman Oct 6, 2025
9d78c0b
up
Goaman Oct 6, 2025
e32dd50
up
Goaman Oct 6, 2025
704630b
up
Goaman Oct 6, 2025
3d080ff
up
Goaman Oct 7, 2025
a4bd6cf
reorganise
Goaman Oct 7, 2025
69cdf18
up
Goaman Oct 7, 2025
f505173
up
Goaman Oct 7, 2025
76884c6
up
Goaman Oct 7, 2025
cf018d3
up
Goaman Oct 7, 2025
f0d9a98
up
Goaman Oct 7, 2025
0066523
up
Goaman Oct 7, 2025
458817d
up
Goaman Oct 8, 2025
d2d3ac5
up
Goaman Oct 8, 2025
dde51e0
up
Goaman Oct 8, 2025
a507d62
up
Goaman Oct 8, 2025
83e438d
up
Goaman Oct 8, 2025
af7c21b
up
Goaman Oct 8, 2025
b731fef
up
Goaman Oct 9, 2025
8fd890c
up
Goaman Oct 9, 2025
c1c6310
up
Goaman Oct 9, 2025
ac0c8eb
up
Goaman Oct 13, 2025
75a08c7
up
Goaman Oct 14, 2025
2cd6ac9
up
Goaman Oct 20, 2025
3f505f6
up
Goaman Oct 20, 2025
bf09247
up
Goaman Oct 20, 2025
5d18332
add ability to draft
Goaman Oct 20, 2025
868639d
up
Goaman Oct 21, 2025
db2d8ed
up
Goaman Oct 21, 2025
a177f87
up
Goaman Oct 21, 2025
0beba35
up
Goaman Oct 21, 2025
c471ea3
up
Goaman Oct 21, 2025
e1a5da0
remove debugger
Goaman Oct 21, 2025
742b59c
up
Goaman Oct 21, 2025
84ef98f
up
Goaman Oct 21, 2025
7579558
up
Goaman Oct 21, 2025
83a2322
up
Goaman Oct 21, 2025
f09e8ce
up
Goaman Oct 21, 2025
8f29cd3
up
Goaman Oct 22, 2025
8f49baf
up
Goaman Oct 22, 2025
6a19c13
up
Goaman Oct 23, 2025
90e604d
up
Goaman Oct 28, 2025
5765eee
up
Goaman Oct 28, 2025
de7e486
up
Goaman Oct 29, 2025
18430bd
up
Goaman Oct 30, 2025
5cfa5d7
upup
Goaman Oct 30, 2025
713f66f
up
Goaman Oct 30, 2025
8f4f789
up
Goaman Nov 4, 2025
76c820e
up
Goaman Nov 4, 2025
0be6c5c
up
Goaman Nov 4, 2025
6b66b07
up
Goaman Nov 4, 2025
7ef313d
up
Goaman Nov 5, 2025
5fd0397
up
Goaman Nov 5, 2025
b897d65
up
Goaman Nov 5, 2025
addc179
up
Goaman Nov 5, 2025
eabf194
up
Goaman Nov 5, 2025
45517c7
up
Goaman Nov 5, 2025
2370b75
up
Goaman Nov 6, 2025
2d2512f
up
Goaman Nov 6, 2025
259a21e
up
Goaman Nov 8, 2025
c464d4a
up
Goaman Nov 19, 2025
624ab87
up
Goaman Nov 19, 2025
ccb8fb0
[WIP] temp
Goaman Nov 19, 2025
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
10,342 changes: 6,611 additions & 3,731 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"build:devtools-chrome": "npm run dev:devtools-chrome -- --config-env=production",
"build:devtools-firefox": "npm run dev:devtools-firefox -- --config-env=production",
"test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
"test:debug": "node node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
"test:watch": "jest --watch",
"playground:serve": "python3 tools/playground_server.py || python tools/playground_server.py",
"playground": "npm run build && npm run playground:serve",
Expand Down
18 changes: 18 additions & 0 deletions signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# encountered issues
## dropdown issue
- there was a problem that writing in a state while the effect was updated.
- the tracking of signal being written were dropped because we cleared it
after re-running the effect that made a write.
- solution: clear the tracked signal before re-executing the effects
- reading signal A while also writing signal A makes an infinite loop
- current solution: use toRaw in order to not track the read
- possible better solution to explore: do not track read if there is a write in a effect.
## website issue
- a rpc request was made on onWillStart, onWillStart was tracking reads. (see WebsiteBuilderClientAction)
- The read subsequently made a write, that re-triggered the onWillStart.
- A similar situation happened with onWillUpdateProps (see Transition)
- solution: prevent tracking reads in onWillStart and onWillUpdateProps

# future
- worker for computation?
- cap'n web
24 changes: 24 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
export type ExecutionContext = {
onReadAtom: (atom: Atom) => void;
unsubcribe?: (scheduledContexts: Set<ExecutionContext>) => void;
update?: Function;
atoms?: Set<Atom>;
meta?: any;
// getParent: () => ExecutionContext | undefined;
// getChildren: () => ExecutionContext[];
// schedule: () => void;
};

export type customDirectives = Record<
string,
(node: Element, value: string, modifier: string[]) => void
>;

export type Atom = {
executionContexts: Set<ExecutionContext>;
dependents: Set<DerivedAtom>;
getValue: () => any;
};

export type OldValue = any;

export type DerivedAtom = Atom & {
dependencies: Map<Atom, OldValue>;
computed: boolean;
};
46 changes: 46 additions & 0 deletions src/runtime/cancellableContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export type TaskContext = { isCancelled: boolean; cancel: () => void; meta: Record<string, any> };

export const taskContextStack: TaskContext[] = [];

export function getTaskContext() {
return taskContextStack[taskContextStack.length - 1];
}

export function makeTaskContext(): TaskContext {
let isCancelled = false;
return {
get isCancelled() {
return isCancelled;
},
cancel() {
isCancelled = true;
},
meta: {},
};
}

export function useTaskContext(ctx?: TaskContext) {
ctx ??= makeTaskContext();
taskContextStack.push(ctx);
return {
ctx,
cleanup: () => {
taskContextStack.pop();
},
};
}

export function pushTaskContext(context: TaskContext) {
taskContextStack.push(context);
}

export function popTaskContext() {
taskContextStack.pop();
}

export function taskEffect(fn: Function) {
const { ctx, cleanup } = useTaskContext();
fn();
cleanup();
return ctx;
}
79 changes: 46 additions & 33 deletions src/runtime/component_node.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { OwlError } from "../common/owl_error";
import { Atom, ExecutionContext } from "../common/types";
import type { App, Env } from "./app";
import { BDom, VNode } from "./blockdom";
import { makeTaskContext, TaskContext } from "./cancellableContext";
import { Component, ComponentConstructor, Props } from "./component";
import { fibersInError } from "./error_handling";
import { OwlError } from "../common/owl_error";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
import { addAtomToContext, reactive, targets, withoutReactivity } from "./reactivity";
import { STATUS } from "./status";
import { batched, Callback } from "./utils";

let currentNode: ComponentNode | null = null;

Expand Down Expand Up @@ -42,7 +43,7 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
// Integration with reactivity system (useState)
// -----------------------------------------------------------------------------

const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
// const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
Expand All @@ -54,15 +55,7 @@ const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
* @see reactive
*/
export function useState<T extends object>(state: T): T {
const node = getCurrent();
let render = batchedRenderFunctions.get(node)!;
if (!render) {
render = batched(node.render.bind(node, false));
batchedRenderFunctions.set(node, render);
// manual implementation of onWillDestroy to break cyclic dependency
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
}
return reactive(state, render);
return reactive(state);
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -96,6 +89,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
willPatch: LifecycleHook[] = [];
patched: LifecycleHook[] = [];
willDestroy: LifecycleHook[] = [];
taskContext: TaskContext;
executionContext: ExecutionContext;

constructor(
C: ComponentConstructor<P, E>,
Expand All @@ -109,23 +104,34 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.parent = parent;
this.props = props;
this.parentKey = parentKey;
this.taskContext = makeTaskContext();
this.executionContext = {
meta: this,
update: () => {
this.render(false);
},
onReadAtom: (atom: Atom) => addAtomToContext(atom, this.executionContext),
atoms: new Set<Atom>(),
};
const defaultProps = C.defaultProps;
props = Object.assign({}, props);
if (defaultProps) {
applyDefaultProps(props, defaultProps);
}
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
for (const key in props) {
const prop = props[key];
if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop);
}
}
// for (const key in props) {
// const prop = props[key];
// if (prop && typeof prop === "object" && targets.has(prop)) {
// props[key] = useState(prop);
// }
// }
this.component = new C(props, env, 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();
withoutReactivity(() => {
this.component.setup();
});
currentNode = null;
}

Expand All @@ -142,7 +148,11 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
const component = this.component;
try {
await Promise.all(this.willStart.map((f) => f.call(component)));
let prom: Promise<any[]>;
withoutReactivity(() => {
prom = Promise.all(this.willStart.map((f) => f.call(component)));
});
await prom!;
} catch (e) {
this.app.handleError({ node: this, error: e });
return;
Expand Down Expand Up @@ -258,15 +268,18 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}

currentNode = this;
for (const key in props) {
const prop = props[key];
if (prop && typeof prop === "object" && targets.has(prop)) {
props[key] = useState(prop);
}
}
// for (const key in props) {
// const prop = props[key];
// if (prop && typeof prop === "object" && targets.has(prop)) {
// props[key] = useState(prop);
// }
// }
currentNode = null;
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
let prom: Promise<any[]>;
withoutReactivity(() => {
prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
});
await prom!;
if (fiber !== this.fiber) {
return;
}
Expand Down Expand Up @@ -384,8 +397,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
return this.component.constructor.name;
}

get subscriptions(): ReturnType<typeof getSubscriptions> {
const render = batchedRenderFunctions.get(this);
return render ? getSubscriptions(render) : [];
}
// get subscriptions(): ReturnType<typeof getSubscriptions> {
// const render = batchedRenderFunctions.get(this);
// return render ? getSubscriptions(render) : [];
// }
}
26 changes: 26 additions & 0 deletions src/runtime/executionContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ExecutionContext } from "../common/types";

export const executionContexts: ExecutionContext[] = [];
(window as any).executionContexts = executionContexts;
// export const scheduledContexts: Set<ExecutionContext> = new Set();

export function getExecutionContext() {
return executionContexts[executionContexts.length - 1];
}

export function pushExecutionContext(context: ExecutionContext) {
executionContexts.push(context);
}

export function popExecutionContext() {
executionContexts.pop();
}

// export function makeExecutionContext({ update, meta }: { update: () => void; meta?: any }) {
// const executionContext: ExecutionContext = {
// update,
// atoms: new Set(),
// meta: meta || {},
// };
// return executionContext;
// }
6 changes: 6 additions & 0 deletions src/runtime/fibers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { ComponentNode } from "./component_node";
import { fibersInError } from "./error_handling";
import { OwlError } from "../common/owl_error";
import { STATUS } from "./status";
import { popTaskContext, pushTaskContext } from "./cancellableContext";
import { popExecutionContext, pushExecutionContext } from "./executionContext";

export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
let current = node.fiber;
Expand Down Expand Up @@ -133,12 +135,16 @@ export class Fiber {
const node = this.node;
const root = this.root;
if (root) {
pushTaskContext(node.taskContext);
pushExecutionContext(node.executionContext);
try {
(this.bdom as any) = true;
this.bdom = node.renderFn();
} catch (e) {
node.app.handleError({ node, error: e });
}
popExecutionContext();
popTaskContext();
root.setCounter(root.counter - 1);
}
}
Expand Down
32 changes: 27 additions & 5 deletions src/runtime/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Env } from "./app";
import { getCurrent } from "./component_node";
import { popExecutionContext, pushExecutionContext } from "./executionContext";
import { onMounted, onPatched, onWillUnmount } from "./lifecycle_hooks";
import { inOwnerDocument } from "./utils";

Expand Down Expand Up @@ -86,22 +87,43 @@ export function useEffect<T extends unknown[]>(
effect: Effect<T>,
computeDependencies: () => [...T] = () => [NaN] as never
) {
const context = getCurrent().component.__owl__.executionContext;
let cleanup: (() => void) | void;
let dependencies: T;

const runEffect = () => {
pushExecutionContext(context);
try {
cleanup = effect(...dependencies);
} finally {
popExecutionContext();
}
};
const computeDependenciesWithContext = () => {
pushExecutionContext(context);
let r: any;
try {
r = computeDependencies();
} finally {
popExecutionContext();
}
return r;
};

onMounted(() => {
dependencies = computeDependencies();
cleanup = effect(...dependencies);
dependencies = computeDependenciesWithContext();
runEffect();
});

onPatched(() => {
const newDeps = computeDependencies();
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
const newDeps = computeDependenciesWithContext();
const shouldReapply = newDeps.some((val: any, i: number) => val !== dependencies[i]);
if (shouldReapply) {
dependencies = newDeps;
if (cleanup) {
cleanup();
}
cleanup = effect(...dependencies);
runEffect();
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export { Component } from "./component";
export type { ComponentConstructor } from "./component";
export { useComponent, useState } from "./component_node";
export { status } from "./status";
export { reactive, markRaw, toRaw } from "./reactivity";
export { reactive, markRaw, toRaw, effect, withoutReactivity } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export { batched, EventBus, htmlEscape, whenReady, loadFile, markup } from "./utils";
export {
Expand Down
Loading
Loading