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?
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
LP
1 个月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?
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?
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???
React Developer (Frontend)
10 个月???? ???? ? ???? ??? ????
Frontend Developer
10 个月??????