“
This article is a speech I shared at an event
“
Recently, the project happened to solve some team problems with the micro front-end, so I take this opportunity to share with you.
The micro front end, as a solution emerging in the past two years, is not new. Since it is a solution, what problems does the micro front end help us solve? Here I take our project team as an example:
Why do we need a micro front end?
As a whole, our project is a relatively large project, with 17 business lines after the completion of the whole project planning. However, at the beginning of the project, due to various reasons, we did not take the project into full consideration and solved it as a common front-end project. After the end of the first phase of the project and the launch of the first business, we began to develop the second and third business lines, and then we encountered some problems:
Code conflict
The first-phase project will be maintained by the maintenance team after it goes online, and the delivery team will continue the development of subsequent projects. Because all code is jointly maintained as a single large unit in the same REPO, code changes between the two teams often conflict, requiring careful merging. You also need to understand the business of the other side and see if your business will undermine their business.
The deployment of the conflict
Since all the infrastructure, including CI/CD, is common, any team that wants to deploy its own code will inevitably have an impact on the other team, whether feature toggle or chunk base development methods will increase the mental burden of developers.
Technology stack conflict
Since the project is relatively large and the number of teams in the future is uncertain, we cannot limit the technology stack to a limit, otherwise some teams may have to use the technology stack that they are not skilled in, not to mention the possibility that a third party team may join in the future. We do not want to bind the whole project to a certain technology stack.
Based on this background, we found that the micro front end of this set of solutions to solve our problems well. To put it bluntly, in the context of our project, what we wanted most was team autonomy.
We want teams across lines of business to be free to modify their own code without fear of conflict with other teams. They are free to choose the technology stack they are familiar with without too many restrictions. Also, deployment by any team does not affect other teams, which means that parts of the site maintained by other teams are still available if the parts of the site that one team is responsible for fail.
Most importantly, such an architecture allows teams to focus on their own technology and business, reducing unnecessary and ineffective communication and improving development efficiency.
Break up the time
For the split of micro front-end, this is a large workload of technical improvement, and it is different from other technical improvement, it has no template, there is no way to step by step from the Internet to find a thing to copy, must be combined with their own projects to carry out.
On the other hand, we need to agree that in most of our daily development, it is not possible to give developers enough time on projects to make technical improvements, which means that most technical improvements need to be made in conjunction with business development. Then it’s important to find a time for improvement.
So when is that time usually?
Service changes or evolutions
I think most of the students have experienced this situation, in the development of the initial said good demand, due to a variety of reasons need to make a big change. In the face of such a large requirement change, our code usually needs to make corresponding changes, and this change also needs to rewrite some code, and this rewriting process is a good time to split.
During this period we had enough reason to convince the stakeholders to give us time to reorganize the project code to better support the business.
Business stability is no longer a major improvement
At this point, business growth is stabilizing, but the current architecture does hinder development. Then you can improve on this stable architecture. Of course, at this time the business is still developing, we can adopt two strategies:
- One is to split tasks with high priority, and new business development is based on a new architecture
- One is to continue development on the old architecture first, and in the process of splitting, the students in charge of splitting will migrate the business and technology together
Separation principle
When we during break up the front end must be with some sort of purpose, may be tempted to make a gradual upgrade technology stack, it is also possible as we want to improve individual autonomy power of the team, under the different purposes we may adhere to the principle of different, this is also why another micro front split can’t simply copy the homework.
For our project, we pursue the highest autonomy of each team, so we hope that each independent app can minimize communication and dependence on each other, and each app can handle its own business as much as possible.
Under such a general premise, we can split the business in the way of the main module as a supplement. Based on this, we define some principles for the split:
- To ensure business independence, a line of business should be supported by a separate app, so that the business team has full control of the app
- Cross-business pages should not be held separately by each business, but should also be split into a separate app
- Common method libraries and common component libraries are jointly maintained to support their respective businesses
Preparation before splitting
Concept of pre –
single-spa
Single-spa is a micro-front-end framework that does not limit the technology stack used by each app, but renders different apps on the page by controlling the route.
Before we started to split the micro front end, we chose it as our micro front end framework after some research. Actually, at that time, we did not know too much about each framework, such as the well-known Qiankun in China.
At that time, there was a small demand for single-SPA, so I went to Slack to ask according to the documents on the official website. The next morning, I received a reply. Considering the time difference, they gave me a reply as soon as they saw my question. Given the speed of feedback and the lack of optimism about the domestic open source community, we chose Single-SPA directly.
In-broswer module vs build time module
Before I get started, THERE are two concepts THAT I might want to introduce to you to help you understand the architecture that follows. The first is in-Broswer Module, or ES6 Modules, as opposed to the most widely used build Time Module, What is the difference between the two modules? Let’s start with a graph:
module-build-result
In this figure, the two JS files reference each other and the final package result is the Build Time Module. Although you may think of the two files as separate when writing the code, the contents of the two files will be merged into a SINGLE JS file, which will then be referenced by the HTML file.
The in-Broswer module, on the other hand, is a module that the browser requests from the web based on the URL you provide. Each import represents a web request, and the files really become independent modules that depend on each other for web requests.
However, such a module has a disadvantage, that is, it can not be given a name like our daily development can be directly referenced to the corresponding module:
import singleSpa from "single-spa";
Copy the code
Since it needs to locate in the network where this module sends the corresponding request, it needs a full URL:
import singleSpa from "https://cdn.jsdelivr.net/npm/single-spa/esm/single-spa.min.js";
Copy the code
Import-map
This feature makes most programmers dislike it, since most people don’t want to write a long URL to reference a module. To solve this problem, WICG drafted a new browser specification called Import Map:
<script type="importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa/esm/single-spa.min.js"}}</script>
Copy the code
Import map is a special js string of type importMap. Inside this script tag is a JSON object. The key of this JSON object is the name of a module, and its corresponding value is the URL of the module.
Of course, since import-map is a script tag, it makes sense to add SRC attributes to it as an external script:
<script type="importmap" src="https://some.url.to.your.importmap"></script>
Copy the code
In cases where different versions of a package are referenced in your project, you can use the import-map scopes function to limit the scopes referenced in a single file:
<script type="importmap">
{
"imports": {
"lodash": "https://unpkg.com/lodash@3"
},
"scopes": {
"/module-a/": {
"lodash": "https://unpkg.com/lodash@4"}}}</script>
Copy the code
The scopes shown here indicate that if a Module starts with Module-a and there is an import referencing LoDash, that import will refer to version V4, and the other imports will refer to version V3.
With this import-map, we can use the In-Broswer Module in our code as if it were a normal module:
import singleSpa from "single-spa";
Copy the code
Systemjs
And then there’s the traditional front end. Obviously, with this new specification, most browsers don’t support it at the moment, let alone IE, which will never support it. So we need polyfill-SystemJS. If you are interested, you can go to Github to see the documentation. In general, this is a Polyfill specifically for ES-Module.
Let’s see how it works with import-map in a simple demo:
es6-module-syntax
This is a very simple demo, leaving a template in the HTML page and importing an ES-Module. This module is also very simple, and does only one thing: import vue and change the name of the template to whatever we want.
But there is one detail, we must import the VUE with a URL, what happens if we change the URL to the string we normally develop?
import-without-url
This error occurs because we marked the script tag as an ES-Module, so the import keyword is executed by the browser at runtime, but since the string behind it doesn’t tell the browser where Vue is, The browser, of course, can’t find the resource, so it reports an error.
If we want to replace urls with strings we normally develop, we have to rely on import-map, but most browsers don’t support this yet, so we need to introduce systemJS:
how-to-use-systemjs
Since we use SystemJS, we need to modify some code from the original specification in order to follow its rules:
- The first is that we need to start introducing SystemJS
- Then change the type of import-map from
importmap
Instead ofsystemjs-importmap
- Then change the type of es-module from
module
Instead ofsystemjs-module
- Last but not least, in es-Module we no longer use import and export to import and export modules, but use systemJS syntax instead, but don’t worry, Packaging tools such as WebPack and Rollup now support systemJS style packaging, so we can still write code according to the normal specification
Architecture design
At this point we are ready to start the formal split, but we need to design our infrastructure architecture and code organization well in advance before the split can begin.
Infrastructure architecture
Based on single-SPA plus import-Map, our final infrastructure plan would look something like this:
arch-of-micro-fe
- First of all, all of our front-end static resources are deployed separately in AWS S3 service, and the only HTML file is stored in S3 of root container.
- When a user visits our website, traffic will arrive at AWS S3 from the client to the root container. At this time, the user’s browser will first load the HTML page under the root path, and there is an import-map script in the HEAD tag of the HTML page.
- The client then sends another request to S3 where our import-map is located to get the import-map.
- Then we import the root container with SystemJS in the body tag, the whole APP starts up, and then we go to different S3 to get the corresponding static files according to different paths
Deployment strategy
In order to achieve the goal of autonomy of each team, deployment is essential. Our ultimate goal is that different team deployment does not affect the business of other teams. When one team’s online code fails, the other team’s business can still run normally. For a to B project, such a plan makes sense.
delpoy-plan
For this purpose, each team maintains its own CI/CD pipeline for the app. It is important to note that the app address of import-Map’s own team needs to be updated after each deployment as well as for versioning purposes. As long as S3 stores static resources of a certain version, you can quickly deploy and roll back only by updating the corresponding address of import-Map.
pipeline-stage
Local development strategy
There are two strategies for local development. One is to directly start a root container locally and then register the local APP with the root container.
However, such a development approach requires the resolution of dependency issues, such as common method libraries and common component libraries on which the APP depends. There are two ways to solve these dependency problems. One is to package the corresponding dependencies and configure them locally, and reference the packaged dependencies directly during local development. The second approach, which single-SPA also provides, is to run these dependencies directly locally as a shared APP, like a server, and then share them through import-map, referring directly to exported methods and components at development time. Interested readers can follow this link to learn more.
The second way is much simpler, and the development experience is much better. Usually we have a development environment. We can directly open a port on import-map of the online development environment and override the local address of the APP corresponding to import-map by using the tool import-map-overrides. In this case, when you search for this APP online through import-map, you will directly request your local address, and then the code running online is actually your local code, which can be seamlessly developed with various dependencies.
You might think there is a security problem, but the tool can be configured to only open the backdoor locally and in a specific domain, and not anywhere else.
The actual split
problem
Speaking so much, finally began to use, but there is a famous saying in the world called the ideal is full, the reality is very skinny. When you’re excited and ready to plan everything, reality usually doesn’t go your way. Some of our plans, which seemed to be good, were shelved by our dad, and some of them were remade due to poor design and development experience.
It’s too expensive
Cost is always a difficult topic to negotiate with the sponsor father. Our new architecture design adds many things on the basis of the single front-end:
- Multi-repo (of course, this is not expensive, so there is no obstacle, but in the end there is no multi-REPO solution, I will talk about this later
- Many pipeline
- Multiple deployment resources (use separate S3 for each APP
- Additional import Map service
“
You’ve made me $100 for a job I used to do for $10. It’s hard to play with my wallet like this
“
Father Kim said. In that case, we need to negotiate with the funder dad, why these things are necessary, why we need to add so many resources. But the problem with the project was that we didn’t have time to negotiate, so we decided on an “architectural downgrade” :
-
First, build our APP with a pipeline for the time being, and then slice the pipeline when there is enough evidence for the next project
- This decision proved to be completely wrong later. Imagine an Agent with only 1G memory and need to build a front-end project with 5 apps
- Meanwhile, due to the wallet problem of the sponsor’s father, our project only has one agent. Please imagine our daily development HHHHH
-
Temporarily deploy all apps in the same place, separate them with folders, and leave them as they are after a period of time
-
Generate an Import map per build, do not maintain import Map resources separately, and seek to split when the team interacts with each other
Repo split the problem
Our initial vision was to split a separate APP into a separate repo. When we really got started, we thought carefully, is that necessary?
This reminds me of the micro service repO at the back end of the phase 1 project. Since it was a phase 1 project, the calls between different micro services required setup, so most of the time there were more than three Intellij opened locally, plus a mess of other applications, which was a test for the 16GB Macbook.
Going back to the front end, it’s likely that the common code base will be extracted/changed frequently in the course of daily development, which means we’ll need to commit changes and update versions frequently before we can use them.
Furthermore, the size of the two teams does not need to be split so carefully
Is it necessary – cumbersome development process – multiple local ideas
Common code difficult to maintain – different REPO changes – TS type reference issues
Split issues across business pages
The original idea was that a line of business would be a separate APP, and some cross-business pages (i.e. pages that every business would have, such as User Account Management) would also be extracted as a separate APP.
And we did, and we put on the mask of pain:
- “BA said that this page is unified and that if one business changes, that business should also change.” “Smoke!”
- “BA said that the new page would stand alone and that all the new features would work across all businesses.” “Smoke!”
- .
“The logic of this public page is the same as the logic of the other side, we are a copy?” “……”
This strategy resulted in a large number of apps on our project that, upon reflection, seemed unnecessary. Increasing build costs also increased our own development and maintenance costs, which put the cart before the horse, so we made an improvement – cramming all public pages into one APP.
This sounds weird at first, but it really smells good when you actually do it. All changes take effect in all businesses, different businesses have different permissions, and everyone maintains the same code. Wait, didn’t you just say you didn’t want everyone maintaining the same code for fear of conflicts?
This way, we don’t have to maintain multiple copies of code, and there’s no conflict – because the demand side is one way, and if there is a conflict, it’s a conflict of needs, and it needs to be resolved internally by the donor father.
Some people may say, why don’t you try the backend split method, using DDD to guide the split? It just so happens that, at least in our practice, microservices can’t be split in the same way as the front-end.
CSS Conflicts
This is another serious problem that we have. We used Material UI in the project, and the CSS used CSS-in-JS. In addition, because of a set of class name generation rules of our own, the style names of multiple apps conflicted without proper control of scope, resulting in serious mutual influence.
This isn’t a single-SPA problem, but single-SPA does offer some solutions, including isolation of JS lib and CSS, which can be easily searched on the website or github issue, but won’t be explained here. The key lies in the isolation of different JS or CSS solutions.
Write in the last
The above is probably what we have encountered in the process of breaking up the micro front end. The biggest benefit for me from this split is not the technical improvement, but let me understand the two key points of doing the project:
- Everything will not be carried out exactly according to your plan, the bigger the bigger the more so, timely consider emergencies, flexible response, do not stick to the design, based on reality to change the plan is feasible.
- The evolution of architecture should be gradual, steady progress, it is not necessary in an evolution of architecture for the future of all cases, regardless of you can be thoughtful, who is to say the future situation will not change, do not regard the situation to predict the future, live for the present, flexible design, early prevention, what might happen in the future Just prepare Plan B.
reference
[1] single-spa
[2] Microservice unassembly in iterative development
[3] Microfront-end — New experiences for front-end development
[4] systemjs