This article is basically designed to answer a few questions:

  1. Moving to the micro front, what do we really need?
  2. What are the typical components of the industry’s “micro front end” system?
  3. What technologies are commonly used for a “micro front end framework” that is close to the r&d side?
  4. Can our team’s projects now go on the “micro front”? What needs to be done and to what extent?


(Read the full text below for about 20 minutes)


Micro front is not a new concept, everyone heard of more or less contact, here no longer to do a bunch of definition, only research summary/overview of current industry practices, this article is not used in the business oriented micro front classmates or team, through the overview, can simply set up the overall recognition of the “front-end”;

In general, the concept of “micro front” is still in a development process from its creation to the present, without forming a unified consensus/standard (high market share). Each big factory/community has a different definition of the concept and the technology behind it, and each one builds a bunch of wheels.


So, what do we want?

Here is A typical example of micro-front-end structure. In A URL accessed page, there is A main application (base), multiple co-existing subapplications A/B, and nested subapplications C within subapplication B; They can be developed independently by different teams and each application can be launched independently without interference.


“Online independently” “Non-interference”

For us, it is important to “go online independently” and “do not interfere with each other”, both at the online publishing level and at the application coexistence level.

This is the most intuitive capability that we need from the micro front, because the immediate problem we face is “how to release the internal modules that need to be developed by different teams” (that is, “how to separate the large internal modules into sub-applications”).

For example, Main App is v1.0.1 version, Sub App A is V2.1.0 version, different teams of people online their own applications, their release rhythm is not affected;

When Sub App A is upgraded to V2.1.1, the Main App and Sub App B should be completely unchanged. The online page is Sub App A and the area is new.

Let’s simplify this case to the smallest case, one main application, one child application, and look at the “independent online” thing.

Before that, let’s talk about how page loading works when it’s not a micro front end:

Generally, the entry point for the front-end page to apply the packaging result is a

<html>
  <head>.</head>


  <body>
    <div id="root"></div>
    <script src="/ / CDN/entry/[email protected]"></script>
  </body>
</html>
Copy the code

When the page accesses a different route (URL), the js package will asynchronously load the chunk JS corresponding to the route and component, and then render the content/component under the route after getting the code.

Webpack, for example, is loaded by inserting a

<html>
  <head>.<script src="//cdn/chunk/0.f3c200e0.async.js"></script>
    <script src="/ / CDN/the chunk / 1.5 bb06b78 async. Js." "></script>
  </head>

  <body>
    <div id="root"></div>
    <script src="/ / CDN/entry/[email protected]"></script>
  </body>
</html>
Copy the code
// 0.f3c200e0.async.js

(window.webpackJsonp=window.webpackJsonp||[]).push([chunkId], xxxChunk)
Copy the code

On the micro front end frame, the loading is a little different. Specifically, when rendering certain areas of content, the loader is changed from “load chunk” to “load app portal”, and the loader is changed from webPack to “micro front end container”.

Using https://xxx-domiain/main-app/sub-route/xxxx as an example, the simplified process is as follows:

  1. The main application matched to/main-app/sub-souteRoute, rendering the current route content
  2. If the current route contains a child application, the child application entry is loaded asynchronously
  3. Subapplication matched to/sub-route/xxxxRoute, rendering the corresponding route content in its own area

Back to the “independent online” thing, first of all you know that the micro front-end framework is actually the “parent app loading child app entry”, then simply presuppose that the “entry” is a js (or HTML), as shown in the following structure.

So we still have a bunch of problems;

  • How to inject the loading entry script, where to load it from, and how to control the version?
  • Where do I get online? How do I get online?
  • Why don’t you repackage the main app when the sub app is available for upgrade?
  • How to select different versions to go online/roll back/gray?
  • How do I view a list of all current child applications?
  • How to integrate and coordinate switching between multiple versions?
  • .


Microfront-end system

What this actually reflects is that our demand for “micro front end” is not only a “micro front end framework”, but also a whole supporting “micro front end system”;

The last few questions in the last section can be answered by “governance system” and “development suite”, and the “micro front-end framework” is usually just the “runtime container” part of the system.


Governance system

“Governance system” can be viewed simply as an online management platform + online publishing process;

In the current research results, the micro front end of the use of the ground must be matched with such a management platform, although said so absolutely, it is more than the non-micro front end of the online platform two more functions:

  • Application Management – Enables the online application of different versions. Lists the entry addresses of the online application of different versions
  • Dependency management – Clearly manages the dependency relationship between the parent application and the child application by injecting the entry address of the child application into the parent application

