Gold nuggets: juejin.cn/post/684490…

About 0.

Recently for a period of time, due to the large industrial application involved in future work, and after considering industrial application will need to show in the same system, hope to have a breakthrough to solve this problem, namely in a total project show different industrial applications, each industrial application is a separate project, due to the industrial applications may be due to different development teams, The application development technology stack had better not be limited, because a single front-end framework may no longer meet the requirements. Therefore, I learned about the micro front-end and tried to study it, hoping to solve the problem of industrial application summary display with the help of the micro front-end.

Of course, there are not many ways to realize the micro front end, including IFrame, single-SPA, and so on. Single-spa is mainly used in this paper. If you’ve used iframe, you know that iframe and Single-SPA are not at all difficult. If iframe is easy, then single-SPA is hell. Iframe is the fastest way to use a micro front end if your project is in a rush. I believe that there are also many students who want to do a micro front end, but suddenly on the way to study single-SPA, there is also a lack of information, because although there are many micro front end articles on the Internet, most of them are only theoretical introduction and part of the code display, which can not help us to really implement the project.

The main difficulties in the use of single-SPA are as follows: 1. For the overall project, how to perfectly accommodate each sub-project, make it independent of technology stack, and make users feel that it is a whole project. 2. For each sub-project, how to reduce code intrusion, and make it have the ability of independent development, independent operation and independent deployment.

1. What is a micro front end

A microfrontend is A microservice that exists within A browser, that is, A microfrontend is A microservice that exists within A browser. The micro front end is also part of the UI and is usually made up of dozens of components implemented by different frameworks such as React, Vue, and Angular. Each micro front end project can be managed by a different team, and each team can choose its own framework. Although other frameworks may be added during migration or experimentation, it is most practical to use one framework for all microfronts. Each microfront-end project can be placed in a different Git repository, with its own package.json and build tool configuration. So every tiny front end of the construction of the project has an independent packaging process and independent of the deployment, also means that we can quickly complete our micro front-end project set up packaging every time instead of a big MAC (Monolith) project, late have new demand, only need to modify the corresponding micro front end of the project.

2. Why a micro front end

Now with the continuous development of the front-end, enterprise project volume is more and more big, more and more pages, the project becomes swollen and maintenance is also very difficult, sometimes we just change the project simple style, need the whole project set up to repackage, created a lot of trouble to the developer, is also a waste of time. In order to integrate the old project into the new project, the reconstruction needs to be carried out constantly, resulting in high human cost.

In the front-end development work, faced with the following difficulties:

  1. Enterprise engineering projects are getting bigger and bigger, and the construction and deployment speed of projects is slow.
  2. Due to the large number of engineering team members, it is difficult to unify the technology stack, the communication cost between different teams is high, and the development code is prone to conflict, which will affect the whole project.
  3. Old project reconstruction, code changes are too big, consuming serious time.

Comparative analysis:

  1. With independent operation, independent deployment function, construction and deployment speed.
  2. Technology stack independent, with independent development function, avoid development conflict, reduce collaboration costs.
  3. Legacy projects can be part of a microfront-end project to avoid refactoring.

3. Micro front-end implementation

There are two ways to implement the micro front end:

1. Iframe embedding (difficulty: ★)

2. Single-spa Combined Single-page application (difficulties: ★★★★★ ★)

4. iframe

The iframe embedding method is easy to implement and will not be described again.

Why Not Iframe

Why not use iframe? This is the first question that almost any microfront-end solution will be challenged. However, most micro front-end solutions have abandoned iframe solutions for a reason, and it is not for the sake of “show-off” or the pursuit of “maverick”.

The most important feature of iframe is that it provides a browser native hard isolation scheme, no matter the style isolation, JS isolation and other problems can be solved perfectly. However, its biggest problem is that its isolation can not be broken, leading to the application context can not be shared, resulting in the development experience, product experience problems

  1. The URL is not synchronized. The browser refresh iframe URL status is lost, and the back forward button is unavailable.
  2. The UI is not synchronized and the DOM structure is not shared. Imagine an iframe with a mask layer in the bottom right corner of the screen, and we want it to be centered in the browser and automatically centered when the browser resize.
  3. The global context is completely isolated and memory variables are not shared. Iframe internal and external system communication, data synchronization and other requirements, the cookie of the main application should be transparently transmitted to the sub-applications with different root domain names to achieve the effect of free registration
  4. Slow. Each child application entry is a process of browser context reconstruction and resource reloading

Some problems better solve the problem of (1), some problems we can turn a blind eye (question 4), but some problems we are difficult to solve the problem of (3) even unable to solve the problem of (2), and these can’t solve the problem it will bring very serious to product experience problems, finally lead us to abandon the iframe solution.

