micro frontend framework Qiankun source code analysis
上一篇博客我们介绍了Qiankun的沙盒机制,也就是不同的子应用之间是如何做环境隔离的,这篇博客我们就基于上一篇博客讲一下如何利用沙盒去进行子应用的加载和切换。
Qiankun is based on single-spa implementation, all internal use of part of the single-spa interface, a brief introduction to the difference between single-spa and qiankun, single-spa only do sub-application registration, switching, routing monitoring, etc., has not yet reached a commercial level, and qiankun on top of this js and css sandbox isolation, according to the official doc, qiankun is to meet the commercial requirements.
globalState
I will add my own comments to the following code, but there are two points to note:
- This global state, i.e. globalState, is unique, meaning that all child applications use the same global state object
- But each sub-application can register its own callback function for the change of global state, and store it in the deps array with appInstanceId as the key. Although all deps will be triggered when the state changes, using appInstanceId as the key can be used in the sub-application. When destroyed, the callback function of the application is correctly cancelled.
1 | /** |
prefetch
This is mainly to provide strategies and methods for preloading sub-applications
1 | /** |
loader
Loader internal tool function
1 | function assertElementExist(element: Element | null | undefined, msg?: string) { |
createElement
Create a new element with a parameter list of:
- appContent: string type, need to pass in html text
- strictStyleIsolation: Whether to use a separate shadow dom, if true, and the current browser supports shadow dom, then the content in appContent will be inside a shadow dom
- scopedCSS: If true, all the css content in the html created this time will be prefixed. For details, see my other blog, how qiankun does css isolation
- appInstanceId: string type, id of the sub-application
1 | function createElement( |
getAppWrapperGetter
1 | function getAppWrapperGetter( |
getRender
1 | /** |
loadAPP
The code is very long, let’s briefly analyze what they did:
- Call the’importEntry 'interface of single-spa to get the loading entry of the sub-application and other information
- The html entry parsed in the previous step will be passed to the createElement function as appContent, create a new node, mount the html entry under the node, and then return the new node and assign it to’initialAppWrapperElement’
- call’getRender ‘to get the render function, assign to’render’
- pass initialAppWrapperElement into render function
- Call’getAppWrapperGetter ‘to get the enhanced function’initialAppWrapperGetter’, some judgment will be made internally, if there is no problem, the returned function’initialAppWrapperGetter ‘will return’initialAppWrapperElement’
- If you choose to start the sandbox function, call createSandboxContainer to return the sandboxContainer, and assign the global object to the proxy of the sandboxContainer to start the sandbox function
- Get all lifecycle functions by calling getAddOns
- call execHooksChain to execute the beforeLoad function in the lifecycle obtained in the previous step
- Get the lifecycle hook function of the sub-application through execScripts (the parsing result of the single-spa in the first step) and getLifecyclesFromExports
- Eventually generate the parcelConfigGetter function, which returns the bootstrap, mount, and unmount methods for the child application, which will call the different lifecycle functions obtained earlier in sequence
1 | export async function loadApp<T extends ObjectType>( |
Instrumental approach
error
This is just declaring an Error class
1 | export class QiankunError extends Error { |
errorHandler
Exposing a method to add a global error handling listening function
1 | /** |
utils
Here only lists the function signature and its role, the specific code implementation will not be posted here
- function toArray
(array: T | T[]): T[] - function sleep(ms: number)
- function nextTask(cb: () => void): void
- function isConstructable(fn: () => any | FunctionConstructor)
- function isCallable(fn: any)
- function isPropertyFrozen(target: any, p?: PropertyKey): boolean
- function isBoundedFunction(fn: CallableFunction)
- const qiankunHeadTagName = ‘qiankun-head’;
- function getDefaultTplWrapper(name: string)
- function getWrapperId(name: string)
- const nativeGlobal = new Function(‘return this’)()
- const genAppInstanceIdByName = (appName: string): string
- function validateExportLifecycle(exports: any)
- function performanceGetEntriesByName(markName: string, type?: string)
- function performanceMark(markName: string)
- function performanceMeasure(measureName: string, markName: string)
- function isEnableScopedCSS(sandbox: FrameworkConfiguration[‘sandbox’])
- function getXPathForElement(el: Node, document: Document): string | void
Qiankun
This part is the main API that qiankun exposes and uses.
Tools to introduce
1 | import { noop } from 'lodash'; |
registerMicroApps
Use the single-spa interface’registerApplication 'to register sub-applications
1 | export function registerMicroApps<T extends ObjectType>( |
loadMicroApp
The function of this function is to call the loadApp of the loader, and then use the startSingleSpa of single-spa to start
1 | export function loadMicroApp<T extends ObjectType>( |
start
1 | export function start(opts: FrameworkConfiguration = {}) { |
Summary
In general, the internal API division of labor is as follows:
- prefetch provides a way to preload all child application resources,
- loader provides the ability to load resources based on the Sub-App entry, execute the entry file, and then mount the resulting html to a newly created div
- Qiankun has two main APIs exposed to the outside world
- registerMicroApps mainly calls the register interface of sigle-spa to register sub-applications
- loadMicroApp will call the method in the loader to load the sub-application. In fact, the loader eventually calls the load method of single-spa, just doing some sandbox isolation, css isolation, etc.