DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • SAP Commerce Cloud Architecture: All You Need to Know!
  • MuleSoft: Do You Have an Extra Mule Under the Hood?
  • A Data-Driven Approach to Application Modernization
  • Introduction Garbage Collection Java

Trending

  • Simplify Authorization in Ruby on Rails With the Power of Pundit Gem
  • Comparing SaaS vs. PaaS for Kafka and Flink Data Streaming
  • Scalability 101: How to Build, Measure, and Improve It
  • Stateless vs Stateful Stream Processing With Kafka Streams and Apache Flink
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Creating a Frontend Architecture With Dynamic Plugins

Creating a Frontend Architecture With Dynamic Plugins

By 
Bogdan Nechyporenko user avatar
Bogdan Nechyporenko
DZone Core CORE ·
Feb. 24, 20 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
13.2K Views

Join the DZone community and get the full member experience.

Join For Free

The following are some of the most used approaches to handle pluggability on frontend:

  1. The main application works like a layout for all the features it contains, where each feature has switch on/switch off functionality. If a plugin is present, it will be displayed in a certain place. But, if you want to develop a new plugin, you will need to modify the main application, so it will be aware about it.
  2. Load plugins dynamically and add them to the main application as sub-applications in an iframe. That gives certain flexibility, as you can use different versions of the same third-party libraries, but there are also some costs, including:
    • The bundle size blows really fast. All required third-party plugins have to be included inside the plugin again.
    • To reuse already written logic in a core plugin, you either have to copy and paste it or create a shared-module with common functionality and include it in the core and custom plugin. In this latter scenario, when this shared functionality is different from plugin to plugin, it can become a mess really quickly. 
    • It will not allow you to bring smallchanges to an application, like replacing a button with a new one on the fly.

Keeping these limitations in mind, let’s have a look at a new approach. First, I will explain it with a simple example and then on a more advanced level.

As an easy example, we can model an application, where all custom plugins will be automatically registered to a navigation panel. Let’s take a look at a navigation panel inside of our main core UI plugin:
Example navbar

Example navbar

We want any new plugin to be added to the panel. We have no idea how the new plugin is going to communicate with a main application. Therefore to make it work, we need to define some conventions and communicational channels:

  • We have to define how we are going to detect UI plugins among other types of plugins.
  • Each plugin should contain metadata in a uniform format:
    • Give a title to the plugin in the navigation panel.
    • Define the place for the tab; otherwise, the order of custom tabs will be uncontrollable.
    • Specify the location of bundles that will be loaded to a page.
    • Define permissions and load plugins based on role.
  • Create an event-based communication channel between the core application and plugin and between plugins.

On top of that, we need to perform a validation that third-party libraries should be of the same version, as plugins will not bundle a third-party library, but rather, reuse it from the main application.

With these requirements in mind, let's try to go deeper into details and make our panel look like this (after custom plugin is loaded):

Navbar with new plugin

Navbar with new plugin

As you might have noticed, there is an extra “Reports” tab added at the navbar's second position. 

We have to define how we are going to detect UI plugins among other types of plugins

For that, we can add a file descriptor to each UI plugin that will indicate that it is a custom UI plugin, i.e. custom-ui-extension.json. With this, when we scan the folder/classpath, we can filter for new plugins.

Each plugin should contain a metadata in the uniform format.

Let’s design how this might look for above-mentioned requirements:

JSON
 




x
10


 
1
{
2
    "entry": "reports-bundle.js",
3
    "name": "reports-plugin",
4
    "weight": 15,
5
    "permissions": [
6
        "admin"
7
    ],
8
    "title": "Reports"
9
}
10

          



Title is defined obviously.  

For the position of tabs, I took the assumption that the core plugin tabs were built in the order where the first tab has a “weight” 10, second 20, etc. 

Custom plugins can identify themselves between which tabs they need to be placed between. There is still room to add extra plugins if several custom plugins have to be squeezed in between “Home" and “Shop” tabs. 

If two plugins have the same weight,  they can be ordered with some extra logic, like place them alpha-numerically so the position will not be applied randomly. The entry field contains information about the bundle and location. It can be for example: ${plugin-name}/web/${entry}. 

As an alternative, you can define in the metadata, the full path to the bundle file and give flexibility of UI plugins to have a different structure. The permissions field containing the list of restricted permission names for which this plugin will have an effect. If permissions is an empty list — then, there's open access. 

Create an event-based communication channel between core application and plugin and between plugins

