creating pure web component from react components
Asked Answered
B

4

11

i am trying to build web components from react components, it is all working fine, but there are two problems i am trying to solve:

  1. Is there a way to convert such web components to pure web component (using webpack, transpile or some other way), so that react and other dependencies are not bundled?
  2. Is there a way to just include the required portion of dependencies or it is all/none only, and have to use external setting of webpack to use host's version?

thanks

Belia answered 6/4, 2021 at 14:42 Comment(0)
B
13

For the first question, there is no direct way to convert React component into a Web Component. You will have to wrap it into a Web Component Class:

export function MyReactComponent() {
  return (
    <div>
      Hello world
    </div>
  );
}


class MyWebComponent extends HTMLElement {
  
  constructor() {
    super();
    // Do something more
  }

  connectedCallback() {
    
    // Create a ShadowDOM
    const root = this.attachShadow({ mode: 'open' });

    // Create a mount element
    const mountPoint = document.createElement('div');
    
    root.appendChild(mountPoint);

    // You can directly use shadow root as a mount point
    ReactDOM.render(<MyReactComponent />, mountPoint);
  }
}

customElements.define('my-web-component', MyWebComponent);

Of course, you can generalize this and create a reusable function as:

function register(MyReactComponent, name) {
  const WebComponent = class extends HTMLElement {
    constructor() {
      super();
      // Do something more
    }
  
    connectedCallback() {
      
      // Create a ShadowDOM
      const root = this.attachShadow({ mode: 'open' });
  
      // Create a mount element
      const mountPoint = document.createElement('div');
      
      root.appendChild(mountPoint);
  
      // You can directly use shadow root as a mount point
      ReactDOM.render(<MyReactComponent />, mountPoint);
    }
  }
  
  customElements.define(name, MyWebComponent);
}

register(MyReactComponent, 'my-web-component');

Same register function can be now re-used across all the components that you want to expose as web components. Further, if your component accepts props that should be passed, then this function can be changed to accept third argument as array of string where each value would be registered as a setter for this component using Object.define. Each time a setter is called, you can simply call ReactDOM.render again.

Now for the second question, there are multiple scenarios with what you are trying to do.

  • If you are bundling application and loading dependencies like React or others using CDN, then Webpack externals is a way to go. Here you will teach Webpack how to replace import or require from the global environment where app will run.
  • If you are bundling a library which you intend to publish to NPM registry for others to use in their applications, then you must build your project using library target configuration. Read more about authoring libraries here.

Bundling for libraries is slightly trickier as you will have to decide what will be your output format (common.js, ES Modules or UMD or Global or multiple formats). The ideal format is ES Module if you are bundling for browser as it allows better tree shaking. Webpack previously did not support Module format and has recently started supporting it. In general, I recommend Webpack for applications and Rollup.js for libraries.

Blackbeard answered 8/4, 2021 at 17:33 Comment(5)
Thank you Harshal for the answer. webpack externals is the way we have been thinking of. so overall right now there seems no util/package to convert a wrapped react component to pure web component one?Belia
I am unable to use global stores etc within the react functional componentOxymoron
@nagendranag You need two things in your functional component. First use useState which gives you initial state from some of the global stores which you can import or receive as prop and then use useEffect hook to subscribe to the global store changes so that rendering can be triggered on state change.Blackbeard
how can I use other things like i18n, Apollo client, etc I should be able to use everything which is used in the appOxymoron
@nagendranag It is not possible to cover it in comments what you are asking as it is very open-ended. But general approach is same that I mentioned. You can try asking as a new question on StackOverflow but has to be more specific.Blackbeard
S
7

If you're looking to do this manually, the answer from Harshal Patil is the way to go. I also wanted to point out a library that I helped create, react-to-webcomponent. It simplifies this process and seamlessly supports React 16-18.

Shelton answered 18/6, 2022 at 1:33 Comment(1)
I just want to note that it doesn't support React children, which is a huge gap imoLori
C
0

React is not a library made to build native web-components.

Writing web-components by hand is not the best option neither as it won't handle conditions, loops, state change, virtual dom and other basic functionalities out of the box.

React, Vue Svelte and other custom libraries certainly have some pros while native web-components have many other advantages like simplicity of ecosystem and being ported by browsers and W3C.

Some libraries that will help you write native web-components in a modern and handy way:

  • Lego that is alightweight, native and full-featured in a Vue style.
  • Nativeweb lightweight and raw web-components
  • ElemX a proof-of-concept that binds native web-component to ElemX functionalities.

If you really wanted to wrap a React component into a native web component, you could do something like:

class MyComponent extends HTMLElement {
  constructor() {
    this.innerHTML = '<MyReactComponent />'
  }
}

customElements.define('my-component', MyComponent)

And use it in your HTML page <my-component />

Contraction answered 18/7, 2021 at 9:24 Comment(0)
B
0

For react 18+ with dom unmount

import React from "react"
import ReactDOM from "react-dom/client"

export function defineWebComponent(name: string, Component: React.FC) {
    customElements.define(
        name,
        class extends HTMLElement {
            private root: ReactDOM.Root

            constructor() {
                super()
                this.root = ReactDOM.createRoot(this)
            }

            connectedCallback() {
                this.root.render(
                    <React.StrictMode>
                        <Component/>
                    </React.StrictMode>
                )
            }

            disconnectedCallback() {
                this.root.unmount()
            }
        })
}

usage

// main.ts

import App from './App.tsx'

defineWebComponent('app-root', App)

then compile and import script on html

<!-- index.html -->

<app-root>
</app-root>

Optional

// make typescript happy

import App from "../App.tsx";
import type {ComponentProps} from "react"

declare module "react" {
    namespace JSX {
        interface IntrinsicElements {
            "app-root": ComponentProps<typeof App>
        }
    }
}
Blaney answered 17/5 at 7:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.