Written in the beginning

Micro front end series articles:

  • Microfront-end Best Practices based on Qiankun – from 0 to 1
  • The Qiankun Based Micro front End best Practices (illustrated) – communication between applications
  • 10,000 words long text + illustrated + comprehensive analysis of micro front end framework

The rest of the articles in this series are scheduled to be completed in a month or two.

The plan is as follows:

  • Life cycle;
  • IE compatibility;
  • Production environment deployment;
  • Performance optimization, caching scheme;

The introduction

This article will be aimed at the micro front-end framework Qiankun source code for in-depth analysis, before the source code to explain, let’s first understand what is a micro front-end.

Microfront-end is a kind of architecture similar to microservices, which applies the concept of microservices to the browser side, that is, the single-page front-end application is transformed from a single single application to a number of small front-end applications into one application. Each front-end application can be independently developed and deployed. They can also be developed in parallel while sharing components that can be managed via NPM or Git Tags or Git submodules.

Qiankun is a mature micro-front-end framework launched by Ant Financial. It is based on the secondary development of single-SPA, which is used to transform Web applications from single individual applications into multiple small front-end applications aggregated into one application. (See picture below)

So, without further ado, our source code parsing begins.

Initialize global configuration –start(opts)

We started with two basic APIS – registerMicroApps(Apps, lifeCycles?) – Register sub-applications and start(OPts?) – Start the main application. Since registerMicroApps has a lot of callback functions set and the initial configuration items set in start are read, we will start parsing from the start function.

Let’s start with the start function (see figure below) :

We parse the start function line by line:

  • Line 196Set:window__POWERED_BY_QIANKUN__Properties fortrue, used in sub-applicationswindow.__POWERED_BY_QIANKUN__Value to determine whether to run in the primary application container.
  • Line 199 ~ 198: Sets the configuration parameters (with default values) and stores the configuration parameters inimportLoaderConfigurationIn the object.
  • Line 203 ~ 201Check:prefetchProperty, and add global events if preloading is requiredsingle-spa:first-mountAfter the first subapplication is mounted, the resources of other subapplications are preloaded to optimize the loading speed of subsequent subapplications.
  • Line 205: according to thesingularModeParameter Specifies whether to set the mode to single instance.
  • Line 217 ~ 209: according to thejsSandboxParameter Setting Whether to enable the sandbox operating environment. In older versions, disable this option to ensure compatibility with IE. (The new version supports IE by default in single-instance mode, but does not support IE in multi-instance mode.)
  • Line 222: call thesingle-spastartSingleSpaMethod to start the applicationsingle-spaWe’ll look at this separately, but it’s easy to think of this as launching the main application.

As you can see, the start function is responsible for initializing some global Settings and then starting the application. Some of these initialized configuration parameters will be used in the callback function for the registerMicroApps registered child application, so let’s move on.

Register a subapplication –registerMicroApps(apps, lifeCycles?)

The registerMicroApps function registers the child application and, when the child application is activated, creates a running sandbox that calls different lifecycle hook functions at different stages. (See picture below)

As you can see above, the registerMicroApps function at lines 70 to 71 does something to prevent the same child application from being registered twice.

The child application is registered by calling the single-spa registerApplication method at line 74.

Let’s look directly at the registerApplication method, which is the core function for registering child applications in single-SPA. The function takes four arguments, which are

  • Name (name of the child application)
  • Callback function (called when activeRule is active)
  • ActiveRule (activation rules for sub-applications)
  • Props (the data that the main application needs to pass to the child application)

These parameters are implemented directly by single-SPA and can be briefly understood here as registering sub-applications (which we will expand on in the single-SPA section). When activeRule activation rules are complied with, the child application is activated, the callback function is executed, and some lifecycle hook functions are returned (see figure below).

Note that these lifecycle hook functions belong to single-SPA, and it is up to single-SPA to decide when to call them. Let’s take the function name as a simple guide here. (Bootstrap – initialize the child application, mount – mount the child application, unmount – unmount the child application)

If you’re still confused, that’s ok, let’s use a picture to help you understand. (See picture below)

Get the child application resource – import-html-entry

