Created
October 20, 2018 01:57
-
-
Save goldhand/5dcd9334f12c07e0a28f2014911babce to your computer and use it in GitHub Desktop.
Render React components and modules as web components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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