Skip to content

Commit e69c284

Browse files
committed
feat: enable module support
1 parent 99271d5 commit e69c284

File tree

7 files changed

+156
-13
lines changed

7 files changed

+156
-13
lines changed

cypress.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"baseUrl": "http://localhost:1234",
3+
"chromeWebSecurity": false,
4+
"defaultCommandTimeout": 10000,
5+
"modifyObstructiveCode": false,
6+
"video": false,
7+
"fixturesFolder": false
8+
}

cypress/integration/test.spec.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference types="cypress" />
2+
3+
const { watchFile } = require("fs")
4+
5+
context('Page load', () => {
6+
beforeEach(() => {
7+
cy.visit('/')
8+
cy.wait(1000)
9+
})
10+
describe('React integration', () => {
11+
12+
it('Should mount', () => {
13+
cy.get('#app')
14+
.should('exist', 'success')
15+
})
16+
it('Should have foo property on button', () => {
17+
cy.get('.clicker')
18+
// .its('foo')
19+
// .should('eq', 3)
20+
.then(($el) => {
21+
const el = $el[0]
22+
cy.wrap(el.foo).should('eq', 3)
23+
})
24+
})
25+
it('Should allow toggling className items based on domClass prop', () => {
26+
cy.get('.clicker')
27+
.then(($el) => {
28+
cy.wrap($el[0].className).should('eq', 'clicker hello')
29+
})
30+
})
31+
it('Should return element when ref function is sent', () => {
32+
cy.get('h1')
33+
.then(($el) => {
34+
const el = $el[0]
35+
cy.wrap(el.foo).should('eq', 'bar')
36+
})
37+
})
38+
})
39+
})

example/index.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import xs from 'xstream';
22
import {createElement} from 'react';
33
import {render} from 'react-dom';
4+
import {setModules} from '../src/Incorporator'
45
import {h, makeComponent} from '../src/index';
56