As mentioned in the previous section “entry loading”, the entry loading of a child application is the parent application to load a JS URL, such as: https://cdn/… /[email protected], then the child application online is to update the URL address (version), each time online is a new entry address (version);

The entry URL is injected into the parent app via the launch platform, rather than hardcode written into the parent app’s code. The process of injection and which sub-applications are injected are all done in the on-line management platform.

Other parts, such as “version release”, “gray scale scheme” and “privatization”, are consistent with the general front-end online deployment platform, and will not be described again.

Whenever there is actual line demand, this governance system can not be missing, missing means that the team has to do another out.

Attached: some research results

To load the container Management platform
Qiankun(based on single – spa) OneX (Inside Ali – Ant Financial)
MicroX (Inside Ali) MicroX + CSKit (Ali Internal – Ali Cloud Intelligence)
icestark Iceworks (Inside Ali – Flying Ice)
alfajs Alfa (Ali Inside – Ali Cloud Console)


The development of form a complete set

The documentation section is particularly important, not just for the introduction of the micro front end framework, but also for the development itself, as well as the “go live release process,” which process and how to do it.

Build/release these are slightly different, as the entry point to the package may change, or even multiple; The format of the child application is also more limited, such as the need for more “load-in” related files.

Integration and coordination between multiple parent-child applications involves:

  • Local development child applications can be launched independently from the parent application development debugging
  • Debug local child application and parent application access, both of which are started locally
  • Online bug recurrence, need to debug child application and parent application access, one of the local startup, the other loading online


Micro material

The dotted line of “micro materials” is because it is relatively late in the system construction (and far from the current demand of Aeolus). In the early stage of micro front-end operation, it does not need to be realized;

And the emergence of “micro material” is essentially a transformation of the micro front end form, from “different page level application combination, no-bundle” to “according to the interface protocol, can directly load remote components, functions, no-bundle”.

Blurring the boundaries of App/Page/Component (widget)/Function/Plugin In the form of native browser ESM import and DENo import (call mode is also quite a back-end “remote procedure call” (RPC) feeling), directly lower the threshold of component reuse, remote loading threshold, so the material market this kind of thing will also have;

// The pseudo code example loads a function level micro component and executes it
import foo from 'https://cdn/... /foo.js'
const foo = import('https://cdn/... /foo.js')
const foo = load('https://cdn/... /foo.js')
foo(xxx)
Copy the code

In this scenario, simply distinguish the boundaries of the current names

  • App – an entire micro front-end application, which can also have many modules, multiple pages (Page)
  • Page – A slightly larger microfront-end component with routing can be called a Page, such as a data query Page
  • Widget – a Widget (Widget) that has no routing, such as a uniquely styled button
  • Function – a Function that is executed by loading remotely. For example, imagine loading lodash a func with UMD (the interface format is defined outside the application).
  • Plugin – strict function defined by the interface format and execution context (the interface format is defined by the application)


Runtime container

This is part of what a “micro front end framework” in its usual narrow sense does;


What does it mainly do? Here are some things:

  • The application is loaded– According to the registered child application, through the given URL, load the convention format child application entry, and mount to the given location
    • There are two entry formats commonly used in the industry,HTMLJS
      • JS entry is more pure, using HTML entry is easier to transform old projects
    • Parent-child entry combination(that is, determine dependencies)There are also two modes,Build time combinationRuntime combination
      • Part of the framework is based on data like manifest to get the child application registration and entry address
      • Some frameworks support working with the management platform, and the runtime accepts the entry address of the platform’s dynamic injection (there are frameworks that claim to decouple the runtime injection from the management platform, but in reality you have to implement the injection logic yourself if you don’t).
  • The life cycle– Load/mount/update/uninstall etc
    • Load/mount initialization, permission guard, i18N language, etc
    • When uninstalling, do cleaning, such as uninstalling script tag, style tag, child application DOM and so on
    • As well as routing, father – son communication to do two-way update bridge
  • Routing synchronization– The URL is updated synchronously during the route switchover of the sub-application. When url redirect/update, the child application is updated synchronously
    • That is, to make the route equal to the URL for the child application
  • Application of communication– Supports easy communication between parent and child applications, unlike postMessage.
    • What, you asked the brothers to communicate with each other? Of course, everyone uses the parent app as EventHub
  • The sandbox isolation– Each application needs to be executed in an “isolated” environment in order to “complement interference”
    • Without isolation, CSS global styles may conflict and JS global variables may be contaminated/tampered with/replaced
    • There are many solutions and evolutions in this part of the industry, which will be discussed in the next chapter
  • Exception handling – The same way all of these things are handled when an error occurs, such as a load failure or a route match failure


