As applications complexity increases, frontends are also getting more and more complex. Micro frontend architectures, like microservice architectures, try to reduce the complexity by defining smaller and loosely coupled components that can be deployed individually. Cumulocity IoT is built on top of a micro frontend architecture.
Introduction
There are several options for building a micro frontend architecture:
Server-side - This is the classic approach of a page loaded on a different URL. You can serve unique frontends from different teams (even on separate web servers) on each URL. This already fulfills all the requirements of a micro frontend, as it is loosely coupled, can be developed independently, and deployed separately. However, there are challenges when using this approach, for example, with the communication between the different micro frontends and ensuring a similar look and feel across the other frontends.
Cumulocity IoT already allows using micro frontends via application hosting. You can deploy different frontends via the Application API) and switch between them via the app switcher. If you use the Web SDK, it ensures the same look and feel across all applications.
Compile-time - Another option to allow a micro frontend architecture is to bundle and build your micro frontend as a library and provide it (for example, via npm). The packages can then be used to compose or build a new application. This has many benefits, as the developer has full control. The communication can be clearly defined and the look and feel can be aligned. However, it requires a new build each time one of the components changes. The coupling is much closer.
Cumulocity IoT provides different npm modules which let you use this approach. You can, for example, only use the API client, a default application with the styling, or import different features as Angular modules from the Cumulocity IoT components library. Take a look at the Cumulocity IoT npm options to see all provided libraries.
Runtime - Providing different frontends dynamically while the application is running. You can load different parts of an application in an iframe or only load a script bundle from a different server. However, the communication and coupling with, for example, an iframe is nearly as hard as when using the server side integration. Therefore, the runtime integration is more complex. It allows you to plug deep into the application and shares, for example, the communication or the state layer. New technologies like Module Federation allow for sharing certain dependencies and defining their scope.
We introduced a new plugin concept into Cumulocity IoT which gives you the ability to extend any Web SDK based application at runtime.
Cumulocity IoT is already based on a micro frontend architecture. In fact, the server-side option is a concept of the platform since the beginning. The compile-time option was introduced in 2018. However, there is also a need for the runtime extension of applications, which is why it is introduced to Cumulocity IoT. See the sections below for more information.
Introducing plugins: Dynamically extending platform web applications
Plugins are a new concept to dynamically load features at runtime and allow an extension of any Web SDK based web application. To extend an application:
In the Administration application, clone the application you want to extend, for example clone the Cockpit application.
Open the Cockpit application details and click the Plugins tab.
Click Install plugins and select a plugin.
The application is now extended by the plugin you selected.
This is basically a script injection. The Cockpit application will now request a script called remoteEntry.js from the plugin. In terms of the micro frontend, the application that executes the call is called the “shell” which injects the “remote” into its scope.
Tip
If you have any issue with an application which includes a plugin, you can exclude all plugins via the ?noPlugin=true query parameter.
Those plugins can use any of the concepts that are integrated into the Web SDK. From just adding a certain button on a device to a full feature set with its own navigator node, route, and component. There are many options, but they are all within the borders of the Web SDK. Cumulocity IoT does not support, for example, React or Vue. If you want to use other frameworks, refer to Cumulocity IoT’s compile time integration.
We decided to limit the options to give the same developer experience as we use to build current Angular applications. As a developer, you can start your first plugin by using the c8ycli almost the same way you build a new application.
Custom widget plugin
See Add a custom widget to a dashboard > Create the widget components on how to create a simple widget, what its structure looks like and how to add it to your application.
The following tutorial focuses on how you can add this widget to an application using the micro frontend architecture and how this process differs from the previous one.
Info
The solution below is fully based on the Module Federation functionality introduced in Webpack 5. For more information on the functionality refer to Webpack: Module Federation.
1. Initialize the widget plugin example
Use the command shown below to start a multi-step process of creating a sample plugin:
c8ycli new
Select the plugin name, for example, “widget-plugin”:
? Enter the name of the project: (my-application) widget-plugin
Select the version for which you want to create a sample application, for example, “1016.0.233”, as micro frontend are supported with version higher then 10.16.0.0:
? Which base version do you want to scaffold from?
1015.0.372 (latest)
1018.106.0 (next)
1018.0.47
1017.0.151
❯ 1016.0.233
1014.0.359
other
Select an application template as the basis for your plugin, in our case, “widget-plugin”:
? Which base project do you want to scaffold from?
administration
application
cockpit
devicemanagement
hybrid
tutorial
❯ widget-plugin
After a few seconds, you should see the following message:
Application created. Go into the folder "widget-plugin" and run npm install
Navigate to your application folder and execute npm install.
The application folder should look like the example shown below.
For this tutorial, the most important files are package.json and README.md.
You have now created your first plugin that uses the micro frontend architecture.
2. Differences in approach to creating custom widgets
There are a couple of differences between a simple widget and one that is built according to the micro frontends architecture.
The biggest difference is the package.json file, where fields such as isPackage, package and exports are located.
The following list shows the fields and what they are responsible for:
isPackage: Indicates if the application is a package. In case of a widget that is added using micro frontends, set the value to true.
package: The type of package (for example, plugin).
exports: Important field. Defines the Angular modules that will be made available by the widget-plugin for the shell application (see also the README.md file):
name: The name of the exported module (that is, “Example widget plugin”).
module: The name of the Angular module class (that is, “WidgetPluginModule”).
path: The path to the TypeScript file with the module. Since the file is nested, use the following path: ./widget/widget-plugin.module.ts.
description: A brief description of what the module does.
contextPath: The context path tells on which URL your plugin can be loaded. As this is also used to generate a global variable, choose a valid JavaScript variable. For example, your contextPath should not start with a number. To avoid conflicts it is a good practice to add a prefix to your context path, for example, the acronym of your company: acme-.
Info
When creating plugins, the custom modules are the backbone of this approach. The exported module is treated as the entry point that links the plugin with the application, which is referred to as the shell. You can create and export several modules, which have to contain ready-made functionality.
Furthermore, these modules behave like lazy loading modules. They are not loaded upfront as one big package, but instead like a collection of smaller packages loaded on demand.
You can extend each module with additional features through the HOOK concept, see Extend an existing application and use hooks for more information. For example, a plugin can add another entry to the navigation menu using HOOK_NAVIGATOR_NODES, see Hooking a navigator node for more information.
There is also a difference in how to start the local development server, see the following step for more information on the server’s role.
3. Local server, debugging and deployment
Local server
To facilitate the process of creating a new plugin, the local server command was extended with a new flag to proxy all requests to the shell application “Cockpit”.
This link redirects you to the Cockpit login screen.
Once logged in, go to your dashboard and click Add widget, then select Module Federation widget from the list of available widgets.
For the rest of the widget editing process follow the process for regular widgets. Refresh your browser to see your changes.
Debugging
Another difference in the package.json file between a regular widget and a widget modified for the micro frontend architecture is the field remotes, see the example below:
...
"remotes": {
"widget-plugin": [ // contextPath
"WidgetPluginModule"// module class name
]
}
...
Info
The remotes field is used to import modules. To properly import a module, specify the context path of the plugin (the contextPath field in package.json) followed by the name of the module class.
The plugin imports itself via a field called remotes.
We recommend this as the first step in verifying the correctness of the exported module. It facilitates the application debugging.
After importing your own modules, execute npm start to see if the local server starts.
To check the plugin at a later stage, we recommend you to test it locally with various shell applications, using npm start -- --shell cockpit.
Deployment
Uploading the widget is the same as for regular widgets.
Execute the following commands sequentially:
npm run build
and
npm run deploy
Follow the console prompt to deploy the application to your tenant.
4. Adding a deployed widget to the shell application
To add the uploaded widget-plugin to the dashboard in the Cockpit application, follow these steps:
Access the Packages tab in Administration application > Ecosystem > applications > Packages, where you can see the details of your plugin.
If you already have a custom Cockpit application, navigate to its Details page and then to the Plugins tab. Install the widget-plugin.
If you don’t have your own version of the Cockpit application, navigate to Administration application > Ecosystem > Applications and click Add application. In the resulting dialog, select the option Duplicate existing application. From the list of applications select Cockpit (Subscribed). Edit the available fields such as Name, Application key, and Path. Use the default values and proceed. Install the widget-plugin in the cloned application.
Your custom widget is now available in your version of the Cockpit application.
Navigate to the dashboard where the newly added widget is available in the list of widgets to add.
The widget-plugin was installed from within the Administration application. This is the main difference between the regular and the new approach regarding widgets.
The micro frontends architecture allows you to add new functionality while the application is running (runtime), whereas the old approach only allowed new functionality to be added before the application was built (compile time).
Custom package blueprint
With micro frontends it is possible to add new functionality while the application is running (run-time), whereas the old approach only allowed new functionality to be added before the application was built (compile-time).
Blueprints are combinations of multiple UI functionalities that can be hosted by the platform (static files) and can be used to scaffold a new solution from scratch. On the other hand, a package is the composition of plugins and blueprints. As a blueprint can export plugins as well, they can be packed together into one package and deployed to the platform.
Initialize the blueprint example
Use the command shown below to start a multi-step process of creating a sample blueprint:
c8ycli new
Select the plugin name, for example, “package-blueprint”:
? Enter the name of the project: (my-application) package-blueprint
Select the version for which you want to create a sample application, for example, “1016.0.233”, as micro frontends are supported with version higher then 10.16.0.0:
? Which base version do you want to scaffold from?
1015.0.372 (latest)
1018.106.0 (next)
1018.0.47
1017.0.151
❯ 1016.0.233
1014.0.359
other
Select an application template as the basis for your plugin, in our case, “package-blueprint”:
? Which base project do you want to scaffold from?
cockpit
devicemanagement
hybrid
> package-blueprint
tutorial
widget-plugin
administration
After a few seconds, you should see the following message:
Application created. Go into the folder "package-blueprint" and run npm install
Navigate to your application folder and execute npm install.
The application folder should look like the example shown below.
For this tutorial, the most important files are package.json and README.md.
You have now created your first package blueprint that uses Module Federation.
Stepper setup (optional)
The HOOK_STEPPER can be additionally provided to allow application customization during the first load of an application. In this optional step we show a small single step example in which the user can select whether the navigator will be collapsed or not on startup.
Create a new setup-step1.component.ts file with the following content:
Now on the first application start, users will have to complete the single step wizard above.
Differences in approach to creating custom applications
There are a couple of differences between a simple widget and one that is built according to the micro frontend guidelines.
The biggest difference is the package.json file, where fields such as isPackage and package and exports (not in the current blueprint app) are located.
The following list shows the fields and what they are responsible for:
isPackage: Indicates if the application is a package. In case of a widget that is added using a micro frontend, set the value to true.
package: The type of package (for example, blueprint, but the type of the package can also be a plugin).
exports: Important field. Defines the Angular modules that will be made available by the widget-plugin for the shell application (see also the README.md file).
name: The name of the exported module.
module: The name of the Angular module class.
path: The path to the TypeScript file with the module.
description: A brief description of what the module does.
contextPath: The context path tells on which URL your blueprint can be loaded. As this is also used to generate a global variable, choose a valid JavaScript variable. For example your contextPath should not start with a number. To avoid conflicts it is good practice to add a prefix to your context path, for example, the acronym of your company: acme-.
Info
A blueprint can also include plugins, which can later be used to extend other applications.
Deployment
Uploading the package is the same as for regular widgets.
Execute the following commands sequentially:
npm run build
and
npm run deploy
Follow the console prompt to deploy the application to your tenant.