DEV Community

Zhiyue Yi
Zhiyue Yi

Posted on

Include Your React Widgets in Any Web Page

Visit my Blog for the original post: Include Your React Widgets in Any Web Page

Hello everyone! It's been a long time since my last post because I was so busy with my study in the last semester and, of course, damn COVID-19! But anyway, I finally graduate from the university, so it's time to make a post! Woohoo!!

To combat COVID-19, I participated a government open source project, which requires me to build a react widget library and integrate to any web pages which can be written in any web frameworks, such as Angular, Vue or even Svelte.

Initially, I thought I may need web component to solve the problem, but eventually, I found a native javascript way to solve it, thanks to Dhrubajit for inspiring me!

The code is HERE and the Demo is HERE

Main Idea

To make the widget universal for all kinds of web applications, we can utilise <script> tag, which can load a bundled JavaScript file including the React run time and the widget itself. Then, in the target web page, we can create a <div> tag with a specific ID. Based on the ID, we can render the widget inside of the <div> tag. In this way, we can load our React widget in any web page, because they are all basically JavaScript!

The target of this demo is to allow the following HTML codes

<div id="simple-calendar" is-click-disabled min-year="2020"></div>
<script src="./simple-calendar.js"></script>
Enter fullscreen mode Exit fullscreen mode

To render an equivalent react component as

<SimpleCalendar id="simple-calendar" isClickDisabled={true} minYear={2020}>
Enter fullscreen mode Exit fullscreen mode

Setup

For the setup, you can just follow what I did in the demo project. The widget source codes are included in the src folder and the demo web page is in the docs folder.

The idea is that, we need to use Webpack to bundle whatever in the src folder, and then produce the bundled JavaScript file named simple-calendar.js to the docs folder. Then, the docs/index.html page can load the bundled JavaScript file by <script src="./simple-calendar.js"></script>. In this way, the React widget SimpleCalendar can be rendered in the docs/index.html page.

What's most interesting is that, docs/index.html is just a plain static web page, and it can still pass initial configurations to the <div> tag to render the react SimpleCalendar component.

Webpack Configuration

It's fairly easy to create a simple WebPack configuration for this Demo. Basically we just want to bundle the whole src folder with the entry file index.ts, and then output the bundled file to docs folder with a name as simple-calender.js.

I recommend you to read Demystifying Webpack written by my good friend Dhrubajit because his tutorial on Webpack configuration is AWESOME!

const path = require("path");

const config = {
  entry: path.join(__dirname, "./src/index.ts"),
  output: {
    path: path.resolve(__dirname, "./docs"),
    filename: "simple-calendar.js",
  },
  devtool: "source-map",
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".css", ".txt"],
  },
  module: {
    rules: [
      {
        test: /\.ts(x?)$/,
        exclude: /node_modules/,
        include: [path.resolve("src")],
        loader: "ts-loader",
        options: {
          transpileOnly: false,
          compilerOptions: {
            module: "es2015",
          },
        },
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};

module.exports = config;
Enter fullscreen mode Exit fullscreen mode

HTML Tag Wrapper

Here comes the core function to achieve including react widgets in any web page, which is to find the element from the web page with a pre-defined element ID.

And then we need to get all the HTML tag attributes from the element, parse them into a key-value pair object, dump the object into the React Component (which is our SimpleCalendar component in src/simple-calendar.tsx file) and lastly, use ReactDOM to render the component.

function HtmlTagWrapper(Component: (props?: any) => JSX.Element) {
  const el = document.getElementById("simple-calendar");
  const attrs = el.attributes;

  const props = attrToObj(attrs);
  console.log(props);
  ReactDOM.render(<Component {...props} />, el);
}
Enter fullscreen mode Exit fullscreen mode

Attributes to Object

It's actually a bit tricky to transform the attributes to key-value pair objects, because attributes are of the NamedNodeMap type. According to NamedNodeMap - Mozilla, a NamedNodeMap object can be access by index as in an array, or by key name as in an object.

function attrToObj(attrs: NamedNodeMap) {
  const attrsObj: { [key: string]: unknown } = {};
  const length = attrs.length;
  for (let i = 0; i < length; i++) {
    const { name, value } = attrs[i];
    attrsObj[parseKey(name)] = parseValue(value);
  }
  return attrsObj;
}
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, I can simply grab the name and value from attrs[i].

And here comes another tricky part for name and value. I have to parse them so that the constructed attrsObj can have correct keys and values.

Parse Keys and Values

Let's say you have a simple HTML div tag as <div id="simple-calendar" is-click-disabled min-year="2020">

You intend to have an attrsObj to be constructed as

{
  "id": "simple-calendar",
  "isClickDisabled": true,
  "minYear": 2020
}
Enter fullscreen mode Exit fullscreen mode

However, from const { name, value } = attrs[i];, the values you get are all strings, the keys you get are all small letters connected with -

Therefore, you need to parse them, so that the values can be string, number, boolean respectively and the keys can be in camel cases without delimiters.

function parseValue(value: any) {
  if (value === "" || value === "true") {
    return true;
  }

  if (value === "false") {
    return false;
  }

  if (Number(value).toString() === value) {
    return Number(value);
  }

  return value;
}

function parseKey(key: string) {
  const parts = key.split("-");
  const newParts = [parts[0]];
  for (let i = 1; i < parts.length; i++) {
    const firstLetter = parts[i].slice(0, 1);
    const restOfLetters = parts[i].slice(1);
    const newPart = firstLetter.toUpperCase() + restOfLetters;
    newParts.push(newPart);
  }
  return newParts.join("");
}
Enter fullscreen mode Exit fullscreen mode

Put Them Together

By implementing all of parts I mentioned above, finally you can just write an HTML div tag as

<div id="simple-calendar" is-click-disabled min-year="2020"></div>
<script src="./simple-calendar.js"></script>
Enter fullscreen mode Exit fullscreen mode

To render an equivalent react component as

<SimpleCalendar id="simple-calendar" isClickDisabled={true} minYear={2020}>
Enter fullscreen mode Exit fullscreen mode

And most importantly, you can put your <div> and <script> in any kind of web page because they are simply standard JavaScript and HTML!

Top comments (2)

Collapse
 
atscub profile image
Abraham Toledo

Hey, thanks for the great tutorial. Will this work even if the host website also user React potentially with a different version? Is the React object isolated from the global scope?

Thank you so much!

Collapse
 
kumailp profile image
Kumail Pirzada

@zhiyueyi Hey this is great! Can I know if there's a way to run it live with hot reloading? Right now I have to do npm run build && npm start. It would be difficult to develop this way