Last active
February 28, 2019 13:07
-
-
Save magnusjt/32250b89bbc3a529c413f1fc8cc256f8 to your computer and use it in GitHub Desktop.
Create react app inside webcomponent (v1) with shadow dom fallback
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
| const ReactDOM = require('react-dom') | |
| const retargetEvents = require('react-shadow-dom-retarget-events') | |
| function getStyleElementsFromReactWebComponentStyleLoader(){ | |
| try{ | |
| return require('react-web-component-style-loader/exports').styleElements | |
| }catch(e){ | |
| return [] | |
| } | |
| } | |
| module.exports = function createWebComponent(reactApp, tag, observedAttributes = []){ | |
| customElements.define(tag, class extends HTMLElement{ | |
| constructor(){ | |
| super() | |
| if(this.attachShadow){ | |
| this._constructWithShadowDOM() | |
| }else{ | |
| this._constructWithoutShadowDOM() | |
| } | |
| } | |
| _constructWithShadowDOM(){ | |
| const shadowRoot = this.attachShadow({mode: 'open'}) | |
| const mountPoint = document.createElement('div') | |
| const styles = getStyleElementsFromReactWebComponentStyleLoader() | |
| for(let style of styles){ | |
| shadowRoot.appendChild(style.cloneNode(true)) | |
| } | |
| shadowRoot.appendChild(mountPoint) | |
| this.appInstance = null | |
| let that = this | |
| ReactDOM.render(reactApp, mountPoint, function(){ | |
| that.appInstance = this | |
| that._callLifeCycleHook('webComponentConstructed', that) | |
| }) | |
| retargetEvents(shadowRoot) | |
| } | |
| _constructWithoutShadowDOM(){ | |
| const mountPoint = document.createElement('div') | |
| const styles = getStyleElementsFromReactWebComponentStyleLoader() | |
| for(let style of styles){ | |
| this.appendChild(this._getTagScopedStyle(style)) | |
| } | |
| this.appendChild(mountPoint) | |
| this.appInstance = null | |
| let that = this | |
| ReactDOM.render(reactApp, mountPoint, function(){ | |
| that.appInstance = this | |
| that._callLifeCycleHook('webComponentConstructed', that) | |
| }) | |
| } | |
| _getTagScopedStyle(style){ | |
| let replacedStyle = document.createElement('style') | |
| replacedStyle.innerHTML = style.innerHTML.replace(/:host/g, tag) | |
| return replacedStyle | |
| } | |
| // Determines what attributes we will get change-callback for | |
| static get observedAttributes(){ | |
| return observedAttributes | |
| } | |
| _callLifeCycleHook(name, ...args){ | |
| if(this.appInstance[name]){ | |
| this.appInstance[name](...args) | |
| } | |
| } | |
| connectedCallback(){ | |
| this._callLifeCycleHook('webComponentConnected') | |
| } | |
| disconnectedCallback(){ | |
| this._callLifeCycleHook('webComponentDisconnected') | |
| } | |
| attributeChangedCallback(attrName, oldVal, newVal){ | |
| this._callLifeCycleHook('webComponentAttributeChanged', attrName, oldVal, newVal) | |
| } | |
| adoptedCallback(){ | |
| this._callLifeCycleHook('webComponentAdopted') | |
| } | |
| }) | |
| } |
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
| Basically the same as react-web-component (https://github.com/WeltN24/react-web-component), except it works with webcomponents v1. | |
| NB: There is no need to include a heavy-handed-barely-working shadow dom polyfill. | |
| ### Scoped styles | |
| If shadow dom is not available, it falls back on a normal div. | |
| Styles must be scoped by adding the following postcss plugin to your build: | |
| require('postcss-scopify')(':host') | |
| This will scope all styles to the custom element (actually, it prefixes all your styles with ":host"). | |
| If shadow dom is supported, this makes no difference. | |
| If shadow dom is not supported, we replace :host with the tag of the webcomponent (ex: my-element). | |
| This means that you can use the :host tag in your css even when using a browser without shadow dom! | |
| NB: Remember that the styles you include in your react app are scoped, but the global styles on the page | |
| will still leak inside. It's a good idea to include a css reset of some kind to mitigate this (e.g. bootstrap). | |
| If you were to use a shadow dom polyfill with shadycss, you would still need to do this. | |
| ### What polyfills do I need? | |
| Polyfill for custom elements (and html imports): | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.1.0/webcomponents-hi-ce.js"></script> | |
| Polyfill for es5 definition of web component in v1 style: | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.1.0/custom-elements-es5-adapter.js"></script> | |
| ### Observed attributes? | |
| In web components v1, you need to explicitly list the attributes you want change callback on. | |
| Use the observedAttributes array for this. |
Author
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Lately I've decided to skip shadow dom entirely when using react inside a web component. The reason is that several react libraries depend on beeing able to access events from inside the custom element (various datepickers and dialogues). These libraries could be updated to fix the problem, but for now it's easier to just not use shadow dom.