This paper discusses some basic realization of the universe and helps to understand the principle of the universe.

Focus on a few issues around and try to come to an understanding

  • Basic principle of Qiankun
  • Its routing mechanism and life cycle
  • Shortcomings and disadvantages
  • What should a best practice be
  • The shape of future frameworks

Simple principles of single-SPA

Single-spa is probably the most complete microfront-end solution you’ve ever seen (19 years) due to the design of The Universe and the thinking of the Ant team

Since our subapplications are lazy loaded, when the browser is refreshed, the resources of the main framework will be reloaded, while the static resources of the subapplication will be asynchronously loaded. Since the routing system of the main application has been activated at this time, the resources of the subapplication may not be fully loaded. As a result, no rule matching subApp/ subApp/123/detail is found in the routing registry, and the NotFound page or direct routing error is reported. This problem occurs in all lazy load scenarios, and was called Future State by the AngularJS community a few years ago. The solution is also very simple, we need to design such a routine mechanism: the main framework configures the subApp routing as subApp: {url: '/subApp/**', entry:'./ subapp.js'}, when the browser address is /subApp/ ABC, the framework needs to load entry resources first. After the entry resources are loaded, ensure that the routing system of the sub-application is registered in the main framework, The child application's routing system takes over the URL change event. At the same time, when the subapplication route is cut out, the main framework needs to trigger the corresponding Destroy event. When the subapplication listens to this event, it calls its own uninstall method to uninstall the application. For example, in the React scenario, destroy = () => reactdom.unmountatNode (container). To implement such a mechanism, we can either hijack the URL change event ourselves to implement our own routing system, or we can build on the UI Router library that the community already has, In particular, the React-Router implements Dynamic Routing capability after V4, so we only need to duplicate part of the route discovery logic. Here we recommend going straight to single-SPA, a related practice with a better communityCopy the code

The micro Frontend architecture is designed to address the maintainable issues that arise when an application becomes Frontend Monolith over a long period of time. Qiankun is based on this kind of background single-page application proposed solutions. The main framework of Qiankun chose mature single-SPA as the routing scheme. Meanwhile, the framework itself also considered father-child application integration and application isolation.

Then there’s Single-SPA, which is a JavaScript microfront-end framework for aggregating multiple single-page applications into a monolithic application. Let’s take a look at its design purpose and the problem it solves: an overview of the Single-SPA architecture

Single-spa takes inspiration from the modern framework component lifecycle and applies the lifecycle to the entire application. Single-spa now supports almost any framework. Since JavaScript is notorious for the short life of many of its frameworks, we decided to make it easy to use in any framework you wantCopy the code

It is important to note that the design of single-SPA is based on a modern framework, one of the key features of modern SPA frameworks (vue, React, etc.) : each application can respond to URL routing events and must know how to initialize, mount, and uninstall itself from the DOM.

Taking vUE as an example, the initialization of a VUE project is usually as follows:

After the mount node is defined in the index file (div ID =”app”), we will create a new vue instance and mount it to the corresponding DOM node when the main entry file is executed.

An attempt, what if instead of starting the instance when I enter the page, I decide when to load and unload the instance? Can I switch to loading another instance after an instance is unloaded? And whether this can achieve the purpose of switching spa applications freely, so we further have such a design

This is actually the most basic thing single-SPA does, but let’s go back to its simplest use: Chinese documents

  1. Create an HTML
<html>
<body>
    <script src="single-spa-config.js"></script>
</body>
</html>
Copy the code
  1. Create a startup file, load the script file for the micro-application, define the activation rules for the route, and register the micro-application information. Using qiankun registerMicroApps to load microapplications is also based on the use of such registerApplication.
//main.js import * as singleSpa from 'single-spa'; const name = 'app1'; /* Loading is a function that returns promise and is used to load/parse application code. * Its purpose is to facilitate lazy loading -- Single-SPA will only download the application's code if it needs to. * In this example, import () is supported in Webpack and returns promises, but single-SPA can use any load function that returns promises. */ const app = () => import('./app1/app1.js'); /* Single-spa configures top-level routing to determine which application is active for the specified URL. * You can implement this route any way you like. * A useful convention is to prefix the URL with the name of the active application to keep the top-level routing simple. */ const activeWhen = '/app1'; singleSpa.registerApplication({ name, app, activeWhen }); singleSpa.start();Copy the code
  1. Define the life cycle of the child application
