1. Install Qiankun
npm i qiankun -S
2. Modify main application
- The main application is divided into two forms: routing loading and manual loading
- Loading in the form of routing, adding the startup code in the startup file, which is suitable for the scenario of replacing sub applications based on a large area, such as switching the home application to directly replace the current application.
registerMicroApps([ { name: 'stream-media', // app name registered entry: '//localhost:9001 ', / / load the application address container: '#Stream media ', / / load the application bound domi d activeRule: '/test/stream-media', // Load application's route } ]); start();
- Manual loading is suitable for dynamically creating a small page in other routes
import React, {useEffect, useRef} from 'react'; import {loadMicroApp} from 'qiankun'; import {withRouter} from "react-router-dom"; import GlobalShared from "@/share"; const StreamMedia: React.FC<any> = (props: any) => { const divRef: any = useRef<HTMLDivElement>(null); useEffect(() => { const videoStreamApp = loadMicroApp({ name: 'video-stream', // app name registered // Deployment address entry: process.env.REACT_APP_VIDEO_STREAM as string, container: '#video-stream', props: Object.assign({ token: sessionStorage.logintoken, height: divRef.current.offsetHeight, // margin-bottom GlobalShared }, props.location?.state) }, { // sandbox: {strictStyleIsolation: true} / / forced isolation }); return () => { /** * Uninstall the micro app */ videoStreamApp.unmount(); } }, []); return ( <div ref={divRef} id="video-stream" style={{position: 'relative', height: '100%'}}></div> ) }; export default withRouter(StreamMedia);
3. Modify micro application
- config-overrides. There are two main modifications in JS: one is to specify the output, and the other is to allow cross domain
/** * Microservice parameter processing * @type {string} */ config.output.library = `stream-media`; // Note that this is the sub application name registered by the main application config.output.libraryTarget = "umd"; config.output.publicPath = 'http://localhost:9001/'; // Note that this is the startup address of your sub application devServer: overrideDevServer( // dev server plugin (config) => { /** * Allow cross domain configuration * @type {{"Access-Control-Allow-Origin": string}} */ config.headers = {"Access-Control-Allow-Origin": "*",}; return config; } )
- Three functions need to be exposed in the startup file index
export async function bootstrap() { console.log('[react16] react app bootstraped'); } export async function mount(props: any) { console.log('[react16] props from main framework', props); startUp(props); } export async function unmount(props: any) { const {container} = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); }
- Add public path under the root path JS is used to modify the publicPath path
if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
4. The development environment deals with the cross domain problem of loading micro application js, json and other files
- Micro applications need processing paths
In micro applications, the exposed parameters are extracted into env file management
#Note that this is the sub application name registered by the main application
REACT_APP_QIANKUN_library= video-stream
#Supported import forms
REACT_APP_QIANKUN_libraryTarget= umd
#Note that this is the startup address of your sub application
REACT_APP_QIANKUN_publicPath= http://localhost:9004/
When loading js, you need to handle the next path when starting as a micro application
// @If TS ignore is started as a micro application, modify the path if (window.__POWERED_BY_QIANKUN__) { GlobalStaticFile = GlobalStaticFile.map((url: string) => { // @ts-ignore return Path.resolve(process.env.REACT_APP_QIANKUN_publicPath, url); }); }
It is suggested to use the prefix json when loading the main file, which is convenient for cross domain application and management
If you use fetch to report an error, please delete the 'content type': 'application / JSON' attribute of headers
Access to fetch at 'http://localhost:9004/bin/layout.json' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
/** * Get resource path * @param url */ const getPathName = (url: string) => { // @ts-ignore if (!window.__POWERED_BY_QIANKUN__) { return Path.resolve(window.location.pathname, url); } // return url; return Path.resolve(process.env.REACT_APP_QIANKUN_library as string, url); };
- The main application needs to handle cross domain
setupProxy Cross domain processing in /** * Dealing with cross domain problems of micro applications */ app.use( createProxyMiddleware('/video-stream', { target: 'http://localhost:9004', // changeOrigin: true, // needed for virtual hosted sites ws: true, // proxy websockets secure: false, // Verify SSL certificate. Apply to https pathRewrite: { '^/video-stream': '' } }) );
5. Handling style isolation
Add prefix, '@ ant prefix': process env. REACT_ APP_ QIANKUN_ library,
addLessLoader({ lessOptions: { javascriptEnabled: true, localIdentName: '[local]--[hash:base64:5]', modifyVars: Object.assign({}, getThemeVariables({ // dark: true, / / enable dark mode compact: false, // Turn on compact mode }), { '@ant-prefix': process.env.REACT_APP_QIANKUN_library, }), } }),
The ant message (creation method) cannot be destroyed and the style is unavailable
Idea: if the style is unavailable, download the style independently, maintain it independently, and add a prefix
/* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ /* stylelint-disable no-duplicate-selectors */ /* stylelint-disable */ /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ .video-stream-ant-message { ... } AntdMessage.config({ prefixCls: `${process.env.REACT_APP_QIANKUN_library}-ant-message`, }); // Destruction problem: manual destruction AntdMessage[fnKey]({ content: _msg, key: key, className: key, duration: 3, }).then((t: any) => { /** * There is a bug in the ant component implementation when the micro component is destroyed * Keys cannot be destroyed directly */ AntdMessage.destroy(key); const msg: any = document.getElementsByClassName(`${key}`); if (msg && msg[0] && msg[0].parentNode) { msg[0].parentNode.removeChild(msg[0]) } });
6. Parent-child communication problem
- Pass props to micro applications (directly, bound to client sessionStorage localStorage)
- Use Actions to communicate
// The api provided by qiankun uses observer mode to handle data flow import { initGlobalState, MicroAppStateActions } from "qiankun"; const initialState = {}; const actions: MicroAppStateActions = initGlobalState(initialState); export default actions; //assignment actions.setGlobalState({ token }); // Register observers to monitor changes actions.onGlobalStateChange((state, prevState) => { // State: status after change; prevState: state before change console.log("Main application: the value before change is ", prevState.token); console.log("Main application: the login status changes, and the changed value is ", state.token); }); // The sub application needs to accept actions in the index // The communication mode is also through setGlobalState // When TODO communication service is complex, this scheme is difficult to maintain and the data flow is difficult to track
- Pass it to micro application in shared mode
redux is used to manage the state, and the class of managing the state is passed to the micro application as a tool to achieve the purpose of data sharing and interaction
- // src/share/Store.ts
/** * Type of exposure */ export enum SharedType { TOKEN = 'SET_TOKEN' } /** * The bound data can be added later */ type SharedState = { token: string, } /** * Action support */ type SharedAction = { type: string, value: any, } const reducer = (state: SharedState, action: SharedAction): SharedState => { switch (action.type) { case SharedType.TOKEN: return Object.assign({}, state, {token: action.value}); default: return state; } }; const SharedStore = createStore<SharedState, SharedAction, any, any>(reducer as any, {} as any); export default SharedStore;
- //Main application Src / share / index ts
import SharedStore, {SharedType} from './Store'; class Shared { /** * Get token */ public getToken = (): string => { return SharedStore.getState()[SharedType.TOKEN]; } /** * Set token * @param token */ public setToken = (token: string): void => { SharedStore.dispatch({ type: SharedType.TOKEN, value: token }); } } const GlobalShared = new Shared(); export default GlobalShared; // login is through GlobalShared Settoken is used to assign a value and pass the whole GlobalShared to the micro application
- //Micro app / shared / index ts
import GLOBALCONFIG from "@share/HttpClient/GLOBALCONFIG"; class Shared { /** * Get token */ public getToken(): string { return localStorage.getItem(GLOBALCONFIG.USERlocalStorage) || ''; } /** * Set token * @param token */ public setToken(token: string): void { localStorage.setItem('autoLogin', token); } } class SharedModlue { static shared = new Shared(); /** * The shared passed in by the parent application is overwritten * @param shared */ static overloadShared(shared: any) { SharedModlue.shared = shared; } /** * Get the token in shared */ static getShared() { return SharedModlue.shared } } export default SharedModlue;
- //Override in index render function
if (GlobalShared) SharedModlue.overloadShared(GlobalShared);
7. After deployment, static resource requests must support cross domain and nginx configuration
location /video-stream { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; if ($request_method = 'OPTIONS') { return 204; } try_files $uri /video-stream/index.html; index /video-stream/index.html; }