preface

Also, in order not to waste time, if you don’t know what the micro sidebar is, this article can be closed.

If you have a strong interest in microfronds, you can also try to learn how I implemented microfronds in this application scenario. I hope this article will enlighten you.

The development template code of “Micro front + Enterprise micro sidebar” has been uploaded to Github, clickwecom-sidebar-qiankun-tplYou can see the need for direct white piao + Star.

Let me show you the results of the project:

The framework of the project is as follows:

Does it look Low? It is necessary to do a Low out, so that you have their own play space.

Enterprise micro sidebar

According to international conventions, a quick look at what the enterprise sidebar is, noh, is the red box at the bottom:

The sidebar is essentially an H5 page, and the corresponding name and URL need to be configured in the background of enterprise wechat:

If you’re familiar with the microfront end, you’re familiar with the development pattern of displaying different pages by configuring names and urls. Because no matter which microfront-end framework they are, they register microapplications exactly the same way they configure sidebar applications:

import { registerMicroApps } from 'qiankun';

registerMicroApps(
  [
    {
      name: 'app1'.entry: '//localhost:8080'.container: '#container'.activeRule: '/react'.props: {
        name: 'kuitos',},},], {beforeLoad: (app) = > console.log('before load', app.name),
    beforeMount: [(app) = > console.log('before mount', app.name)],
  },
);
Copy the code

Why use a micro front end

Similarity doesn’t mean you have to be on the micro front end. However, when managing multiple applications, the following problems occur:

  • All sidebar applications are hard isolated. Switching between different applications requires reloading
  • Basic information is not shared. Reloading requires re-initialization of the JS-SDK and retrieval of group chat, private chat, and user identity information, which is required for every application and should not be retrieved every time
  • Facilitate multi-team collaboration. For scenarios where H5 is already embedded in the sidebar

Micro front end idea

The “register multiple sidebar applications” approach mentioned earlier by registering multiple microapplications is a good management approach.

In addition, I also hope to have the following functions:

  • Microapplications can get some common information from the main application, such asuserIdSuch as
  • Microapplications can also access the main applicationjsSdkObject, directly usedjsSdkInteract with enterprise wechat
  • The main application will do it automaticallyUser authentication 和 JS-SDKMicroapplications no longer need to do common logic and automatically own the data needed by the business
  • In addition to automatically registering microapplications like the Router, the master application can also register microapplications manually in the specified Container

The Qiankun micro-front-end framework perfectly solves this problem.

Main application – Initialization

As you can see from the analysis above, the main application needs to do two things:

  • Perform common logic: get user identity, initialize JS-SDK
  • Get public data:userId.context.chatAnd other sidebar public and business data that need to be shared

The wecom-sidebar-react-tpl React sidebar development template I wrote earlier already implements most of the content, so I use the ready-made public logic here.

This project adds the main application configuration:

import {initGlobalState, MicroAppStateActions, registerMicroApps, start} from "qiankun";
import {JsSDK} from ".. /jsSdk";
export const subAppContainerId = 'sub-app-container';
export const subAppContainer = ` #${subAppContainerId}`;

// Initialize state
export const microAppStateActions: MicroAppStateActions = initGlobalState({});

// Get the props to be passed to the microapplication
const initPassProps = async (jsSdk: JsSDK) => {
  const res = await jsSdk.invoke<{ chatId: string} > ('getCurExternalChat');
  return {
    jsSdk,
    isChat:!!!!! res } }// Launch the main application of Qiankun
const initQiankunMainApp = async (jsSdk: JsSDK) => {
  const passProps = await initPassProps(jsSdk);

  // Add a state change listener
  microAppStateActions.onGlobalStateChange((state, prev) = > {
    console.log('[Main application]', state, prev);
  });

  // Register and start the micro front end
  registerMicroApps([
    {
      name: 'react-app'.entry: '//localhost:3001'.container: subAppContainer,
      activeRule: '/#/react-app'.props: passProps
    },
    {
      name: 'sidebar-app'.entry: '//localhost:3002'.container: subAppContainer,
      activeRule: '/#/sidebar-app'.props: passProps
    }
  ]);

  // Start the main application
  start();
}

