- Micro front End 01: Js isolation mechanism analysis (Snapshot sandbox, two kinds of proxy sandbox)
- Micro Front End 02: Analysis of microapplication loading process (from registration of microapplication to internal implementation of loadApp method)
- Micro front End 03: Sandbox Container Analysis of Universe (concrete application of Js sandbox mechanism after establishment)
- Microfront 04: Resource loading mechanism for The Universe (internal implementation of import-HTml-Entry)
- Micro front End 05: Implementation of loadMicroApp method and analysis of data communication mechanism
In microfront 01: Qiankun’s Js isolation mechanism (snapshot sandbox, two kinds of proxy sandbox), we know the core principle and concrete implementation of Qiankun’s sandbox. But knowing this is not enough, because the sandbox itself is a tool that needs to be put into practice to create value. We also mentioned some of the logic related to sandbox in loading microapplications in Microfront 02: Qian’s analysis of microapplication loading process (from registration of microapplications to internal implementation of loadApp methods), but we didn’t go into it due to space limitations. This article will explain the concrete application of the universe to the sandbox in detail.
The main logic of the sandbox container
The actual application of the sandbox mechanism is essentially the control of the sandbox container. As for what is a sandbox container, we will directly look at the code:
SRC /sandbox/index.ts
/ * * *@param appName
* @param elementGetter
* @param scopedCSS
* @param useLooseSandbox
* @param excludeAssetFilter
* @param globalContext* /
export function createSandboxContainer(appName: string, elementGetter: () => HTMLElement | ShadowRoot, scopedCSS: boolean, useLooseSandbox? : boolean, excludeAssetFilter? : (url: string) => boolean, globalContext? :typeof window.) {
let sandbox: SandBox;
if (window.Proxy) {
sandbox = useLooseSandbox ? new LegacySandbox(appName, globalContext) : new ProxySandbox(appName, globalContext);
} else {
sandbox = new SnapshotSandbox(appName);
}
// A lot of code is omitted here... Placeholder 1
return {
instance: sandbox,
async mount() {
// A lot of code is omitted here... Take 2
sandbox.active();
// A lot of code is omitted here... Placeholder 3
},
async unmount() {
// A lot of code is omitted here... Placeholder 4
sandbox.inactive();
// A lot of code is omitted here... Placeholder 5}}; }Copy the code
As you can see from snippet 1, a sandbox container is an object. The object contains three properties: instance, mount, and unmount. Instace represents the instance of the sandbox. Mount and unmount are two methods that the holder of the sandbox container can call at the appropriate time. For the sandbox instance, let’s first look at the globalContext passed in when we created the sandbox instance. Remember we passed it in microfront 01: A minimalist version of the Js isolation mechanism (snapshot sandbox, two proxy sandboxes). I used Window directly, so why pass in globalContext instead of Window directly in the real source code? The answer is simple: arguments exist because they have variable values, otherwise they are written to death, in other words, more flexible. For example, what if the carrier of our microapplication is another microapplication? Without this flexibility, complex and varied scenarios cannot be well supported. As a well-known framework in the industry, qiankun’s handling of details is certainly worth learning from the efforts of many developers. After talking about sandbox instance creation, let’s take a look at the mount and unmount methods. If you ignore the omitted code in the omitted snippet comment, mount and unmount simply call sandbox. Active and sandbox. Inactive to activate or deactivate the sandbox. In that case, the existence of the sandbox doesn’t make much sense, but before I introduce the other logic in the mount and unmount methods, let’s look at the three lines in placeholder 1 of code snippe1:
SRC /sandbox/index.ts
const bootstrappingFreers = patchAtBootstrapping(appName, elementGetter, sandbox, scopedCSS, excludeAssetFilter);
let mountingFreers: Freer[] = [];
let sideEffectsRebuilders: Rebuilder[] = [];
Copy the code
Function patchAtBootstrapping
Let’s just focus on the first line of code for the moment, which calls the function patchAtBootstrapping:
File: / / the code snippet, belong to the SRC/sandbox/patchers/index. The ts
export function patchAtBootstrapping(appName: string, elementGetter: () => HTMLElement | ShadowRoot, sandbox: SandBox, scopedCSS: boolean, excludeAssetFilter? : CallableFunction,) :Freer[] {
const patchersInSandbox = {
[SandBoxType.LegacyProxy]: [
() = > patchLooseSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter),
],
[SandBoxType.Proxy]: [
() = > patchStrictSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter),
],
[SandBoxType.Snapshot]: [
() = > patchLooseSandbox(appName, elementGetter, sandbox.proxy, false, scopedCSS, excludeAssetFilter),
],
};
returnpatchersInSandbox[sandbox.type]? .map((patch) = > patch());
}
Copy the code
The patchAtBootstrapping function does only one thing: it executes and returns the result as an array, depending on the sandbox type. Why an array type? As far as this method is concerned, there is nothing wrong with returning the function value directly, because you can see from the code that in patchAtBootstrapping, no matter what sandbox type, only one function is executed. They are wrapped in arrays because other functions that perform a similar function to patchAtBootstrapping, such as patchAtMounting, have multiple functions to execute. The advantage of this method is that it ensures the unification of data format, which is conducive to the unified processing of subsequent related logic. At the same time, it also has good scalability. If the function patchAtBootstrapping needs to execute multiple functions in the future, there is no need to change the overall structure of the code. This is something we should learn from.
Function patchStrictSandbox
As for the three functions of patchLooseSandbox, patchStrictSandbox and patchLooseSandbox. Next, I will only go into patchStrictSandbox, because patchStrictSandbox is the most important. The internal main logic of the other two functions is similar to that of patchStrictSandbox. Interested friends can read it by themselves. Now let’s look at the code of the patchStrictSandbox function:
File: / / code snippet four, belong to the SRC/sandbox/patchers/dynamicAppend/forStrictSandbox ts
export function patchStrictSandbox(
appName: string,
appWrapperGetter: () => HTMLElement | ShadowRoot,
proxy: Window,
mounting = true,
scopedCSS = false, excludeAssetFilter? : CallableFunction,) :Freer {
// A lot of code is omitted here... Placeholder 1
return function free() {
// A lot of code is omitted here... Take 2
return function rebuild() {
// A lot of code is omitted here... Placeholder 3
};
};
}
Copy the code
After a lot of code is omitted, we can intuitively see the main structure of the function, the process we can use pseudo-code to describe the call process:
// Code snippet 5
letFreeFunc = patchStrictSandbox(many parameters...) ;// Step 1: execute code inside this function, affecting the program state
let rebuidFun = freeFunc(); // Step 2: Undo the effects of step 1 on the program state
rebuidFun();// Step 3: Restore the program to the state where it was when step 1 completed
Copy the code
Now that we understand the main logic of patchStrictSandbox, let’s look at the code omitted from placeholder 1 in snippet 4:
File: / / code snippet six, belong to the SRC/sandbox/patchers/dynamicAppend/forStrictSandbox ts
export function patchStrictSandbox(
appName: string,
appWrapperGetter: () => HTMLElement | ShadowRoot,
proxy: Window,
mounting = true,
scopedCSS = false, excludeAssetFilter? : CallableFunction,) :Freer {
//********************* part 1 *********************/
let containerConfig = proxyAttachContainerConfigMap.get(proxy);
if(! containerConfig) { containerConfig = { appName, proxy, appWrapperGetter,dynamicStyleSheetElements: [].strictGlobal: true,
excludeAssetFilter,
scopedCSS,
};
proxyAttachContainerConfigMap.set(proxy, containerConfig);
}
const { dynamicStyleSheetElements } = containerConfig;
/ * * * * * * * * * * * * * * * * * * * * * * * the second part of * * * * * * * * * * * * * * * * * * * * * /
const unpatchDocumentCreate = patchDocumentCreateElement();
const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(
(element) = > elementAttachContainerConfigMap.has(element),
(element) = > elementAttachContainerConfigMap.get(element)!,
);
// A lot of code is omitted here...
}
Copy the code
The first part of the logic of the patchStrictSandbox function
Let’s analyze code fragment six in the first part, you can see there are several important variables of this part, proxyAttachContainerConfigMap, dynamicStyleSheetElements, proxy, containerConfig, This part of the code to do three things, one is based on proxy access to the cache variable proxyAttachContainerConfigMap configuration object, if there are assigned to the variable containerConfig. Second, if there is no corresponding configuration object proxy cache, then make an initialization configuration object, and the proxy as the key, in the configuration object for the value, stored in the cache variable proxyAttachContainerConfigMap. Three get from containerConfig dynamicStyleSheetElements. There are several points worth examining here. First, the proxy is what, why the proxy as the key to store configuration object in the proxyAttachContainerConfigMap? Proxy is actually the sandbox instance created in snippet 1 above, corresponding to the sandbox variable in snippet 1.
Secondly, in the code snippet no.6 middle school, proxyAttachContainerConfigMap assignment only the initial value, given from the cache variable proxyAttachContainerConfigMap based on proxy configuration object for the operation, Explain proxyAttachContainerConfigMap must have update containerConfig operating in other place, otherwise it is not necessary to only cache an initialization value. We’ll talk about where to update containerConfig, and which properties of containerConfig to update the corresponding values.
Finally, dynamicStyleSheetElements is what? The actual type is HTMLStyleElement[], and HTMLStyleElement represents a
// Note that while styles render the same effect, there are actually some differences between controlling styles via CssStyleRule and normal styles mounted to the DOM as text, which will be discussed laterdiv{
color:red;
}
Copy the code
This is enough for now, and we will cover CSSStyleRule when we analyze how CSS resources are processed.
Note: please read the English VERSION of THE MDN document. For the explanation of HTMLStyleElement, the Chinese version is still backward and inconsistent with the English version
The second part of the logic of the patchStrictSandbox function
At this point we’ll go back to the second part of Code Snippet 6 and put the relevant code here for easy reading:
const unpatchDocumentCreate = patchDocumentCreateElement();
const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(
(element) = > elementAttachContainerConfigMap.has(element),
(element) = > elementAttachContainerConfigMap.get(element)!,
);
Copy the code
patchDocumentCreateElement
Let’s look at patchDocumentCreateElement the code:
File: / / code snippet seven, belong to the SRC/sandbox/patchers/dynamicAppend/forStrictSandbox ts
function patchDocumentCreateElement() {
// omit a lot of code...
const rawDocumentCreateElement = document.createElement;
Document.prototype.createElement = function createElement(// omit a lot of code...) :HTMLElement {
const element = rawDocumentCreateElement.call(this, tagName, options);
// Key point 1
if (isHijackingTag(tagName)) {
// Omit a lot of code
}
return element;
};
// Critical point 2
if (document.hasOwnProperty('createElement')) {
document.createElement = Document.prototype.createElement;
}
// Keypoint 3
docCreatePatchedMap.set(Document.prototype.createElement, rawDocumentCreateElement);
}
return function unpatch() {
// Keypoint 4
// Omit some code this time...
Document.prototype.createElement = docCreateElementFnBeforeOverwrite;
document.createElement = docCreateElementFnBeforeOverwrite;
};
}
Copy the code
After omitting some code, patchDocumentCreateElement function realization of functions, gradually clear. This function does three main things. One is to rewrite the Document. The prototype. The createElement method, rewriting in the purpose of the seven key points in the code fragment 1, specific point 1 internal do anything because of its simple logic is temporarily not in here. Create createElement (createElement); create createElement (createElement); As for the code snippet above mentioned point 2, it is a change of the document, this point should be with the rest of the logic relationship, otherwise there is no need to judge the document, temporarily not found using the processing place, later found to fill the related logic and details, but the meaning is not too big, look at the situation again. Three is to return a function, the function will revert to rewrite the Document. The prototype. The createElement method when the Document. The prototype. The influence of the createElement method.
Because of the length, let’s move on to the second part of code snippet 6:
const unpatchDocumentCreate = patchDocumentCreateElement();
const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions(
(element) = > elementAttachContainerConfigMap.has(element),
(element) = > elementAttachContainerConfigMap.get(element)!,
);
Copy the code
PatchDocumentCreateElement just now, we analyzed the function, and can now know unpatchDocumentCreate is a function in the code snippet, execution will clear on the Document. The prototype. The influence of the createElement method. Here I can no longer enter function patchHTMLDynamicAppendPrototypeFunctions is analyzed, the principle and function patchDocumentCreateElement similar, But what it affects and restores is HTMLHeadElement. Prototype. The appendChild, HTMLHeadElement. Prototype. RemoveChild, HTMLBodyElement. Prototype. RemoveChild, HTMLHe AdElement. Prototype. InsertBefore prototype method, etc.
Function patchStrictSandbox free function
At this point, move your eyes to placeholder 2 in code fragment 4 as follows:
// A lot of code is omitted here...
if (allMicroAppUnmounted) {
unpatchDynamicAppendPrototypeFunctions();
unpatchDocumentCreate();
}
recordStyledComponentsCSSRules(dynamicStyleSheetElements);
Copy the code
From the analysis of the above, we know that the execution unpatchDynamicAppendPrototypeFunctions, unpatchDocumentCreate after two functions, the influence of the corresponding prototype function will remove rewrite. We look at recordStyledComponentsCSSRules (dynamicStyleSheetElements); , the code is as follows:
export function recordStyledComponentsCSSRules(styleElements: HTMLStyleElement[]) :void {
styleElements.forEach((styleElement) = > {
if (styleElement instanceof HTMLStyleElement && isStyledComponentsLike(styleElement)) {
if (styleElement.sheet) {
styledComponentCSSRulesMap.set(styleElement, (styleElement.sheet asCSSStyleSheet).cssRules); }}}); }Copy the code
Core is only one line of code: styledComponentCSSRulesMap. Set (styleElement, (styleElement. Sheet as CSSStyleSheet). CssRules); . CssRules stands for specific CSS styles. In the case of this line of code, these styles are loaded remotely, which is equivalent to taking a CSS file from the web and parsing it to generate a style tag. The style tag is not a string. The specific code here is verbose and will not be posted for the time being. Essentially, it saves a relationship between a style tag object and its contents. The cssRules saved here will be used in the analysis that follows.
Rebuild function of free function in patchStrictSandbox
At this point, move your eyes to placeholder 3 in code fragment 4, which looks like this:
return function rebuild() {
rebuildCSSRules(dynamicStyleSheetElements, (stylesheetElement) = > {
const appWrapper = appWrapperGetter();
if(! appWrapper.contains(stylesheetElement)) { rawHeadAppendChild.call(appWrapper, stylesheetElement);return true;
}
return false;
});
};
Copy the code
The corresponding rebuildCSSRules function is as follows:
export function rebuildCSSRules(styleSheetElements: HTMLStyleElement[], reAppendElement: (stylesheetElement: HTMLStyleElement) => boolean,) {
styleSheetElements.forEach((stylesheetElement) = > {
const appendSuccess = reAppendElement(stylesheetElement);
if (appendSuccess) {
if (stylesheetElement instanceof HTMLStyleElement && isStyledComponentsLike(stylesheetElement)) {
const cssRules = getStyledElementCSSRules(stylesheetElement);
if (cssRules) {
for (let i = 0; i < cssRules.length; i++) {
const cssRule = cssRules[i];
const cssStyleSheetElement = stylesheetElement.sheet asCSSStyleSheet; cssStyleSheetElement.insertRule(cssRule.cssText, cssStyleSheetElement.cssRules.length); }}}}}); }Copy the code
From the logic of the code, you can see two things intuitively. One is to add the previously generated style tag to the microapplication. Second, insert the previously saved cssRule into the corresponding style tag. Why do we have to do insertRule? The dynamic control style through cssRule is different from the normal style tag. Cssrules are invalidated once the style tag associated with them leaves the document. This is why you need to save and reset.
At this point, the code at placeholder 1 in snippet 1 of this article is complete. With a clear understanding of the code for placeholder 1, this article is almost complete. Because mount and unmount are essentially changing and restoring state using the bootstrappingFreers function provided by placeholder 1.
Please follow my wechat subscription number: Yang Yitao to get the latest news.
After reading this article, feel the harvest of friends like it, can improve the digging force value, I hope to become an excellent writer of digging gold this year.