This document describes how Module Federation is implemented in ODH Dashboard.
For more information on Module Federation concepts, see module-federation.io.
ODH Dashboard uses Module Federation to dynamically load remote modules at runtime, enabling a plugin-based architecture where external packages can extend the application.
The frontend implementation consists of several key components:
- Host Application: The main ODH Dashboard application acts as the Module Federation host
- Remote Modules: Federated packages that expose functionality to be consumed by the host
- Shared Dependencies: Common libraries shared between host and remotes to avoid duplication
- Extension System: A plugin system that allows remotes to register UI extensions
At this time, all known modules must exist in the monorepo. The system automatically discovers federated modules by scanning all workspace packages for a module-federation configuration property in their package.json files.
The host application defines shared dependencies that are made available to all remote modules:
All federated modules must include these shared dependencies in their configuration:
const deps = require('../package.json').dependencies;
const config = {
// ...
shared: {
react: { singleton: true, requiredVersion: deps.react },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
'react-router': { singleton: true, requiredVersion: deps['react-router'] },
'react-router-dom': { singleton: true, requiredVersion: deps['react-router-dom'] },
'@patternfly/react-core': { singleton: true, requiredVersion: deps['@patternfly/react-core'] },
}
};Include these if your module uses the corresponding functionality:
const config = {
// ...
shared: {
'@openshift/dynamic-plugin-sdk': {
singleton: true,
requiredVersion: deps['@openshift/dynamic-plugin-sdk'],
},
'@odh-dashboard/plugin-core': {
singleton: true,
requiredVersion: deps['@odh-dashboard/plugin-core'],
},
}
};Each federated module must include a module-federation property in its package.json with the following structure:
- name (string): The unique identifier for the federated module. Must match the
nameproperty in the module's webpackModuleFederationPluginconfiguration - remoteEntry (string): The path to the remote entry file (typically
/remoteEntry.js) - authorize (boolean, optional): Whether requests to this module require authorization (defaults to false)
- tls (boolean, optional): Whether to use TLS for connections (defaults to true)
- proxy (array): Array of API proxy configurations for backend services
- path (string): The URL path to proxy (e.g.,
/api/v1/models) - pathRewrite (string, optional): Path rewrite rule (defaults to empty string)
- path (string): The URL path to proxy (e.g.,
- local (object): Development server configuration
- host (string, optional): Development host (defaults to
localhost) - port (number): Development server port
- host (string, optional): Development host (defaults to
- service (object): Production service configuration
- name (string): Kubernetes service name
- namespace (string, optional): Kubernetes namespace (uses current namespace if not specified)
- port (number): Service port
The proxy configuration allows the module's frontend API requests to be routed to their respective Backend for Frontend (BFF). Since all requests first go through the dashboard's backend reverse proxy, it must be configured to know which paths belong to which module and how to forward the request.
When a federated module makes an API call, the request flows through the following path:
- Module frontend makes API request (e.g.,
/my-module/api/data) - Dashboard backend receives the request and matches it against configured proxy paths
- Backend forwards the request to the appropriate module's service
- Response is returned back through the same proxy chain
The proxy array defines which API paths should be forwarded to the module's backend service:
{
"proxy": [
{
"path": "/my-module/api",
"pathRewrite": "/api"
}
]
}In this example:
- Frontend requests to
/my-module/api/*are intercepted by the dashboard backend - The path is rewritten from
/my-module/api/*to/api/* - The rewritten request is forwarded to the module's backend service
- This allows the module's backend to serve APIs at
/api/*while the frontend accesses them at/my-module/api/*
- Use
authorize: truefor modules that require authentication. By setting this property, the backend reverse proxy will forward the user token in the headerAuthorization: Bearer <token> - Validate all shared dependencies and their versions
- Ensure remote modules are served from trusted sources
- Consider implementing Content Security Policy (CSP) headers
{
"name": "@odh-dashboard/my-module",
"module-federation": {
"name": "my-module",
"remoteEntry": "/remoteEntry.js",
"authorize": true,
"tls": true,
"proxy": [
{
"path": "/my-module/api",
"pathRewrite": "/api"
}
],
"local": {
"host": "localhost",
"port": 9000
},
"service": {
"name": "my-module-ui-service",
"namespace": "my-namespace",
"port": 8080
}
}
}The backend automatically configures proxies for federated modules based on their configuration:
For each configured module, the backend sets up:
-
Static Asset Proxy: Serves the module's built JavaScript files
- Pattern:
/_mf/{moduleName}/* - Routes to the module's development server or production service
- Pattern:
-
API Proxy: Forwards API requests to the module's backend service
- Uses the
proxyconfiguration from the module-federation config - Supports path rewriting and authorization
- Uses the
Federated modules can extend the host application by exposing extensions through the ./extensions export. For comprehensive information about the extension system, extension points, and best practices, see Extensibility Documentation.
Your module's webpack configuration must expose extensions:
// webpack config
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'my-module',
exposes: {
'./extensions': './src/extensions',
},
// ... other config
}),
],
};Extensions should export an array of extension objects:
// src/extensions.ts
import type { Extension } from '@openshift/dynamic-plugin-sdk';
const extensions: Extension[] = [
{
type: 'app.route',
properties: {
path: '/my-module',
component: () => import('./MyModuleComponent'),
},
},
// ... more extensions
];
export default extensions;For detailed information about:
- Extension point types and naming conventions
- Code references and lazy loading
- Extension flags and feature gating
- Helper components like
LazyCodeRefComponent - Best practices for extension design
Please refer to the Extensibility Documentation.
Install the required webpack plugin:
npm install @module-federation/enhancedconst { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
const deps = require('./package.json').dependencies;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'my-module', // Must match package.json module-federation.name
filename: 'remoteEntry.js',
exposes: {
'./extensions': './src/extensions',
},
shared: {
// Required shared dependencies
react: { singleton: true, requiredVersion: deps.react },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
'react-router': { singleton: true, requiredVersion: deps['react-router'] },
'react-router-dom': { singleton: true, requiredVersion: deps['react-router-dom'] },
'@patternfly/react-core': {
singleton: true,
requiredVersion: deps['@patternfly/react-core'],
},
// Optional shared dependencies (include if used)
'@openshift/dynamic-plugin-sdk': {
singleton: true,
requiredVersion: deps['@openshift/dynamic-plugin-sdk'],
},
'@odh-dashboard/plugin-core': {
singleton: true,
requiredVersion: deps['@odh-dashboard/plugin-core'],
},
},
// Important for compatibility with runtimeChunk: "single"
runtime: false,
}),
],
output: {
// Required to allow for dynamic resolution of asset paths
publicPath: 'auto',
},
};- Add
module-federationconfiguration to yourpackage.json - Configure webpack with
ModuleFederationPluginandpublicPath: 'auto' - Create an extensions file to export your module's functionality
- Build and serve your module locally
- Start your federated module's development server
- Start the main ODH Dashboard application
- The dashboard will automatically discover and load your module
- Module not loading: Verify the module name matches between
package.jsonand webpack config - Shared dependency conflicts: Ensure all required shared dependencies are configured with correct versions
- Proxy issues: Check that the backend service is running and accessible
- Asset loading issues: If you see failing requests for
__federation_expose_files without the module name in the path, addoutput.publicPath = 'auto'to your webpack configuration (see Webpack Configuration section above)