Skip to content

Commit

Permalink
feat: require component props to be bindable (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
prevwong authored Dec 13, 2023
1 parent 69615ab commit 914c0a2
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 20 deletions.
7 changes: 7 additions & 0 deletions .changeset/olive-sloths-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rekajs/parser': patch
'@rekajs/types': patch
'@rekajs/core': patch
---

Requires component props to explicitly specify that they can be bindable
40 changes: 24 additions & 16 deletions packages/core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ export class ComponentViewEvaluator {
propValue = [propValue, ...classListBinding].filter(Boolean).join(' ');
}

accum.push([prop.name, propValue]);
accum.push([prop, propValue]);

return accum;
}, [] as Array<[string, any]>);
}, [] as Array<[t.ComponentProp, any]>);
}

private computeViewTreeForComponent(component: t.Component) {
Expand All @@ -105,9 +105,9 @@ export class ComponentViewEvaluator {

const props = this.computeProps(component).reduce(
(accum, [prop, value]) => {
accum.push([prop, value]);
accum.push([prop.name, value]);

const tplPropValue = this.template.props[prop];
const tplPropValue = this.template.props[prop.name];

/**
* External components should expose a `on{Prop}Change` prop in order to
Expand All @@ -118,9 +118,9 @@ export class ComponentViewEvaluator {
* }
*
*/
if (t.is(tplPropValue, t.PropBinding)) {
if (t.is(tplPropValue, t.PropBinding) && prop.bindable) {
accum.push([
`on${capitalize(prop)}Change`,
`on${capitalize(prop.name)}Change`,
(updatedValue: any) => {
this.evaluator.reka.change(() => {
this.ctx.env.reassign(tplPropValue.identifier, updatedValue);
Expand Down Expand Up @@ -181,9 +181,9 @@ export class ComponentViewEvaluator {
});

this.computeProps(component).forEach(([prop, value]) => {
this.env.set(prop, {
this.env.set(prop.name, {
value,
readonly: false,
readonly: !prop.bindable,
});
});
},
Expand All @@ -195,22 +195,30 @@ export class ComponentViewEvaluator {

if (!this.rekaComponentPropsBindingComputation) {
this.rekaComponentPropsBindingComputation = computed(() => {
for (const [prop, value] of Object.entries(
this.template.props
)) {
if (!t.is(value, t.PropBinding)) {
continue;
component.props.forEach((prop) => {
if (!prop.bindable) {
return;
}

const currPropValue = this.env.getByName(prop, false, true);
const tplProPValue = this.template.props[prop.name];

if (!tplProPValue || !t.is(tplProPValue, t.PropBinding)) {
return;
}

const currPropValue = this.env.getByName(
prop.name,
false,
true
);

this.evaluator.reka.change(() => {
this.ctx.env.reassign(
t.assert(value, t.PropBinding).identifier,
t.assert(tplProPValue, t.PropBinding).identifier,
currPropValue
);
});
}
});
});
}

Expand Down
3 changes: 2 additions & 1 deletion packages/parser/src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ export class Lexer {

return this.tokenize(TokenType.ELEMENT_DIRECTIVE);
}
break;

return this.tokenize(TokenType.AMPERSAND);
}
case '$': {
return this.tokenize(TokenType.DOLLAR);
Expand Down
8 changes: 7 additions & 1 deletion packages/parser/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,14 @@ class _Parser extends Lexer {
this.consume(TokenType.LPAREN);

const props: t.ComponentProp[] = [];
let bindable = false;

while (!this.match(TokenType.RPAREN)) {
const propName = this.consume(TokenType.IDENTIFIER).value;
if (this.match(TokenType.AMPERSAND)) {
bindable = true;
}

let propName = this.consume(TokenType.IDENTIFIER).value;

const kind = this.parseKind();

Expand Down Expand Up @@ -463,6 +468,7 @@ class _Parser extends Lexer {
name: propName,
init,
kind,
bindable,
})
);

Expand Down
16 changes: 16 additions & 0 deletions packages/parser/src/stringifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,22 @@ class _Stringifier {
this.stringify(node.init);
}
},
ComponentProp: (node) => {
if (node.bindable) {
this.writer.write(`@`);
}

this.writer.write(node.name);

if (node.kind) {
this.stringifyInput(node.kind);
}

if (node.init) {
this.writer.write(`=`);
this.stringify(node.init);
}
},
RekaComponent: (node) => {
this.writer.write(`component ${node.name}(`);

Expand Down
11 changes: 11 additions & 0 deletions packages/parser/src/tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,15 @@ describe('Parser', () => {
},
});
});
it('should be able to parse identifier component props', () => {
const parsed = Parser.parseProgram(
`component Test(@value: string) => (<text />)`
);

expect(parsed.components[0].props[0]).toMatchObject({
type: 'ComponentProp',
name: 'value',
identifier: true,
});
});
});
1 change: 1 addition & 0 deletions packages/parser/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum TokenType {
SLASH = '/',
STAR = '*',
DOLLAR = '$',
AMPERSAND = '@',

NOT = '!',
NEQ = '!=',
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/generated/types.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,11 @@ type ComponentPropParameters = {
name: string;
kind?: Kind;
init?: Expression | null;
bindable?: boolean;
};

export class ComponentProp extends Variable {
declare bindable: boolean;
constructor(value: ComponentPropParameters) {
super('ComponentProp', value);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/types/src/types.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ Schema.define('MemberExpression', {

Schema.define('ComponentProp', {
extends: 'Variable',
fields: (t) => ({
bindable: t.defaultValue(t.boolean, false),
}),
});

Schema.define('Component', {
Expand Down
2 changes: 1 addition & 1 deletion site/constants/dummy-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ component PropBinding() {
</div>
)
component Input(value = "") => (
component Input(@value = "") => (
<div className={"w-full mt-8 p-5 bg-neutral-100"}>
<text value={"Input prop value: " + value} />
<input className="w-full mt-6" type="text" value:={value} />
Expand Down
2 changes: 1 addition & 1 deletion site/constants/encoded-dummy-program.ts

Large diffs are not rendered by default.

1 comment on commit 914c0a2

@vercel
Copy link

@vercel vercel bot commented on 914c0a2 Dec 13, 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 – ./

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

Please sign in to comment.