All that microfront-end stuff

Microfront-end is an architecture similar to microservices. It applies the concept of microservices to the browser side, that is, the Web application is transformed from a single application to a single application
An application that aggregates multiple small front-end applications into one. The front-end applications are ok
Independent operation,
Independent development,
Independent deployment.

They can also be developed in parallel while sharing components — which can be managed through NPM or Git tags or Git subModules.

Note: The front-end application here refers to a single application page with separate front and back ends, so it only makes sense to talk about the micro front end here.

directory

  • All that microfront-end stuff
  • There are six ways to implement a microfront end
    • Foundation: Application Route Distribution -> Route Distribution Application
      • Back end: function call -> remote call
      • Front end: Component call -> application call
    • Routing distribution micro front end
    • Create containers using iFrame
    • Homemade frame compatible application
    • Modular integration: the application will be microcomponentized
    • Pure Web Components technology build
    • Build with Web Components
      • Integrate existing frameworks into Web Components
      • Web Components integrated into existing frameworks
    • compound
  • Why are microfronts becoming popular – aggregation of Web applications
    • Front-end legacy system migration
    • The back end is decoupled and the front end is converged
    • Compatible legacy systems
  • How to deconstruct a single front-end application — a microservice split of front-end applications
    • The front end is incognito
      • Independent development
      • Independent deployment
      • Do we really need technology irrelevant?
      • User experience is not affected
    • Design concept of micro front end
      • Design concept 1: Centralized routing
      • Design idea 2: logo application
      • Design Idea 3: Life cycle
      • Design concept 4: independent deployment and configuration automation
    • Actual micro front-end architecture design
      • Independent deployment and configuration automation
      • Inter-application routing – events
  • Four split strategies for a large Angular application microfront
    • Front-end microservitization: Route lazy loading and its variants
    • Microservitization solution: sub-application pattern
    • Scheme comparison
      • Standard the LazyLoad
      • LazyLoad variant 1: Build-time integration
      • LazyLoad variant 2: Post-build integration
      • Front-end microservitization
    • Total contrast
  • Front-end microservitization: Use Mooa to develop microfront-end applications
    • Mooa concept
    • Micro front end main project creation
    • Mooa sub-application creation
    • Navigate to a specific child application
  • Front-end microservitization: Microservitization of Angular applications using custom Iframe
    • Iframe microservice architecture design
    • A customized IFrame model for Mooa, a micro-front-end framework
    • The Mooa Iframe communication mechanism is a micro-front-end framework
      • Publish the main application event
      • Listen for child application events
    • The sample
  • resources

Why are microfronts becoming popular – aggregation of Web applications

New technology is adopted less because it is advanced than because it solves pain points.

In the past, I’ve always wondered if people really need microservices, if they really need a micro front end. After all, there is no silver bullet. When people consider whether to adopt a new architecture, in addition to its benefits, they also consider the numerous risks and technical challenges that exist.

Front-end legacy system migration

In the two months since the release of the Mooa microfront-end framework and its counterpart microfront-end Stuff, I’ve been receiving inquiries about microfront-end architecture on and off. One of the interesting things I discovered in the process is that addressing legacy systems is the most important reason people adopt microfrontend solutions.

In these consultations, developers are faced with a situation similar to the one I encountered before: designing a new front-end architecture. They started thinking about front-end microservitization because of legacy systems.

In the past, single-page applications using backbone. js, angular. js, Vue. Js 1, etc., have been running stably online with no new features. For such applications, there is no reason to waste time and energy rewriting old ones. The applications here, written using an old, no longer used technology stack, can be called legacy systems. These applications, in turn, need to be combined into new applications. More often than not, I see old apps written in Angular.js and new apps starting with Angular 2+. This is a very common technology stack for business stable teams.

Without rewriting the original system, manpower can be freed up to develop new business. This is not just an attractive feature for business people; It can be quite challenging for technical people not to rewrite old businesses while still doing some technical challenges.

The back end is decoupled and the front end is converged

One of the selling points of front-end microservices is that they are compatible with different types of front-end frameworks. This reminds me of the benefits of micro-services and the reasons why many projects are implemented micro-services:

In the early days, a big selling point for backend microservices was the ability to develop backend applications using different technology stacks. However, the reality is that organizations and organizations that adopt microservices architectures are generally medium to large scale. Compared with the medium and small size, the selection of framework and language is more strict, such as the internal framework, limited language. As a result, it is rare to see the full use of different technology stacks to take advantage of microservices. In these large organizations, the main reason for adopting microservices is to use microservices architecture to decouple dependencies between services.

In terms of front-end microservitization, on the other hand, aggregation is more desirable, especially for applications that are To B (To Bussiness).

In the last two or three years, there has been a trend in mobile apps where users don’t want to install as many apps. Often a large commercial company will offer a range of applications. These apps also reflect, to some extent, the organization of the company. However, in the eyes of users they are a company and they should only have one product. Similarly, this trend is happening on the desktop Web. Aggregation has become a technology trend, and the aggregation embodied in the front end is microservitization architecture.

Compatible legacy systems

So, at this point, we need to use new technology, new architecture, to accommodate and accommodate these old applications. And front-end micro service, just fit people want this selling point bai.

There are six ways to implement a microfront end

The micro-front-end architecture is similar to the micro-service architecture. It applies the concept of micro-service to the browser side, that is, the Web application is transformed from a single single application to multiple small front-end applications.

