Skip to content

Commit 65ef9a1

Browse files
committed
first level of memory leak contained
1 parent becc989 commit 65ef9a1

5 files changed

Lines changed: 367 additions & 284 deletions

File tree

src/component/Component.js

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import DOMReconciler from "./DOMReconciler.js";
33
import BrokerRegistrar from "../broker/BrokerRegistrar.js";
44
import State from "../core/State.js";
55
import { container } from "../core/Container.js";
6+
import {
7+
cleanupDisconnectedComponents,
8+
getOjsChildren,
9+
} from "../utils/helpers.js";
610

711
/**
812
* Base Component Class
@@ -33,10 +37,10 @@ export default class Component {
3337
* List of events that the component emits
3438
*/
3539
this.EVENTS = {
36-
rendered: "rendered", // component is visible on the dom
37-
rerendered: "rerendered", // component was rerendered
38-
mounted: "mounted", // the component is now registered
39-
unmounted: "unmounted", // removed from the markup engine memory
40+
rendered: "rendered", // component ui is computed
41+
rerendered: "rerendered", // component was ui was recomputed.
42+
mounted: "mounted", // the component is now on the dom
43+
unmounted: "unmounted", // removed from the repository
4044
};
4145

4246
/**
@@ -50,6 +54,11 @@ export default class Component {
5054
*/
5155
this.mounted = false;
5256