//app1.js let domEl; Export function bootstrap(props) {return promise.resolve ().then() => {//  domEl = document.createElement('div'); domEl.id = 'app1'; document.body.appendChild(domEl); }); } export function mount(props) {return promise.resolve ().then(() => {// Here the framework is usually used to mount UI components to the DOM. Please refer to https://single-spa.js.org/docs/ecosystem.html. / / if we use the vue, etc., here is equivalent to the vue instantiation and mount, https://cn.vuejs.org/v2/api/#vm-mount. DomEl textContent = 'App 1 is mounted. '}); } export function unmount(props) {return promise.resolve ().then(() => {// See https://single-spa.js.org/docs/ecosystem.html / / if we use the vue, etc., here is equal to the destruction of vue example is destroy (), see the vue document. DomEl textContent = ' '; })}Copy the code
Note: The single-API has two sets of apis, Application API and Parcel API. The Parcel API provides the ability to manually mount the Application, stripping the registerApplication default route listening capability. Manual loading of Qiankun's "loadMicroApp" is based on this approachCopy the code

Single-spa does a couple of things here

  1. Emulating modern applications that implement their own life cycle and perform different life cycle functions at different stages (The bootstrap, mount and unmount to)
  2. You need to complete the sub-application logic in the corresponding life cycle, for example, initialize vue on mount and unmount and destroy vue on mount and Unmount
  3. Single-spa interception listens for the top-level routing event of the system, passing throughactiveWhenRule switching life cycles of different applications (for example, switching pages) : Bootstrap page1, mount page1, unmount Page1, Bootstrap Page2, mount Page2, unmount Page2 ···). After the subapplication is loaded, the framework sends the routing event to the subapplication. Child applications can continue to respond to their own routing logic

Qiankun’s encapsulation of single-SPA

Loadmicroapps registerMicroApps and loadMicroApp registerMicroApps