Reference article:
Why Not Iframe

5. single-spa

Single-spa implementation principle:

Firstly, the micro-front-end route is registered, and single-SPA is used as the micro-front-end loader and the single entrance of the project to accept the access of all page urls. According to the matching relationship between the page URL and the micro-front-end, the corresponding micro-front-end module is selected to load, and then the micro-front-end module responds to the URL. That is, the micro-front-end module route to find the corresponding components, rendering page content.

Reference article:
Single – spa website

6. Micro front-end implementation process

❤️❤️❤️ Project source code address ❤️❤️❤️ ❤️

6.1 Base Project (Renovation of the parent project)

Base project creation:

yarn create react-app portal
yarn add antd
// create config-overrides. Js support antD loading on demand
// fixBabelImports('import', {
// libraryName: 'antd',
// libraryDirectory: 'es',
// style: true,
/ /}),
Copy the code

The micro front-end loading principle adopted in this paper is as follows:

First, create a DOM node in the parent project, input the node to be mounted during the project registration process, and then complete the child project to run in the parent project.

6.2.1 Creating routes and subprojects to mount DOM nodes

The code is as follows:

  <div className="App" >
      <Layout>
        <Sider trigger={null} collapsible collapsed={collapsed}>
          <div className="logo" />
          <ul >
            <li key="react" >
              <Link to="/react">React</Link>
            </li>
            <li key="vue" >
              <Link to="/vue">Vue</Link>
            </li>
            <li key="angular" >
              <Link to="/angular">Angular</Link>
            </li>
          </ul>
        </Sider>
        <Layout className="site-layout">
          <Header className="site-layout-background" style={{ padding: 0}} >{React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, { className: 'trigger', onClick: () => { setCollapse(! collapsed) }, })}</Header>
          <Content
            className="site-layout-background"
            style={{
              margin: '24px 16px',
              padding: 24.minHeight: 280,}} <blockquote style=' padding: 10px 10px 10px 1rem; The font - size: 0.9 em. margin: 1em 0px; color: rgb(0, 0, 0); border-left: 5px solid #9370DB; background: rgb(239, 235, 233); '></blockquote>
            <div id="vue" />
            <div id="react-app" />
            <app-root></app-root>
          </Content>
        </Layout>
      </Layout>
    </div>
Copy the code

6.2.2 Micro-front-end application registration

SRC file to create singlespa.js file.

Import the file into the project entry file index.js.


import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom'
import "./singleSpa.js"; // Introduce a micro front-end configuration file;

Copy the code
// Project directory structure├─ public ├─ SRC │ ├─ ├─ ├.js │ ├── ├.js │ ├── ├.js │ ├── Package │ ├.js │ ├── The README. Md ├ ─ ─ yarn. The lockCopy the code

Singlespa.js

import * as singleSpa from 'single-spa';

// Register the application mode.
/ / Single - Spa + Vue Cli micro front landing guide (project isolation remote loading, automatic) (https://juejin.cn/post/6844904025565954055#heading-2)
/** * runScript a promise synchronization method. Instead of creating a script tag and loading the service *@param  {string}         Url Request file address */
const runScript = async (url) => {
    // The same goes for loading the CSS
    return new Promise((resolve, reject) = > {
        const script = document.createElement('script');
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        const firstScript = document.getElementsByTagName('script') [0];
        firstScript.parentNode.insertBefore(script, firstScript);
    });
};

// Register the micro front-end service
/* Register the function used; Return a module object (singleSpa) from the JS export (subproject) to load; If this function does not need to be imported online, it only needs to be imported locally: () => import(' XXX /main.js') */
singleSpa.registerApplication(
    'vue'.async() = > {await runScript('http://127.0.0.1:8080/js/chunk-vendors.js');
            await runScript('http://127.0.0.1:8080/js/app.js');
            return window.singleVue;
        },
        // Configure the micro front-end module prefix
        // The pure function checks whether it is active based on its arguments
        (location) = > location.pathname.startsWith('/vue')); singleSpa.start();// Start registration, don't forget!
Copy the code

RegisterApplication Parameter meanings:

1. AppName: string Application name

2, applicationOrLoadingFn: () = > < XSL: | Promise > return Promise load Function or the application of parsed.

// Apply as a parameter, which consists of an object with a lifecycle.
const application = {
  bootstrap: () = > Promise.resolve(), //bootstrap function
  mount: () = > Promise.resolve(), //mount function
  unmount: () = > Promise.resolve(), //unmount function
}
registerApplication('applicatonName', application, activityFunction)
Copy the code

The loading function as an argument must return either a Promise or an asynchronous function that will be called with no arguments when the application is first loaded, and returning a Promise must be worked out with the application. The most common way to import load functions is :() => import(‘/path/to/application.js’)

3, activityFn: (location) => Boolean

The activity function, which must be a pure function, provides window.location as the first argument and returns a verdict if the application is active. In common use, the first parameter of the activity function is used to determine whether the child application is active.

6.2 Micro-front-end Project (sub-project transformation)

6.2.1 Vue sub-project transformation

Environment Preparation:

npm install -g @vue/cli	// Global install vue-cli
vue create vue-project	// Create a subproject

// Project directory structure├ ─ ─ public ├ ─ ─ the SRC │ ├ ─ ─ the main, js │ ├ ─ ─ assets │ ├ ─ ─ components │ └ ─ ─ App. Vue ├ ─ ─ vue. Config. Js ├ ─ ─ package. The json ├ ─ ─ The README. Md └ ─ ─ yarn. The lockCopy the code

Modify the main.js file to register

import Vue from 'vue'
import App from './App.vue'
import singleSpaVue from "single-spa-vue";

Vue.config.productionTip = false
// el is the DOM node of the child project to be mounted to the parent project!!
const vueOptions = {
  el: "#vue".render: h= > h(App)
};

// The singleSpaNavigate method is mounted under the window after the main application is registered successfully
// To run independently, avoid empty subproject pages,
// Determine if HTML is not rendered independently in a microfront-end environment
if (!window.singleSpaNavigate) {
  new Vue({
    render: h= > h(App),
  }).$mount('#app')}const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: vueOptions,
});