As we can see from the analysis above, The first parameter in the registerMicroApps method in Qiankun is apps-array

>. The name, activeRule, and props are given to single-spa. There are also entry and Render parameters that have not yet been used.

We need to focus on entry (the entry address of the child application) and render (the rendering rule triggered when the child application is activated), two parameters that are not yet used, which are deferred to the callback function after the single-SPA child application is activated.

So let’s assume that at this point our child application is active, and let’s see what’s going on here. (See picture below)

As you can see from the figure above, after the child application is activated, the import-html-entry library is first used at lines 81~84 to enter the child application from the entry. When the child application is loaded, an object is returned (see figure below).

Let’s explain these fields

field explain
template After commenting the contents of the script filehtmlTemplate file
assetPublicPath Resource Address Root path that can be used to load sub-application resources
getExternalScripts Method: Get the imported script file from outside
getExternalStyleSheets Method: Get the externally imported stylesheet file
execScripts Method: Execute all files in the template fileJSScript file, and you can specify the scope of the script –proxyobject

We print the results of the getExternalScripts and getExternalStyleSheets functions. The results are as follows:

From the image above we can see that we imported three js script files externally. This template file has no external CSS stylesheet and the corresponding stylesheet array is empty.

Then we examine the execScripts method, which specifies a proxy (window by default) object, executes all JS files in the template file, and returns the last property of the proxy object after JS execution (see figure 1 below). In a microfront-end architecture, this object typically contains lifecycle hook functions for subapplications (see figure 2 below) that the main application can invoke at certain stages to mount and destroy the subapplication.

The configuration item getTemplate is also passed in the importEntry function of Qiankun. This is actually a second processing of the HTML object file, but it will not be extended here.

The main application mounts the subapplication HTML template

Let’s go back to the Source code in Qiankun (see below).

As you can see from the figure above, at lines 85 to 87, the single instance is first tested. In single-instance mode, new child application mounting behavior does not begin until the old child application is unmounted.

In line 88, the render function is passed in when the child application is registered, with HTML Template and loading as the input parameters. The contents of the render function are generally to mount the HTML in the specified container (see figure below).

At this stage, the main application has mounted the underlying HTML structure of the child application into one of the containers of the main application, and then the child application needs to be mounted using the corresponding mount method (such as Vue.$mount) to the child application state.

In this case, a similar loading effect can be enabled on the page according to the loading parameter until all the content of the child application is loaded.

Sandbox operating environment – genSandbox

Go back to the source code Qiankun and look at the callback function when the child application is activated (see figure below).

Lines 90 to 98 are the core part of Qiankun, and the key to state independence between several sub-applications, which is the js sandbox environment. If the useJsSandbox option is turned off, then the sandbox environment of all child applications is Windows, which can easily contaminate the global state.

We took a look inside genSandbox to see how Qiankun created the (JS) sandbox environment. (See picture below)

From the figure above, we can see that the internal sandbox of genSandbox is mainly divided into LegacySandbox and SnapshotSandbox based on whether Windows.Proxy is supported.

There is also a sandbox known as ProxySandbox, which seems to be the best solution so far. Because it behaves slightly differently from the old version, it is used for the time being only for the multi-instance schema.

ProxySandbox sandbox may be used as a single instance sandbox once it is stable.

LegacySandbox

Let’s take a look at how LegacySandbox performs state isolation (see below).

Let’s examine a few properties of the LegacySandbox class:

field explain
addedPropsMapInSandbox Record global variables that are added during the sandbox run
modifiedPropsOriginalValueMapInSandbox Record global variables that are updated during the sandbox’s run
currentUpdatedPropsValueMap Record the global variables that have been manipulated during the sandbox’s run. The above twoMapUsed forClose the sandboxRestore the global state whilecurrentUpdatedPropsValueMapIs in theActivate the sandboxRestores the sandbox to its independent state
name The name of the sandbox
proxy Proxy objects can be understood as child applicationsglobal/windowobject
sandboxRunning Whether the sandbox is currently running
active Activate the sandbox to start when the child application is mounted
inactive Close the sandbox and start it when the child application is uninstalled
constructor Constructor to create a sandbox environment

