Converting a react app to appendable widget using web components
image from an article of Jason Beres [Infragistics]

Converting a react app to appendable widget using web components

Overview

I was inspired to write this article because it was one of the coolest challenges that I had last week so I decided to share it with you, I hope you enjoy it!

let's talk about the challenge, imagine you have an application such as online support chats and you want to convert it into a simple script to be appendable for other applications or websites there are some solutions like using iframes or XHR requests to fetch results and append child to a specific container like div, that actually I didn't like them so I looked for web components and here we are!

Technologies

I'm using vite and rollup for compiling and building the application and widget

react js is our main library for creating the application and @r2wc/react-to-web-component is for converting react components to web components

Let's Start The Coding!

First Step -> Creating a vite-react-ts application

To create the project and get started, enter the below command in your terminal or CLI, this command will create a vite application using react and typescript template

# npm 7+, extra double-dash is needed:
npm create vite@latest widget-app -- --template react-ts

#yarn
yarn create vite widget-app --template react-ts        

now enter the below command to install your node module packages

#npm
npm i

#yarn
yarn        

Second Step -> Creating widget file

1. you need to install @r2wc/react-to-web-component

run the below command

#npm
npm i @r2wc/react-to-web-component

#yarn 
yarn add @r2wc/react-to-web-component        

2. Then create a widget.tsx file in the src folder, this file makes our whole application as a widget

to define our web component we need to use customElements that

"Defines a new custom element, mapping the given name to the given constructor as an autonomous custom element."

what this package will solve for us is to convert JSX.Element to CustomElementConstructor so we can pass it's object to customElements

import r2wc from '@r2wc/react-to-web-component';
import App from './App';

const WebComponentWidget = r2wc(App, {
  props: {
    applicationId: 'string',
  },
});

customElements.define('my-widget', WebComponentWidget);        

Third Step -> Compiling and exporting our widget

here we have three challenges for this project, what are they?

  1. when you build the vite application all compiled file names will be hashed for example widget.tsx file will be -> widget-fpzOp2_c.js and every time that we are building the application the file name will change so the widget will be useless after a build
  2. we don't import widget file to any page or component so the compiler will ignore it and the widget won't build at all
  3. the styles are in separate files so we should bundle and merge them to a single file

at first, to define __dirname keyword we need to install @types/node

#npm
npm install --save @types/node

#yarn
yarn add @types/node --save-dev        

what we are going to do is apply some changes in vite.config.ts using rollup to define static input source and output destination and name for our widget file

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        widget: './src/Widget.tsx',
      },
      output: {
        entryFileNames: (assetInfo) => {
          return assetInfo.name === 'widget' ?    'assets/js/[name].js':'assets/[name].js' ;
        },
      },
    },
  },
})        

and also to merge the styles of the component we need to use vite-plugin-css-injected-by-js for merging our styles so here is our vite.config.ts in the latest step

#npm
npm i vite-plugin-css-injected-by-js

#yarn 
yarn add vite-plugin-css-injected-by-js        

the experimental part of the configuration file means that all href and src paths should have our domain prefix because when you use the web component origin of the component will be changed so if you have an image like /assets/background.jpg its origin is wrong and should be https://localhost:4173/assets/background.jpg

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";

export default defineConfig({
  plugins: [react(), cssInjectedByJsPlugin()],
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, "index.html"),
        widget: "./src/Widget.tsx",
      },
      output: {
        entryFileNames: (assetInfo) => {
          return assetInfo.name === "widget"
            ? "assets/js/[name].js"
            : "assets/[name].js";
        },
      },
    },
  },
  experimental: {
    renderBuiltUrl(filename: string) {
      return "https://localhost:4173/" + filename;
    },
  },
});

        

now just simply build and run your application

#npm
npm run build && npm run preview

#yarn
yarn build && yarn preview        

now your widget is ready to use!


let's use our widget

so here is the easy part

create a simple index.html and use it like below

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://localhost:4173/assets/js/widget.js" type="module"></script>
</head>
<body>
    <my-widget applicationId="test-1" />
</body>
</html>        

applicationId is a prop that we defined in widget.tsx file

also remember that type of script should be module

nice post!!! I'm trying to develop a widget that ideally should be instantiated multiple times on the same page with different params. However, it is only being rendered for the first one in the list. Is it possible to have more than one with the same name but different IDs?

Felipe Bastos L?bo Silva

Software Engineer | QuintoAndar

7 个月

Nice example. If for example I use react query and other libs inside my component, how the application that will use the widget will deal with these dependencies?

回复
Azamat Raimbekov

Lead of the Front-End Development team at MDigital

7 个月

I have a case where I need both a widget and a regular project assembly that works through npm run preview Is it possible to make both the widget and npm run preview???

matin zarif amin

React Developer (Frontend)

10 个月

???? ???? ? ???? ??? ????

Sobhan Kazemi

Frontend Developer

10 个月

??????

要查看或添加评论,请登录

社区洞察

其他会员也浏览了