DEV Community

Cover image for 🌐 How to share components between sites
Anthony Max
Anthony Max Subscriber

Posted on • Edited on

🌐 How to share components between sites

It is not uncommon to encounter such a task when creating a web application, when it is necessary to transfer a component between sites. Usually, these are some kind of general buttons, blocks like footer, header, and the like.

Component

For example, we can take the button component, which we will transfer between the components. It will look like this:

<button class="button">Click Me</button>
<style>
  .button {
    background-color: #4caf50;
    color: white;
    border: none;
    padding: 12px 24px;
    text-align: center;
    text-decoration: none;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s, transform 0.2s;
  }

  .button:hover {
    background-color: #45a049;
  }

  .button:active {
    transform: scale(0.95);
  }
</style>
Enter fullscreen mode Exit fullscreen mode

The result on the site will be as follows:

button

Now, let's take two sites for which you need to make the component common. Let it be example1 example2. They can all be hosted on different hosting sites. Let one be deployed from GithHub, and the other from some local hosting.

Now, the main question arises - how to share?

Several ways of sharing

I will describe several methods that are usually used for this. From the most banal to the most practical.

Sharing components

Both of these approaches will look something like this.

1. Output to a file and connection by script

This method assumes that there will be a function that returns HTML markup. And, this function can be connected via a file remotely. It doesn't matter where this file will be located. You just need to connect it from there.

createButton.js

// buttonModule.js
(function (global) {
  // Define the createButton function
  function createButton() {
    // Create a <style> element and add styles
    const style = document.createElement('style');
    style.textContent = `
      .button {
        background-color: #4caf50;
        color: white;
        border: none;
        padding: 12px 24px;
        text-align: center;
        text-decoration: none;
        font-size: 16px;
        border-radius: 5px;
        cursor: pointer;
        transition: background-color 0.3s, transform 0.2s;
      }
      .button:hover {
        background-color: #45a049;
      }
      .button:active {
        transform: scale(0.95);
      }
    `;

    // Create the button element
    const button = document.createElement('button');
    button.className = 'button';
    button.textContent = 'Click Me';

    // Return the elements (style and button)
    return { style, button };
  }

  // Expose the function to the global scope
  global.buttonModule = {
    createButton,
  };
})(window);
Enter fullscreen mode Exit fullscreen mode

example1/root/index.html,
example2/root/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Button Module</title>
  <script src="https://.../buttonModule.js"></script>
</head>
<body>
  <div id="wrapper"></div>
  <script>
    // Use the buttonModule
    const { style, button } = buttonModule.createButton();
    const wrapper = document.getElementById("wrapper");
    wrapper.append(style); // Attach styles to the document
    wrapper.append(button); // Add the button to the page
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Here we connect the module via a site that is not connected to our two sites. It could be the same GitHub.

Features (Advantages):

  • Easy to implement with standard <script> tags in HTML without additional setup.
  • Requires no modern tools or configurations like Webpack or Vite.
  • Suitable for small, single-page applications or quick experiments.
  • Minimal setup, enabling faster development.
  • Can be seamlessly integrated into existing projects that already rely on global variables.

Drawbacks:

  • If there are many components, there will be a thousand scripts, which makes this method suitable only for a single use.
  • Adds variables or objects to the global scope, increasing the risk of naming conflicts.
  • Difficult to avoid clashes when multiple scripts are used.
  • Makes the project harder to scale or refactor.
  • Scripts depend on the correct order of loading, which must be manually managed.
  • Less maintainable and not aligned with current best practices.

2. Using a third-party library and moving the component to the API

For this method, we will use a module such as HMPL. It will allow you to connect components from the server using simple templates based on objects.

HMPL repo

To begin with, let's take the component to the server. Create a separate HTML and give it via API request. What will the .html file look like:

button.html

<button class="button">Click Me</button>
<style>
  .button {
    background-color: #4caf50;
    color: white;
    border: none;
    padding: 12px 24px;
    text-align: center;
    text-decoration: none;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s, transform 0.2s;
  }

  .button:hover {
    background-color: #45a049;
  }

  .button:active {
    transform: scale(0.95);
  }
