Skip to content

Commit

Permalink
feat: add h2 hyperscript function for assisting snabbdom/react migration
Browse files Browse the repository at this point in the history
  • Loading branch information
ntilwalli committed Sep 16, 2020
1 parent 2205231 commit 47765a3
Show file tree
Hide file tree
Showing 10 changed files with 637 additions and 437 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
/lib
/dist
/.cache
fixtures
support
plugins
node_modules
package-lock.json
pnpm-lock.yaml
Expand Down
6 changes: 5 additions & 1 deletion cypress.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"baseUrl": "http://localhost:1234",
"video": false
"chromeWebSecurity": false,
"defaultCommandTimeout": 10000,
"modifyObstructiveCode": false,
"video": false,
"fixturesFolder": false
}
12 changes: 12 additions & 0 deletions cypress/integration/test.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
/// <reference types="cypress" />

const { watchFile } = require("fs")

context('Page load', () => {
beforeEach(() => {
cy.visit('/')
cy.wait(500)
})
describe('React integration', () => {

it('Should mount', () => {
cy.get('#app')
.should('exist', 'success')
})
it('Should have foo property on button', () => {
cy.get('.clicker')
// .its('foo')
// .should('eq', 3)
.then(($el) => {
cy.wrap($el[0].foo).should('eq', 3)
})
})
})
})
8 changes: 4 additions & 4 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import xs from 'xstream';
import {createElement} from 'react';
import {render} from 'react-dom';
import {h, makeComponent} from '../src/index';
import {h2, makeComponent} from '../src/index';

function main(sources) {
const init$ = xs.of(() => 0);
Expand All @@ -20,9 +20,9 @@ function main(sources) {
.fold((state, fn) => fn(state));

const vdom$ = count$.map(i =>
h('div', [
h('h1', `Hello ${i} times`),
h('button', {sel: btnSel}, 'Reset'),
h2('div', [
h2('h1', `Hello ${i} times`),
h2('button', {sel: btnSel, className: 'clicker', domProps: {foo: 3}}, 'Reset'),
]),
);

Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@
"compile": "npm run compile-cjs && npm run compile-es6",
"compile-cjs": "tsc --module commonjs --outDir ./lib/cjs",
"compile-es6": "echo 'TODO' : tsc --module es6 --outDir ./lib/es6",
"test": "$(npm bin)/mocha test/*.ts --require ts-node/register --recursive; cypress run",
"test": "$(npm bin)/mocha test/*.ts --require ts-node/register --recursive",
"full-test": "npm test; npm run cypress:run",
"start": "parcel example/index.html",
"serve-test": "start-server-and-test start http://localhost:1234 test"
"serve-test": "start-server-and-test start http://localhost:1234 full-test",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
}
}
162 changes: 162 additions & 0 deletions src/h2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {
createElement,
ReactElement,
ReactNode,
ElementType,
ReactHTML,
Attributes,
Component,
ComponentType,
createRef,
forwardRef
} from 'react';
import {incorporate} from './incorporate';

export type PropsExtensions = {
sel?: string | symbol;
domProps?: any
};

type PropsLike<P> = P & PropsExtensions & Attributes;

type Children = string | Array<ReactNode>;

export function domPropify(Comp: any): ComponentType<any> {
class DomProps extends Component<any, any> {
private ref: any;
private domProps: any;
constructor(props) {
super(props);
this.domProps = this.props.domProps;
this.ref = props.forwardedRef || createRef();
}

public componentDidMount() {
if (this.domProps && this.ref) {
Object.entries(this.domProps).forEach(([key, val]) => {
this.ref.current[key] = val;
});
}
}

render() {
const p: any = {ref: this.ref, ...this.props};
delete p.forwardedRef
delete p.domProps;
return createElement(Comp, p);
}
}

return forwardRef((props, ref) => {
return createElement(DomProps, {...props, forwardedRef: ref});
});
}

export function domHookify(Comp: any): ComponentType<any> {
class DomHooks extends Component<any, any> {
private ref: any;
private hooks: any;
constructor(props) {
super(props);
this.hooks = this.props.domHooks;
this.ref = props.forwardedRef || createRef();
}

public componentDidMount() {
if (this.hooks && this.hooks.insert && this.ref) {
this.hooks.insert({elm: this.ref.current})
}
}

public componentDidUpdate() {
if (this.hooks && this.hooks.update && this.ref) {
this.hooks.update({elm: this.ref.current})
}
}

public componentWillUnmount() {
if (this.hooks && this.hooks.destroy && this.ref) {
this.hooks.destroy({elm: this.ref.current})
}
}

render() {
const p: any = {ref: this.ref, ...this.props};
delete p.forwardedRef
delete p.domHooks;
return createElement(Comp, p);
}
}

return forwardRef((props, ref) => {
return createElement(DomHooks, {...props, forwardedRef: ref});
});
}

function createElementSpreading<P = any>(
type: ElementType<P> | keyof ReactHTML,
props: PropsLike<P> | null,
children: Children,
): ReactElement<P> {
if (typeof children === 'string') {
return createElement(type, props, children);
} else {
return createElement(type, props, ...children);
}
}

function hyperscriptProps<P = any>(
type: ElementType<P> | keyof ReactHTML,
props: PropsLike<P>,
): ReactElement<P> {
if (!props.sel) {
return createElement(type, props);
} else {
return createElement(domHookify(domPropify(incorporate(type))), props);
}
}

function hyperscriptChildren<P = any>(
type: ElementType<P> | keyof ReactHTML,
children: Children,
): ReactElement<P> {
return createElementSpreading(type, null, children);
}

function hyperscriptPropsChildren<P = any>(
type: ElementType<P> | keyof ReactHTML,
props: PropsLike<P>,
children: Children,
): ReactElement<P> {
if (!props.sel) {
return createElementSpreading(type, props, children);
} else {
return createElementSpreading(domHookify(domPropify(incorporate(type))), props, children);
}
}

export function h2<P = any>(
type: ElementType<P> | keyof ReactHTML,
a?: PropsLike<P> | Children,
b?: Children,
): ReactElement<P> {
if (a === undefined && b === undefined) {
return createElement(type, null);
}
if (b === undefined && (typeof a === 'string' || Array.isArray(a))) {
return hyperscriptChildren(type, a as Array<ReactNode>);
}
if (b === undefined && typeof a === 'object' && !Array.isArray(a)) {
return hyperscriptProps(type, a);
}
if (
a !== undefined &&
typeof a !== 'string' &&
!Array.isArray(a) &&
b !== undefined
) {
return hyperscriptPropsChildren(type, a, b);
} else {
throw new Error('Unexpected usage of h() function');
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export {ScopeContext} from './context';
export {Scope} from './scope';
export {ReactSource} from './ReactSource';
export {h} from './h';
export {h2} from './h2'
export {incorporate} from './incorporate';
export {StreamRenderer} from './StreamRenderer';
Loading

0 comments on commit 47765a3

Please sign in to comment.