Skip to content

Commit ad494e1

Browse files
committed
feat: enable module support
1 parent 3d4f87f commit ad494e1

File tree

7 files changed

+156
-13
lines changed

7 files changed

+156
-13
lines changed

cypress.json

+8
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

+39
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

+28-2
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

+9-1
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@
2929
"@types/mocha": "^5.2.7",
3030
"@types/node": "^10.5.2",
3131
"@types/react": "16.9.3",
32+
"cypress": "^5.2.0",
3233
"mocha": "^6.2.0",
34+
"parcel": "^1.12.3",
3335
"react": "16.9.0",
3436
"react-dom": "16.9.0",
3537
"react-test-renderer": "16.9.0",
38+
"start-server-and-test": "^1.11.3",
3639
"symbol-observable": "^1.2.0",
3740
"ts-node": "^7.0.0",
3841
"typescript": "3.6.3",
@@ -46,6 +49,11 @@
4649
"compile": "npm run compile-cjs && npm run compile-es6",
4750
"compile-cjs": "tsc --module commonjs --outDir ./lib/cjs",
4851
"compile-es6": "echo 'TODO' : tsc --module es6 --outDir ./lib/es6",
49-
"test": "$(npm bin)/mocha test/*.ts --require ts-node/register --recursive"
52+
"full-test": "npm test; npm run cypress:run",
53+
"test": "$(npm bin)/mocha test/*.ts --require ts-node/register --recursive",
54+
"serve-test": "start-server-and-test start http://localhost:1234 full-test",
55+
"start": "parcel example/index.html",
56+
"cypress:open": "cypress open",
57+
"cypress:run": "cypress run"
5058
}
5159
}

src/Incorporator.ts

+68-8
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 {
@@ -38,27 +94,31 @@ export default class Incorporator extends PureComponent<Props, State> {
3894
}
3995

4096
private materializeTargetProps() {
41-
const {targetProps, targetRef, scope} = this.props;
97+
const {targetProps, scope} = this.props;
4298
let output = {...targetProps};
4399
output = this.incorporateHandlers(output, scope);
44-
if (targetRef) {
45-
output.ref = targetRef;
100+
if (this.ref) {
101+
output.ref = this.ref;
46102
}
47103
delete output.sel;
104+
moduleEntries.forEach(pair => delete output[pair[0]])
48105
return output;
49106
}
50107

51108
public render() {
52109
const {target} = this.props;
53110
const targetProps = this.materializeTargetProps();
111+
54112
if (targetProps.children) {
55113
return createElement(target, targetProps, targetProps.children);
56114
} else {
57115
return createElement(target, targetProps);
58116
}
59117
}
60118

61-
public componentWillUnmount() {
119+
public componentWillUnmount() {
120+
moduleProcessor(onUnmounts, this.element || (this.ref && this.ref.current), this.props.targetProps)
121+
62122
this.unsubscribe();
63123
}
64124
}

src/h.ts

+3-2
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: ReactType<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

+1
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)