The change is that these front-end applications can be independently run, independently developed, and independently deployed. Also, they should be able to be developed in parallel while sharing components — components that can be managed through NPM or Git tags, Git subModules.

Note: The front-end application here refers to a single application page with separate front and back ends, so it only makes sense to talk about the micro front end here.

Based on my practice and research on micro-front-end in the last six months, the micro-front-end architecture can be generally carried out in the following ways:

  1. Redirect multiple applications using HTTP server routing
  2. Design communication and loading mechanisms on top of different frameworks such as Mooa and single-SPA
  3. Build a single application by combining multiple independent applications and components
  4. The iFrame. Use iFrame and custom messaging mechanisms
  5. Build applications using pure Web Components
  6. Build with Web Components

Foundation: Application Route Distribution -> Route Distribution Application

In a single front-end and single back-end application, there is a typical feature that routes are distributed by the framework, and the framework assigns routes to corresponding components or internal services. What microserver does in this process is change the call from a function call to a remote call, such as a remote HTTP call. The micro front end, on the other hand, is similar in that it transforms in-application component calls into more fine-grained inter-application component calls. In other words, we used to just distribute routes to application components for execution, but now we need to find the corresponding application based on the route, and then distribute the application to the corresponding components.

Back end: function call -> remote call

In most CRUD type Web applications, there are some very similar patterns: Home page -> List -> Details:

  • The home page, used to present specific data or pages to the user. This data is usually finite in number and of multiple models.
  • Lists, or aggregations of data models, typically feature a collection of a certain kind of data, with as many summaries of data as possible (e.g., Google only returns 100 pages), typically seen in the search results pages of Google, Taobao and JINGdong.
  • Detail, display as much content as possible of a data.

Here is an example of a Spring framework for returning to the home page:

@RequestMapping(value="/")
public ModelAndView homePage(){
   return new ModelAndView("/WEB-INF/jsp/index.jsp");
}
Copy the code

For a detail page, it might look like this:

@RequestMapping(value="/detail/{detailId}")
public ModelAndView detail(HttpServletRequest request, ModelMap model){
   ....
   return new ModelAndView("/WEB-INF/jsp/detail.jsp", "detail", detail);
}
Copy the code

So, in the case of microservices, it would look like this:

@RequestMapping("/name")
public String name(){
    String name = restTemplate.getForObject("http://account/name", String.class);
    return Name" + name;
}
Copy the code

In this process, a service discovery service is added to manage the relationship between different microservices.

Front end: Component call -> application call

Formally, the routing of a single front-end framework is not very different from the routing of a single back-end application: different templates are returned for different pages based on different routes.

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
  { path: 'detail/:id', component: DetailComponent },
];
Copy the code

When we microservize it, it may become the route of application A:

const appRoutes: Routes = [
  { path: 'index', component: IndexComponent },
];
Copy the code

In addition, the route applying B:

const appRoutes: Routes = [
  { path: 'detail/:id', component: DetailComponent },
];
Copy the code

The key is how to distribute routes to these different applications. At the same time, they are responsible for managing different front-end applications.

Routing distribution micro front end

The routing distribution micro front-end distributes different services to different independent front-end applications through routing. This can usually be done through a reverse proxy of the HTTP server, or through the application framework’s own routing.

At present, the routing distribution micro-front-end architecture should be the most widely and easily adopted “micro-front-end” scheme. This approach, however, looks more like an aggregation of multiple front-end applications, in which we just piece together these different front-end applications to make them look like a complete whole. But they’re not. Every time A user goes from application A to application B, the page usually needs to be refreshed.

On a project a few years ago, we were doing a legacy rewrite. We made a migration plan:

  1. First, use static site generation to dynamically generate the home page
  2. Second, the React schedule stack is used to refactor the detail page
  3. Finally, replace the search results page

The whole system is not migrated all at once, but one step down. So when we go through the different steps, we need to go live with this functionality, and we need to use Nginx for route distribution.

Here is an example of an Nginx configuration based on routing distribution:

http { server { listen 80; server_name www.phodal.com; The location/API / {proxy_pass http://http://172.31.25.15:8000/api; } the location/web/admin {proxy_pass http://172.31.25.29/web/admin; } the location/web/notifications {proxy_pass http://172.31.25.27/web/notifications; } location / { proxy_pass /; }}}Copy the code

In this example, requests for different pages are distributed to different servers.

We subsequently used a similar approach on other projects, mainly because of cross-team collaboration. When the team reaches a certain size, we have to face this problem. In addition, there is the Angluar cliff jump upgrade issue. So, in this case, the user front end uses Angular rewrite and the back end uses angular.js and so on to keep the existing technology stack. There are some similar technical decisions in different scenarios.

So in this case, it applies to the following scenarios:

  • There is a big difference between different technology stacks, and it is difficult to compatibility, migration and transformation
  • The project did not want to spend a lot of time on this system transformation
  • The existing system will be replaced in the future
  • System functions have been very complete, basically there will be no new requirements

In the case of satisfying the above scenarios, iframe can also be used to solve the problem for better user experience.

Create containers using iFrame

IFrame is a very old technology that everyone thinks is common, but it has always worked.

HTML inline frame elements
<iframe>Represents a nested browsing context, effectively embedding another HTML page into the current page.

Iframe can create a completely new independent hosting environment, which means that our front-end applications can run independently of each other. There are several important prerequisites for adopting iframe:

  • Websites don’t need SEO support
  • An application management mechanism is available.

If we are doing an application platform, we will integrate third-party systems in our system, or systems under multiple departments and teams, obviously this is a good solution. Some typical scenarios, such as migrating a traditional Desktop application to a Web application:

If this type of application is too complex, it is bound to be a microservitization split. Therefore, when adopting iframe, we need to do two things:

  • Design management application mechanism
  • Design application communication mechanism

Loading mechanism. At what point do we load and unload these applications? In this process, how to use animation transition, so that the user looks more natural.

Communication mechanism. Creating postMessage events and listening directly in each application is not a friendly thing to do. It is too intrusive for the application itself, so it is simpler to fetch the Window object of the iFrame element via iframeel.contentWindow. Then you need to define a set of communication specifications: what format to use for event names, when to start listening for events, and so on.

For those interested, check out the micro-front-end framework I wrote about earlier: Mooa.

Anyway, iframe may not bring any benefit to our KPI this year, so let’s build a wheel.

Homemade frame compatible application

Whether it is Angular based on Web Components, or React based on VirtualDOM, existing front-end frameworks are inseparable from the basic HTML element DOM.

So, we just need:

  1. Introduce or create the DOM where appropriate on the page
  2. When a user performs an operation, the corresponding application can be loaded (triggered to start the application) and uninstalled.

The first problem, creating the DOM, is an easy one to solve. The second problem, however, is not at all easy, especially when it comes to removing the DOM and the corresponding application’s listening. When we have a different technology stack, we need to design this logic specifically.

Although single-SPA already has startup and uninstall handling for most frameworks (React, Angular, Vue, etc.), it is still not suitable for production use. When I was designing a micro-front-end architecture for the Angular framework based on single-SPA, I ended up rewriting my own framework, Mooa.

Although the difficulty of getting started in this way is relatively high, it is convenient to customize and maintain in the later stage. Regardless of the user experience issues that come with each application load, the only risk is that third-party libraries are incompatible.

But, in any case, it is technically more boastful and interesting than iFrame. Again, like iframe, we still face a series of modest problems:

  • A mechanism for managing applications needs to be designed.
  • For high-traffic toC applications, there will be a large number of requests when they are first loaded

We want to split apps and blabla… What else can we do?

Modular integration: the application will be microcomponentized

Combinatorial integration refers to the process of separating and recombining applications before, during, and after construction by software engineering.

By this definition, it may not be a micro front end at all — it can satisfy the three elements of a micro front end: independent operation, independent development, and independent deployment. However, combined with the component Lazyload feature of the front-end framework, which loads the corresponding business component or application as needed, it looks like a micro front-end application.

At the same time, CSS styles don’t need to be reloaded because Pollyfill is already loading the first time as much as possible with all the dependencies.

Common ways are:

  • Build components and applications independently, generate chunk files, and then classify the generated chunk files. (This approach is more like microservices, but at a higher cost)
  • During development, components or applications are developed independently, while during integration, components and applications are combined, and finally a single application is generated.
  • At Runtime, the application Runtime is loaded, followed by the corresponding application code and templates.

The relationship between applications is shown in the figure below (ignoring the “front-end microservitization” in the figure) :

This approach seems ideal, allowing multiple teams to develop in parallel while building the right deliverables.

First, however, there is a serious limitation: you must use the same framework. For most teams, this is not a problem. Teams that adopt microservices will not use different languages and technologies for development just because microservices are a front end. Of course, if we want to use another framework, it’s not a problem, we just need to combine the homemade framework compatibility application from the previous step to meet our needs.

Second, there is a limit to this approach: specification! Specification! Specification! . In adopting this approach, we need to:

  • Uniform dependencies. Unify the versions of these dependencies and add them as new dependencies are introduced.
  • Specification of application components and routing. Avoid conflicting component names between different applications.
  • Build complex. In some scenarios, we need to modify the build system; in others, complex architectural scripts are required.
  • Share common code. This is obviously a problem to be faced often.
  • Develop code specifications.

So this approach looks more like a software engineering problem.

Now, we’ve got four options, each with its own pros and cons. Obviously, the combination would be a better approach.

Given the limitations of existing and commonly used technologies, let’s take the long view again.

Pure Web Components technology build

In the process of learning About Web Components development micro front-end architecture, I tried to write my own Web Components framework: OAN. After adding some basic Web front-end framework functionality, I found that this technique is particularly suited as a cornerstone of a microfront-end.

Web Components are a different set of technologies that allow you to create reusable custom elements whose functionality is encapsulated outside of your code and use them in your Web applications.

It mainly consists of four technical components:

  • Custom Elements, which allow developers to create Custom elements such as
  • The Shadow DOM, or Shadow DOM, is usually attached to the main document DOM and can control its associated functionality. The Shadow DOM cannot be controlled directly by the other main document DOM.
  • HTML templates, namely<template><slot>Element for writing tag templates that are not displayed on the page.
  • HTML Imports, used to introduce custom components.

Each component is introduced by the link tag:

<link rel="import" href="components/di-li.html">
<link rel="import" href="components/d-header.html">
Copy the code

Then, in the respective HTML files, create the corresponding component elements and write the corresponding component logic. A typical Web Components application architecture is shown below:

You can see that this is very similar to the way we used iframe above, in that the component has its own separate Scripts and Styles, and the corresponding domain name for deploying the component separately. However, it’s not as good as it looks, and the difficulties of building front-end applications directly using pure Web Components include:

  • Rewrite existing front-end applications. Yes, now we need to complete the functionality of the entire system using Web Components.
  • The upstream and downstream ecosystems are not perfect. Lack of support for third-party controls is why jQuery is so popular.
  • The system architecture is complex. When an application is broken down into one component after another, communication between components becomes a particular problem.

ShadowDOM in Web Components is more like a new generation of front-end DOM containers. Unfortunately, not all browsers fully support Web Components.

Build with Web Components

Web Components is far away from us now, but it is an architecture for future evolution to build front-end applications based on Web Components. Or in the future, we can start building our applications in this way. Fortunately, there are frameworks in place to make this possible.

Currently, there are two ways to build microfront-end applications with Web Components:

  • Use Web Components to build frame-independent Components, and then introduce those Components into the corresponding framework
  • Introduce an existing framework in Web Components, similar to the form of iframe

The former is a component-based approach, or it is like migrating future “legacy systems” to future architectures.

Integrate existing frameworks into Web Components

Existing Web frameworks already have forms that support Web Components, such as Angular’s createCustomElement, which implements a Component in the form of Web Components:

platformBrowser() .bootstrapModuleFactory(MyPopupModuleNgFactory) .then(({injector}) => { const MyPopupElement = createCustomElement(MyPopup, {injector}); CustomElements. Define (' my - popup, MyPopupElement); });Copy the code

In the future, there will be more frameworks that can be integrated into Web Components applications using forms like this.

Web Components integrated into existing frameworks

The alternative, Stencil style, is to build Components directly into Web Components that can then be referenced directly in their React or Angular counterparts.

Here is an example of a Stencil generated Web component referenced in React:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import 'test-components/testcomponents';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Copy the code

In this case, we can build components that are independent of the framework.

The same Stencil still only supports recent browsers like Chrome, Safari, Firefox, Edge, and IE11

compound

Compound, right is the above several categories, randomly pick several combinations together.

I will not nonsense ~~.

Microfront-end architecture selection guide

In the previous section, “Six or seven Ways to Implement front-end Microservitization”, we introduced the architecture of some different solutions we used in the process of implementing micro-front-end. In this article, I will summarize how to choose the right solution according to different situations.

Quick selection guide chart

I’ll jump to the conclusion:

The key points are explained as follows:

Frame constraints. In backend microservices, people use libraries in other languages to develop new services, such as Python for artificial intelligence. But on the front end, it’s almost impossible. So when we only have one front-end framework, we have a much wider range of options when we adopt micro-front-end technology. Unfortunately, most organizations need to be legacy compatible.

IE problem. When we implemented the micro front end a few years ago, and again this year, one of the first things we did was support IE. In the projects I met, basically all need to support IE, so the selection of technology is limited to a certain extent. On our projects that don’t require IE support, they can use WebComponents technology to build microfront-end applications.

Depend on independence. That is, whether the dependency of each micro-front-end application should be managed uniformly or managed by itself in each application. Unified management can solve the problem of repeatedly loading dependencies, but independent management brings extra traffic overhead and waiting time.

Comparison of micro front-end solutions: a brief comparison

If you’re still not familiar with any of these aspects, read six or seven Ways to Implement Front-end Microservitization.

way Development costs Maintenance costs The feasibility of Same framework requirements To realize the difficulty The potential risk
Routing distribution low low high no u This plan is too common
iFrame low low high no u This plan is too common
Apply microservitization high low In the no U u u u Customize and Hook for each framework
Micro parts, high In the low is U u u u u Hack for build systems such as WebPack
The application of the In the In the high is U u u Unify build specifications for different applications
Pure Web Components high low high no End to end New technology, browser compatibility issues
Combining with the Web Components high low high no End to end New technology, browser compatibility issues

Again, some complex concepts are explained as follows:

Application microservitization means that each front-end application has an independent servitization front-end application, which is supported by a unified application management and startup mechanism, such as micro-front-end framework single-SPA or MOOA.

Microcomponentization means that different front-end applications can use the same set of dependencies by hacking the build system. It improves the problem of reloading dependent files in the application of microservitization.

Microapplication, also known as combinatorial integration, means to split single applications in the development environment and combine applications together to build an application in the construction environment through software engineering. For more details, we can look forward to the article “Dismantling and Microservitization of a single front-end application”.

Comparison of microfront-end solutions: Complex approach

I saw an article related to micro-services before, which introduced the differences between different micro-services. It adopted an interesting comparison method, especially in detail. Here it is shown in the same way:

Architectural goals describe
A. Independent development Independently developed and unaffected
B. Independent deployment Can be deployed separately as a service
C. Support different frameworks You can use different frameworks at the same time, such as Angular, Vue, React
D. Tree shaking optimization Eliminates unused code
E. Environmental isolation The context between applications is not disturbed
F. Multiple applications are running concurrently Different applications can run simultaneously
G. Shared dependencies Whether different applications share the underlying dependency libraries
H. Dependency conflict Whether different versions of dependencies cause conflicts
I. Integrated compilation Applications are compiled as a whole, not built separately

So, for the following table, a~ J in the table represent the different architectural considerations above.

(PS: Considering the length of several words of Web Components, it is temporarily shortened to WC~~)

way a b c d e f g h i
Routing distribution O O O O O O      
iFrame O O O O O O      
Apply microservitization O O O     O      
Micro parts, O O     O  
The application of the O O   O O O
Pure WC O O   O O O O
Combined with the WC O O O O O O     O

In the figure, O indicates support, blank indicates not support, and – indicates not affected.

Combined with the previous selection guide:

(PS: This figure adopts Keynote rendering)

Did you find the architecture you had in mind?

How to deconstruct a single front-end application — a microservice split of front-end applications

Refresh the page? Route splitting? No, dynamically load components.

This paper is divided into the following four parts:

  • Introduction to the concept of front-end microservitization
  • Design concept of micro front end
  • Actual micro front-end architecture design
  • Front-end microservitization based on Mooa

The front end is incognito

There are some solutions for front-end microcogging:

  • Web Components obviously make a good infrastructure. However, it is not possible to copy existing applications on a large scale.
  • The iFrame. Are you serious?
  • Single-spa, another micro-front-end framework, is clearly a better approach. However, it is not Production Ready.
  • The application is segmented by route, and this jump affects the user experience.
  • And so on.

Therefore, when we consider front-end microservitization, we want to:

  • Independent deployment
  • Independent development
  • Technology has nothing to do
  • User experience is not affected

Independent development

Over the past few weeks, I’ve spent a lot of time learning the code for Single-SPA. However, I found it too cumbersome to develop and deploy, and not up to the standard for standalone deployment. With single-SPA’s design, I need to name my application in the entry file before I can build:

declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), pathPrefix('/inferno'));
Copy the code

Also, in my application, I need to specify my life cycle. This means that when I develop a new application, I have to update two pieces of code: the main project and the application. At this point, we’ll most likely be working on the same source code.

Working in the same source code can become quite unreliable when multiple teams are involved – for example, the other team uses Tab, we use 2 Spaces, and the other team uses 4 Spaces.

Independent deployment

The biggest problem with a single front-end application is that the JS and CSS files constructed are quite huge. The micro front end means that the file is split into multiple files that can be deployed independently.

Do we really need technology irrelevant?

Wait, do we really need technology? If we didn’t need technology agnostic, the micro front end problem would be easy to solve.

In fact, for most companies and teams, technology irrelevance is just a trivial matter. When several founders of a company use Java, there is a good chance that they will continue to use Java for future selections. Unless, that is, some additional service uses Python to implement artificial intelligence. Therefore, in most cases, it is still unique to the technology stack.

This is especially true for front-end projects: there is basically only one framework in a department.

So we chose Angular.

User experience is not affected

It is a simple and efficient way to use route jump to implement front-end microservitization. However, there will be a blank screen during the route jump. In this process, both the app before the jump and the app before the jump lose control of the page. If something goes wrong with the app, the user will be stunned.

Ideally, it should be manageable.

Design concept of micro front end

Design concept 1: Centralized routing

Is the Internet decentralized by nature? No, DNS decided it wasn’t. TAB determines that it’s not.

Microservices, by their nature, should be decentralized. But it can’t be completely decentralized. For a microservice, it requires a service registry:

The service provider must register the notification service address, and the service caller must be able to discover the target service.

For a front-end application, this is routing.

In terms of pages, only when we add a menu link on the page can users know that a page is available.

In code, we need a place to manage our applications: ** Discover which applications exist and which applications use which routes.

Managing our routing is really managing our application.

Design idea 2: logo application

When designing a micro-front-end framework, I struggled with the problem of giving each project a name — how to normalize the thing. Until, once again, I thought of Conway’s law:

System design (product structure equals organization form, each design system organization, its resulting design equals communication structure between organizations.

In other words, it is impossible for two projects in the same organization to have the same name.

So, the problem was solved very simply.

Design Idea 3: Life cycle

Single-spa designs a basic lifecycle (although it is not uniformly managed) that includes five states:

  • Load determines which application to load and binds the lifecycle
  • Bootstrap to obtain static resources
  • Mount to install the application, such as creating a DOM node
  • Unload deletes the application life cycle
  • Unmount: unmounts an application, for example, deleting a DOM node

So I basically stuck with this life cycle in my design. Obviously, things like load are redundant in my design.

Design concept 4: independent deployment and configuration automation

In a sense, the entire system revolves around application configuration. If the configuration of the application can be automated, then the entire system can be automated.

When we only develop a new component, we just need to update our component and update the configuration. The configuration itself should be automatically generated.

Actual micro front-end architecture design

Based on the above premise, the working process of the system is as follows:

The overall engineering process is as follows:

  1. When the main project is running, it will go to the server to get the latest application configuration.
  2. After obtaining the configuration, the main project creates applications one by one and binds the application life cycle.
  3. When the main project detects a route change, it looks for a corresponding route to match the application.
  4. If the matching application is matched, the corresponding application is loaded.

Therefore, its corresponding structure is shown below:

The overall process is shown in the figure below:

Independent deployment and configuration automation

Our deployment strategy is as follows: our application uses a configuration file called apps.json, and the main project gets this configuration. Each time we deploy, all we need to do is point apps.json to the latest configuration file. The configured file classes are as follows:

  1. 96a7907e5488b6bb.json
  2. 6ff3bfaaa2cd39ea.json
  3. dcd074685c97ab9b.json

The configuration of an application is as follows:

{
  "name": "help",
  "selector": "help-root",
  "baseScriptUrl": "/assets/help",
  "styles": [
    "styles.bundle.css"
  ],
  "prefix": "help",
  "scripts": [
    "inline.bundle.js",
    "polyfills.bundle.js",
    "main.bundle.js"
  ]
}
Copy the code

The selector here corresponds to the DOM node required by the application, and the prefix is used on the URL route. These are automatically generated from the index.html file and package.json.

Inter-application routing – events

Because now the application becomes two parts: main engineering and application. There is a problem: only one project can capture route changes. When the secondary routing of the application is changed by the main project, it cannot be effectively communicated to the sub-application. In this case, the child application can only be notified by event, and the child application also needs to monitor whether it is the route of the current application.

if (event.detail.app.name === appName) {
  let urlPrefix = 'app'
  if (urlPrefix) {
    urlPrefix = `/${window.mooa.option.urlPrefix}/`
  }
  router.navigate([event.detail.url.replace(urlPrefix + appName, '')])
}
Copy the code

Similarly, when we need to jump from application A to application B, we also need A mechanism like this:

window.addEventListener('mooa.routing.navigate', function(event: CustomEvent) {
  const opts = event.detail
  if (opts) {
    navigateAppByName(opts)
  }
})
Copy the code

The rest, such as Loading animations, are similar.

Four split strategies for a large Angular application microfront

In the last month, we have spent a lot of time chewing through the chips to design solutions to break up a large Angular Discussions range from using Angular’s Lazyload to front-end microservitization. Finally, we have the result, using the Lazyload variant: the build-time integration of code.

For the past few weeks, as a “professional” consultant, I’ve been busy designing a servified Angular spin-off for clients. The main purpose is to achieve the following design objectives:

  • Build plug-in Web development platform to meet the needs of rapid business change and distributed multi-team parallel development
  • Build service-oriented middleware and build a front-end micro-service platform with high availability and reuse
  • Support front-end independent delivery and deployment

Simply put, this is to support application plug-in development and multi-team parallel development.

The main problem to be solved by plug-in development is the separation of large and bloated applications. Large front-end applications are faced with a large number of legacy codes, coupled codes of different businesses, and slow loading speed and low running efficiency when online.

In the end, two solutions fall into place: route lazy loading and its variants and front-end microservitization

Front-end microservitization: Route lazy loading and its variants

Route lazy loading means that the application is divided into different codes based on different routes and components are loaded only when routes are accessed. This can be done in frameworks such as Angular and Vue via routing + Webpack packaging. And, inevitably, there are questions:

It was difficult to develop in parallel with multiple teams, and routing split meant we were still working in a single source code base. You can also try breaking them down into different projects and compiling them together.

Every release needs to be recompiled, and yes, when we just update the code for a submodule, we need to recompile the entire application and redistribute the application. You can’t build it and release it independently.

Unified Vendor versions, unified third-party dependencies are a good thing. Here’s the thing: every time we add a new dependency, we probably need to have a meeting to discuss it.

However, the biggest problem with standard Route Lazyload is that it is difficult to develop multiple teams in parallel, and I say “difficult” because there are ways around this. In daily development, a small team will always work in one codebase, while a large team will work in a different codebase.

So, we tried something on top of the standard routing lazy load.

For a team of 20 or 30 people, they may be in different departments in the business, and there may be some inconsistent technical specifications, such as the question of four Spaces, two Spaces, or using tabs. Especially if it’s a different company and team, they may have to give up on testing, code static inspection, code style uniformity, and so on.

Microservitization solution: sub-application pattern

In addition to routing lazy loading, we can also use the sub-application pattern, where each application is independent of each other. We have a pedestal project that loads the standalone Angular app when the user clicks on the route. If the routes belong to the same application, they do not need to be loaded repeatedly. Also, you can rely on the browser cache to do this.

In addition to routing lazy loading, another alternative is mooA-like application embedding. Here is an example of HTML generated based on Mooa framework + Angular development:

< app - root _nghost - c0 = "" ng - version =" 4.2.0 ">... <app-home _nghost-c2=""> < app-app1_nghost-c0 ="" ng-version="5.2.8" style="display: none; ><nav _ngcontent-c0="" class="navbar"></app-app1> <iframe frameborder="" width="100%" height="100%" src="http://localhost:4200/app/help/homeassets/iframe.html" id="help_206547"></iframe> </app-home> </app-root>Copy the code

Mooa offers two modes. One is based on a single-SPA experiment that loads and renders two Angular apps on the same page. One is to provide a separate application container based on iFrame.

The following problems are solved:

  • The home page loads faster because you only need to load the features that the home page needs, rather than all the dependencies.
  • Multiple teams develop in parallel, with each team working independently on its own project.
  • Modular updates independently, now we only need to update our application individually, not the entire application.

However, it still contains the following problems:

  • Reloading dependencies, that is, modules that we used in application A, will be reused in application B. Some of this can be done automatically through the browser’s cache.
  • It takes time to open the application for the first time, but preloading is part of the solution.
  • Running in non-IFrame mode can lead to unexpected third-party dependency conflicts.

So after summarizing a series of discussions, we formed a series of contrasting schemes:

Scheme comparison

In this process, we did a lot of scheme design and comparison, so we want to write an article to compare the previous results. Take a look:

Table comparison:

x Standard the Lazyload Build-time integration Post-build integration Application of independent
The development process Multiple teams develop in the same codebase Multiple teams are working in different codebase Multiple teams are working in different codebase Multiple teams are working in different codebase
Build and publish When building, you only need to take this one piece of code to build and deploy Build your application by combining code from different code bases Will compile directly into individual project modules and merge through lazy loading at run time It will compile directly into several different applications that are loaded through the main project at runtime
Applicable scenario Single team, less dependent libraries, single business Multiple teams, less libraries and single business Multiple teams, less libraries and single business Multiple teams, dependent libraries, complex business
presentation Development, construction and operation in one Development separation, build integration, operation integration Development separation, build separation, operation integration Separate development, build, and run

Detailed introduction is as follows:

Standard the LazyLoad

Development process: Multiple teams develop in the same code base and build with only one piece of code to deploy.

Behavior: Develop, build, and run in one

Application scenario: A single team with few libraries and single services

LazyLoad variant 1: Build-time integration

Development process: Multiple teams develop in different codebase, integrate the code from different codebase during construction, and then build the application.

Application scenario: Multiple teams, less dependent libraries, single service

Variants – Build-time integration: Development separation, build-time integration, operational integration

LazyLoad variant 2: Post-build integration

Development process: Multiple teams develop in different code bases, compile into different pieces of code at build time, and merge them together through lazy loading at run time.

Application scenario: Multiple teams, less dependent libraries, single service

Variation-post-build integration: development separation, build separation, operation integration

Front-end microservitization

Development process: Multiple teams develop in different code bases, which compile into several different applications at build time and load through the main project at run time.

Application scenario: Multiple teams, multiple libraries, and complex services

Front-end microservitization: separation of development, build, and operation

Total contrast

The overall comparison is shown in the following table:

x Standard the Lazyload Build-time integration Post-build integration Application of independent
Dependency management Unified management Unified management Unified management Each application is managed independently
Deployment way Unified deployment Unified deployment It can be deployed separately. When updating dependencies, full deployment is required Can be fully independently deployed
The first screen to load Depending on the same file, the loading speed is slow Depending on the same file, the loading speed is slow Depending on the same file, the loading speed is slow Rely on their management, home loading fast
Load applications and modules for the first time Only load modules, fast Only load modules, fast Only load modules, fast Load separately, slightly slower
Upfront construction cost low Design and Build process Design and Build process Design communication mechanism and loading mode
Maintenance costs A code base is hard to manage Multiple code bases are not uniform Component dependencies need to be maintained later Low maintenance cost
Packaging optimization Tree shaking, AoT compilation, delete useless code Tree shaking, AoT compilation, delete useless code The components on which the application depends cannot be identified and useless code cannot be removed Tree shaking, AoT compilation, delete useless code

Front-end microservitization: Use Mooa to develop microfront-end applications

Mooa is a micro-front-end framework for Angular. It is a single-SPa-based micro-front-end solution optimized for IE 10 and IFRAME.

Mooa concept

Unlike single-SPA, Mooa adopts a master-slave architecture, or master-slave design.

A Web page can have two or more Angular applications at the same time: one Angular application exists as the main project, and the rest are subapplication modules.

  • Main project, responsible for loading other applications, and user rights management and other core control functions.
  • Sub-application, responsible for the specific business code of different modules.

In this mode, the main project controls the behavior of the whole system, and the sub-applications make some corresponding responses.

Micro front end main project creation

To create the main project for Mooa, a micro-front-end framework, not much needs to be modified, just use Angular-CLI to generate the corresponding application:

ng new hello-world
Copy the code

Then add mooa dependencies

yarn add mooa
Copy the code

Then create a simple configuration file, apps.json, and place it in assets:

[{
    "name": "help",
    "selector": "app-help",
    "baseScriptUrl": "/assets/help",
    "styles": [
      "styles.bundle.css"
    ],
    "prefix": "help",
    "scripts": [
      "inline.bundle.js",
      "polyfills.bundle.js",
      "main.bundle.js"
    ]
  }
]]
Copy the code

Next, we’ll write the appropriate create application logic in our app.component.ts:

mooa = new Mooa({ mode: 'iframe', debug: false, parentElement: 'app-home', urlPrefix: 'app', switchMode: 'coexist', preload: true, includeZone: true }); constructor(private renderer: Renderer2, http: HttpClient, private router: Router) { http.get<IAppOption[]>('/assets/apps.json') .subscribe( data => { this.createApps(data); }, err => console.log(err) ); } private createApps(data: IAppOption[]) { data.map((config) => { this.mooa.registerApplication(config.name, config, mooaRouter.hashPrefix(config.prefix)); }); const that = this; this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { that.mooa.reRouter(event); }}); return mooa.start(); }Copy the code

Create a route for the application:

{
  path: 'app/:appName/:route',
  component: HomeComponent
}
Copy the code

Next, we can create a Mooa child application.

Mooa sub-application creation

Mooa officially provides a sub-application module, which can be used directly:

git clone https://github.com/phodal/mooa-boilerplate
Copy the code

Then execute:

npm install
Copy the code

After the dependencies are installed, initialization of the project takes place, such as changing the package name. In this case, we’ll call our application help.

Then, we can finish building the child application.

Next, execute yarn Build to build our application.

Copy the files in the dist directory to the SRC /assets/help directory of the main project and then start the main project.

Navigate to a specific child application

In Mooa, there is a routing interface mooaplatform. navigateTo, which is used as follows:

mooaPlatform.navigateTo({
  appName: 'help',
  router: 'home'
});
Copy the code

It will trigger a MOOA_event.routing_NAVIGATE event. When we call the mooa.start() method, we develop the corresponding event to listen for:

window.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE, function(event: CustomEvent) {
  if (event.detail) {
    navigateAppByName(event.detail)
  }
})
Copy the code

It will be responsible for directing the application to the new application.

Well, it’s that simple. The DEMO video is as follows:

See mooa.phodal.com/ for the Demo address

GitHub example: github.com/phodal/mooa

Front-end microservitization: Microservitization of Angular applications using custom Iframe

Angular uses the Component idea to run multiple Angular applications on a page at the same time. Multiple Angular applications can exist under a DOM node in a form similar to the following:

<app-home _nghost-c3="" ng-version="5.2.8"> <app-help _nghost-c0="" ng-version="5.2.2" style="display:block; > < div _ngcontent - c0 = "" > < / div > < / app -help > < app - app1 _nghost - c0 =" "ng - version =" 5.2.3 requires "style =" display: none;" > < nav _ngcontent - c0 = "" class =" navbar "> < / div > < / app - app1 > < app - app2 _nghost - c0 =" "ng - version =" 5.2.2 "style =" display: none;" ><nav _ngcontent-c0="" class="navbar"></div></app-app2> </app-home>Copy the code

This, however, inevitably requires the following additional work:

  • Create a child application project template to unify the Angular version
  • When building, remove the child application’s dependencies
  • Modifying a Third-party Module

Among them, the most troublesome is the third party module conflict. On second thought, in mid-March, I added an IFrame mode to Mooa.

Iframe microservice architecture design

Here, the overall design philosophy is consistent with the previous “How to Deconstruct a single front-end Application — A Microservice split of front-end Applications” :

The main process is as follows:

  • When the main project is running, it will go to the server to get the latest application configuration.
  • After obtaining the configuration, the main project creates applications one by one and binds the application life cycle.
  • When the main project detects a route change, it looks for a corresponding route to match the application.
  • If the matching application is matched, the iframe of the corresponding application is created or displayed, and iframes of other sub-applications are hidden.

The loading style isn’t too different from the previous Component mode:

To control the different iframes, you need to do several things:

  1. Assign ids to different child applications
  2. Hook in the child application to notify the main application that the child application is loaded
  3. Create event listeners in the child application to respond to URL change events in the master application
  4. The need to listen for subroutines to redirect in the main application

Since most of the code could be reused with the previous Mooa, I implemented the functionality in Mooa.

A customized IFrame model for Mooa, a micro-front-end framework

Iframe can create a new, separate hosting environment, which means that our Angular apps can run independently of each other. The only thing we need to do is create a communication mechanism.

It can be used directly without modifying the child application code. At the same time, it is optimized in general IFrame mode. Using the normal iframe pattern means that we need to load a lot of duplicate components, even if it’s tree-shaking optimized, it brings a lot of duplicate content. If there are too many children, the experience may not be as friendly when initializing the application. However, loading all the dependencies on the main application when initializing the application is not a very friendly experience.

So I wondered if I could create a more IFrame friendly mode to handle applications and dependencies. Here is the iframe code for the resulting page:

<app-home _nghost-c2="" ng-version="5.2.8"> <iframe frameborder="" width="100%" height="100%" src="http://localhost:4200/assets/iframe.html" id="help_206547" style="display:block;" ></iframe> <iframe frameborder="" width="100%" height="100%" src="http://localhost:4200/assets/iframe.html" id="app_235458 style="display:none;" ></iframe> </app-home>Copy the code

Yes, the SRC of both iframes is the same, but it does represent two different iframe applications. There is no content in the iframe.html:

<! doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>App1</title> <base href="/"> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> </body> </html>Copy the code

(PS: Detailed code can be found at github.com/phodal/mooa)

An Iframe exists only for the purpose of creating an Iframe. There is little difference between an Angular application and an Iframe. But, for us, there is a big difference. We can control the IFrame and what we load in our own way. Such as:

  • Common CSS styles in Style Guide. For example, when integrating with iframe, remove what is not needed
  • Remove JavaScript that does not need to be reloaded. For example, zone.min.js, polyfill

Note: Some common UI components still need to be loaded repeatedly. This is the problem with iframe mode.

The Mooa Iframe communication mechanism is a micro-front-end framework

In order to communicate between the main project and sub-project, we need to implement the following event strategies:

Publish the main application event

Since, we use Mooa to control iframe loading. This means that we can get the iframe via document.getelementById and then publish the event via iframeel.contentWindow as follows:

let iframeEl: any = document.getElementById(iframeId)
if (iframeEl && iframeEl.contentWindow) {
  iframeEl.contentWindow.mooa.option = window.mooa.option
  iframeEl.contentWindow.dispatchEvent(
    new CustomEvent(MOOA_EVENT.ROUTING_CHANGE, { detail: eventArgs })
  )
}
Copy the code

In this way, the child application can receive the corresponding event response directly without modifying the code.

Listen for child application events

Because we also want to be able to handle subroutine events directly in the main project without modifying the original code. Therefore, we use the same method to listen for events in the main application in the child application:

iframeEl.contentWindow.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE, function(event: CustomEvent) {
  if (event.detail) {
    navigateAppByName(event.detail)
  }
})
Copy the code

The sample

Again, we will use the Mooa framework as an example. We just need to configure the IFrame mode when creating the Mooa instance:

this.mooa = new Mooa({ mode: 'iframe', debug: false, parentElement: 'app-home', urlPrefix: 'app', switchMode: 'coexist', preload: true, includeZone: true }); . that.mooa.registerApplicationByLink('help', '/assets/help', mooaRouter.matchRoute('help')); that.mooa.registerApplicationByLink('app1', '/assets/app1', mooaRouter.matchRoute('app1')); this.mooa.start(); . this.router.events.subscribe((event: any) => { if (event instanceof NavigationEnd) { that.mooa.reRouter(event); }});Copy the code

Subroutines are used directly: github.com/phodal/mooa… That’s it.

resources

Related information:

  • MDN Shadow DOM (Shadow DOM)
  • Web Components
  • Shadow DOM V1: Standalone network component