Skip to content

Commit cb2b738

Browse files
committed
[WIP] temp
1 parent c1c6310 commit cb2b738

File tree

8 files changed

+93
-83
lines changed

8 files changed

+93
-83
lines changed

src/common/types.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1+
export type customDirectives = Record<
2+
string,
3+
(node: Element, value: string, modifier: string[]) => void
4+
>;
5+
6+
// Reactivity system
7+
18
export enum ComputationState {
29
EXECUTED = 0,
310
STALE = 1,
411
PENDING = 2,
512
}
6-
713
export type Computation<T = any> = {
814
compute?: () => T;
915
state: ComputationState;
1016
sources: Set<Atom | Derived<any, any>>;
17+
isEager?: boolean;
1118
isDerived?: boolean;
1219
value: T; // for effects, this is the cleanup function
1320
childrenEffect?: Computation[]; // only for effects
14-
};
15-
16-
export type customDirectives = Record<
17-
string,
18-
(node: Element, value: string, modifier: string[]) => void
19-
>;
21+
} & Opts;
2022

23+
export type Opts = {
24+
name?: string;
25+
};
2126
export type Atom<T = any> = {
2227
value: T;
2328
observers: Set<Computation>;
24-
};
29+
} & Opts;
2530

2631
export interface Derived<Prev, Next = Prev> extends Atom<Next>, Computation<Next> {}
2732

2833
export type OldValue = any;
34+
35+
export type Getter<V> = () => V | null;
36+
export type Setter<T, V> = (this: T, value: V) => void;
37+
export type MakeGetSetReturn<T, V> = readonly [Getter<V>] | readonly [Getter<V>, Setter<T, V>];
38+
export type MakeGetSet<T, V> = (obj: T) => MakeGetSetReturn<T, V>;

src/runtime/component_node.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { OwlError } from "../common/owl_error";
22
import { Atom, Computation, ComputationState } from "../common/types";
33
import type { App, Env } from "./app";
44
import { BDom, VNode } from "./blockdom";
5-
import { makeTaskContext, TaskContext } from "./cancellableContext";
65
import { Component, ComponentConstructor, Props } from "./component";
76
import { fibersInError } from "./error_handling";
87
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
@@ -44,7 +43,6 @@ function applyDefaultProps<P extends object>(props: P, defaultProps: Partial<P>)
4443
// Integration with reactivity system (useState)
4544
// -----------------------------------------------------------------------------
4645