Let’s now go into the details of how LegacySandbox implements the sandbox runtime using the set and get properties of window.Proxy. (See picture below)

Note: The proxy object in the child application sandbox (line 62) can be simply understood as the child application’s Window global object (as shown below), and the child application’s operations on global properties are operations on the properties of the Proxy object. With this understanding, move on.

// Subapplication script file execution process:
eval(
  // Pass proxy as window parameter
  // The global object of the child application is the proxy object of the sandbox of the child application
  (function(window) {
    /* Subapplication script file content */
  })(proxy)
);
Copy the code

In lines 65 to 72, when set is called to set properties on the child proxy/ Window object, All attribute set and update the record in addedPropsMapInSandbox or modifiedPropsOriginalValueMapInSandbox first, Then unified records into currentUpdatedPropsValueMap.

Modify the global Window properties in line 73 to complete the value setting.

When get is called from the child proxy/window object, the value is directly from the window object. Values for non-constructors will be bound to the window object to the this pointer before returning the function.

The sandbox isolation of LegacySandbox is achieved by restoring the atomic application state when the sandbox is activated and restoring the master application state (the global state before the child application is mounted) when the sandbox is uninstalled. The detailed implementation is as follows (see the following figure).

As can be seen from the figure above:

  • Line 37: When the sandbox is activated, the sandbox passescurrentUpdatedPropsValueMapQuery the independent state pool of the child application (sandbox may be activated multiple times, here is the global variables that have been modified during sandbox activation), and then return the atomic application state.
  • 44 ~ 45: When closing the sandbox, passaddedPropsMapInSandboxDelete global variables that are added during the sandbox run. PassmodifiedPropsOriginalValueMapInSandboxRestores global variables that were modified during the sandbox run to the state before the child application was mounted.

As you can see from the above analysis, LegacySandbox’s sandbox isolation mechanism is implemented using the snapshot pattern. Let’s draw a diagram to help understand this (see the following figure).

Multi-instance sandbox – ProxySandbox

ProxySandbox is a new sandbox mode that is currently used for multi-instance mode state isolation. ProxySandbox may become a single instance sandbox after stabilization. Let’s see how ProxySandbox works with state isolation (see below).

Let’s examine several properties of the ProxySandbox class:

field explain
updateValueMap Record the updated values in the sandbox, which is a separate pool of status for each child application
name The name of the sandbox
proxy Proxy objects can be understood as child applicationsglobal/windowobject
sandboxRunning Whether the sandbox is currently running
active Activate the sandbox to start when the child application is mounted
inactive Close the sandbox and start it when the child application is uninstalled
constructor Constructor to create a sandbox environment

We will now explain in detail how ProxySandbox implements the sandboxed runtime environment from the set and get properties of window.Proxy. (See picture below)

Note: The proxy object in the child application sandbox can be simply understood as the child application’s Window global object (code below), and the child application’s operations on global properties are operations on the proxy object’s properties. With this understanding, move on.

// Subapplication script file execution process:
eval(
  // Pass proxy as window parameter
  // The global object of the child application is the proxy object of the sandbox of the child application
  (function(window) {
    /* Subapplication script file content */
  })(proxy)
);
Copy the code

When set is called to set properties on a child proxy/ Window object, all property Settings and updates hit updateValueMap and are stored in the updateValueMap collection (line 38). In the old version, the diff algorithm is used to restore the snapshot of the window object state. The state between the child application is isolated, but the window object between the child application and the child application is contaminated.

When get is called from the child proxy/window object, the value is first taken from the child’s sandbox status pool updateValueMap, and then from the main application’s window object if there is no hit (line 49). Values for non-constructors will be bound to the window object to the this pointer before returning the function.

In this way, the isolation between ProxySandbox sandbox applications is complete and all child applications have controlled access to the value of the Proxy/Window object. The value is applied only to the updateValueMap set inside the sandbox. The value is also taken first from the subapplication independent status pool (updateValueMap), and then from the proxy/ Window object if not found.

In comparison, ProxySandbox is the most complete sandbox mode, completely isolating the operation of window objects and addressing the problem that snapshot mode can still pollute Windows while the application is running.