import { registerMicroApps, loadMicroApp, start } from 'qiankun'; registerMicroApps([ { name: 'react app', // app name registered entry: '//localhost:7100', container: '#yourContainer', activeRule: '/yourActiveRule', }, { name: 'vue app', entry: { scripts: ['//localhost:7100/main.js'] }, container: '#yourContainer2', activeRule: '/yourActiveRule2', }, ]); // Or manually loadMicroApp({name: 'app', entry: '//localhost:7100', container: '#yourContainer',}); start();Copy the code

Actually also based on registerApplication and Parcel in single-SPA, we can confirm from source github1s.com/umijs/qiank…

The activeRule field corresponds to the activeWhen field, and the other fields and lifecycle hooks are also basic, but we need to focus on another issue here: sub-page load entry: ‘//localhost:7100’.

Entry file loading: JS Entry vs HTML Entry

Looking back at the basic example of single-spa, we found it a bit counter-intuitive and counter-intuitive to define a child page by const app = () => import(‘./app1/app1.js’) when loading the child app. In modern SPA applications, we usually start from the entry file loading framework (Vue/React), package to generate a JS bundle, and load the final static resources in the template HTML. With Single-SPA, we need to repackage the entry file to expose it as a lifecycle hook function, and implement the logic of building container nodes for the child application, namely HTML mount nodes (Single-SPA currently does this by encapsulating different framework libraries, which works just fine). The benefits of this approach can be optimized at the resource loading level, such as runtime dependency sharing. When we have multiple sub-applications sharing a set of style components, or a framework such as Vue/React, we can set the externals of webpack during the deployment of the application, ignore the packaging of these dependencies, and then use the dynamic module loading tool systemJS. To avoid reloading, single-SPA also provides a way for webPack5-oriented module federation to solve the sharing problem.

The problem with the above is that the work we have to do to load the child application using single-SPA is tedious, at least two points

  1. Changed the way the child application was developed, so the node mount was either implemented by itself, or a different single-SPA framework library was introduced, and then the entry file was adapted
  2. Modified some of the original dependency introduction methods of sub-applications (such as module introduction to script introduction, etc., to achieve dependency sharing)

Qiankun has different considerations in the processing of entry loading. In line with the original intention of separating applications and quickly integrating different technical framework applications, qiankun adopted the loading mode of HTML :(qiankun does not advocate shared dependency among applications, and suggests that each application be independent from each other)

import { importEntry } from 'import-html-entry';
// get the entry html content and script executor
const { entry, name: appName } = app;
const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts);
Copy the code

The main principle is that fetch is used to obtain resources of url request, parse corresponding templates and script files, and obtain corresponding templates and script dependencies (including lifecycle hooks) of sub-applications. Import-html-entry is mainly used in the implementation of FETCH URL. The advantage of this is that it basically retains the original development mode of sub-applications (except for the addition of lifecycle hooks), and the cost is much lower than JS Entry. It also ADAPTS to the background of rapid integration of different technical framework projects.

For both of these options, read on for what may be the most complete microfront-end solution you’ve ever seen (19 years). Let me copy the summary

Deficiencies and positioning

Qiankun deficiencies of the above has been reflected, that cannot be achieved depend on Shared, however, this framework is also positioning problem, namely as the application of heaven and earth is more focused on the isolation and decoupling between applications, so that can be integrated with minimum workload of a large number of existing applications, so qiankun considerations and application isolation (style isolation and js sandbox), on the issue of the positioning, Here’s a good article on the two granularities of the microfront end

Microapplication loader: The granularity of "micro" is the application, i.e. HTML (or main.js), which can only do application-level sharing of micromodule loaders: The granularity of "micro" is module, JS module, which can share at the module level. So the difference is the granularity of microservices. The granularity of what can be served is application level, while single-SPA is module level.Copy the code

Some principles

About the dependence

When combing through the single-SPA documentation, the authorities actually have recommended Settings for what should be defined as a run-time module (subapplications, large shared dependency libraries, etc.) and what should be defined as a build-time module. It is also recommended to load large JavaScript libraries (React, component libraries, etc.) only once

The universe does not recommend shared dependencies (although external can be used). We can understand project integration between teams with different backgrounds and separate development for maintenance purposes. However, there are quite a few new projects or projects with strong dependencies (such as shared project component library, map library, etc.). It is still recommended to share dependencies. One way to achieve this in the future is through module federation of Webpack5 (mentioned below). Call the common dependency library in the main application script introduction, etc

About routing and manual registration applications

Depends on the scene, for a simple scenario to framework to monitor routing, also have zi lu by complex situations (such as in the main application menu bar, on the application of the two belong to the same child different routing link jump, etc.), routing rules may let the caller confusion at this moment, is a good way, in the main application of encapsulated in the component way application, Then the loading and destruction of sub-applications are controlled through the component life cycle. At this time, in the outer layer of the main application, different sub-applications can be easily managed and switched through v-if and other commands

const App
onMounted(() => {
    App = loadMicroApp({
        name: 'app',
        entry: '/app/index.html',
        container: '#app-container', 
    })
})
onBeforeUnmount(() => {
    if (App) {
        App.unmount()
    } 
})
Copy the code

Direction of optimization

Qiankun provides a set of micro front-end framework that is very convenient to use, but it is still worth optimizing to reduce the overall volume and solve the dependency sharing problem. One of the big trends and solutions is module federation in webpack5.

Module federation is a Webpack-specific technique for sharing build-time modules (# in-browser-vs-build-time-modules). It involves each MicroFrontend bundling all of its dependencies, even shared dependencies. This means that there are multiple copies of each shared dependency — one for each MicroFrontend. In the browser, the first copy of the shared dependency is downloaded, but the subsequent MicroFrontend reuses the shared dependency without downloading their copy.

More may follow webpack.js.org/concepts/mo…

The latter

  1. Another benefit of microapplication was found in the project: the technical framework upgrade process is very easy to use, vuE3 can be used first in a simple sub-application, and the subsequent sub-application upgrade transition
  2. Sometimes you can’t microapply for microapplication’s sake, but the argument hasn’t been made yet
  3. Although the granularity of single-SPA is smaller, some scenes are not as simple and easy to organize as Those of Qiankun due to their complexity