// Initializes the main application content
export default initQiankunMainApp;
Copy the code

The initQiankunMainApp function needs to be passed into the jsSdk, and then interacts with the enterprise wechat through the jsSdk to obtain the content of private chat and group chat, and then passes it to the micro front end through props. When the microapplication is mounted, the initial public data is available.

If the data changes, such as when login is called, globalState can be updated with the following code:

function login() {... The login logic microAppStateActions. SetGlobalState ({msg: 'New content'})}Copy the code

After the update, microapplications also need to add onGlobalStateChange to listen for data changes. However, because you are only fetching public data, globalState generally does not change very frequently.

It is easy to register the micro application after configuring the name and entry. ActiveRule I wrote /#/xxx-app because I used the Hash Router in the main application. I’ll talk about routing later.

Since we need to register the microapplication after processing the public logic, we need to write this in the entry file index.tsx:

import { ConfigProvider} from 'antd';
// Since the antD component's default text is In English, it needs to be changed to Chinese
import zhCN from 'antd/lib/locale/zh_CN';
import App from './App'
import {fetchUserId, fetchSignatures} from './api'
import config from './_config'
import {invokeResMock, mockUserId, wxResMock} from "./mock";
import {checkRedirect, createJsSdk, initSdk} from "./lib";

import 'antd/dist/antd.css';
import initQiankunMainApp from "./lib/utils/initQiankunMainApp";

export const jsSdk = createJsSdk(wxResMock, invokeResMock);

const AppWrapper = (
  <ConfigProvider locale={zhCN}>
    <App />
  </ConfigProvider>
)

checkRedirect(config, fetchUserId, mockUserId) // Redirect to get code (user identity)
  .then(() = > initSdk(config, fetchSignatures)) // Initialize JsSdk
  .then(() = > initQiankunMainApp(jsSdk)) // Initialize the master application and register the micro-application
  .then(() = > ReactDOM.render(AppWrapper, document.getElementById('root'))) // Render the main application content
Copy the code

Primary application – Routing

The main-micro architecture would have been simpler, but I wanted the main app to be used as a sidebar app, with its own style and some simple functionality, so I thought it was a reasonable requirement to have its own routing system in the main app.

I used Hash Router here because if I use Browser Router in History mode, it would be too troublesome to initialize JS-SDK every time I switch routes. See step 2 in the documentation for details.

If you want to use history mode, you can also initialize it in the route switch callback, but I always have a feeling that some strange bugs may occur

I put all the features of the previous wecom-sidebar- React-TPL project on the home page, so there is only one home page for routing:

const RouterConfig: FC = () = > {
  return (
    <Switch>
      <Route exact path="/">
        <Home/>
      </Route>
    </Switch>)}export default RouterConfig;
Copy the code

The first Home page is the
component of the main application. It is just a normal React component. The rest of the microapps are sidebar-app and React-app.

Microapplication – Initialization

I used create-React-app to create these two micro-applications, and then configured the micro-applications according to the “Project Practice” section of the official qiankun document. I didn’t want to go through this again, but I found some problems in the configuration process, so LET’s expand on that.

Step 1 – publicPath

Add public-path.ts file to/SRC:

const updatePublicPath = () = > {
  if (window.__POWERED_BY_QIANKUN__) {
    // @ts-ignore
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  }
}

updatePublicPath();

export default {}
Copy the code

The CRA TS template will report a tsLint error:

TS1208: 'public-path.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.
Copy the code

Error: a file must export one thing, so it can only be written as above.

Then import and execute it on the first line of the index.tsx entry file:

import './public-path';
Copy the code