Let’s draw a diagram of ProxySandbox for further understanding (see below)

SnapshotSandbox

The SnapshotSandbox sandbox is used when the window.proxy property is not supported. Let’s take a look at the internal implementation (see figure below)

Let’s examine a few properties of the SnapshotSandbox class:

field explain
name The name of the sandbox
proxy Proxy object, where iswindowobject
sandboxRunning Whether the current sandbox is activated
windowSnapshot windowsnapshot
modifyPropsMap The sandbox was modified during operationwindowattribute
constructor Constructor to activate the sandbox
active Activate the sandbox to start when the child application is mounted
inactive Close the sandbox and start it when the child application is uninstalled

The sandbox environment of SnapshotSandbox is realized by recording the snapshot of the Window status when it is activated and restoring the Window object when it is closed. (See picture below)

Let’s start with the active function, which takes a snapshot of the current Window object when the sandbox is activated (lines 38-40). After the snapshot is taken, the function internally restores the window state to the last sandbox running environment recorded by modifyPropsMap, that is, restores the window properties that were modified during the sandbox activation (history).

When the sandbox is closed, the inactive function is called to record the changed window object property value (line 54) in the modifyPropsMap set by traversing and comparing each property before the sandbox is closed. After recording the modifyPropsMap, restore the Window object to the state before it was sandboked (line 55) via the windowSnapshot, which is equivalent to removing all contamination of the window during the run of the child application.

The SnapshotSandbox sandbox uses snapshots to isolate the state of window objects. In contrast to ProxySandbox, SnapshotSandbox will contaminate the Window object during child application activation and belongs to a backward compatibility scheme for browsers that do not support the Proxy property.

Let’s draw a picture of the SnapshotSandbox to further understand it (see below)

Mount the sandbox –mountSandbox

Continuing back to the diagram, the genSandbox function not only returns a sandbox, but also a mount and unmount methods that are called when the child application is mounted and unmounted, respectively.

Let’s take a look inside the mount function (see figure below)

First, the child application sandbox is activated inside the mount (line 26), and after the sandbox starts, the hijacking of global listens (line 27) begins. Let’s focus on how patchAtMounting is implemented internally. (See picture below)

PatchAtMounting calls the following four functions internally:

  • PatchTimer (Timer hijacking)
  • PatchWindowListener (Window event listener hijack)
  • PatchHistoryListener (window.history event listener hijack)
  • patchDynamicAppend

The above four functions implement a uniform hijack of the window-specified object, and we can take a look at the internal implementation of some parsing.

Timer hijacking –patchTimer

Let’s take a look at the hijack of timer by patchTimer (see the picture below).

As can be seen from the figure above, setInterval is overloaded internally to collect the intervalId values of each enabled timer (line 23~24), so that free function can be called to clear all timers when the child application is uninstalled (see figure below).

Let’s take a look at the setInterval validation when the child app is loaded (see figure below).

As you can see from the figure above, setInterval has been replaced with a hijack function when entering the child application to prevent global timer contamination.

Dynamically add style sheets and script file hijacking –patchDynamicAppend

The implementation of patchWindowListener and patchHistoryListener are similar to the implementation of patchTimer and will not be repeated here.

We need to focus on parsing the patchDynamicAppend function, which is used to hijack the operation on the head element (see figure below).

As can be seen from the figure above, patchDynamicAppend mainly deals with dynamically added style and script tags.

Let’s start by looking at the handling of the style stylesheet (see figure below)

As can be seen from the figure above, the main processing logic is in lines 68~74. If the current subapplication is active (the main reason for judging the active state of the subapplication is: When the main application switch routing may automatically add dynamic style sheets, the need to avoid the main application of style sheets is added to the child go wrong in the head nodes), then the dynamic style style sheet will be added to the application container (see below), in a child when application uninstall stylesheet can also be unloaded, and the application together to avoid pollution. At the same time, the dynamic style sheet will also be stored in the dynamicStyleSheetElements array, also mentions its usefulness in the rear.

Let’s take a look at the processing of script files (see figure below)

The processing of dynamic script files is a little more complicated, let’s also parse a wave:

Fetch is used to fetch the imported script file at lines 83 through 101, and then execScripts is used to specify the proxy object (as the window object) to execute the script file contents. Both load and Error events are also triggered.

Add the annotated script file contents to the child application container as comments at lines 103 through 106.

Lines 109 to 113 are the execution of the embedded script file and will not be repeated.

We can see that the main purpose of hijacking dynamically added scripts is to replace the Window object in the dynamic script runtime with a proxy object, so that the running context of dynamically added script files of child applications is also replaced with the child application itself.

HTMLHeadElement. Prototype. RemoveChild logic is added is the application of container, the other, is not said.

Finally, let’s look at the free function (see figure below)

This free function is different from other patches implementations. A copy of cssRules is cached here and will be restored by the rebuild function when remounted. This is because the browser automatically clears the style element table when the STYLE element DOM is removed from the document. If you don’t do this, the style tag will appear on the remount, but there will be no render style issue.

Uninstalling the sandbox – unmountSandbox

Let’s go back to the mount function itself (see below)

As you can see from the figure above, the patchAtMounting function hijack the global listeners and returns the free function to remove the hijack. Call free when uninstalling the application to remove the hijack of these global listeners (see figure below)

As you can see from the figure above, the sideEffectsRebuilders are returned after free and rebuild is called at mount time to rebuild the dynamic stylesheet. This is a little bit of a loop, but if you don’t see it very well, you can turn it up and look at it again.

Now that we’ve finished parsing the core part of Qiankun, the sandbox mechanism, let’s move on to other parts.

Here we draw a picture to give a general overview of the sandbox creation process (see below)

Register internal lifecycle functions

After the sandbox environment is created, some internal lifecycle functions are registered in lines 100 to 106 (see figure below)

In the figure above, the mergeWith method on line 106 is used to connect the built-in lifeCycles to the passed lifeCycles.

LifeCycles are life cycle functions that are shared by all sub-applications and can be used to perform the same logical operations across multiple sub-applications, such as loading effects. (See picture below)

In addition to the lifecycle functions passed in from the outside, we need to pay attention to what the built-in lifecycle functions do in Qiankun (see figure below).

Let’s parse the code above one by one:

  • 13 ~ 15 lines: before loading the child applicationbeforeLoad(will only be executed once) to inject an environment variable indicating the child’s applicationpublicThe path.
  • 17 ~ 19: Before the child application is mountedbeforeMount(which may be executed multiple times) may also inject the environment variable.
  • 23 ~ 30 lines: Before uninstalling the child applicationbeforeUnmountTo restore the environment variables to their original state.

From the above analysis we can conclude that we can take the environment variable in the child application and set it to the value __webpack_public_path__ so that the child application matches the correct resource path when running in the main application. (See picture below)

The triggerbeforeLoadLifecycle hook functions

The beforeLoad Lifecycle hook function is triggered immediately after the lifecycle function is registered (see figure below)

As you can see from the figure above, in line 108, the beforeLoad lifecycle hook function is triggered.

The execScripts method of import-html-entry is then executed at line 110. The jsSandbox that is specified for the script file executes the subapplication’s script file and returns an object containing the subapplication’s lifecycle hook function (see figure below).

In lines 112 to 121, we check the lifecycle hook function of the child application. If no lifecycle hook function is found in the exported object of the child application, the sandbox object will continue to search for the lifecycle hook function. If no lifecycle hook functions are found, an error is thrown. Therefore, bootstrap, mount, and unmount are required for the child application to be properly embedded in the main application.

Here we draw a picture to summarize the initialization process of the child application before mounting (see figure below).

Into themountMount process

With some initial configuration in place (such as sub-application resources, running sandbox environment, lifecycle hook functions, etc.), Qiankun internally put it together and returned three functions as lifecycle functions within a single-SPA (see figure below).

The logic inside single-SPA will be expanded later. Here we can simply understand the three lifecycle hook functions inside single-SPA:

  • bootstrap: when the child application is initialized, it will be called only once;
  • mount: may be called multiple times when the child application is mounted.
  • unmount: when the child application is uninstalled, it may be called several times;