57+
/**
58+
* Has the component being unmounted
59+
*/
60+
this.unmounted = false;
61+
5362
/**
5463
* Has the component rendered
5564
*/
@@ -60,11 +69,6 @@ export default class Component {
6069
*/
6170
this.rerendered = false;
6271

63-
/**
64-
* Is the component visible
65-
*/
66-
this.visible = true;
67-
6872
/**
6973
* The argument Map for rerendering on state changes
7074
*/
@@ -85,9 +89,29 @@ export default class Component {
8589

8690
this.name = name ?? this.constructor.name;
8791

88-
this.emitter.once(this.EVENTS.rendered, (th) => (th.rendered = true));
89-
this.on(this.EVENTS.rerendered, (th) => (th.rerendered = true));
90-
this.on(this.EVENTS.mounted, (th) => (th.mounted = true));
92+
this.emitter.once(this.EVENTS.rendered, (componentId) => {
93+
console.log("Component rendered:", componentId);
94+
let repo = container.resolve("repository");
95+
let component = repo.findComponent(componentId);
96+
if (component) component.rendered = true;
97+
console.log("Component rendered:", component);
98+
});
99+
100+
this.on(this.EVENTS.rerendered, (componentId) => {
101+
console.log("Component rerendered:", componentId);
102+
let repo = container.resolve("repository");
103+
let component = repo.findComponent(componentId);
104+
if (component) component.rerendered = true;
105+
console.log("Component rerendered:", component);
106+
});
107+
108+
this.on(this.EVENTS.mounted, (componentId) => {
109+
console.log("Component mounted:", componentId);
110+
let repo = container.resolve("repository");
111+
let component = repo.findComponent(componentId);
112+
if (component) component.handleMounted();
113+
console.log("Component mounted:", component);
114+
});
91115

92116
/**
93117
* Compare two Nodes
@@ -193,7 +217,7 @@ export default class Component {
193217
}
194218

195219
this.releaseMemory();
196-
this.mounted = false;
220+
this.unmounted = true;
197221

198222
return true;
199223
}
@@ -203,8 +227,10 @@ export default class Component {
203227
* @param {string} event
204228
* @param {Array<*>} args
205229
*/
206-
emit(event, args = []) {
207-
this.emitter.emit(event, this, event, ...args);
230+
emit(event, ...args) {
231+
args.push(event);
232+
233+
this.emitter.emit(event, ...args);
208234
}
209235

210236
/**
@@ -325,7 +351,12 @@ export default class Component {
325351
final.parent = args[i].parent;
326352
}
327353

328-
const keys = ["resetParent", "replaceParent", "firstOfParent"];
354+
const keys = [
355+
"resetParent",
356+
"replaceParent",
357+
"firstOfParent",
358+
"reconcileParent",
359+
];
329360

330361
for (let reserved of keys) {
331362
if (args[i][reserved]) {
@@ -360,8 +391,17 @@ export default class Component {
360391
wrap(...args) {
361392
const h = container.resolve("h");
362393
const lastArg = args[args.length - 1];
363-
let { index, parent, resetParent, states, replaceParent, firstOfParent } =
364-
this.getParentAndListen(args);
394+
let {
395+
index,
396+
parent,
397+
resetParent,
398+
states,
399+
replaceParent,
400+
firstOfParent,
401+
reconcileParent,
402+
} = this.getParentAndListen(args);
403+
404+
let reconciler = new this.Reconciler();
365405

366406
// check if the render was called due to a state change
367407
if (lastArg && lastArg["called-by-state-change"]) {
@@ -371,21 +411,15 @@ export default class Component {
371411

372412
let current =
373413
h.dom.querySelectorAll(
374-
`ojs-${this.kebab(this.name)}[uid="${this.id}"][s-${stateId}="${
375-
stateId
376-
}"]`
414+
`ojs-${this.kebab(this.name)}[uid="${
415+
this.id
416+
}"][s-${stateId}="${stateId}"]`
377417
) ?? [];
378418

379-
let reconciler = new this.Reconciler();
380-
381419
current.forEach((e) => {
382420
let arg = this.argsMap.get(Number(e.getAttribute("uid")));
383421

384-
let attr = {
385-
componentId: this.id,
386-
event: this.EVENTS.rerendered,
387-
eventParams: [{ componentId: this.id }],
388-
};
422+
let attr = {};
389423

390424
let shouldReconcile = true;
391425

@@ -405,8 +439,11 @@ export default class Component {
405439
reconciler.reconcile(markup, e.childNodes[0]);
406440
}
407441
}
442+
443+
this.emit(this.EVENTS.rerendered, this.id);
408444
});
409445

446+
queueMicrotask(cleanupDisconnectedComponents);
410447
return;
411448
}
412449

@@ -437,7 +474,15 @@ export default class Component {
437474
class: "__ojs-c-class__",
438475
};
439476

440-
if (parent) attr.parent = parent;
477+
// we render in the parent node
478+
// if we don't need to reconcile the parent
479+
if (parent) {
480+
if (reconcileParent) {
481+
attr.parent = parent.cloneNode();
482+
} else {
483+
attr.parent = parent;
484+
}
485+
}
441486

442487
states.forEach((id) => {
443488
attr[`s-${id}`] = id;
@@ -451,23 +496,32 @@ export default class Component {
451496
return children.length > 1 ? children : children[0];
452497
}
453498

454-
if (!this.visible) attr.style = "display: none;";
455-
456499
let cAttributes = {};
457500

458501
if (markup instanceof HTMLElement) {
459502
cAttributes = JSON.parse(markup?.getAttribute("c-attr") ?? "{}");
460503
markup.setAttribute("c-attr", "");
461504
}
462505

463-
attr = {
464-
...attr,
465-
componentId: this.id,
466-
event,
467-
eventParams: [{ componentId: this.id }],
468-
};
506+
const rendered = h[`ojs-${this.kebab(this.name)}`](
507+
attr,
508+
markup,
509+
cAttributes
510+
);
511+
512+
if (reconcileParent && parent) {
513+
reconciler.reconcile(rendered.parentElement, parent);
514+
}
469515

470-
return h[`ojs-${this.kebab(this.name)}`](attr, markup, cAttributes);
516+
this.emit(this.EVENTS.rendered, this.id);
517+
518+
if (parent && parent.isConnected) {
519+
this.emit(this.EVENTS.mounted, this.id);
520+
}
521+
522+
queueMicrotask(cleanupDisconnectedComponents);
523+
524+
return rendered;
471525
}
472526

473527
isHtml(markup) {
@@ -524,4 +578,28 @@ export default class Component {
524578
removeListener(eventName, listener) {
525579
return this.emitter.removeListener(eventName, listener);
526580
}
581+
582+
handleMounted() {
583+
if (this.mounted) return;
584+
585+
this.mounted = true;
586+
587+
// get all components under this component and
588+
// fire their mounted event.
589+
590+
let markups = this.markup();
591+
592+
let root = markups[0];
593+
594+
if (!root) return;
595+
596+
let children = getOjsChildren(root);
597+
598+
children.forEach((child) => {
599+
let component = container
600+
.resolve("repository")
601+
.findComponent(child.getAttribute("uid"));
602+
if (component) component.emit(this.EVENTS.mounted, component.id);
603+
});
604+
}
527605
}

0 commit comments

Comments
 (0)