Be sure to include it in the first line, because it directly determines the publicPath of your static resource. For example, if you use an image resource in a component:

import logo from './logo.svg';

function App(props: Props) {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  );
}
Copy the code

If not execute the publicPath, or run separately the react – app, the logo here path becomes/static/media/logo. 6 ce24c58. SVG.

The URL for our main application is localhost:3000, and the URL for our micro application is localhost:3001. So when embedded to the main application, image URL becomes the localhost: 3000 / static/media/logo. 6 ce24c58. SVG, but there is no the SVG, main application and resources will be submitted to the 404 error.

Public-path. ts: localhost:3001 (localhost:3001, localhost:3001, localhost:3001);

Step 2 – Basename

Add basename to the Router.

import {HashRouter} from "react-router-dom";

function App() {
  return (
    <HashRouter basename={window.__POWERED_BY_QIANKUN__ ? '/sidebar-app' :'/'} >
      <div className={styles.app}>
        <VerticalMenu />
        <RouterConfig />
      </div>
    </HashRouter>
  );
}
Copy the code

Note: If both the master application and micro application use Router, the Router type (history mode/Hash mode) must be the same, otherwise there will be many problems.

Note: When I wrote the React Router for the main application, the React Router was already in v6.x, while the main application was still running v5.x.

After adding basename, you can write path directly instead of /sidebar-app/home:

export const routes = [
  {url: '/'.label: 'home'.page: Home},
  {url: '/external-user'.label: 'private chat'.page: ExternalUser},
  {url: '/external-chat'.label: 'chatting'.page: ExternalChat},
  {url: '/action'.label: 'operation'.page: Action},
]

const RouterConfig: FC = () = > {
  return (
    <Routes>
      {routes.map(route => (
        <Route key={route.url} path={route.url} element={<route.page/>}} / >))</Routes>)}Copy the code

Routes can also be written as path, and the following routes use the routes array above:

const VerticalMenu: FC = () = > {
  const location = useLocation();

  return (
    <Menu
      style={menuStyle}
      className={styles.menu}
      defaultSelectedKeys={[location.pathname]}
      mode="vertical"
    >
      {routes.map(route => (
        <Menu.Item className={styles.item} key={route.url}>
          <Link to={route.url}>{route.label}</Link>
        </Menu.Item>
      ))}
    </Menu>)}Copy the code

Step 3 – Expose the life cycle

Export the lifecycle callback function at the micro-application entry index.tsx:

import './public-path'
import {ConfigProvider} from "antd";
import React from 'react';
import {Provider} from "react-redux";
import ReactDOM from 'react-dom';
import App from './App';
// Since the antD component's default text is In English, it needs to be changed to Chinese
import zhCN from 'antd/lib/locale/zh_CN';

import 'antd/dist/antd.css';
import store from "./store";

const AppWrapper = (
  <ConfigProvider locale={zhCN}>
    <Provider store={store}>
      <App />
    </Provider>
  </ConfigProvider>
);

// Render the application
const render = (props: any) = > {
  const { container } = props;
  const containerElement = container ? container.querySelector('#root') : document.querySelector('#root');
  ReactDOM.render(AppWrapper, containerElement);
}

// Everywhere qiankun needs life cycle hooks
export const bootstrap = async() = > {console.log('[sidebar-app] bootstrap');
}

export const mount = async (props: any) => {
  props.onGlobalStateChange((state: any) = > {
    // Update the jsSdk to store
    store.dispatch({ type: 'SET_JSSDK'.payload: state.jsSdk })
  });

  / / update the jsSdk
  store.dispatch({ type: 'SET_JSSDK'.payload: props.jsSdk })

  console.log('[sidebar-app] mount', props);
  render(props);
}

export const unmount = async (props: any) = > {console.log('[sidebar-app] unmount', props);
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}
Copy the code

