Practice of micro-front-end umi + qiankun from construction to deployment

Practice of micro-front-end umi + qiankun from construction to deployment

In the near future, qiankun needs to be used in the project to realize the micro service of the micro front end, share and record the construction process and the problems encountered

Project framework

The whole is the main application built by umi, both sub-applications are built by create-react-app, enter the system, the menu main is the main application page, click the menu sub-react to switch to the first sub application, and the menu sec-sub to switch to the second sub application. application,

The root directory of the project is the main application, and the child folder is sub-react and sec-sub. The initial directory structure after construction is as follows:

Main application configuration

Non-umi build main application

The main application is responsible for the rendering of the navigation and the issuance of the login state, providing a mounted container div for the sub-application, and the main application can also add its own business logic

The main application is not limited to the technology stack. You only need to provide a container DOM, then register the micro application and start it. Follow the main application registration process on the official website
to install qiankun first:

$ yarn add qiankun # or npm i qiankun -S

Registered micro-applications are generally written in the entry file.

Register the micro application and start:

Import {registerMicroApps, Start} from 'qiankun' ; Import {SUB_REACT, SUB_REACT_SECOND} from "@/utils/Proxy" ; //this is a global variable defined registerMicroApps([ { name : "reactApp" , //sub-application name //entry: SUB_REACT,//sub-application entry path is a defined global variable entry : 'http://localhost:3000/' , //sub-application entry path container : "#subapp" , //the id of the sub- app mounting div activeRule : "/sub-react" , props : { //pass the value to the sub-application msg : { data : { mt : "you are one" , }, }, historyMain : ( value ) => { history.push(value); }, }, }, { name : "reactAppSecond" , entry : SUB_REACT_SECOND, container : "#subapp" , //the id of the sub-app mounting div activeRule : "/sec_sub" , props : { msg : { data : { mt : "you are one" , }, }, }, }, ]); //start qiankun start(); Copy code

The layout.jsx subapp will be mounted on the div with the layout page id as subapp

<Content style={{ margin : "3px 3px" }}> < div id = "subapp" style = {{ padding: 24 , background: "# fff ", minHeight: 360 }} > {this.props.children} </div > </Content> Copy code

Umi builds the main application

Because the main application of the project was built by umi, I didn t care about it at the beginning. After the main application registered the sub-application, there was no problem with the container after the menu switch appeared after the sub-application was mounted. The specific manifestation is that the sub-application flashed past and became blank. ,

There is another specific reason: because the main project and sub-projects use the id as app, which causes the sub-project to be directly hung on the app, resulting in the loss of the container that carries the sub-project in the main project. At this time, the sub-project should be changed. id, please refer to the article for details

The following are the main application configuration umi build official website Reference

Enabling method

yarn add @ umijs/plugin-qiankun -D duplicated code

Configure qiankun to open.

.umirc.ts file