47-
// const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
4846
/**
4947
* Creates a reactive object that will be observed by the current component.
5048
* Reading data from the returned object (eg during rendering) will cause the
@@ -90,7 +88,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
9088
willPatch: LifecycleHook[] = [];
9189
patched: LifecycleHook[] = [];
9290
willDestroy: LifecycleHook[] = [];
93-
taskContext: TaskContext;
9491
signalComputation: Computation;
9592

9693
constructor(
@@ -105,15 +102,11 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
105102
this.parent = parent;
106103
this.props = props;
107104
this.parentKey = parentKey;
108-
this.taskContext = makeTaskContext();
109105
this.signalComputation = {
110-
// data: this,
111106
value: undefined,
112-
compute: () => {
113-
this.render(false);
114-
},
107+
compute: () => this.render(false),
115108
sources: new Set<Atom>(),
116-
state: ComputationState.EXECUTED,
109+
state: ComputationState.STALE,
117110
};
118111
const defaultProps = C.defaultProps;
119112
props = Object.assign({}, props);
@@ -122,19 +115,13 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
122115
}
123116
const env = (parent && parent.childEnv) || app.env;
124117
this.childEnv = env;
125-
// for (const key in props) {
126-
// const prop = props[key];
127-
// if (prop && typeof prop === "object" && targets.has(prop)) {
128-
// props[key] = useState(prop);
129-
// }
130-
// }
131-
const currentContext = getCurrentComputation();
118+
const previousComputation = getCurrentComputation();
132119
setComputation(this.signalComputation);
133120
this.component = new C(props, env, this);
134121
const ctx = Object.assign(Object.create(this.component), { this: this.component });
135122
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
136123
this.component.setup();
137-
setComputation(currentContext);
124+
setComputation(previousComputation);
138125
currentNode = null;
139126
}
140127

@@ -151,11 +138,10 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
151138
}
152139
const component = this.component;
153140
try {
154-
let prom: Promise<any[]>;
155-
withoutReactivity(() => {
156-
prom = Promise.all(this.willStart.map((f) => f.call(component)));
157-
});
158-
await prom!;
141+
const previousComputation = getCurrentComputation();
142+
setComputation(undefined);
143+
await Promise.all(this.willStart.map((f) => withoutReactivity(() => f.call(component))));
144+
setComputation(previousComputation);
159145
} catch (e) {
160146
this.app.handleError({ node: this, error: e });
161147
return;
@@ -270,11 +256,11 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
270256
applyDefaultProps(props, defaultProps);
271257
}
272258

273-
let prom: Promise<any[]>;
274-
withoutReactivity(() => {
275-
prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
276-
});
277-
await prom!;
259+
const previouwsComputation = getCurrentComputation();
260+
setComputation(undefined);
261+
await Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
262+
setComputation(previouwsComputation);
263+
278264
if (fiber !== this.fiber) {
279265
return;
280266
}
@@ -391,9 +377,4 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
391377
get name(): string {
392378
return this.component.constructor.name;
393379
}
394-
395-
// get subscriptions(): ReturnType<typeof getSubscriptions> {
396-
// const render = batchedRenderFunctions.get(this);
397-
// return render ? getSubscriptions(render) : [];
398-
// }
399380
}

src/runtime/fibers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { ComponentNode } from "./component_node";
33
import { fibersInError } from "./error_handling";
44
import { OwlError } from "../common/owl_error";
55
import { STATUS } from "./status";
6-
import { popTaskContext, pushTaskContext } from "./cancellableContext";
76
import { runWithComputation } from "./signals";
7+
import { ComputationState } from "../common/types";
88

99
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
1010
let current = node.fiber;
@@ -135,16 +135,16 @@ export class Fiber {
135135
const node = this.node;
136136
const root = this.root;
137137
if (root) {
138-
pushTaskContext(node.taskContext);
138+
// todo: should use updateComputation somewhere else.
139139
runWithComputation(node.signalComputation, () => {
140140
try {
141141
(this.bdom as any) = true;
142142
this.bdom = node.renderFn();
143143
} catch (e) {
144144
node.app.handleError({ node, error: e });
145145
}
146+
node.signalComputation.state = ComputationState.EXECUTED;
146147
});
147-
popTaskContext();
148148
root.setCounter(root.counter - 1);
149149
}
150150
}

src/runtime/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,14 @@ export const blockDom = {
3232
html,
3333
comment,
3434
};
35-
3635
export { App, mount } from "./app";
3736
export { xml } from "./template_set";
3837
export { Component } from "./component";
3938
export type { ComponentConstructor } from "./component";
4039
export { useComponent, useState } from "./component_node";
4140
export { status } from "./status";
4241
export { reactive, markRaw, toRaw } from "./reactivity";
43-
export { effect, withoutReactivity } from "./signals";
42+
export { effect, withoutReactivity, derived } from "./signals";
4443
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
4544
export { batched, EventBus, htmlEscape, whenReady, loadFile, markup } from "./utils";
4645
export {

src/runtime/signals.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
import { Atom, Computation, ComputationState, Derived } from "../common/types";
1+
import { Atom, Computation, ComputationState, Derived, Opts } from "../common/types";
22
import { batched } from "./utils";
33

44
let Effects: Computation[];
5-
let CurrentComputation: Computation;
5+
let CurrentComputation: Computation | undefined;
66

7-
export function effect<T>(fn: () => T) {
7+
export function signal<T>(value: T, opts?: Opts) {
8+
const atom: Atom<T> = {
9+
value,
10+
observers: new Set(),
11+
name: opts?.name,
12+
};
13+
const read = () => {
14+
onReadAtom(atom);
15+
return atom.value;
16+
};
17+
const write = (newValue: T | ((prevValue: T) => T)) => {
18+
if (typeof newValue === "function") {
19+
newValue = (newValue as (prevValue: T) => T)(atom.value);
20+
}
21+
if (Object.is(atom.value, newValue)) return;
22+
atom.value = newValue;
23+
onWriteAtom(atom);
24+
};
25+
return [read, write] as const;
26+
}
27+
export function effect<T>(fn: () => T, opts?: Opts) {
828
const effectComputation: Computation = {
929
state: ComputationState.STALE,
1030
value: undefined,
@@ -18,8 +38,9 @@ export function effect<T>(fn: () => T) {
1838
return fn();
1939
},
2040
sources: new Set(),
21-
childrenEffect: [],
41+
name: opts?.name,
2242
};
43+
effectComputation.childrenEffect = [];
2344
CurrentComputation?.childrenEffect?.push?.(effectComputation);
2445
updateComputation(effectComputation);
2546

@@ -33,7 +54,22 @@ export function effect<T>(fn: () => T) {
3354
CurrentComputation = previousComputation!;
3455
};
3556
}
36-
export function derived<T>(fn: () => T): () => T {
57+
export function computed<T>(fn: () => T, opts?: Opts) {
58+
// todo: handle cleanup
59+
let computedComputation: Computation = {
60+
state: ComputationState.STALE,
61+
sources: new Set(),
62+
isEager: true,
63+
compute: () => {
64+
return fn();
65+
},
66+
value: undefined,
67+
name: opts?.name,
68+
};
69+
updateComputation(computedComputation);
70+
}
71+
export function derived<T>(fn: () => T, opts?: Opts): () => T {
72+
// todo: handle cleanup
3773
let derivedComputation: Derived<any, any>;
3874
return () => {
3975
derivedComputation ??= {
@@ -46,6 +82,7 @@ export function derived<T>(fn: () => T): () => T {
4682
isDerived: true,
4783
value: undefined,
4884
observers: new Set<Computation>(),
85+
name: opts?.name,
4986
};
5087
onDerived?.(derivedComputation);
5188
updateComputation(derivedComputation);
@@ -96,17 +133,18 @@ export function withoutReactivity<T extends (...args: any[]) => any>(fn: T): Ret
96133
export function getCurrentComputation() {
97134
return CurrentComputation;
98135
}
99-
export function setComputation(computation: Computation) {
136+
export function setComputation(computation: Computation | undefined) {
100137
CurrentComputation = computation;
101138
}
139+
// todo: should probably use updateComputation instead.
102140
export function runWithComputation<T>(computation: Computation, fn: () => T): T {
103141
const previousComputation = CurrentComputation;
104142
CurrentComputation = computation;
105143
let result: T;
106144
try {
107145
result = fn();
108146
} finally {
109-
CurrentComputation = previousComputation!;
147+
CurrentComputation = previousComputation;
110148
}
111149
return result;
112150
}

tests/components/lifecycle.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ describe("lifecycle hooks", () => {
995995
`);
996996
});
997997

998-
test("onWillRender", async () => {
998+
test.only("onWillRender", async () => {
999999
const def = makeDeferred();
10001000

10011001
class Child extends Component {

tests/components/props_validation.test.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -682,29 +682,6 @@ describe("props validation", () => {
682682
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
683683
});
684684

685-
test("props validation does not cause additional subscription", async () => {
686-
let obj = {
687-
value: 1,
688-
otherValue: 2,
689-
};
690-
class Child extends Component {
691-
static props = {
692-
obj: { type: Object, shape: { value: Number, otherValue: Number } },
693-
};
694-
static template = xml`<t t-esc="props.obj.value"/>`;
695-
}
696-
class Parent extends Component {
697-
static template = xml`<Child obj="obj"/><t t-esc="obj.otherValue"/>`;
698-
static components = { Child };
699-
700-
obj = useState(obj);
701-
}
702-
const app = new App(Parent, { test: true });
703-
await app.mount(fixture);
704-
expect(fixture.innerHTML).toBe("12");
705-
// expect(app.root!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
706-
});
707-
708685
test("props are validated whenever component is updated", async () => {
709686
let error: Error;
710687
class SubComp extends Component {

tests/helpers.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,20 @@ import { TemplateSet, globalTemplates } from "../src/runtime/template_set";
2020
import { BDom } from "../src/runtime/blockdom";
2121
import { compile } from "../src/compiler";
2222
import { OwlError } from "../src/common/owl_error";
23+
import { derived, effect } from "../src/runtime/signals";
2324

2425
const mount = blockDom.mount;
2526

2627
export function nextMicroTick(): Promise<void> {
2728
return Promise.resolve();
2829
}
2930

31+
// todo: investigate why two ticks are needed
32+
export async function waitScheduler() {
33+
await nextMicroTick();
34+
await nextMicroTick();
35+
}
36+
3037
let lastFixture: any = null;
3138

3239
export function makeTestFixture() {
@@ -47,20 +54,20 @@ export async function nextTick(): Promise<void> {
4754
await new Promise((resolve) => requestAnimationFrame(resolve));
4855
}
4956

50-
interface Deferred extends Promise<any> {
51-
resolve(val?: any): void;
52-
reject(val?: any): void;
57+
interface Deferred<T = any> extends Promise<T> {
58+
resolve(val?: T): void;
59+
reject(val?: T): void;
5360
}
5461

55-
export function makeDeferred(): Deferred {
62+
export function makeDeferred<T = any>(): Deferred<T> {
5663
let resolve, reject;
5764
let def = new Promise((_resolve, _reject) => {
5865
resolve = _resolve;
5966
reject = _reject;
6067
});
6168
(def as any).resolve = resolve;
6269
(def as any).reject = reject;
63-
return <Deferred>def;
70+
return <Deferred<T>>def;
6471
}
6572

6673
export function trim(str: string): string {
@@ -293,8 +300,6 @@ declare global {
293300
}
294301
}
295302

296-
import { derived, effect } from "../src/runtime/signals";
297-
298303
export type SpyDerived<T> = (() => T) & { spy: jest.Mock<any, T[]> };
299304
export function spyDerived<T>(fn: () => T): SpyDerived<T> {
300305
const spy = jest.fn(fn);

0 commit comments

Comments
 (0)