In the mount callback, we can accept the props from the main application mentioned above, extract isChat and jsSdk from the props, and set them to the Redux Store as the global state of the entire microapplication.

MapStateToProps, mapDispatchToProps, useDispatch, and useSelector are all examples of how to use redux. Get the jsSdk to call the API like the main application.

Or if you don’t want to use redux, it’s also possible to re-render the app after each state change, which I implemented in react-app:

import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

// Render function
const render = (props: any) = > {
  const { container, user } = props;
  ReactDOM.render(<App user={user} />, container ? container.querySelector('#root') : document.querySelector('#root'));
}

// The output function of qiankun micro application
export const bootstrap = async() = > {console.log('react-app bootstrap');
}

export const mount = async (props: any) = > {// Each time the state changes, render again, and pass the updated state and props to the App component
  props.onGlobalStateChange((state: any) = > {
    console.log('[react-app] onGlobalStateChange', state); render({ ... props, ... state, }) });console.log('react-app mount', props);
  render(props);
}

export const unmount = async (props: any) = > {console.log('react-app: unmount', props);
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}
Copy the code

Step 4 – Modify the Webpack configuration

Just install a rescripts/ CLI to replace CRA’s garbage scaffolding with rescripts:

npm i -D @rescripts/cli
Copy the code

.rescriptsrc.js is added 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': The '*'}; config.historyApiFallback =true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;


    returnconfig; }};Copy the code

Finally modify package.json:

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

The entire microapplication configuration is done.

The master application manually loads the microapplication

In addition to registering the micro application as a route, loadMicroApp can also be called to load the micro application under the specified Container element.

For example, I manually loaded the React-app in the “home page” of the main app and passed in the user user identity object when loading:

const Home: FC = () = >{...return (
    <Spin spinning={loading}>
      <div>{/* Manually load microapplications */}<MicroAppComponent user={user}/>
      </div>
    </Spin>)}Copy the code

MicroAppComponent code:

import {FC, useEffect, useRef} from 'react';
import {loadMicroApp, MicroApp} from "qiankun";

let microAppComponent: MicroApp;

interfaceProps { user? : UserResponse }const MicroAppComponent: FC<Props> = (props) = > {
  const containerRef = useRef<HTMLDivElement>(null);

  // Pass props when an update is applied
  useEffect(() = > {
    if (microAppComponent && microAppComponent.update) {
      microAppComponent.update(props).then()
    }
  })

  useEffect(() = > {
    // Initialize the microapplication
    if (containerRef.current) {
      microAppComponent = loadMicroApp({
        name: 'react-app-nested'.entry: '//localhost:3001'.container: containerRef.current,
        props,
      });
    }

    // Unmount the micro application
    return () = > {
      microAppComponent.unmount().then();
    }
  }, [props])

  return (
    <div>
      <h2>Micro application</h2>

      <div ref={containerRef}/>
    </div>)}export default MicroAppComponent;
Copy the code

conclusion

Ok, so let’s wrap things up, after all, this project took a while to do.

  • The main application is developed based on wecom-sidebar- React-TPL. The pre-configuration, Mock, and initialization logic are inherited from this project
  • Main application useloadMicroAppManually load a microapplication
  • The main application inregisterMicroAppsRegister microapps (sidebar apps) and clickpropsPass in shared data andJsSdk
  • Microapplications in the life cycle of exposuremountThe parameters of thepropsTo get the data passed by the master application
  • After receiving the master app data, the micro app can choose to put it in redux’s Store and manage it in the redux storeonGlobalStateChangeIn the callback againrenderThe whole app, take your pick
  • Both master and micro applications can have their own routes, but the route type must be the same, otherwise there will be a big surprise! Microapplications need to be added to the RouterbasenameGet rid of the prefix

Finally, my own suggestion is that the main application should have its own style, welcome page, homepage, route, or write its own department’s sidebar application, and then use Qiankun to leave an entrance for hosting other departments’ sidebars.