defineConfig({ ......, qiankun : { master : { //Register sub-application information apps : [ { name : 'app1' , //unique id entry : '//localhost:7001' , //html entry }, { name : 'app2' , //unique id entry : '//localhost:7002' , //html entry }, ], }, }, }); Copy code

app.js file configuration

The following detailed configuration can be written in the app.ts file as a supplement after registration in the .umirc.ts file

Reason for adding in app.ts: you cannot use props to pass parameters when registering in the .umirc.ts file

import {SUB_REACT, SUB_REACT_SECOND} from "@/utils/proxy" ; //Use export const qiankun = { master : { //Register sub-application information apps : [ { entry : SUB_REACT, //html entry name : "reactApp" , //sub-application name container : "#subapp" , //sub-application mounted div activeRule : "/sub-react" , props : { //sub-application Pass value msg : { data : { mt : "you are one" , }, }, historyMain : ( value:any ) => { history.push(value); }, }, }, { entry : SUB_REACT_SECOND, //html entry name : "reactAppSecond" , container : "#subapp" , //sub-app mounted div activeRule : "/sec_sub" , props : { //sub-app transfer value msg : { data : { mt : "you are one" , }, }, historyMain : ( value:any ) => { history.push(value); }, }, }, ], }, } Copy code

router.js file configuration

{ title : "sub-react" , path : "/sub-react" , component : "../layout/index.js" , routes : [ { title : "sub-react" , path : "/sub-react" , microApp : "reactApp" , microAppProps : { autoSetLoading : true , //open sub-app loading //className: "reactAppSecond",//sub-app package element Class name //wrapperClassName: "myWrapper", }, }, ], }, { title : "sec_sub" , path : "/sec_sub" , component : "../layout/index.js" , routes : [ { title : "sec_sub" , path : "/sec_sub" , microApp : "reactAppSecond" , microAppProps : { autoSetLoading : true , //open sub-application loading //className: "reactAppSecond", //wrapperClassName: "myWrapper", }, }, ], }, Copy code

Sub-application configuration

Refer to the sub-application configuration of the official website

Add the PORT variable to the new .env file, and the port number is consistent with the configuration of the parent application.

SKIP_PREFLIGHT_CHECK = true PORT = 3000 PUBLIC_URL=/ Copy code

Add public-path.js in the src directory:

if ( window .__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window .__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } Copy code

Set the base of history mode routing:

<= {BrowserRouter the basename window ? POWERED_BY_QIANKUN__ .__ '/App-REACT' : '/' }> copy the code

The entry file index.js is modified. In order to avoid conflicts between root id #root and other DOMs, the search scope needs to be limited.

import React from "react" ; import ReactDOM from "react-dom" ; import "./index.css" ; import App from "./App" ; import Main from "./Main" ; import Home from "./Home " ; import reportWebVitals from "./reportWebVitals" ; import {BrowserRouter, Switch, Route, Redirect} from "react-router-dom" ; import "./public-path.js"; function render ( props ) { const {container} = props; console .log(props); ReactDOM.render( < BrowserRouter basename = { window.__POWERED_BY_QIANKUN__ ? "/sub-react " : "/child/sub-react/" } > < Switch > < Route path = "/" exact render = {(propsAPP) => < App { ...propsAPP } propsMainAPP = {props}/> } > </Route > < Route path = "/main" exact render = {(propsAPP) => < Main { ...propsAPP } propsMainAPP = {props}/> } > </Route > < Route path = "/home" exact component = {Home} > </Route > {/* Sub-applications must not be written, otherwise there will be a routing jump bug */} {/* < Redirect from = "*" to = "/" > </Redirect > */} </Switch > </BrowserRouter > , container ? container.querySelector( "#root-sub-app" ) : document .querySelector( "#root-sub-app" ) ); } //Indicates that the sub-application is in a non-qiankun environment, that is, when it is running independently if (! window .__POWERED_BY_QIANKUN__) { console .log( "When running independently" ); render({}); } export async function bootstrap () { console .log( "[react16] react app bootstraped" ); } export async function mount ( props ) { //props.onGlobalStateChange((state, prev) => { ////state: state after change; prev state before change //console.log(state, prev); //}); render(props); } export async function unmount ( props ) { const {container} = props; ReactDOM.unmountComponentAtNode( container ? container.querySelector( "#root-sub-app" ) : document .querySelector( "#root-sub-app" ) ); } reportWebVitals(); Copy code

Modify webpack configuration

Install the plug-in @rescripts/cli, of course, you can also choose other plug-ins, such as react-app-rewired.

npm i -D @rescripts/cli Add .rescriptsrc.js to the root directory:

const {name} = require ( './package' ); module .exports = { webpack : ( config ) => { config.output.library = ` ${name} -[name]` ; config.output.libraryTarget = 'umd' ; config.output.jsonpFunction = `webpackJsonp_ ${name} ` ; config.output.globalObject = 'window' ; return config; }, devServer : ( _ ) => { const config = _; config.headers = { 'Access-Control-Allow-Origin' : '*' , }; config.historyApiFallback = true ; config.hot = false ; config.watchContentBase = false ; config.liveReload = false ; return config; }, }; Copy code

Modify package.json:

- "start" : "react-scripts start" , + "start" : "rescripts start" , - "build" : "react-scripts build" , + "build" : "rescripts build" , - "test" : "react-scripts test" , + "test" : "rescripts test" , - "the EJECT" : "REACT-scripts the EJECT" Copy the code

Loading of sub-applications when umi is the main application

Refer to the router.js file configuration of the main umi application above, and turn on the loading sub-application

After the sub-application is loaded, the props that can be passed through the main application

setLoading(false)
To manually close loading

<button onClick={ () => { propsMainAPP.setLoading( false ); }} > Close sub-application loading </button> Copy code

Father-son app communication

initGlobalState(state)

usage

Define the global state and return the communication method. It is recommended to use it in the main application. The micro-application obtains the communication method through props.

return

MicroAppStateActions

onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, monitor the global state in the current application and trigger the callback if there is a change, fireImmediately = true trigger the callback immediately

setGlobalState: (state: Record<string, any>) => boolean, set the global state according to the first-level attribute, only the existing first-level attributes can be modified in the micro application

offGlobalStateChange: () => boolean, remove the state monitor of the current application, it will be called by default when the micro application umount

Example

Main application:

Import {initGlobalState,} MicroAppStateActions from 'qiankun' ; //Initialize state const actions: MicroAppStateActions = initGlobalState(state); actions.onGlobalStateChange( ( state, prev ) => { //state: state after change; prev state before change console .log(state, prev); }); actions.setGlobalState(state); actions.offGlobalStateChange(); Copy code

Micro application:

//Obtain the communication method from the life cycle mount, and use the same method as the master export function mount ( props ) { props.onGlobalStateChange( ( state, prev ) => { //state: state after change; prev state before change console .log(state, prev); }); props.setGlobalState(state); } Copy code

Pass through props during registration and realize mutual jumps between applications

{ name : "reactApp" , //Sub-application name //entry: SUB_REACT,//Sub-application entry path This is the defined global variable entry : 'http://localhost:3000/' , //Sub-application entry path container : "#subapp" , //the id of the sub- application mounted div activeRule : "/sub-react" , props : { //pass the value to the sub-application msg : { data : { mt : "you are one" , }, }, historyMain : ( value ) => { //Pass the route jump method to the sub-application to realize the jump to the main application page in the sub-application history.push(value); }, }, }, Copy code

Sub-application independent warehouse-aggregation management

Simply put all sub-repositories in the aggregate directory and .gitignore them.

My process here is to use the main application as an aggregate library, clone the sub-application in the root directory of the main application, and drop the sub-application file .gitignore

The aggregation library must be able to do one-click install and one-click start of the entire project. We refer to qiankun's examples and use npm-run-all

Aggregate library install npm

i npm-run-all -D
.

The package.json of the aggregate library adds clone, start, install, pull, and build commands:

The order depends on your situation

The --serial of npm-run-all means to execute sequentially one by one, and --parallel means to run in parallel at the same time.

"clone" : "npm-run-all --serial clone:*" , "clone:md" : "md child " , "clone:sub-react" : "cd child && git clone http://xxx/sub -react.git" , "clone:sub-react-second" : "cd child && git clone http://xxx/sub-react-second.git" , "installAll" : "npm-run-all --parallel installAll:*" , "installAll:sub-react" : "cd child && cd sub-react && npm i" , "installAll:sub-react-second" :"cd child && cd sub-react-second && npm i" , "checkout" :"npm-run-all --serial checkout:*" , "checkout:sub-react" : "cd child && cd sub-react && git checkout develop " , "checkout:sub-react-second" : "cd child && cd sub-react-second && git checkout develop" , "start" : "npm-run-all --parallel start:*" , "start:sub-react" : "cd child && cd sub-react && npm start" , "start:sub-react-second" : "cd child && cd sub-react-second && npm start" , "start:main": "umi dev" , "build" : "npm-run-all --serial build:*", "build:main" : "cross-env UMI_ENV=dev umi build" , "build:sub-react" : "cd child && cd sub-react && npm run build" , "build:sub-react-second" : "cd child && cd sub-react-second && npm run build" , "pull" : "npm-run-all --parallel pull:*" , "pull:main" : "git pull" , "pull:sub- react" : "cd child && cd sub-react && git pull" , "pull:sub-react-second" :"cd child && cd sub-react -second && git pull" copying the code

deploy

My method is to package the main application and sub-applications into the dist file of the root directory. When doing the aggregation library, I modified the packaging path of the sub-application and packaged it directly into the dist file packaged by the main application.

Used when setting the packaging command

npm-run-all --serial
Means to execute one by one in order

We put all the sub-applications in the secondary directory of xx.com/child/, and leave the root path/to the main application.

Demo after deployment

Problems encountered

Routing switching problem

The main application is switched to the sub-application, and then the sub-applications switch to each other before the switch cannot be over. The reason is that the sub-application s route redirection causes

{ /* Sub-applications must not be written, otherwise there will be routing jump bugs */ } { /* <Redirect from="*" to="/"></Redirect> */ } Copy code

umi main application configuration problem

The umi main application needs to use umi's built-in qiankun, otherwise the mounting container will be lost after switching the sub-application

Concluding remarks

The above is the realization of qiankun framework, I hope to give you some reference

github source github.com/Mine-7/Qian...

Reference article

The practice of micro front end in Xiaomi CRM system

The practice of micro-front-end qiankun from construction to deployment

Build a micro front-end platform of umi + qiankun + antd