The sandbox isolation

Usually in multiple applications, there are only two parts that need to be isolated, JS & CSS;

JS isolation

Snapshot

When the child application is mounted, create a snapshot of the global Window variable and put it in the closure. Then, throw the global window to the child application. When the child application is unmounted, restore the global Window variable using the snapshot.


This is the practice of early parts of the framework, and in fact it does not form “isolation”, just to prevent multiple sub-applications from “contaminating” each other; There are also many restrictions:

  • The parent and child applications cannot coexist. The entire page under a URL route is a child application
  • Multiple child applications cannot coexist because there is only one global window and one snapshot
  • The security of the snapshot method is not strict enough, and deep copy will encounter problems such as document and history


We don’t do that anymore. It’s all sandbox thinking.


Sandbox


Wasm VM

Recompile a Wasm JS interpreter and put it in the browser, putting the child applications directly into the VM to execute;

Isolation is very strict, I have seen a lot of technical articles, but there is no research to have an actual micro front-end framework to do this,

The reason is the same as if you don’t use Web Worker/iframe, the isolation is too strict, communication is very troublesome, communication overhead is very large;

(But has some uses outside of the micro front, such as StackBlitz)


with() + new Function(code) + Proxy

The with syntax is used to change the scope chain. It is used to intercept the search for window when writing to global variables, such as array. from instead of window.array.


New Function executes code in the same way as eval, but eval can access variables in the current local scope. The return Function from new Function, no matter where it is executed, can only access global scope, which is exactly what we want.


The Proxy provides an object that is used in the with and New Function closures to act as the window scope, limiting access to some elements in the real window by whitelist attributes. Using Proxy to delete/add global variables/API will not affect the real global window;


Also hijack the various operations on document/history/localtion, The sandbox environment consists of inserting elements on document.body, rewriting history. Push and synchronizing it to the URL, and blocking localtion path so that children only get internal routes.


// Simplified pseudocode example
window = new Proxy(pick(window, whiteListProperties), { ... })
document = new Proxy(document, {... })... sandbox =new Function(`
  return function ({ window, location, history, document }, code){
    with(window) {
      ${code}
    }
}`)


sandbox().call(window, { window, location, history, document }, code)
Copy the code


However, the extent of window interception is limited. It can even be simply understood as a “shallow copy” rather than a “deep copy”. It is easy to escape and pollute by using the global generic API, such as changing the behavior of array.prototype. push directly.


with() + new Function(code) + Proxy + iframe contex

In order to solve the problem of global API escape of Proxy Window more safely, we can take an iframe window as the window of sandbox environment context.


Instead of sandboking the subapplication code directly, the subapplication is still executed in the with + new Function. The iframe is just an empty same-origin iframe created. The only use is to take its iframe.contentWindow object and pass it to the child application as a window;


Because of the strict isolation of iframe, all global objects have nothing to do with the outer layer (except parent), so the inside and outside of Array Array. Is a more perfect and elegant sandbox scheme;


(Proxy interception for document/history/localtion is the same as in the previous scheme)

// Simplified pseudocode example
frame = document.body.appendChild(document.createElement('iframe', {src: 'about:blank'.sandbox: "allow-scripts allow-same-origin allow-popups allow-presentation allow-top-navigation".style: 'display: none; ',}))window = new Proxy(frame.contentWindow, { ... })
document = new Proxy(document, {... })... sandbox =new Function(`
  return function ({ window, location, history, document }, code){
    with(window) {
      ${code}
    }
}`)


sandbox().call(window, { window, location, history, document }, code)
Copy the code


Realms

Tc39 also proposes a new specification, Realms, Stage 2, which allows the creation of completely separate global objects and global scopes, suitable for sandboxes (there is also some discussion of escape),

At present, there is no research on any micro front end framework in use, only Figma mentioned that it is used for its own plug-in scheme (after “micro material” mentioned above, plug-ins can also be included in the category of “micro front end”);


CSS isolation

Unlike JS isolation, which is relatively mature, CSS isolation is completely immature in the industry and is a bit of a problem for most micro front-end frameworks.


Most will tell you to use engineering prefixes to prevent conflicts, such as BEM/CSS modules/CSS in JS/custom prefixes, etc.

But this doesn’t solve the problem of different applications depending on different versions of the same UI library;

Also, most history projects have a lot of hard-coded classnames that are hard to overhaul;


Uninstallation during application switchover

This is the same as the “mount/uninstall” principle of Snapshot for JS isolation.


Shadow DOM

