Original text: zhuanlan.zhihu.com/p/151442255

Two months ago, we officially launched Qiankun 2.0, and after 15+ beta and a lot of internal polish, today we’re officially releasing the new @umijs/ plugin-Qiankun based on It.

This update is fully compatible with @umijs/ plugin-Qiankun in the plugin layer, so it is only a minor update.

New features

Version 2.3.0 not only fixes a number of issues with the previous Qiankun Plugin, but also brings some exciting new features by fully migrating the underlying version to Qiankun 2.0.

Configuration to streamline

When configuring microapplications, you do not need to manually configure base and mountElementId.

export default {
  qiankun: {
    master: {
      apps: [
        {
          name: 'microApp1',
          entry: '//test.com/app1',
- base: '/app1',
- mountElementId: 'app1-root'}}}Copy the code

In the previous mode, we need to prepare a mountable node mountElementId in advance for each microapplication in the main application and a route /base agreed by both parties in advance to complete a microapplication access.

But this approach runs into some tricky problems:

Container load/unload timing issues

For example, our main application rendering may be asynchronous/time-varying, so we must ensure that the microapplication’s mountElementId container is ready before rendering. Otherwise, an exception such as Target is not Container will be thrown when the child application is mounted. Previously we provided a defer: Boolean configuration to resolve this issue, and the lazy initialization of the Qiankun framework was completed by opening this configuration + manually calling qiankunStart(). However, this approach does not fundamentally solve the problem, and can be problematic in more complex scenarios where each microapplication’s mount point may be rendered asynchronously.

Similarly, during microapplication uninstallation, the mountElementId container may be removed by other application logic due to the influence of other logic in the main application (such as route switching). As a result, microapplication uninstalls will also throw an exception similar to Target Container is not a DOM element.

The base configuration is incorrect

Before our main application wanted to render a micro application correctly, both sides needed to be consistent in their routing base. For example, the main application side is configured when registering the micro application:

{
  name: 'microApp1',
  entry: '//test.com/app1',
+ base: '/app1'
}
Copy the code

Then the microApp1 application must also use the same base configuration, such as:

// config.js
{
+ base: '/app1',
  plugins: [...],
}
Copy the code

Otherwise, the url may not be recognized by the micro application due to inconsistent base configurations, and the micro application cannot be loaded normally.

Meanwhile, in more complex scenarios, such as when I want to load a microapplication under a dynamic set of URL paths such as [/users/:userId, /members/:mid], this can be cumbersome to handle and may not even be supported.

And the new micro application access way, will be perfect to solve some of these problems.

A new way to access microapplications

👆 The configuration mentioned above only declares a set of microapplications; further configuration is required when to bind the render microapplication.

The new plug-in provides two ways to bind microapplications:

####A. Route binding

Suppose we have a set of routes like this:

export default {
  routes: [{path: '/login'.component: 'login'},
    {
      path: '/'.component: '@/layouts/index'.routes: [{path: '/list'.component: 'list' },
        { path: '/admin'.component: 'admin'},],},]}Copy the code

If we want to load microapplication app1 and microapplication app2 under /users and /admin/:operation respectively, then we need to add a few lines of code to the route configuration:

export default {
  routes: [
    {
      path: '/',
      component: '@/layouts/index',
      routes: [
        { path: '/list', component: 'list' },
        { 
          path: '/admin',
          component: 'admin',
+ routes: [
+ {
+ path: '/admin/:operation', microApp: 'app2',
+}
+]},],},+ { path: '/users', microApp: 'app1'},]}Copy the code

When the React-Router matches the url of the /users or /admin/:operation rule, the react-Router will automatically render its associated microapplications.

In route binding mode, the Qiankun Plugin will automatically inject matching microappsbaseInformation, micro applications readbaseThe routing Settings are automatically updated at run time after this information (requiring the microapplication to also use the latest version of the plug-in).

B. MicroApp component

In more complex scenarios where we might want to control the rendering of a microapplication, we can use the way we provide the React component, such as:

import { MicroApp } from 'umi';

function MyPage(props) {
  const { loading } = props;
  
  if (loading) {
    return <Spin />;
  }
  
  return (
    <div>
      <MicroApp name="microApp1"/>
    </div>
  )
}
Copy the code

When loading is false, the MyPage component renders microApp1 as previously declared.

Brand new application communication mode

Before version 2.3.0, there were two methods of communication between primary and microapplications: props and Hooks. However, there is a problem with both of these methods, which is that they are not ready to use out of the box. For example, I want to implement the function of the props component for the micro application to automatically trigger Rerender after the main application updates the props. Both methods are awkward to implement.

With the help of umi@3, we have provided a friendlier, more powerful inter-application communication mechanism based on the Model plug-in.

Data is delivered to the active application

Different microapplications use different patterns of communication.

MicroApp component type

If you are using the MicroApp component mode to consume the MicroApp, then the data will be transmitted the same way as the normal React component communication, just via the Props:

function MyPage() {
  const [name, setName] = useState(null);
  return <MicroApp name={name} onNameChange={newName= > setName(newName)} />
}
Copy the code
Route binding

If you are using routing bindings to consume your app, then you need to export the function qiankunGlobalState in SRC /app.ts. The return value of the function will be transferred to the app as props. For example:

// src/app.ts
export function useQiankunStateForSlave() {
  const [globalState, setGlobalState] = useState({});
  
  return {
    globalState,
    setGlobalState,
  }
}
Copy the code

When the master application needs to change globalState and automatically trigger a subapplication update, only:

import { useModel } from 'umi'; function MyPage() { const { setGlobalState } = useModel('@@qiankunStateForSlave'); Return <button onClick={() => setGlobalState({})}>Copy the code

Note that since the global state is being updated, the change may cause all currently mounted microapplications to trigger the update. If you need to precisely update a particular microapplication, use the MicroApp component pattern.

Micro-application consumption data

Microapplications can directly obtain the status data from the master application through useModel(‘ @@QiankunStateFromMaster ‘).

import { useModel } from 'umi';

function MyPage() {
  const masterState = useModel('@@qiankunStateFromMaster');
  
  return <div>{ masterState.userName }</div>
}
Copy the code

Upgrade guide

V2.3.0 is fully compatible with versions prior to V2, but we recommend that you upgrade to the latest version for a better development experience.

  1. Remove unnecessary application configurations

    export default {
      qiankun: {
        master: {
          apps: [
            {
              name: 'microApp',
              entry: '//umi.dev.cnd/entry.html',
    - base: '/microApp',
    - mountElementId: 'microApp',
    - history: 'browser',}}}Copy the code
  2. Remove unnecessary global configurations

    export default {
      qiankun: {
        master: {
          apps: [],
    - defer: true,}}}Copy the code
  3. Remove unnecessary mount containers

    -export default MyContainer() {
    - return (
    - 
            
    -
    - -) -} Copy the code
  4. Associated microapplications

    For example, we configured the microApp named microApp base as /microApp and mountElementId as subapp- Container, then we only need:

    A. Add a route to /microApp

    export default {
      routes: [..., {path: '/microApp'.microApp: 'microApp'}}]Copy the code

    B. Use microApp in the component corresponding to the /microApp route

    export default {
      routes: [..., {path: '/microApp'.component: 'MyPage'}}]Copy the code
    import { MicroApp } from 'umi';
    export default MyPage() {
      return (
        <div>
      		<MicroApp name="microApp" />
    	  </div>)}Copy the code
  5. Remove some invalid configurations, such as manually adding sub-application route configurations

Roadmap

  • Dynamic History Type support (coming soon 🎉)

    To decouple the microapplication configuration, modify the microapplication history configuration by setting the microapplication props at runtime. For example:

    See https://github.com/ReactTraining/history/blob/master/docs/api-reference.md / / HistoryOptions configuration type HistoryProp = { type: 'browser' | 'memory' | 'hash' } & HistoryOptions; <MicroApp history={{ type: 'browser', basename: '/microApp' }} />Copy the code
  • Runtime unity for multi-layer nested microapplication scenarios

  • Micro application automatic mountElementId, avoid multiple UMI subapplication mountElementId conflict

  • Automatic loading

  • Native integrated development support

Finally, I would like to thank the students who participated in the development of version 2.3.0 @Tianyi (Troy. Lty) @Quanlong (Brickspert.fjL) @Zaoxian (Tianyi.mty) @Chaolin. JCL