67
function main(sources) {
@@ -19,10 +20,18 @@ function main(sources) {
1920
.merge(init$, increment$, reset$)
2021
.fold((state, fn) => fn(state));
2122

23+
const getRef = el => {
24+
el.foo='bar';
25+
}
2226
const vdom$ = count$.map(i =>
2327
h('div', [
24-
h('h1', `Hello ${i} times`),
25-
h('button', {sel: btnSel}, 'Reset'),
28+
h('h1', {ref: getRef}, `Hello ${i} times`),
29+
h('button', {
30+
sel: btnSel,
31+
className: 'clicker',
32+
domProps: {foo: 3},
33+
domClass: {hello: true, goodbye: false}
34+
}, 'Reset'),
2635
]),
2736
);
2837

@@ -33,4 +42,21 @@ function main(sources) {
3342

3443
const App = makeComponent(main);
3544

45+
setModules({
46+
domProps: {
47+
componentDidUpdate: (element, props) => {
48+
Object.entries(props).forEach(([key, val]) => {
49+
element[key] = val;
50+
});
51+
}
52+
},
53+
domClass: {
54+
componentDidUpdate: (element, props) => {
55+
Object.entries(props).forEach(([key, val]) => {
56+
val ? element.classList.add(key) : element.classList.remove(key);
57+
});
58+
}
59+
}
60+
})
61+
3662
render(createElement(App), document.getElementById('app'));

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"react": "16.13.1",
4040
"react-dom": "16.13.1",
4141
"react-test-renderer": "16.13.1",
42+
"cypress": "^5.2.0",
43+
"parcel": "^1.12.3",
44+
"start-server-and-test": "^1.11.3",
4245
"symbol-observable": "^1.2.0",
4346
"ts-node": "^7.0.0",
4447
"typescript": "3.6.3",
@@ -53,6 +56,11 @@
5356
"compile-cjs": "tsc --module commonjs --outDir ./lib/cjs",
5457
"compile-es6": "echo 'TODO' : tsc --module es6 --outDir ./lib/es6",
5558
"prepublishOnly": "npm run compile",
56-
"test": "mocha test/*.ts --require ts-node/register --recursive"
59+
"test": "mocha test/*.ts --require ts-node/register --recursive",
60+
"full-test": "npm test; npm run cypress:run",
61+
"serve-test": "start-server-and-test start http://localhost:1234 full-test",
62+
"start": "parcel example/index.html",
63+
"cypress:open": "cypress open",
64+
"cypress:run": "cypress run"
5765
}
5866
}

src/Incorporator.ts

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {PureComponent, createElement} from 'react';
1+
import {PureComponent, createElement, createRef} from 'react';
22
import {Scope} from './scope';
33

44
type Props = {
@@ -12,20 +12,76 @@ type State = {
1212
flip: boolean;
1313
};
1414

15+
let moduleEntries: any = []
16+
17+
let onMounts: any[] = []
18+
let onUpdates: any[] = []
19+
let onUnmounts: any[] = []
20+
21+
export function setModules(mods: any) {
22+
if (mods === null || typeof mods !== 'object') return;
23+
moduleEntries = Object.entries(mods)
24+
onMounts = moduleEntries.map(mod => [mod[0], mod[1].componentDidMount]).filter(mod => mod[1])
25+
onUpdates = moduleEntries.map(mod => [mod[0], mod[1].componentDidUpdate]).filter(mod => mod[1])
26+
onUnmounts = moduleEntries.map(mod => [mod[0], mod[1].componentWillUnmount]).filter(mod => mod[1])
27+
}
28+
29+
export function hasModuleProps (props) {
30+
return props
31+
? moduleEntries.some(([mkey]) => props.hasOwnProperty(mkey))
32+
: false
33+
}
34+
35+
function moduleProcessor (base, current, props) {
36+
if (current && base.length) {
37+
base.forEach(([key, f]) => {
38+
const prop = props[key]
39+
if (prop) f(current, prop)
40+
});
41+
}
42+
}
43+
1544
export default class Incorporator extends PureComponent<Props, State> {
45+
private ref: any;
46+
private selector: string | symbol;
47+
private unsubscribe: any;
48+
private element: any;
49+
private setRef: any;
50+
1651
constructor(props: Props) {
1752
super(props);
53+
this.element = null
54+
1855
this.state = {flip: false};
1956
this.selector = props.targetProps.sel;
20-
}
2157

22-
private selector: string | symbol;
23-
private unsubscribe: any;
58+
const useRef = hasModuleProps(props.targetProps)
59+
if (props.targetRef) {
60+
if (typeof props.targetRef === 'function' && useRef) {
61+
this.setRef = element => {
62+
this.element = element;
63+
props.targetRef(element);
64+
};
65+
66+
this.ref = this.setRef;
67+
} else {
68+
this.ref = props.targetRef;
69+
}
70+
} else {
71+
this.ref = useRef ? createRef() : null;
72+
}
73+
}
2474

2575
public componentDidMount() {
2676
this.unsubscribe = this.props.scope.subscribe(this.selector, () => {
2777
this.setState((prev: any) => ({flip: !prev.flip}));
2878
});
79+
80+
moduleProcessor(onMounts, this.element || (this.ref && this.ref.current), this.props.targetProps)
81+
}
82+
83+
public componentDidUpdate() {
84+
moduleProcessor(onUpdates, this.element || (this.ref && this.ref.current), this.props.targetProps)
2985
}
3086

3187
private incorporateHandlers<P>(props: P, scope: Scope): P {
@@ -46,27 +102,31 @@ export default class Incorporator extends PureComponent<Props, State> {
46102
}
47103

48104
private materializeTargetProps() {
49-
const {targetProps, targetRef, scope} = this.props;
105+
const {targetProps, scope} = this.props;
50106
let output = {...targetProps};
51107
output = this.incorporateHandlers(output, scope);
52-
if (targetRef) {
53-
output.ref = targetRef;
108+
if (this.ref) {
109+
output.ref = this.ref;
54110
}
55111
delete output.sel;
112+
moduleEntries.forEach(pair => delete output[pair[0]])
56113
return output;
57114
}
58115

59116
public render() {
60117
const {target} = this.props;
61118
const targetProps = this.materializeTargetProps();
119+
62120
if (targetProps.children) {
63121
return createElement(target, targetProps, targetProps.children);
64122
} else {
65123
return createElement(target, targetProps);
66124
}
67125
}
68126

69-
public componentWillUnmount() {
127+
public componentWillUnmount() {
128+
moduleProcessor(onUnmounts, this.element || (this.ref && this.ref.current), this.props.targetProps)
129+
70130
this.unsubscribe();
71131
}
72132
}

src/h.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Attributes,
88
} from 'react';
99
import {incorporate} from './incorporate';
10+
import { hasModuleProps } from './Incorporator';
1011

1112
export type PropsExtensions = {
1213
sel?: string | symbol;
@@ -32,7 +33,7 @@ function hyperscriptProps<P = any>(
3233
type: ElementType<P> | keyof ReactHTML,
3334
props: PropsLike<P>
3435
): ReactElement<P> {
35-
if (!props.sel) {
36+
if (!props.sel && !hasModuleProps(props)) {
3637
return createElement(type, props);
3738
} else {
3839
return createElement(incorporate(type), props);
@@ -51,7 +52,7 @@ function hyperscriptPropsChildren<P = any>(
5152
props: PropsLike<P>,
5253
children: Children
5354
): ReactElement<P> {
54-
if (!props.sel) {
55+
if (!props.sel && !hasModuleProps(props)) {
5556
return createElementSpreading(type, props, children);
5657
} else {
5758
return createElementSpreading(incorporate(type), props, children);

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export {Scope} from './scope';
44
export {ReactSource} from './ReactSource';
55
export {h} from './h';
66
export {incorporate} from './incorporate';
7+
export {setModules} from './Incorporator'
78
export {StreamRenderer} from './StreamRenderer';

0 commit comments

Comments
 (0)