</style>
Enter fullscreen mode Exit fullscreen mode

After that, we will need to somehow transfer this file to the server. Let the backend be on Node.js. We will use express.js as one of the most popular frameworks for creating API. First, we will set the route by which we will receive our component:

buttonController.js

const express = require("express");
const expressRouter = express.Router();
const path = require("path");

const buttonController = (req, res) => {
  res.sendFile(path.join(__dirname, "../button.html"));
};

expressRouter.use("/getButton", buttonController);
Enter fullscreen mode Exit fullscreen mode

app.js

const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const cors = require("cors");
const PORT = 8000;
const app = express();
const routes = require("./routes/buttonController");

app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors({ origin: true, credentials: true }));

app.set(express.static(path.join(__dirname, "src")));

app.use("/api", routes);

app.listen(PORT);
Enter fullscreen mode Exit fullscreen mode

After this, we will have a route by which we can now easily take the component. On the site we connect HMPL. It can be connected in several ways, let's consider the main ones:

Via script

<script src="https://unpkg.com/json5/dist/index.js"></script>
<script src="https://unpkg.com/hmpl-js/dist/hmpl.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Via import

import hmpl from "hmpl-js";
Enter fullscreen mode Exit fullscreen mode

We use method 1, since index.html is the default on our sites.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Button Module</title>
  </head>
  <body>
    <script src="https://unpkg.com/json5/dist/index.js"></script>
    <script src="https://unpkg.com/hmpl-js/dist/hmpl.min.js"></script>
    <script>
      const templateFn = hmpl.compile(
        `<div id="wrapper">{{ src: "https://.../api/getButton" }}</div>`
      );
      const btnWrapper = templateFn().response;
      document.body.append(btnWrapper);
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Here we do almost the same as in the first method, but the trick is that now you can safely reuse the component. Let's say you can do it like this:

const btnWrapper1 = templateFn().response;
const btnWrapper2 = templateFn().response;
Enter fullscreen mode Exit fullscreen mode

Also, there is a lot of additional functionality that comes from the module - indicators, request error handling, etc. Since the module is based on fetch, you can effectively customize requests and do much more.

Features (Advantages):

  • Reusing components
  • Suitable for both small and large applications with thousands of components
  • A huge amount of functionality aimed specifically at this server-oriented approach to components displayed on the client
  • Flexibility in use

Drawbacks:

  • Connecting two script files
  • Creating an additional API

This approach kind of implements the SSR approach, but on the client without the key element for it - visibility for robots, but otherwise - it's a cool method that can make the solution of the problem easier.

Conclusion

Depending on the situation, you can use either the first approach or the second. In the first one, you have full control over the process, but it is still not suitable when you need to work with several components, since you will have to constantly import files, which is not good.

Thank you all for reading! I hope you found this article helpful!

Top comments (4)

Collapse
 
santanu_biswas profile image
Santanu Biswas • Edited

Hi, @anthonymax ,
This is really a good content. You added 2 approaches here. There are few more approaches are there.

  1. You can publish your reusable components into npm registry and later when you need in any project then install and import it to reuse.
  2. You can use module federation concept to share components to others websites.
  3. You can publish your reusable components into bit.dev and you can reuse it.
Collapse
 
anthonymax profile image
Anthony Max

Hello! Yes, you can do it via npm, but I thought that since the first method is almost the same, I thought that it was not worth adding additionally. In theory, you can do it on npm, and in any other service that provides access to the file via a link.

Collapse
 
santanu_biswas profile image
Santanu Biswas • Edited

Yes, from a performance perspective, importing a reusable component from a package manager registry is almost similar to adding it via a script tag. The main difference is that in the former case, the initial build size may be larger, but there are no additional network calls. Additionally, managing dependencies with other external libraries is easier when using a component imported from package manager registry, compared to directly adding them via a script tag.

Thanks for this beautiful content.
Your hmpl library is really good for performance perspective using proxy server.

Thread Thread
 
anthonymax profile image
Anthony Max

Thank you very much! I just think that in npm the components will be common to everyone, and with the library you can just send credentials and take html from the server with protection.