syed
syed

Reputation: 111

creating pure web component from react components

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

Upvotes: 11

Views: 15869

Answers (4)

mindlid
mindlid

Reputation: 2666

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>
        }
    }
}

Upvotes: 1

Christopher Baker
Christopher Baker

Reputation: 71

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.

Upvotes: 7

vinyll
vinyll

Reputation: 11419

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 />

Upvotes: -2

Harshal Patil
Harshal Patil

Reputation: 21030

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.

Upvotes: 13

Related Questions