The benefit of this type of communication channel is in loose coupling between plugins. If some events cannot be handled, they will be ignored, and the system can proceed to work normally. If the system is going to be huge or you want a customer to have the ability to create their own UI plugins, you need to create a detailed API. Which in own side will also increase a quality of it, as you will need to justify in documents all events/fields why they needed and what they do.

Now, we have logic for how we are going to get information about plugins and communicate between them. Let’s see how they have to be dynamically added to a core application.

Once the bundle of a plugin is loaded, we need to dynamically append the script to the body of the document object for the page:

JavaScript
 




xxxxxxxxxx
1


 
1
const script = document.createElement('script');
2
script.src = scriptUrl; // fully qualified path to the loaded script
3
script.async = true;
4
document.body.appendChild(script);



Now, all scripts which were in the plugin are accessible from the core application. And for that, we need to have some convention how the custom plugins built. With current example would be enough to have one export object containing:

JavaScript
 




xxxxxxxxxx
1


 
1
{
2
    component: ...,
3
    eventChannel: ...
4
}



Component — you dynamically render on a newly created tab (so that also means that you need to have a logic that will dynamically create tabs in this lifecycle) and also register eventChannel in the system. And, it will depend on technology. For example, with Redux/Redux-saga you will need to register reducers and run sagas (how to do this with these technologies, I’ll cover in another article of implementing this approach by using these tools). 

When you build a custom plugin, you need to mark all third-party libraries as external, so they won’t be included in a bundle. Once the code is mingled, the required libraries will be taken from the main application. 

One thing you might consider is that to create a development environment for speed development, as I might assume that you don’t want to boot up the whole infrastructure but in the same time being able to use the communication channel from core application for quick testing. For that, you can ship that part from the core application as a separate small module and use it only for development purposes. 

That's basically it; the approach works. It gives you really smooth integration, as it was developed in one git repository, has no duplicated libraries loaded or duplicated code between core and custom plugin, and you can easily communicate with each module natively, without any iframe bridges. 

Let’s now have a look at a more complicated scenario. Let’s assume I want on a home page to update an existing button with a button with dropdown. 

    Approach:

  1. Use hierarchical identifications of elements in the core application:
    1. Like tab has attribute: component-id=“home".
    2. Button has attribute: component-id=“open-profile".
  2. Specify in the plugin for each component where it should be placed. So then, the entry object will look like:
JSON
 




xxxxxxxxxx
1


 
1
{
2
     overrides: [{
3
         replace-component-having-path: ‘home/open-profile’
4
         component: ...
5
    }]
6
}


 

And metadata will require fewer fields for such type of plugin:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "entry": “home-addons-bundle.js",
3
    "name": “home-addons-plugin"
4
}



Then, the logic is to find that component with the help of CSS selectors. I made the original one invisible and place a new component instead. The same can be done for all buttons, and then you don’t need a hierarchical structure. Instead, find all the components with the desired classifier and replace them.

With this approach, you only bundle add-ons in this plugin. But it’s not necessarily a technical limitation to separate each plugin like that. It can be one bundle containing and add-ons and several tabs. I wouldn’t do it like that, but if somebody prefers this method, then metadata will contain an array of entries, like:

JSON
 




xxxxxxxxxx
1
12


 
1
[{
2
    "entry": "reports-bundle.js",
3
    "name": "reports-plugin",
4
    "weight": 15,
5
    "permissions": [
6
        "admin"
7
    ],
8
    "title": "Reports"
9
}, {
10
    "entry": “home-addons-bundle.js",
11
    "name": “home-addons-plugin"
12
}]



If you don't need customization with overrides, you might consider having enhancements, where you want to display extra widgets alongside or give that possibility to customers. Then, you can mark all components and provide them with an API, so that any place in your application can be customized and not be a part of a core product.

I'm looking forward to hearing your comments. To make it more clear, I’m going to create a sandbox application with minimal possible configuration to show the cases described in this article and demonstrate more complicated scenarios. 

I will use Node.js, React, Redux, and Webpack. You can achieve the same with other tools, and I’ll be curious to see how you can do it and encourage you to reach me with your ideas and solutions. Thank you for your attention!


Further Reading

  • Everything React: Tutorials for Beginners and Experts Alike.
  • How to Connect Your React Application With Redux.
application Architecture

Opinions expressed by DZone contributors are their own.

Related

  • SAP Commerce Cloud Architecture: All You Need to Know!
  • MuleSoft: Do You Have an Extra Mule Under the Hood?
  • A Data-Driven Approach to Application Modernization
  • Introduction Garbage Collection Java

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!