Skip to content

Descriptions

Here are some detail aspects of OpenMicrofrontend Descriptions.

Assets

The assets section defines:

  • Where to find the JS and CSS assets
  • How to load them
  • Hints for browser caching
  • Shared modules

Here is a full example:

assets:
  basePath: /public
  buildManifestPath: /build.yaml
  js:
    moduleSystem: ESM
    initial:
      - Microfrontend.js
    importMap:
      imports:
        externalModule1: https://ga.jspm.io/npm:externalModule1@1.2.2/index.js
        externalModule2: https://ga.jspm.io/npm:externalModule2@5.1.8/index.js
  css:
    - styles.css

Asset Names

The asset names in the Description must remain stable between two deployments, i.e., they must not contain a hash.

The reason is that in an environment with rolling updates (like Kubernetes), there can be multiple release versions running at the same time.

You should consider exposing a Build Manifest to give the Host Application means for cache busting.

Asset Paths

The actual path of an asset is calculated by combining the basePath with the relative path of the asset. The basePath defaults to /.

In the example above the initial JS asset would be served at /public/Microfrontend.js.

In this example:

assets:
  js:
    initial:
      - public/assets/Microfrontend.js

it would be served at /public/assets/Microfrontend.js.

Module System

Possible moduleSystem values are:

Default is none which means the JS assets should just be added as <script> tags.

Note

There are some limitations when using ESM, explained in detail in the Implementation Hints section.

Import Maps (Module Sharing)

If your Microfrontends require external (shared) modules, you can define them in the importMap section. Scoped module specifier maps (scopes) are not supported.

Tip

importMaps are the recommended way to share modules between Microfrontends and to reduce bundle sizes.
If you use a proprietary solution like Module Federation, there is currently no standardized way to declare that in the Description. But you could always use Annotations.

Note

There are some limitations when using ESM, explained in detail in the Implementation Hints section.

Build Manifest (Browser Caching)

Since the asset names are stable, the Host Application needs some means to make sure the Browser loads new assets after a deployment.

This is where the Build Manifest comes into play. The Build Manifest is a JSON file served by the Microfrontend Server which contains either a version or a timestamp property that can be used for cache busting, e.g., by adding it as a query parameter to every asset (?.v=1.0.0).

The simplest approach is to use package.json as Build Manifest.

The buildManifestPath property is the absolute path to the Build Manifest JSON file.

rendererFunctionName

The Renderer is the function that needs to be called to start the Microfrontend in the Host Frontend.

The rendererFunctionName property is the name of a function which is either

  • A global variable (window object)
  • A named export of one of the initial modules (if modulesSystem is ESM or SystemJS)
  • A property of the default export of one of the initial modules (if modulesSystem is ESM or SystemJS)

The Renderer needs to satisfy the signature defined here . Typically, a generator (such as OpenMicrofrontends Generator) is used to create a tailored and type-safe signature.

Security Schemes

Some routes (paths) on the Microfrontend Server might have security requirements.

You can describe security requirements with the top-level securitySchemes section and then apply it to the route as security requirements.

Both concepts have been adopted from OpenAPI and are described in detail here:

Example:

securitySchemes:
  BasicAuth:
    type: http
    scheme: basic
  BearerAuth:
    type: http
    scheme: bearer
  ApiKeyAuth:
    type: apiKey
    in: header
    name: X-API-Key
  OpenID:
    type: openIdConnect
    openIdConnectUrl: https://example.com/.well-known/openid-configuration
microfrontends:
- name: My Microfrontend
  apiProxies:
    bff:
      path: /api
      security:
      - ApiKeyAuth: []
  ssr:
    path: /ssr
    security:
    - ApiKeyAuth: []

User Permissions

If the Microfrontend has some functionality that depends on the user and his permissions, this can be described in the userPermissions section.

Example:

userPermissions:
  permissions:
    - name: showDetails
      description: The authenticated user is permitted to see details
    - name: deletePermitted
      description: The authenticated user is permitted to delete items
  provided:
    path: /permissions
    security:
      - ApiKeyAuth: []

The actual permissions need to determined at runtime, either:

  1. By a Microfrontend Server route, like in the example above via provided
  2. By the Host Application

Which option you use depends on the system architecture:

  • If your Microfrontend is exposed to third parties and comes with a separate security context the first one is the best choice
  • If your Microfrontend is used internally as part of a larger frontend application, it makes sense that the Host Application determines the permissions, e.g., role-based
  • If your Microfrontend doesn't need to know anything about security and just needs to forward access tokens, the second option also is the better choice. In this case, the Microfrontend could be used in completely different environments and security contexts.

If you define a route that determines the user permissions, it must return a JSON object like this for a GET request:

{
  "showDetails": true,
  "deletePermitted": false
}

Important

User Permission cannot be use to actually protect something because they can easily be changed in the browser.
Their purpose is only to control UI behavior.

API Proxies

The Microfrontend can request API (backend) proxies from the Host Application. A proxy is typically required when:

  • The API is not publicly available
  • The endpoint requires security measures the frontend cannot provide
  • CORS problems want to be avoided

Example:

apiProxies:
  proxy1:
    description: Proxy for the internal BFF API
    path: /api
    security:
      - ApiKeyAuth: []
  proxy2:
    description: A proxy for an external API
    targets:
      - url: https://localhost:1234/api
        description: Local
      - url: http://my-api.my-namespace:1234/api
        description: Test environment
The target of the proxy can either be the Microfrontend Server (BFF) or an external API.

BFF

To proxy the Backend-for-Frontend (BFF) of your Microfrontend, you only have to define the absolute base path.

External API

For External APIs you can provide a list of possible targets. The actual target for the current environment needs to be determined by the Host Application. And it can be one not listed in the Description.

Usage in the Microfrontend

The Host Application will pass an object with the relative proxy paths to the Renderer:

const renderer: MyMicrofrontendRenderer = async (host, context) => {
    const {config, apiProxyPaths, permissions} = context;

    // The relative path in *apiProxyPaths.bff* will forward the call to <microfrontend-server>/api
    const response = await fetch(`${apiProxyPaths.bff}/customers/${config.customerId}`);

}

Server-Side Rendering

If your Microfrontend supports Hybrid Rendering (SSR + Hydration) you can tell the Host Application where to get the pre-rendered HTML from.

Example:

ssr:
  path: /ssr
  security:
    - ApiKeyAuth: []

The Host Application can then:

  1. POST the context and config to the route (ssr.path)
  2. Add the returned HTML as innerHTML to the Microfrontend container
  3. Start the Microfrontend with the flag serverSideRendered set to true

Server-Side Renderer

The Server-Side Renderer needs to satisfy the signature defined here . Typically, a generator (such as OpenMicrofrontends Generator) is used to create a tailored and type-safe signature.

The Server-Side Renderer takes the POST body as input, and its result needs to be returned as the response.

Usage in the Microfrontend

The serverSideRendered flag is passed to the Renderer:

const renderer: MyMicrofrontendRenderer = async (host, context) => {
    const {config, serverSideRendered} = context;

    if (serverSideRendered) {
        // Hydrate
    } else {
        // Client-side Rendering
    }
}

Config

The config section can be used to define an arbitrary configuration object that needs to be passed to the Renderer. It can be used to change the behavior of the Microfrontend or to pass some content IDs.

Example:

config:
  schema:
    type: object
    properties:
      customerId:
        type: string
    required:
      - customerId
    additionalProperties: false
  default:
    customerId: '1000'

The default config must be provided, and it must validate against the schema.

Tip

schema needs to be a valid JSON schema and the type has to be object.

Note

A valid default config is required because this allows Host Applications and tools to preview Microfrontends.

Usage in the Microfrontend

const renderer: MyMicrofrontendRenderer = async (host, context) => {
    const {config} = context;

    // type: string
    const customerId = config.customerId;
}

Messages

The messages section can be used to define a list of messages the Microfrontend publishes and/or subscribes to.

This allows an exchange of messages between Microfrontends in a type-safe way.

Example:

messages:
  ping:
    publish: true
    subscribe: true
    schema:     
      type: object
      properties:
        ping:
          const: true
      required:
        - ping

This describes an exchange of messages via topic ping and a message body that looks like this:

{
  "ping": true
}

Tip

schema needs to be a valid JSON schema and the type has to be object.

Exchange Messages between Microfrontends

To make sure different Microfrontends use compatible messages, it makes sense to provide the content schemas as external JSON files and reference them:

{
  "$id": "https://my-company.com/messages/ping",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "ping": {
      "const": true
    }
  },
  "additionalProperties": false
}
messages:
  ping:
    publish: true
    subscribe: true
    schema:
      $ref: './pingMessage.json'

Usage in the Microfrontend

In the Renderer you will get a messageBus object with type-safe publish and subscribe methods:

const renderer: MyMicrofrontendRenderer = async (host, context) => {
    const {config, messageBus} = context;

    messageBus.publish('ping', {
        ping: true
    });
    // @ts-expect-error
    message.publish('someOtherTopic', {});
}

Usage in the Host Frontend

The Host Application must provide a global Message Bus with generic publish and subscribe implementations.

The Starter returns a messages object with type-safe methods for the opposite direction (so, everything published by the Microfrontend can be subscribed to and the other way round).

const {close, messages} = await startMyMicrofrontend('http://my-microfrontend.my-namespace:7810', hostElement, {
    id: '1',
    config: {
    },
    messageBus: globalMessageBus,
});

messages.subscribe('ping', (message) => {
    console.log(message);
});

// @ts-expect-error
message.subscribe('someOtherTopic', {});

Annotations

The annotations section can be used to add arbitrary meta-data and information for Host Applications.

It could, for example, be used to:

  • Provide meta-data for dynamic Cockpits that allows automatic discovery of suitable Microfrontends for a specific context
  • Provide some integration hints, like preferred width
  • Provide security hints, like how to determine the user permissions in specific environments
  • State some extra requirements

Validation

You can use the JSON schema to validate your Description:

```yaml
$schema: 'https://open-microfrontends.org/schemas/1-0-0.json'
openMicrofrontends: 1.0.0

Or use the OpenMicrofrontends Generator, which also performs semantic checks:

omg microfrontends.yaml --validationOnly


Example Descriptions