export const bootstrap = vueLifecycles.bootstrap; / / start
export const mount = vueLifecycles.mount; / / mount
export const unmount = vueLifecycles.unmount; / / unloading

export default vueLifecycles;
Copy the code

The root directory creates vue.config.js to modify the Webpack configuration

module.exports = {
    /* Key: Set publicPath to avoid file request failure when parent project loads child project. * /
    publicPath: "//localhost:8080/".configureWebpack: {
        devtool: 'none'.// Do not package sourcemap
        output: {
            library: "singleVue".// Export the name
            libraryTarget: "window".// Mount the target, which can be viewed in the browser by printing window.singlevue}},devServer: {
        contentBase: '/'.compress: true,}};Copy the code

The sub-project transformation can be divided into two steps as a whole:

1. Modified the entry files of the sub-project, registered the micro-front-end, and determined the mounted nodes of the sub-project;

2. Modified the export file of subproject Webpack, and created singleVue method under Window after packaging.

6.2.1 React Sub-project transformation

Environment Preparation:

yarn create react-app react  // Create a subproject
yarn add single-spa-react / / installation of single - spa - react

// Project directory structure├ ─ ─ public ├ ─ ─ the SRC │ ├ ─ ─ App. Js │ ├ ─ ─ index. The js │ └ ─ ─ serviceWorker. Js ├ ─ ─ the config │ ├ ─ ─ jest │ ├ ─ ─ webpack. Config. Js │ └ ─ ─ webpackDevServer. Config. Js ├ ─ ─ scripts │ ├ ─ ─ build. Js │ ├ ─ ─ start. Js │ └ ─ ─ test. The js ├ ─ ─ package. The json ├ ─ ─ the README, md └ ─ ─ yarn. The lockCopy the code

Modify the index.js file to register

import Vue from 'vue'
import App from './App.vue'
import singleSpaVue from "single-spa-vue";

Vue.config.productionTip = false
// el is the DOM node of the child project to be mounted to the parent project!!
const vueOptions = {
  el: "#vue".render: h= > h(App)
};

// The singleSpaNavigate method is mounted under the window after the main application is registered successfully
// To run independently, avoid empty subproject pages,
// Determine if HTML is not rendered independently in a microfront-end environment
if (!window.singleSpaNavigate) {
  new Vue({
    render: h= > h(App),
  }).$mount('#app')}const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: vueOptions,
});

export const bootstrap = vueLifecycles.bootstrap; / / start
export const mount = vueLifecycles.mount; / / mount
export const unmount = vueLifecycles.unmount; / / unloading

export default vueLifecycles;
Copy the code

Change the project startup port number:

// start. Js file in the scripts folder
- const DEFAULT_PORT = parseInt(process.env.PORT, 10) | |3000
+ const DEFAULT_PORT = parseInt(process.env.PORT, 10) | |5000;
Copy the code

Modify the webpack configuration, modify the webpack.config.js file in the config folder

- publicPath:paths.publicUrlOrPath
+ publicPath: 'http://localhost:5000/',
+ library: "singleReact".// Export the name
+ libraryTarget: "window".// Mount the target
Copy the code

Parent project first load child project static file LOGO picture error solution:

  1. Load failed, by checking the found that the parent project load component image address is: http://localhost:3000/static/media/logo.5d5d9eef.svg.
  2. In this case, the address of the parent project is http://localhost:3000/, and the address of the subproject is http://localhost:5000/
  3. It’s not hard to find, logo pictures that request address shall be the sub-project of http://localhost:5000/static/media/logo.5d5d9eef.svg
  4. PublicPath + Configuration path of a resource loader or plug-in. The default publicPath is the root directory of the website, and the current root directory of the website is http://localhost:3000/ when the parent project loads the child project
  5. You can solve this problem by setting output.publicPath to the subproject directory http://localhost:5000/.

6.2.1 Angular subproject Transformation

Environment Preparation:

npm install -g @angular/cli // Install angular/cli globally
TypeError: Cannot read property 'flags' of undefined

npm install @angular/cli@9.0. 0 -g // Specify the version to install
ng new angular-project // Create the project

// Project directory structure├─ E2E ├─ SRC │ ├─ +`main.single-spa.ts`│ ├ ─ ─ +`single-spa`│ │ ├ ─ ─ +`single-spa-props.ts`│ │ └ ─ ─ +`asset-url.ts`│ ├─ app │ ├─ +`empty-route`│ ├ ── garbage`empty-route.component.ts`│ │ ├ ─ ─ empty-route.com ponent. Ts │ │ ├ ─ ─ app.com ponent. Ts │ │ └ ─ ─`app.module.ts` //use src/app/empty-route/ EmptyRouteComponent├── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──`extra-webpack.config.js`├─ Package. json ├─ Angular. json ├─ ├.txtCopy the code

TypeError: Cannot read property ‘flags’ of undefined](https://stackoverflow.com/questions/49544854/typeerror-cannot-read-property-flags-of-undefined)

  • Install single-spa-angular.
  • Generate single-spa-props.ts in src/single-spa/
  • Generate asset-url.ts in src/single-spa/
  • Generate an EmptyRouteComponent in src/app/empty-route/, to be used in app-routing.module.ts.

Registration application

// cannery route/empty-route.ts

import { Component } from '@angular/core';

@Component({
    selector: 'app2-empty-route'.template: ' ',})export class EmptyRouteComponent {}Copy the code
// SRC /app/app.module.ts

+ import { EmptyRouteComponent } from './empty-route/empty-route.component';
@NgModule({
  declarations: [
    AppComponent,
 +  EmptyRouteComponent
  ],
Copy the code
// SRC /single-spa/asset-url.ts

export function assetUrl(url: string) :string {
    // @ts-ignore
    const publicPath = __webpack_public_path__;
    const publicPathSuffix = publicPath.endsWith('/')?' ' : '/';
    const urlPrefix = url.startsWith('/')?' ' : '/'
  
    return `${publicPath}${publicPathSuffix}assets${urlPrefix}${url}`;
  }
Copy the code
// SRC /single-spa/single-spa-props
import { ReplaySubject } from 'rxjs';
import { AppProps } from 'single-spa';

export const singleSpaPropsSubject = new ReplaySubject<SingleSpaProps>(1)
export type SingleSpaProps = AppProps & {
}
Copy the code
// SRC/main.singlespa

import { enableProdMode, NgZone } from '@angular/core';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaAngular } from 'single-spa-angular';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';

if (environment.production) {
    enableProdMode();
}
// if (! window.singleSpaNavigate) {
platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err= > console.error(err));
// }

const lifecycles = singleSpaAngular({
    bootstrapFunction: singleSpaProps= > {
        singleSpaPropsSubject.next(singleSpaProps);
        return platformBrowserDynamic().bootstrapModule(AppModule);
    },
    template: '<app2-root />',
    Router,
    NgZone: NgZone,
});

export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
Copy the code

Modify angular.json to prepare for modifying the export file

npm i -D @angular-builders/custom-webpack // Used to modify the WebPack configuration
npm i -D @angular-builders/dev-server
 "build": {-"builder": "@angular-devkit/build-angular:browser",
        + "builder": "@angular-builders/custom-webpack:browser"."options": {+"customWebpackConfig": {+"path": "./extra-webpack.config.js" // Read the file and modify the Webpack configuration+ +}"deployUrl": "http://localhost:4000/"./ / modify publicPath
            "outputPath": "dist/Delete"."index": "src/index.html", -- -- -- -- -- -- -- --"serve": {-"builder": "@angular-devkit/build-angular:dev-server",
	  + "builder": "@angular-builders/custom-webpack:dev-server"
		"options": {
		"browserTarget": "Delete:build"}, -- -- -- -- -- -- -- --Copy the code

Set export file:

Create extra-webpack.config.js and configure it

module.exports = {
  output: {
    library: "singleAngular".// Export the name
    libraryTarget: "window".// Mount the target}},Copy the code

Reference article:

single-spa-angular

Single-spa-angular sample code address

Presents/cli version Single – spa – presents version
The official sample 8.1.0 3.0.1
In this paper, the sample 9.0.0 4

7. Deploy the Nginx configuration online

During the parent project loading process, all requests:

404 Not Found: nginx 404 Not Found:

The deployment access address format is XX.xx :0000/vue/#/app1

Vue: micro front-end application name;

App1: subproject route

Nginx error analysis:

When making a request for the first time, since it is written like browerRouter, the request will look for the /vue folder in the nginx root file and check if there is an index.html file to return.

Xx :0000/ xx => nginx file xx folder => Folder does not exist 404 is returned

Since /vue is just for registering applications, there is no need to actually look under /vue in nginx to find index.html, since this path is not successful, we still want to look at the original root address.

If nGIx encounters /XXX, the address will be used as a proxy, look for the corresponding folder, if there is no folder, error 404 will be reported, but actually I want to enter my project, I make my own judgment

Solution:

In the case of access with /vue, the judgment is made first, and then nginx is used to redirect to the original root directory.

Matters needing attention:

This redirects, but we don’t want the URL to change, so we need to rewrite “/ XXX “/ ABC last; Rewriting the second argument with HTTP or HTTPS or using permanent would change the URL address bar. (302,301 modify the url of the address bar.)

location /vue/ {	
			root   /home/nginx/static/html/refining/;
	      		rewrite ^/vue(.*) /;
			index index.html index.htm;
 		}
// Note that the redirection leaves the URL unchanged,

Copy the code

Originally thought, this can declare success!! But the reality is harsh. We refresh the browser to make the parent project reload the child project, and find that the parent project still fails to load the child project file, or returns HTML resulting in a type error.

By viewing the request can be found that the actual request is: xx, xx: / vue / # 0000 / static/CSS/main d6XXXXXXXXXX

Let’s continue redirecting static resources:

location ~.*(gif|jpg|jpeg|bmp|png|ico|txt|js|css)$ {
	root   /home/nginx/static/html/refining/;
	rewrite ^/vue(.*) /$1 break;//$1 is the first parameter matched, which is the request address without vue
	index  index.html index.htm;
}
Copy the code

Other reference configurations:

server {
    listen 8080; server_name localhost; root /usr/share/nginx/html; index index.html; ssi on; # redirect/to /browse rewrite ^/$ http://localhost:8080/browse redirect; HTML location/Browse {set $PAGE'browse';
    }
    location /order {
      set $PAGE 'order';
    }
    location /profile {
      set $PAGE 'profile'} # all other paths are rendered /index.html error_page404 /index.html;
}
Copy the code

8. Looking forward to

8.1 Transformation and Optimization

8.1.1 JS files are automatically loaded

Stats-webpack-plugin generates manifest.json for automatic loading.

8.1.2 CSS sandbox

CSS to use postcss – loader, postcss – loader use postcss, we add postcss processing plug-in, for each CSS selectors add called `. The namespace – kaoqin ` selector, the root of the final package of CSS, as shown below:

8.1.3 JS sandbox

8.1.4 Loading AN HTML file To load a subapplication

The access address only needs to be configured once. The manifest dynamic loading is omitted, because the HTML is a complete MANIFEST.

Refer to the article

  • Single-spa + Vue Cli Microfront-end Landing Guide (Project isolation Remote loading, automatic introduction)
  • Microfront-end practice
  • Microfrontend – Portal project
  • Full stack Growth Engineer (How to land the micro front end? Detailed principle)
  • Command line server (HTTP-server) and cross domain
  • Probably the most complete microfront-end solution you’ve ever seen
  • Front end sharing meeting — Preliminary discussion on micro front end Transformation
  • Take your handwritten micro front frame
  • In-depth analysis of Vue source code – complete mount process and template compilation
  • How to Use Ant Design Pro as a Micro front end
  • Microfront-end _single-SPA new idea

Reference code

  • heaven and earth

  • Migrating -to-single- SPa-React-starter Migrating -to-single- Spa-React-Starter migrating-to- Single – Spa-React-Starter

  • microfront-end-single-spa