Mature projects connected to micro front-end – Qiankun

I. Access reasons

  1. Some enterprises want to do secondary development based on the main application. The company would like to retain the source code to access the enterprise secondary development of additional module functions.
  2. Projects are getting bigger and slower to run and package

Two, configure the main application base

1. Install qiankun

yarn add qiankun

2. Register the connected microapplications

Create a new micro/index.js file in SRC

import NProgress from 'nprogress'; import { start, registerMicroApps, addGlobalUncaughtErrorHandler } from 'qiankun'; RegisterMicroApps ([{name: 'MicroAppReact', // development environment entry: '//localhost:3200', // The main application is packaged with code to facilitate nginx configuration to the unified domain name // entry: '/micro/react/', container: '#container', activeRule: '/react', }, ], { beforeLoad(app) { NProgress.start(); console.log('before load', app.name); return Promise.resolve(); }, afterMount(app) { NProgress.done(); console.log('after mount', app.name); return Promise.resolve(); }},); / * * * add global uncaught exception handler * / addGlobalUncaughtErrorHandler ((event) = > {the console. The error (321, event); }); // Export default start;Copy the code

3. Add a microapplication container

Create template.js in SRC /micro

import React from 'react';

function Template() {
  return <div id="container" />;
}

export default Template;

Copy the code

4. Add a micro-application route

Configure the microapp menu in the main app

/ / / SRC/views/router. Js / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the application access -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / {key: 'invoice - record, loader: () => import('.. /micro/template'), path: '/react', },Copy the code

5. Launch qiankun in portal file

// src/index.jsx
import startQiankun from './micro';
 
startQiankun();
Copy the code

Connect to react

As our current technology stack is REACT, only react access is introduced here. But Qiankun itself supports microapplications that tap into multiple technology stacks. Check the official website if necessary

1. Create a new project

Cra scaffolding creates the React project

create-react-app react-micro

2. Overwrite the configuration file

You can expose configuration items such as Webpack directly with NPM Run eject, or you can directly rely on third-party libraries to override configuration

yarn add @rescripts/cli

The root directory is newly created. Rescriptsrc.js

const path = require('path') const { name } = require('./package'); const resolve = dir => path.join(__dirname, dir) const webpackConfig = { webpack: config => { config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; config.output.jsonpFunction = `webpackJsonp_${name}`; config.output.globalObject = 'window'; // config.output.publicPath = '/micro/react/', config.resolve.extensions = ['.js', Alias = {'@components': resolve(' SRC /components'), '@config': resolve('src/config'), '@hooks': resolve('src/hooks'), '@redux': resolve('src/redux'), '@services': resolve('src/services'), // '@static': resolve('src/static'), '@utils': resolve('src/utils'), '@views': resolve('src/views'), } return config; }, devServer: _ => { const config = _; config.headers = { 'Access-Control-Allow-Origin': '*', }; config.historyApiFallback = true; config.hot = false; config.watchContentBase = false; config.liveReload = false; // Config. proxy = {'/ API ': {target: 'https://test.abc.cn', changeOrigin: true, secure: false, }, } return config; }}; Module. exports = [['use-antd', {theme: {theme: {// main color' @primary-color': '#0077FF', // link color' @link-color': '# 0077 ff', '@ the link hover - color' : '# 3392 ff', '@ link - active - color: # 005 FCC, / / successful color' @ success - color: '# 13 ce66', / / warning color '@ warning - color:' # F7BA2A ', / / wrong color '@ error - color:' # FF4949 ', '@ the font-family: / / font "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei"," Microsoft YaHei", Arial, Sans-Serif ", // main size '@font-size-base': '12px', // component/float round '@border-radius-base': '0', '@table-padding-horizontal': '12px', '@tabs-horizontal-margin': '0 20px 0 0', '@tabs-horizontal-padding-lg': '@menu-item-font size': '13px', '@menu-item-height': '41px',}}], webpackConfig]Copy the code

3. Modify package.json- to perform configuration to override

"scripts": {
  "start": "rescripts start",
  "build": "rescripts build",
  "test": "rescripts test",
  "eject": "react-scripts eject"
},
Copy the code

4. Configure the microapplication run port, which is the entry for the main application configuration in the development environment

New root directory.env.development

//.env.development // run PORT = 3200 // websocket listens for hot updates WDS_SOCKET_PORT = 3200Copy the code

5. Import files export microapplication lifecycle hooks

Note: The microapplication was required to export the life-cycle hook function, which was loaded internally by import-entry-HTML. If the microapplication did not export these three life-cycle hook functions, the microapplication would fail to load.

import './public-path' import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; function render(props) { const { container } = props; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, container ? container.querySelector('#root') : document.querySelector('#root') ); } if (! window.__POWERED_BY_QIANKUN__) { render({}); } export async function bootstrap() { console.log('micro app react bootstrap') } export async function mount(props) { console.log('micro app react mount, props11111', props) render(props) } export async function unmount(props) { console.log('micro app react unmount, props', props) const { container } = props ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')) } // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();Copy the code

6. Create a micro-application page

Create a pages folder in SRC, create home.jsx, list.jsx

// home. js import React from 'React' export default function Home() {return (<div> </div>)}Copy the code

7. Add a route

Modify the App. Js

import { BrowserRouter, Route } from 'react-router-dom'
import './App.css';
import Home from './pages/Home';
import List from './pages/List';
 
function App() {
  return (
    <BrowserRouter className="App" basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'} >
      <Route path="/" exact component={Home} />
      <Route path="/list" exact component={List} />
    </BrowserRouter>
  );
}
 
export default App;
Copy the code

By now, the main application has been connected to the micro application. After starting the main application and the micro application respectively, you can see the corresponding micro application module under the main application menu.

4. Communication between applications

1. Communication mode

One is Actions, which is officially provided for inter-application communication, and the other is Shared communication, in which the main application and micro-application maintain their own Redux state pool. I’m going to start with Actions

2. Communication principle

  • We can register observers to the observer pool and then modify the globalState to trigger all observer functions to communicate between components.

  • The initGlobalState method is provided internally to register MicroAppStateActions instances for communication, which have three methods:

  • SetGlobalState: setGlobalState – when a new value is set, a shallow check is performed internally, and if a globalState change is detected, a notification is triggered to inform all observer functions.

  • OnGlobalStateChange: Registers the observer function – in response to globalState changes, the observer function is triggered when globalState changes.

  • OffGlobalStateChange: Cancel the observer function – the instance no longer responds to globalState changes

3. Configure the active application

  • Register a MicroAppStateActions instance in the main application and navigate
// src/shared/actions.jsx
import { initGlobalState } from 'qiankun';
 
const initialState = {};
const actions = initGlobalState(initialState);
 
export default actions;
Copy the code
  • After registering the MicroAppStateActions instance, we use the instance in the component we need to communicate with and register the observer function, using the example of passing user information and permission information to the child application after logging in.
import { generatePath } from 'react-router-dom'; import router from '@views/router'; // import actions from '.. /shared/actions'; /** * const BUTTON_LEVEL = 4; const getRouter = (menuCode) => router.find((item) => item.key === menuCode); const getChildren = (list = [], menuCode = null) => { const items = []; list.forEach((item) => { if (item.parentCode === menuCode && item.level ! == BUTTON_LEVEL) { const routerItem = getRouter(item.menuCode) || {}; const { path, params } = routerItem; items.push({ ... item, ... routerItem, url: path ? generatePath(path, params) : '', children: getChildren(list, item.menuCode), }); }}); return items; }; const getDefaultRouter = (items = []) => { let defaultRouter = null; items.forEach((item) => { if (! defaultRouter) { if (item.path) { defaultRouter = item; } else { defaultRouter = getDefaultRouter(item.children); }}}); return defaultRouter; }; export default { user: (state = null, { type, payload }) => { let navItems = []; let defaultRouter = null; Switch (type) {case 'user/login': // Request user information in the global state pool actions.setGlobalState({... payload }); navItems = getChildren(payload.menuList); defaultRouter = getDefaultRouter(navItems); return { ... state, ... payload, defaultRouter, navItems, }; case 'user/logout': return null; case 'user/set': { return { ... state, ... payload, }; } default: return state; }}};Copy the code

4. Configure microapplications

At this point, the master application has put user information and permission information in the global state pool, and the micro application can monitor the changes of data by registering observers

  • Set up an Actions instance
Const emptyAction = () => {// warning: Error (' This Action is empty ')} class Actions {// Default is empty Action Actions = {setGlobalState: emptyAction, onGlobalStateChange: EmptyAction,} // setActions setActions(actions) {if (actions) {const {setGlobalState, onGlobalStateChange } = actions this.actions.setGlobalState = setGlobalState this.actions.onGlobalStateChange = OnGlobalStateChange} // Map setGlobalState(... args) { return this.actions.setGlobalState? . (... Args)} // Map onGlobalStateChange(... args) { return this.actions.onGlobalStateChange? . (... args) } } const actions = new Actions() export default actionsCopy the code
  • Inject actual Actions into the render function of the entry file index.js
import './public-path' import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import actions from './shared'; Function render(props) {if (props) {// Inject actions into actions. SetActions (props); } const { container } = props; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, container ? container.querySelector('#root') : document.querySelector('#root') ); } if (! window.__POWERED_BY_QIANKUN__) { render({}); } export async function bootstrap() { console.log('micro app react bootstrap') } export async function mount(props) { console.log('micro app react mount, props', props) render(props) } export async function unmount(props) { console.log('micro app react unmount, props', props) const { container } = props ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')) } // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();Copy the code
  • The global state pool data is now available on the page.
// src/pages/Home.jsx import React, { useEffect, useState } from 'react' import actions from '.. /shared'; export default function Home() { const [name, setName] = useState(''); useEffect(() => { actions.onGlobalStateChange(state => { const {userName} = state console.log('micro app react state', State) setName(userName)}, true)}, []) return ({name} </div>)}Copy the code

This completes the communication between the master application and the microapplication.

Configure hot update

1. Hot update principle

When running webpack-dev-server, it starts an HTTP server and a WebSocket server. After code changes, it reconstructs the file that produces the new hash value and tells the browser via websocket (notification in message, Similar to —–a[“{“type”:”hash”,”data”:”f4fdfa57″}”]), the client will make two requests, i.e. hot-update to webpack.

2. Configure microapplications

Port for writing down websocket requests from microapplications

// SRC /.env.development // Run PORT PORT = 3200 // hot update listener PORT WDS_SOCKET_PORT = 3200Copy the code

Deployment — Nginx configuration

Note:

  • The publicPath configured for microapplications must be the same as the prefix configured for nginx
  • The entry of the main application is the same as the prefix configured by nginx
server { listen 8005; listen [::]:8005; server_name cmptest.jss.cn; The location / {# main applied static resource path root/Users/zengxiaobai/projects/nuonuo/qiankun_demo/react_main; index index.html index.htm; try_files $uri $uri/ /index.html; } location /micro/react {# config static resource path proxy_pass http://localhost:3200/; Static resource path # # production environment root/Users/zengxiaobai/projects/nuonuo/qiankun_demo; # index index.html index.htm; # try_files $uri $uri/ /micro/react/index.html; add_header "Access-Control-Allow-Origin" $http_origin; Add_header "access-control-allow-methods" "*"; Add_header "access-control-allow-headers" "*"; } location /baseweb/ {# application interface reverse proxy proxy_pass https://test.abc.cn; } location /customization/ {# application interface reverse proxy proxy_pass https://test.abc.cn; }}Copy the code

7. Demo address

Address:

Difficulties encountered and solutions

In Suspense, routes for the main application are loaded as needed. In loading state, container cannot be found and an error occurs

Solution: Limit the startup time of the micro application in the main application entry file.

import startQiankun from './micro';
 
let container;
 
if (!container) {
  const interval = setInterval(() => {
    container = document.getElementById('container');
    if (container) {
      console.log(2);
      clearInterval(interval);
      startQiankun();
    }
  }, 100);
}
Copy the code

2. In the case that the main application only provides compressed packages, the micro-application needs the permission of the main application. How to finance secondary development

Solution: The secondary development team starts a native Nginx. Microapplication proxy to localhost:3200, refer to the previous nginx deployment

The source code interpretation