Skip to content

Instantly share code, notes, and snippets.

@goldhand
Created October 20, 2018 01:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goldhand/5dcd9334f12c07e0a28f2014911babce to your computer and use it in GitHub Desktop.
Save goldhand/5dcd9334f12c07e0a28f2014911babce to your computer and use it in GitHub Desktop.
Render React components and modules as web components
import * as React from 'react';
import {hot} from "react-hot-loader";
const slotChildren = (UnwrappedComponent, options = {}) => {
// enable hot reloading in each react tree
const Component = hot(options.module || module)(UnwrappedComponent);
const slotName = `slot-${options.name}`;
class SlotWrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
const {children, ...props} = this.props;
return (
<Component {...props}>
{children}
<slot name={slotName} />
</Component>
);
}
}
SlotWrapper.displayName = `SlotChildren(${Component.displayName || Component.name || '[component]'})`;
SlotWrapper.createChildSlot = () => {
const childSlot = document.createElement('span');
childSlot.setAttribute('slot', slotName);
return childSlot;
};
return SlotWrapper;
};
export default slotChildren;
import * as React from 'react';
import * as R from 'ramda';
import ReactDOM from 'react-dom';
import slotChildren from './SlotChildren';
const toWebComponent = (UnwrappedComponent, options = {}) => {
const Component = slotChildren(UnwrappedComponent, options);
class WebComponent extends HTMLElement {
constructor(...args) {
super(...args);
this.name = options.name;
this.mountPoint = document.createElement('span');
this.shadow = this.attachShadow({mode: 'open'});
this.childMount = Component.createChildSlot();
}
get props() {
return this._props;
}
set props(props) {
this._props = props;
this.update();
}
get parent() {
return this._parent;
}
set parent(parent) {
this.path = parent.path
? `${parent.path}.${this.name}`
: parent.name;
this._parent = parent;
}
connectedCallback() {
this.appendChild(this.childMount);
this.shadow.appendChild(this.mountPoint);
ReactDOM.render(
<Component {...this.props} />,
this.mountPoint,
);
}
update() {
ReactDOM.unmountComponentAtNode(this.mountPoint);
ReactDOM.render(<Component {...this.props} />, this.mountPoint);
}
}
// Add WebComponent to registry
customElements.define(options.name, WebComponent);
const renderElement = R.curry((props, parent) => {
const component = document.createElement(options.name);
component.props = props;
component.parent = parent;
parent.childMount.appendChild(component);
// just for fun
if (component.path) console.log(component.path); // eslint-disable-line no-console
return component;
});
renderElement.Close = (child) => {
if (child && child.parent) {
return child.parent;
} else {
console.info('no parent to return, must be the end...'); // eslint-disable-line no-console
}
};
renderElement.AndClose = R.curry((props, parent) => {
renderElement(props, parent);
return parent;
});
return renderElement;
};
export default toWebComponent;
import * as React from 'react';
import toWebComponent from '../WebComponent';
const styles = {
greeting: {
color: 'green',
},
user: {
color: 'blue',
}
};
const Greet = ({
user,
greeting,
attrs,
children,
}) => (
<div>
<h1 style={styles.greeting}>{greeting}</h1>
<h3 style={styles.user}>{user}</h3>
<ul>
{attrs
? attrs.map(attr => <li key={attr.name}>{attr.name + ': ' + attr.value}</li>)
: null
}
</ul>
{children}
</div>
);
export default toWebComponent(Greet, {name: 'greet-component', module});
import * as React from 'react';
import toWebComponent from '../WebComponent';
const style = {
color: 'rebeccapurple',
};
const Header = ({
children,
}) => (
<h1 style={style}>{children}</h1>
);
export default toWebComponent(Header, {name: 'header-component', module});
import * as React from 'react';
import * as R from 'ramda';
import Greet from './components/Greet';
import Header from './components/Header';
import Container from './components/Container';
import Text from './components/Text';
const greetProps = {
user: 'Will', greeting: 'Hello Good Sir!', attrs: [
{name: 'height', value: '6 feet'},
{name: 'hair', value: 'blond'},
{name: 'weight', value: 'HEY EASY BUDDY!'},
],
};
const headerProps = {text: 'I am Header'};
const textProps = {text: 'I am text'};
const containerProps = {children: 'childen container prop'};
const renderApp = R.pipe(
Container(containerProps),
Header.AndClose(headerProps),
Text.AndClose(textProps), Text.Close,
Greet(greetProps),
Container(containerProps),
Text.AndClose({text: 'I am inside "Container.Greet.Container"'}),
Container.AndClose({children: <h3>React and JSX still work</h3>}),
Container.Close,
Greet.Close,
Container.Close,
);
export default childMount => renderApp({childMount, name: 'App'});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment