An architectural style in which multiple independently deliverable front-end applications are successfully integrated into a larger whole

It can be concluded from the brief description that the core of the micro front end is disassembly and integration, a pluggable architecture that integrates the disassembly and non-interference sub-applications together, which is the micro front end.

The picture above is A flow chart of the front, A, B, C three different distribution in three of the different warehouse application, after to compile and test, to generate three different products, the three products can deploy application alone, eventually by applying subject (base) to apply the son together, to form A larger, more robust products.

Why do you need a micro front end?

The business value

  • For example, the company has many platform applications scattered all over the place, and it is difficult to find them and remember their domain names. In this case, we can aggregate these applications into one main body, which is not only convenient to use, but also convenient to do some special functions, such as access to unified work order notification.
  • Multi-application fault: For example, when I was querying orders, I also wanted to see the user access path. Generally, the user access information of the company was recorded in a separate system, so I had to open two Windows to view it, which was inconvenient.
  • Legacy system migration: For historical reasons, many applications written before may be used in the old technology stack, which currently runs stably and does not need to be updated. For this kind of application, we don’t have to waste time rewriting it. Now that it’s available, we’re going to integrate it directly into our new application.

Value engineering

  • Technology stack independent: Every developer is good at a different technology stack. Using a micro front end can set aside the boundaries of the technology stack, allowing each sub-application to have full autonomy, a wide range of access, backward compatibility, forward compatibility.
  • Independent development: The repository is independent, each developer only needs to maintain their own repository, eliminating code conflicts, slow compilation and other problems.
  • Incremental upgrade: In the face of various complex scenarios, it is often difficult for us to upgrade or reconstruct the technology stack of an existing system completely, and the micro front end is a very good means and strategy to implement the gradual reconstruction.
  • Standalone deployment: State isolation between each microapplication.

Implementation scheme

There are many schemes to realize the micro front end, and there is no established specification at present. The realization of the micro front end is not necessarily a new technology, nor is it too complicated, but you can choose the one that suits you. Here I have collected several for your reference

iframe

Iframe is an old HTML tag that everyone thinks is common, but it has always worked. There are a lot of background applications that have been cobbled together through iframe. It can effectively embed another page/single page in the current page, and the CSS, JS in the two pages are isolated from each other, do not interfere with each other

  • advantages
    • Perfect isolation, natural support for style isolation and global variable isolation
    • No intrusion on existing code
  • disadvantages
    • Perfect isolation, this is also a disadvantage, can not share some data or resources, iframe internal and external systems communication, data synchronization and other requirements
    • During page switching, the browser rolls back, which is prone to redirect errors
    • Resources need to be reloaded each time they are launched, and each time a child application enters, it is a process of browser context reconstruction and resource reloading
    • Life cycle issues are more difficult to deal with. Eg: After unloading, after rendering
    • Communication mechanism: Creating postMessage events and listening in each application is unfriendly and intrusive, and requires defining a ToPic specification

Route distribution

The business is distributed to different front-end pages through different routes, which is also a kind of micro front-end. Because it is composed of multiple independently deliverable applications. But this is more like a patchwork of apps.

However, this example also shows that the micro front end doesn’t have to be new or complicated, just choose what works best for you.

NPM package

Publish each child application as an NPM module using Package integration. This approach, which is closer to our current development practice, is a friendly way to use the business component you want to use. But there are a lot of additional development costs associated with this approach.


Microcomponentization: each business team will write their own good, compiled code into the specified server, only load the corresponding business module at run time, similar to VUE Componet.

Complex update process

Suppose we have three management systems, and all three management systems introduce the same sub-business module. If the sub-module is updated, the code needs to be published to NPM, and the three management systems, respectively, update the NPM package and redeploy. The whole process is complex and prone to problems.

Slow build

The increase in dependency packages inevitably increases the compilation time

Module Federation -webpack5 Federation

Multiple independent builds can form an application. These independent builds should not depend on each other, so they can be developed and deployed separately. This is often referred to as a microfront-end, but it’s not limited to that

Module federation was introduced in Webpack5. This mechanism allows a built code base to run dynamically, at runtime, in another code base. Code can be directly called remotely between projects and share arbitrary content

Key principle: Create a rainbow bridge between multiple applications through global variables.

Basic concept: Federation has two roles, one is the referent (child application), which is used to throw itself out, and the other is the referent (main application/base)

  • Referenced party

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
new ModuleFederationPlugin({
  name: 'remote'.${expose} ${name} ${expose}
  library: { type: 'var'.name: 'remote' }, // Declare a globally mounted variable name, where name is umD's name
  filename: 'remote.js'.// The name of the Chunk after the build
  exposes: {
    // as a key configuration item for the referenced party, used to expose the corresponding supplied module
    './Content': './src/Content'
  },
  // Optional, use the HOST dependency first, if not, use its own.
  shared: {
    vue: {
      eager: true // Instead of putting modules into asynchronous blocks, it provides them synchronously. This allows us to use these shared modules in the initial block}}})Copy the code
  • Refer to the
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
new ModuleFederationPlugin({
  name: 'host'.${expose} ${name} ${expose}
  // As the most critical configuration item for the referrer, it is used to declare the name of the remote resource bundle and module to be referenced
  remotes: {
    remote: 'remote@http://localhost:8002/remote.js'}})Copy the code
  • Usage scenarios
    • Micro front-end: Shared and Remotes provide common dependency resource injection, reduce online volume, and integrate sub-applications into main applications.
    • Speed up compilation: Node_Modules resources can be pre-packaged, referenced by runtime, and only project source files can be built at compile time.
    • Multi-page application resource reuse: dependency introduction can be carried out at runtime to realize component reuse and page sharing. At the same time, it also has the capability of online hot update

Problems that need to be solved

You’re talking about intrusion, and when you talk about intrusion, you think about isolation. In the microfront end, there are two fundamental isolation issues.

Style isolation

How to handle style conflicts between different applications? Generally divided into two cases, master, child application style conflict, between child application style conflict

  • Master and child styles are isolated

Here we can use some engineering way, can better solve this kind of problem

  • Isolation between child applications (Dynamic Stylesheet) – Dynamic Stylesheet

  • Shadow Dom

JS isolation

Sandbox: As the name suggests, sandbox allows your application to run in a completely isolated environment, with no impact on external applications, avoiding global variable contamination.

  • The snapshot sandbox

Using the snapshot sandbox, you can activate the snapshot during application loading and cancel the snapshot during application uninstallation. During application switchover, restore the current application environment based on the snapshot. The disadvantage is that there is no support for multiple instances.

 class SnapShotSandbox {
        constructor() {
          this.proxy = window;
          this.modifyPropMap = {}; // Record window changes
          this.active(); // Default record
        }
        / / activation
        active() {
          this.windowSnapShot = {}; // Record the snapshot
          for (const prop in window) {
            // Record the properties in the snapshot
            if (window.hasOwnProperty(prop)) {
              this.windowSnapShot[prop] = window[prop]; }}// Assign the modified attributes to the window
          Object.keys(this.modifyPropMap).forEach((prop) = > {
            window[prop] = this.modifyPropMap[prop];
          });
        }
        / / the deactivation
        inactive() {
          for (const prop in window) {
            if (window.hasOwnProperty(prop)) {
              if (window[prop] ! = =this.windowSnapShot[prop]) {
                // Save the changed attributes to the property table
                this.modifyPropMap[prop] = window[prop];
                // Restore window
                window[prop] = this.windowSnapShot[prop];
              }
            }
          }
        }
      }

      let sandbox = new SnapShotSandbox();
    // Switch applications without affecting global properties
      ((window) = > {
        window.a = 1;
        window.b = 2;
        console.log(window.a, window.b); 
        sandbox.inactive(); / / the deactivation
        console.log(window.a, window.b);
        sandbox.active(); / / activation
        console.log(window.a, window.b);
      })(sandbox.proxy);
Copy the code
  • The Proxy sandbox

 class ProxySandbox {
        constructor() {
          this.proxy = this.init();
        }
        init() {
          const rawWindow = window; // Store the source window
          const fakeWindow = {}; // false window, used to store attributes in the sandbox
          return new Proxy(fakeWindow, {
            set(target, props, value) {
              target[props] = value;
              return true;
            },

            get(target, prop) {
              returntarget[prop] || rawWindow[prop]; }}); }}let sandbox1 = new ProxySandbox();
      let sandbox2 = new ProxySandbox();

      window.a = "I'm the global variable A.";
      console.log(window.a);

      ((window) = > {
        window.a = "The first sandbox";
        console.log(window.a);
      })(sandbox1.proxy);

      ((window) = > {
        window.a = "The second sandbox";
        console.log(window.a);
      })(sandbox2.proxy);
Copy the code