Skip to content

Commit

Permalink
fix: handle templates that references a deleted component (#119)
Browse files Browse the repository at this point in the history
* fix: correctly show error view when component is removed

* fix: component cache disposed too early

* chore: test
  • Loading branch information
prevwong authored Dec 23, 2023
1 parent 52c4c8d commit d6016e1
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 69 deletions.
6 changes: 6 additions & 0 deletions .changeset/khaki-kangaroos-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rekajs/types': patch
'@rekajs/core': patch
---

Show error view when a template references a component that has been removed
92 changes: 62 additions & 30 deletions packages/core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import { createKey } from './utils';

type ComponentViewTreeComputationCache = {
component: t.Component;
computed: IComputedValue<t.RekaComponentView[] | t.ExternalComponentView[]>;
computed: IComputedValue<t.FragmentView>;
};

export class ComponentViewEvaluator {
private declare resolveComponentComputation: IComputedValue<
t.RekaComponentView[] | t.ErrorSystemView[] | t.ExternalComponentView[]
>;
private declare resolveComponentComputation: DisposableComputation<t.FragmentView>;
private declare componentViewTreeComputation: ComponentViewTreeComputationCache | null;

private declare rekaComponentRootComputation: DisposableComputation<t.View> | null;
Expand All @@ -32,6 +30,8 @@ export class ComponentViewEvaluator {

readonly key: string;

private fragment: t.FragmentView;

constructor(
evaluator: Evaluator,
ctx: TemplateEvaluateContext,
Expand All @@ -46,6 +46,13 @@ export class ComponentViewEvaluator {
this.env = env;

this.rekaComponentStateComputation = null;

this.fragment = t.fragmentView({
children: [],
frame: this.evaluator.frame.id,
key: this.key,
template: this.template,
});
}

private computeProps(component: t.Component) {
Expand Down Expand Up @@ -134,33 +141,38 @@ export class ComponentViewEvaluator {
[] as [string, any][]
);

return [
this.fragment.children = [
t.externalComponentView({
frame: this.evaluator.frame.id,
component,
key: this.key,
key: createKey([this.key, 'root']),
template: this.template,
children: children || [],
props: Object.fromEntries(props),
}),
];

return this.fragment;
}

if (component instanceof t.RekaComponent) {
if (this.rekaComponentRootComputation) {
this.rekaComponentRootComputation.get();
return this.fragment;
}

const componentViewTree = t.rekaComponentView({
frame: this.evaluator.frame.id,
key: this.key,
key: createKey([this.key, 'root']),
component,
render: [],
template: this.template,
owner: this.ctx.owner,
});

untracked(() => {
if (this.rekaComponentRootComputation) {
return this.rekaComponentRootComputation.get();
}
this.fragment.children = [componentViewTree];

untracked(() => {
this.rekaComponentRootComputation = new DisposableComputation(
() => {
let render: t.View[] = [];
Expand Down Expand Up @@ -236,7 +248,7 @@ export class ComponentViewEvaluator {
this.rekaComponentStateComputation.get();

render = this.evaluator.computeTemplate(component.template, {
path: [this.key, 'root'],
path: [this.key, 'root', 'render'],
env: this.env,
owner: componentViewTree,
componentStack: [...this.ctx.componentStack, component],
Expand Down Expand Up @@ -275,25 +287,23 @@ export class ComponentViewEvaluator {
return this.rekaComponentRootComputation.get();
});

return [componentViewTree];
return this.fragment;
}

throw new Error('Invalid Component Template');
}

recompute() {
if (this.rekaComponentRootComputation) {
this.rekaComponentRootComputation.get();

return;
}

this.compute();
private reset() {
this.componentViewTreeComputation = null;
this.rekaComponentRootComputation = null;
this.rekaComponentPropsComputation = null;
this.rekaComponentPropsBindingComputation = null;
this.rekaComponentStateComputation = null;
}

compute() {
if (!this.resolveComponentComputation) {
this.resolveComponentComputation = computed(
this.resolveComponentComputation = new DisposableComputation(
() => {
const component = this.env.getByName(
this.template.component.name,
Expand All @@ -302,28 +312,35 @@ export class ComponentViewEvaluator {

if (!component) {
this.componentViewTreeComputation = null;
this.reset();

return [
this.fragment.children = [
t.errorSystemView({
frame: this.evaluator.frame.id,
error: `Component "${this.template.component.name}" not found`,
key: this.key,
key: createKey([this.key, 'root']),
template: this.template,
owner: this.ctx.owner,
}),
];

return this.fragment;
}

if (this.ctx.componentStack.indexOf(component) > -1) {
return [
this.reset();

this.fragment.children = [
t.errorSystemView({
frame: this.evaluator.frame.id,
error: `Cycle detected when attempting to render "${component.name}"`,
key: this.key,
key: createKey([this.key, 'root']),
template: this.template,
owner: this.ctx.owner,
}),
];

return this.fragment;
}

if (
Expand All @@ -333,6 +350,8 @@ export class ComponentViewEvaluator {
return this.componentViewTreeComputation.computed.get();
}

this.reset();

this.componentViewTreeComputation = {
component,
computed: computed(
Expand All @@ -347,18 +366,31 @@ export class ComponentViewEvaluator {
},
{
name: `component-${this.template.component.name}<${this.template.id}>-resolve-computation`,
keepAlive: true,
}
);
}

return this.resolveComponentComputation.get();
this.resolveComponentComputation.get();

if (this.rekaComponentRootComputation) {
this.rekaComponentRootComputation.get();
}

return [this.fragment];
}

get view() {
return this.fragment.children[0];
}

dispose() {
if (!this.rekaComponentRootComputation) {
return;
if (this.resolveComponentComputation) {
this.resolveComponentComputation.dispose();
}

this.rekaComponentRootComputation.dispose();
if (this.rekaComponentRootComputation) {
this.rekaComponentRootComputation.dispose();
}
}
}
22 changes: 11 additions & 11 deletions packages/core/src/evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export type TemplateEachComputationCache = {
};

export class Evaluator {
private _view: IObservableValue<t.RekaComponentView | undefined>;
private _view: IObservableValue<t.FragmentView | undefined>;

private viewObserver: Observer | undefined;
private rootTemplate: t.ComponentTemplate;
Expand Down Expand Up @@ -177,13 +177,13 @@ export class Evaluator {

const index = parent.children.indexOf(existingView);

parent.children[index] = newView;
parent.children.splice(index, 0, newView);

return newView;
});
}

private setView(view: t.RekaComponentView) {
private setView(view: t.FragmentView) {
if (this.viewObserver) {
this.viewObserver.dispose();
}
Expand Down Expand Up @@ -524,7 +524,7 @@ export class Evaluator {
this.tplKeyToComponentEvaluator.set(key, componentEvaluator);
}

return componentEvaluator.compute();
return untracked(() => componentEvaluator!.compute());
}

computeExpr(expr: t.Any, env: Environment) {
Expand Down Expand Up @@ -560,19 +560,19 @@ export class Evaluator {
const _compute = () => {
const views = this.computeRootTemplate();

return t.assert(views[0], t.RekaComponentView);
return t.assert(views[0], t.FragmentView);
};

if (!this.viewObserver) {
const viewObserver = this.viewObserver;

if (!viewObserver) {
this.setView(_compute());
return;
}

this.viewObserver.change(() => {
_compute();

this.tplKeyToComponentEvaluator.forEach((componentEvaluator) => {
componentEvaluator.recompute();
this.tplKeyToComponentEvaluator.forEach((componentEvaluator) => {
viewObserver.change(() => {
componentEvaluator.compute();
});
});
}
Expand Down
Loading

1 comment on commit d6016e1

@vercel
Copy link

@vercel vercel bot commented on d6016e1 Dec 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

reka – ./

rekajs.vercel.app
reka-prevwong.vercel.app
reka.js.org
reka-git-main-prevwong.vercel.app

Please sign in to comment.