About Micro FrontEnd

A implementation of bootstrapper

html container/anchor structure

<div data-widget-type="WIDGET_TYPE">
  <script type="widget">
  {
      "type": "json",
      "generateFrom": "CMS(aem)",
      "purpose": "widget-config-for-feature-and-globals",
      "scope": "per-widget-container"
  }
  <script/>
<div/>
<script src="PATH/TO/BUNDLE/app.js"/>

It is supposed to be self contained and is possibly able to be mounted several time on on page.

bundle/app.js

Idea: register as entry function, Widget as widget base class

register

register = (widgetType: string, widget: Widget) => {
    // widgetType as 'flight-search', 'email-subscription'
    // where we assume one widget type can be mounted several times on one page
    
    // Load config from html
    getConfiguration = (anchorContainer) => {}
    
    // Get the instance from container
    getWidgetInstance = (anchorContainer) => {}
    
    // Render widget on html
    mountWidget = (anchorContainer) => {
        widget = new Widget(id, anchorContainer, configuration);
    }
    
    // Update widget on html
    updateWidget = (anchorContainer, props) => {}
    
    // Update widget on html
    unmountWidget = (anchorContainer) => {}
    
    // render All anchorContainers (potentially multiple)
    renderAll = () => {}
    
    document.addEventListener('DOMContentLoaded', renderAll);
    document.addEventListener('load', renderAll);
}

Widget

class Widget {
    constructor(id, anchorContainer, config){}
    
    // Called on rendering the individual widget
    render(p){
        // - React
        // return <App p={p}/>
        // - Vue ?
        // return null;
        // - Angular ?
        // return null;
    }
    
    // Defines how to mount the widget to the DOM
    renderer() {
        // - React
        // return ReactDOM.render(this.render(this.props), this.node);
        // - Vue
        // new Vue({el: this.node});
        // - Angular
        // platformBrowserDynamic().bootstrapModule(AppModule);
    }
    
    update(props) {
      this.props = props;
      // - Redux
      // this.store.dispatch();
    }
    
    unmount() {
      // - React
      // ReactDOM.unmountComponentAtNode(this.node);
    }
}

A base AppShell for bootstrapping, updating, and unmount.

Thoughts

  • Check requirement, if the bootstrapper needs to support multiple mount for save widget, ideally yes

  • To share a bootstrapper

    • either make a monorepo with all the widgets referencing to single bootstrapper, and bundle it with same vendor bundle (code-split and cache)

    • or make a provider(widget-provider) and reference it to all the package as dependencies and use the provider as the single entry point of bundles

  • We do not actually need a render() as react does, it mixes react behavior with micro-frontend contracts

Micro-frontend Why and How

There is no silver bullet. Architectures, languages, patterns, and abstraction, they all serve to particular problem. For this note, let micro-frontend gets sorted out.

I dont' understand micro-frontend

Ref: https://medium.com/@lucamezzalira/i-dont-understand-micro-frontends-88f7304799a9

Context

Use the right tool for the right job, that should be our goal. Remember that developing software is empirical, not scientific.

Micro-frontend benefits team-wise, collab makes easy in certain(many) org structure. For separated dev, deploy and mgmt. It can also utilise ESI/CSI.

Multiple tech stacks

For concerns about multiple tech stacks, it is a problems for all micro-services architecture. There should be constraints to this anyway.

Bundle size

Shared dependencies on runtime, sofe, System.js.

https://single-spa.js.org/blog/2016/02/26/a-case-for-soa-in-the-browser/

Orchestrating

bootstrap

  • Routing micro-frontend

  • initial App

  • exposing API for frontends communications

E.G. Single-SPA.js

Main apis: bootstrap() mount() unmout()

SSR

For solutions like iframe or custom web component, SSR is almost impossible. For html + js approach, a successful SSR can lead to easy client side 'stitching'(orchestrating)

Integration approaches

Ref: https://martinfowler.com/articles/micro-frontends.html#IntegrationApproaches

  • Server-side template composition (ESI/SSI)

  • Build-time integration, import projects from package.json in a provider app, not too dynamic, introduce a lot over-head when developing, testing, and deploying

  • Run-time integration via iframes

  • Run-time integration via JavaScript, with a bootstrapper/register

  • Run-time integration via Web Components, similar to via javascript while it create a web-component

Shared component libraries

It is tempting to create a Foundation Framework, with all of the common visuals that will be needed across all applications. However, experience tells us that it's difficult, if not impossible, to guess what the components' APIs should be before you have real-world usage of them, which results in a lot of churn in the early life of a component.

Allow the patterns to emerge naturally, and once the component's API has become obvious, you can harvest the duplicate code into a shared library and be confident that you have something proven.

Cross-application communication

Whatever approach we choose, we want our micro frontends to communicate by sending messages or events to each other, and avoid having any shared state. Just like sharing a database across microservices, as soon as we share our data structures and domain models, we create massive amounts of coupling, and it becomes extremely difficult to make changes.

Last updated