We can see that the bootstrap lifecycle functions exposed by the child application are called during the bootstrap phase.

Let’s expand on the mount phase here to see what functions are executed in the child application mount phase (see figure below)

Let’s do line-by-line parsing:

  • Line 133 ~ 127: Detects the single instance mode. In single-instance mode, new child application mounting behavior does not begin until the old child application is unmounted. (Since the execution is sequential, a block at one point will block all subsequent functions.)
  • Line 134: passed when the subapplication is registeredrenderFunction,HTML TemplateloadingAs an input parameter. It usually happens onceunmountThen, do it againmountWhen mounting behaviorHTMLMount in the specified container (see figure below)

    Since render was already called once during initialization, it is possible that the render method has already been executed once during the first call to mount.

    If (frame.querySelector(“div”) === NULL) to prevent the repeated mount of the subapplication.

  • Line 135: triggeredbeforeMountGlobal lifecycle hook function;
  • Line 136: mounts the sandbox. This step activates the corresponding sub-application sandbox and hijacks part of the global listener (e.gsetInterval). At this point the code to start the child application will run in the sandbox. (b) inbeforeMountSome of the previous global operations will pollute the main application, such assetInterval)
  • Line 137: Triggers the child applicationmountLifecycle hook functions, which at this step usually perform the corresponding child application mount operation (e.gReactDOM. Render, Vue. $mount. (See picture below)

  • Line 138: call againrenderFunction, at this pointloadingParameters forfalse, which means that the child application has been loaded.
  • Line 139: triggeredafterMountGlobal lifecycle hook function;
  • Line 144 ~ 140: Set this parameter in single instance modeprevAppUnmountedDeferredThe value of, this value is apromiseWill be used when the current child application is uninstalledresolve, which blocks the mount action of other child applications while the child application is running (Line 134);

If you don’t understand, let’s draw a flow chart to help you understand. (See picture below)

Into theunmountUninstall process

Now that we have combed through the mount process for the child application, let’s go into the unmount process for the child application. During the activation phase of the child application, when activeRule misses, unmount will be triggered. The specific behavior is as follows (see figure below)

As we can see from the figure above, the unmount uninstallation process is much simpler than the mount process. Let’s comb it directly:

  • Line 148: triggeredbeforeUnmountGlobal lifecycle hook function;
  • Line 149: here andmountThe order of the process is slightly different, with the child application being executed firstunmountThe lifecycle hook function ensures that the child application is still running in the sandbox and avoids state contamination. In this case, some state of the child application is usually cleaned up and uninstalled. Destroy the one you just createdvueInstance)

  • Line 150: Uninstalls the sandbox and disables the sandbox activation.
  • Line 151: triggeredafterUnmountGlobal lifecycle hook function;
  • Line 152Trigger:renderMethod, and passed inappContentIs an empty string that can be used to empty the contents of the main application container.
  • Line 156 ~ 153: Triggered in single instance mode after the current child application is uninstalledprevAppUnmountedDeferred.resolve(), enabling the mount behavior of other child applications to continue, no longer blocking.

We also draw a picture of the unmount process to help you understand (see figure below).

conclusion

So far, we have sorted out the general process of The Qiankun framework. Here should make a summary, you read so many words, I guess you are tired, finally with a picture of the general process of Qiankun summarized it.

eggs

Looking forward to

Traditional cloud console applications almost always face the problem of mono applications evolving into Stonehenge applications after the rapid development of business. How do we maintain a big middle platform app?

The above question led to the concept of micro front-end architecture, so the concept of micro front-end has become more and more popular, and our team has been trying to transform the micro front-end architecture recently.

If you want to do a good job, you must first improve its device, so this article for Qiankun source code interpretation, in the sharing of knowledge is also to help their own understanding.

Here are some of our team’s best practices for micro-front-end architectures (see below). If you have any requirements, please leave them in the comments section and we will consider putting out a micro-front-end Architectures Best Practices to help you build them.

One last thing

If you have already seen this, I hope you still click “like” before you go ~

Your “like” is the greatest encouragement to the author, and can also let more people see this article!

If you found this article helpful, please help to light up the Star on Github to encourage!