The Shadow DOM sounds like a real sandbox for CSS isolation. It has the same strict DOM isolation as the iframe. Elements inside the Shadow DOM do not affect elements outside the Shadow DOM.

< span style =” text-align: center;

Source MDN) (FIG.

All you need to do is create a Shadow DOM for each subapp. Any style/CSS links that the subapp inserts into the head will be blocked into the Shadow DOM.

<html>▶ ︎<head>.</head><body><div id="main-app" >.<! Select * from shadow-dom where shadow-DOM = shadow-DOM;<div id="sub-app-a-container">▼ # shadow - root (open) ▶ ︎<style>.</style>▶ ︎<link rel="stylesheet">.</link><div id="sub-app-a">.</div>
        </div>
      </div>

    </body>
</html>
Copy the code

In fact, aside from compatibility, a bunch of bugs in the browser’s Shadow DOM, and the fact that earlier versions of the React-DOM don’t support Shadow DOM events, there’s a problem:


Popup window mask

To be more precise: what about subapplying elements inserted into document.body via JS, such as Tooltip/Popover/Modal?

If they do insert the document. Body, they skip the Shadow DOM, and there is no CSS for the child application.

Where would these Tooltip/Popover/Modal elements be inserted if the JS sandbox document hijacked the insert?

If it is inserted into the Shadow DOM of the child application at the same level as the mounted DOM, there may be some problems with the style of the child application because of the DOM structure (order) change, or because the location, size, margin/padding and body of the child application are not consistent. Causes the positioning of the inserted element (such as Tooltip) to be incorrect. After all, not all inserted elements are fixed;

One hack is to place a Shadow DOM div at the end of document.body for each sub-application. This div has exactly the same positioning, size, and margin/padding properties as document.body. Is equivalent to overwriting the body and is internally synchronized with the style/CSS link tag inserted by the corresponding child application.

This Shadow DOM div is used to hold elements that the child application inserts into document.body (with JS sandbox), so that whether the element is Tooltip/Popover/Modal or unfixed positioning, The CSS obtained is consistent with the internal application, and the location is aligned with the body, basically solve the problem;

<html>▶ ︎<head>.</head><body><div id="main-app" >.<! Select * from shadow-dom where shadow-DOM = shadow-DOM;<div id="sub-app-a-container">▼ # shadow - root (open) ▶ ︎<style>.</style>▶ ︎<link rel="stylesheet">.</link><div id="sub-app-a">.</div>
        </div>
      </div>

      <! -- subapplication A synchronizes all styles of shadow DOM containers --><div id="sub-app-a-global-shadow">▼ # shadow - root (open) ▶ ︎<style>.</style>▶ ︎<link rel="stylesheet">.</link><div id="modal">.</div>
        </div>
    </body>
</html>
Copy the code

However, hacks are not perfect, but the approach based on synchronous CSS may be unable to synchronize, miss, and other problems;

  • Such as the<style>The synchronization inside the tag needs to be monitored all the time, and the two Shadow DOM need to be synchronized back and forth, because new ones can be inserted in either one<style>Tags can also be in the original one<style>Inner label modification;
  • Another example is the CSS in JS scheme that is commonly used for performance purposesCSSStyleSheet.insertRule() APITo create styles so that elements can be influenced by CSS styles but correspond to<style>The tag content is completely empty, manual synchronization based on the tag content cannot be done, and JS sandbox is needed to cooperate with the hijacking insertRule API to do synchronization;
  • And if the child application inserts the DOM through JS not at document.body, but at any other location where the DOM exists, it is also difficult to do hijacking.


Technical debt!!!!!

The following are typical technical debts, but the process of clearing them can also be seen as part of the micro front-end transformation process.

  • Cross-coupling of components between modules

    Modules that introduce the internal components/methods of other modules,

    These referenced items should be split off into common components/methods;

    (such as expression tree components for labels used for data preparation, visual filter components, etc.)


  • Common dependent components/methods are not fully unpackaged

    Common Common components or Service public methods need to be reconstructed, split and sent


  • (Hash History => Browser History)

    Aeolus has always needed and planned to change from Hash History to Browser History, but it hasn’t been done yet.

    If you plan to retrofit, but have done a micro front-end retrofit before then, compatibility with subsequent retrofits may be more difficult to do;

    Different microfront-end containers support different routing patterns and support different parent-child applications to use different patterns.


  • React V17 upgrade to fix Shadow DOM issues

    While the main CSS isolation framework has Shadow DOM support, React will need to upgrade to V17 to fix various issues with the Shadow DOM.

    In addition, as the current UMI and React version are controlled by the internal package of UMI, in fact, this upgrade is accompanied by the upgrade of UMI, and the lazy routing compilation and other features of the new umi also need to be dealt with.


  • Hard-coded JSX className written in the code

    Most of these classnames have no prefix and are simply named (right, left, first, last…). , it is easy to cause conflicts, and this part also needs to be transformed or reconstructed; There’s a lot of this in the code, it’s hard to count;


  • Dev/CI/CD process is still being modified (Monorepo/Dev preformance/CI tasks)

    The transformation of the micro front end also affects the Dev/CI/CD process, so it is best not to do both at the same time;


Common dependency reuse

The micro front-end does not solve the reuse problem, “dependency reuse” itself is not a micro front-end framework to do, some dependencies cannot and should not be reused (such as code execution can have side effects on the internal variables /context of the dependency) (some frameworks do NPM Lib level reuse abstraction, But it can also cause problems with bundle Chunk.


Which frame to use?

Technically, any framework can be used, and the design of each framework is basically to claim a few lines of lightweight non-invasive access, so the access cost and replacement cost are very small; The key is that fengshen himself needs to finish the module separation;

In fact, we can only choose a supporting governance system services, r & D close to us; Otherwise, we need to build a supporting governance system according to the framework and troubleshoot access source level problems by ourselves.

Therefore, we also need to be ready to decouple and even replace the micro front frame at any time.


How far to go?

  • The internal public relies on the split of the split, the outgoing of the packet
  • The corresponding module is moved to another repository (or monorepo directory, such as apps/) and can start development independently (because it can be deployed).
  • The rest is to package and access according to the corresponding framework document
  • Concrete actual transformation process and transition stage engineering have what guiding plan?





Refs

Put the whole process, look at the comparison of relevant and valuable articles listed

The origin of

Techniques, strategies and recipes for building a modern web app with multiple teams using different


review

[Recommended] is some general overview of the micro front end, including design evolution, technology evolution, etc

  • [Live Record]
  • How to design a micro front end landing – InfoQ | Phodal
  • Micro – frontend Architecture in Action – Micro front-end those thing | Phodal
  • How to design and realize a micro front frame QianKun – fang huan
  • What exactly is a micro front? – Front to back
  • 11 Micro front-end Frameworks you Must Know -InfoQ


Webpack5 Module Federation

  • Module Federation | webpack
  • Webpack 5 Federation. A Game-changer to Javascript architecture. – inDepthDev
  • Webpack5 New Feature – Module Federation
  • Three application scenarios survey, Webpack new function Module Federation in-depth analysis – Alibaba cloud developer community
  • How does webPack-packed code run in the browser? I lose if I can’t read it

There is no sandbox, only code packaging and reuse


Qiankun

  • Qiankun Documentation website/Umijs/Qiankun Github
  • @umijs/plugin-qiankun
  • umijs/umi-plugin-qiankun examples
  • The goal is the most complete micro front-end solution – Qiankun 2.0
  • Core value of micro front end · Language finch
  • How to design and realize a micro front frame QianKun – fang huan
  • Flypig micro front end practice: solution of unified operation workbench
  • Sandbox realization of ali Cloud open platform micro front-end solution
  • Click event not firing when React Component in a Shadow DOM

Qiankun is a runtime container and loader, but does not answer engineering and platform questions


Magic microservices

  • Github.com/bytedance/m…

The idea is that Web Components act as an isolation, a layer of pure framework containers that does not contain the management platform

Puzzle

  • Github.com/puzzle-js/p…

Bit.dev

  • Bit: The platform for the modular web
  • teambit/bit
  • Installing Bit | Documentation

Alfa

  • Alfa | Alibaba Cloud Alfa
  • Github.com/aliyun/alib…
  • How to “cheat” a micro front sandbox? – Aliyun developer community


Sandbox isolation

  • Talk about the js sandbox implementation mechanism in the field of micro front end – Teng Lecture Hall
  • How to “cheat” a micro front sandbox? – Ali Cloud Browser VM
  • A brief look at Web Workers and JavaScript sandboxes
  • Alibabacloud-alfa /browser-vm/ SRC/context. js | browser VM sandbox implementation core code

Figma

  • How to build a plugin system on the web and also sleep well at night
  • How Plugins Run · Figma Developers
  • Github.com/tc39/propos…

The Figma blog details how the sandbox isolation of their plugin system has evolved; Figma uses Realms and the same -Origin iframe + null-Origin iframe to create a context for the code in the sandbox.


Welcome to “ByteFE” resume delivery email: [email protected]