diff --git a/404.html b/404.html index a3bde7ea8..fa29d3b94 100644 --- a/404.html +++ b/404.html @@ -93,6 +93,6 @@ } })(); - + diff --git a/_demos/:uuid/index.html b/_demos/:uuid/index.html index a3bde7ea8..fa29d3b94 100644 --- a/_demos/:uuid/index.html +++ b/_demos/:uuid/index.html @@ -93,6 +93,6 @@ } })(); - + diff --git a/api/index.html b/api/index.html index d78992e68..4ff564a66 100644 --- a/api/index.html +++ b/api/index.html @@ -84,7 +84,7 @@
// Initialize state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: new state; prev old state
console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

Slave:

// get actions from mount
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: new state; prev old state
console.log(state, prev);
});
props.setGlobalState(state);
// It will trigger when slave umount, not necessary to use in non special cases.
props.offGlobalStateChange();
-
// ...
}
+
// ...
}
- + diff --git a/cookbook/index.html b/cookbook/index.html index 58e3fea09..8aeff6de0 100644 --- a/cookbook/index.html +++ b/cookbook/index.html @@ -67,7 +67,7 @@

Tutorial

How to choose the routing mode of micro app

The three routes react-router, angular-router, and vue-router all support the hash and history modes. The different modes used by micro apps are slightly different in qiankun.

activeRule uses location.pathname to distinguish micro apps

When the main app uses location.pathname to distinguish micro apps, micro apps can be in hash and history modes.

When registering micro apps, activeRule needs to be written like this:

registerMicroApps([
{
name: 'app',
entry: 'http://localhost:8080',
container: '#container',
activeRule: '/app',
},
]);
  1. When the micro app is in history mode, just set the route base.

  2. When the micro app is in the hash mode, the performance of the three routes is inconsistent

    routingmain app jump /app/#/aboutspecial configuration
    vue-routerResponse about routingnone
    react-routernot responding about routingnone
    angular-routerResponse about routingneed to set --base-href

    Angular app set --base-href in package.json:

    - "start": "ng serve",
    + "start": "ng serve --base-href /angular9",
    - "build": "ng build",
    + "build": "ng build --base-href /angular9",

    After bundled and deployed, the angular micro app can be accessed by the main app, but the lazy-loaded route during independent access will report an error and the path is incorrect. There are two solutions:

    • Solution 1: Modify public-path.js to:

      __webpack_public_path__ = window.__POWERED_BY_QIANKUN__
      ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
      : `http://${ip}:${port}/`; // Fill in your actual deployment address
    • Solution 2: Modify the bundling command and deploy the micro app in the angular9 directory:

      - "build": "ng build",
      + "build": "ng build --base-href /angular9 --deploy-url /angular9/",

activeRule uses hash to distinguish micro apps

When the micro apps are all in the hash mode, hash can be used to distinguish the micro apps, and the routing mode of the main app is not limited.

When registering micro apps, activeRule needs to be written like this:

const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
registerMicroApps([
{
name: 'app-hash',
entry: 'http://localhost:8080',
container: '#container',
activeRule: getActiveRule('#/app-hash'),
// Here you can also write `activeRule:'#/app-hash'` directly,
// but if the main app is in history mode or the main app is deployed in a non-root directory, this writing will not take effect.
},
]);

The react-router and angular-router micro-apps need to set the value of activeRule to the route's base, written in the same way as history.

In the hash mode of the vue-router app, the base for routing is not supported. You need to create an additional empty routing page and use all other routes as its children:

const routes = [
{
path: '/app-vue-hash',
name: 'Home',
component: Home,
children: [
// All other routes are written here
],
},
];

When there are multiple micro apps at the same time

If a page displays multiple micro apps at the same time, you need to use loadMicroApp to load them.

If these micro apps have routing jump requirements, to ensure that these routes do not interfere with each other, you need to use the momery routing. vue-router uses the abstract mode, react-router uses the memory history mode, and angular-router does not support it.

How to deploy

Recommendation: The main app and micro apps are developed and deployed independently, that is, they belong to different git repositories and services.

Scenario 1: The main app and micro apps are deployed to the same server (the same IP and port)

If the number of servers is limited, or cannot be cross-domain and other reasons, the main app and micro apps need to be deployed together.

The usual practice is to deploy the main app in the first-level directory and the micro apps in the second/third-level directory.

If you want to deploy a micro app in a non-root directory, you need to do two things before bundling the micro app:

  1. You must configure the publicPath when building webpack as the directory name. For more information, please see webpack official instructionsvue-cli3 official instructions.

  2. The micro app of the history route needs to set the base, the value is directory name, which is used when the micro app is accessed independently.

Pay attention to three points after deployment:

  1. activeRule cannot be the same as the real access path of the micro app, otherwise it will directly become the micro app page when refreshed on the main app page.
  2. The real access path of the micro app is the entry of the micro app, and the entry can be a relative path.
  3. The / at the end of the entry path of the micro app cannot be omitted, otherwise the publicPath will be set incorrectly. For example, if the access path of the child item is http://localhost:8080/app1, then entry It is http://localhost:8080/app1/.

There are two specific deployment methods, choose one of them.

Suppose we have a main app and 6 micro apps ( respectively vue-hash, vue-history, react-hash, react-history, angular-hash, angular-history) And place it as follows after bundling:

└── html/ # root folder
|
├── child/ # the folder of all micro apps
| ├── vue-hash/ # the folder of the micro app `vue-hash`
| ├── vue-history/ # the folder of the micro app `vue-history`
| ├── react-hash/ # the folder of the micro app `react-hash`
| ├── react-history/ # the folder of the micro app `react-history`
| ├── angular-hash/ # the folder of the micro app `angular-hash`
| ├── angular-history/ # the folder of the micro app `angular-history`
├── index.html # index.html of the main app
├── css/ # the css folder of the main app
├── js/ # the js folder of the main app

At this time, you need to set the publicPath and the route base of the history mode when the micro app is built, and then bundle them into the corresponding directory.

approuting basepublicPathreal access path
vue-hashnone/child/vue-hash/http://localhost:8080/child/vue-hash/
vue-history/child/vue-history//child/vue-history/http://localhost:8080/child/vue-history/
react-hashnone/child/react-hash/http://localhost:8080/child/react-hash/
react-history/child/react-history//child/react-history/http://localhost:8080/child/react-history/
angular-hashnone/child/angular-hash/http://localhost:8080/child/angular-hash/
angular-history/child/angular-history//child/angular-history/http://localhost:8080/child/angular-history/
  • vue-history micro app

    Routing's base configuration:

    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/',

    Webpack's publicPath configuration(vue.config.js):

    module.exports = {
    publicPath: '/child/vue-history/',
    };
  • react-history micro app

    Routing's base configuration:

    <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/child/react-history/'}>

    Webpack's publicPath configuration:

    module.exports = {
    output: {
    publicPath: '/child/react-history/',
    },
    };
  • angular-history micro app

    Routing's base configuration:

    providers: [
    {
    provide: APP_BASE_HREF,
    useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/',
    },
    ];

    The publicPath of webpack is set by deploy-url, modify package.json:

    - "build": "ng build",
    + "build": "ng build --deploy-url /child/angular-history/",

Then the registerMicroApps function at this time is like this (you need to ensure that activeRule and entry are different):

registerMicroApps([
{
name: 'app-vue-hash',
entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/
container: '#container',
activeRule: '/app-vue-hash',
},
{
name: 'app-vue-history',
entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/
container: '#container',
activeRule: '/app-vue-history',
},
// angular and react same as above
],

So far, the main app and the micro apps can run normally, but the main app and the vue-history, react-history, and angular-history micro apps are history routes. The problem of refreshing 404 needs to be solved. nginx` also needs to be configured:

server {
listen 8080;
server_name localhost;
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
-
location /child/vue-history {
root html;
index index.html index.htm;
try_files $uri $uri/ /child/vue-history/index.html;
}
# The configuration of angular-history and react-history is the same as above
}

Solution 2: Place the micro apps directly in the secondary directory, but set a special activeRule

└── html/ # root folder
|
├── vue-hash/ # the folder of the micro app `vue-hash`
├── vue-history/ # the folder of the micro app `vue-history`
├── react-hash/ # the folder of the micro app `react-hash`
├── react-history/ # the folder of the micro app `react-history`
├── angular-hash/ # the folder of the micro app `angular-hash`
├── angular-history/ # the folder of the micro app `angular-history`
├── index.html # index.html of the main app
├── css/ # the css folder of the main app
├── js/ # the js folder of the main app

The basic operation is the same as above, just make sure that activeRule is different from the storage path name of the micro app.

Scenario 2: The main app and micro apps are deployed on different servers and accessed through Nginx proxy

This is generally done because the main app is not allowed to access micro apps across domains. The practice is to forward all requests for a special path on the main app server to the micro app server, that is, a "micro app deployed on the main app server" effect is achieved through the proxy.

For example, the main app is on the A server, and the micro app is on the B server. The path /app1 is used to distinguish the micro app, that is, all requests starting with /app1 on the A server are forwarded to the B server.

the Nginx proxy configuration of the main app is:

/app1/ {
proxy_pass http://www.b.com/app1/;
proxy_set_header Host $host:$server_port;
}

When the main app registers micro apps, entry can be a relative path, and activeRule cannot be the same as entry (otherwise the main app page refreshes and becomes a micro app):

registerMicroApps([
{
name: 'app1',
entry: '/app1/', // http://localhost:8080/app1/
container: '#container',
activeRule: '/child-app1',
},
],

For micro apps bundled by webpack, the publicPath bundled by the micro app's webpack needs to be configured as /app1/, otherwise the micro app's index.html can be requested correctly, But the path of js/css in the micro app's index.html will not carry /app1/.

module.exports = {
output: {
publicPath: `/app1/`,
},
};

After adding /app1/ to the publicPath of the micro app, it must be deployed in the /app1 directory, otherwise it cannot be accessed independently.

In addition, if you don't want the micro app to be accessed independently through the proxy path, you can judge based on some information requested. The requesting micro app in the main app is requested with fetch, which can include parameters and cookie. For example, judge by request header parameters:

if ($http_custom_referer != "main") {
rewrite /index /404.html;
}

Upgrade from 1.x version to 2.x version

The micro apps does not need to be changed, and the main app needs to be adjusted.

The basic modification of registerMicroApps function is as follows:

  1. Remove the render parameter and only need to provide the container.
  2. Add the loader parameter to display the loading status. Originally, the loading status was provided to the render parameter.
  3. The activeRule parameter can be abbreviated as /app, which is compatible with the previous function writing.
  4. The RegisterMicroAppsOpts parameter is removed and placed in the parameter of the start function.

The basic modification of the start function is as follows:

  1. The jsSandbox configuration has been removed and changed to sandbox, and the optional values have also been modified.
  2. Added getPublicPath and getTemplate to replace RegisterMicroAppsOpts.
+
location /child/vue-history {
root html;
index index.html index.htm;
try_files $uri $uri/ /child/vue-history/index.html;
}
# The configuration of angular-history and react-history is the same as above
}

Solution 2: Place the micro apps directly in the secondary directory, but set a special activeRule

└── html/ # root folder
|
├── vue-hash/ # the folder of the micro app `vue-hash`
├── vue-history/ # the folder of the micro app `vue-history`
├── react-hash/ # the folder of the micro app `react-hash`
├── react-history/ # the folder of the micro app `react-history`
├── angular-hash/ # the folder of the micro app `angular-hash`
├── angular-history/ # the folder of the micro app `angular-history`
├── index.html # index.html of the main app
├── css/ # the css folder of the main app
├── js/ # the js folder of the main app

The basic operation is the same as above, just make sure that activeRule is different from the storage path name of the micro app.

Scenario 2: The main app and micro apps are deployed on different servers and accessed through Nginx proxy

This is generally done because the main app is not allowed to access micro apps across domains. The practice is to forward all requests for a special path on the main app server to the micro app server, that is, a "micro app deployed on the main app server" effect is achieved through the proxy.

For example, the main app is on the A server, and the micro app is on the B server. The path /app1 is used to distinguish the micro app, that is, all requests starting with /app1 on the A server are forwarded to the B server.

the Nginx proxy configuration of the main app is:

/app1/ {
proxy_pass http://www.b.com/app1/;
proxy_set_header Host $host:$server_port;
}

When the main app registers micro apps, entry can be a relative path, and activeRule cannot be the same as entry (otherwise the main app page refreshes and becomes a micro app):

registerMicroApps([
{
name: 'app1',
entry: '/app1/', // http://localhost:8080/app1/
container: '#container',
activeRule: '/child-app1',
},
],

For micro apps bundled by webpack, the publicPath bundled by the micro app's webpack needs to be configured as /app1/, otherwise the micro app's index.html can be requested correctly, But the path of js/css in the micro app's index.html will not carry /app1/.

module.exports = {
output: {
publicPath: `/app1/`,
},
};

After adding /app1/ to the publicPath of the micro app, it must be deployed in the /app1 directory, otherwise it cannot be accessed independently.

In addition, if you don't want the micro app to be accessed independently through the proxy path, you can judge based on some information requested. The requesting micro app in the main app is requested with fetch, which can include parameters and cookie. For example, judge by request header parameters:

if ($http_custom_referer != "main") {
rewrite /index /404.html;
}

Upgrade from 1.x version to 2.x version

The micro apps does not need to be changed, and the main app needs to be adjusted.

The basic modification of registerMicroApps function is as follows:

  1. Remove the render parameter and only need to provide the container.
  2. Add the loader parameter to display the loading status. Originally, the loading status was provided to the render parameter.
  3. The activeRule parameter can be abbreviated as /app, which is compatible with the previous function writing.
  4. The RegisterMicroAppsOpts parameter is removed and placed in the parameter of the start function.

The basic modification of the start function is as follows:

  1. The jsSandbox configuration has been removed and changed to sandbox, and the optional values have also been modified.
  2. Added getPublicPath and getTemplate to replace RegisterMicroAppsOpts.
- + diff --git a/faq/index.html b/faq/index.html index 767bcf878..d789a36b1 100644 --- a/faq/index.html +++ b/faq/index.html @@ -65,7 +65,7 @@ FAQ - qiankun -

qiankun

FAQ

Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry

This error thrown as qiankun could not find the exported lifecycle method from your entry js.

To solve the exception, try the following steps:

  1. check you have exported the specified lifecycles, see the doc

  2. check you have set the specified configuration with your bundler, see the doc

  3. Check the webpack of micro app whether it configured with output.globalObject or not, be sure its value was window if it had, or remove it to use default value.

  4. Check your package.json name field is unique between sub apps.

  5. Check if the entry js in the sub-app's entry HTML is the last script to load. If not, move the order to make it be the last, or manually mark the entry js as entry in the HTML, such as:

    <script src="/antd.js"></script>
    <script src="/appEntry.js" entry></script>
    <script src="https://www.google.com/analytics.js"></script>
  6. If the development environment is OK but the production environment is not, check whether the index.html and entry js of the micro app are returned normally, for example, 404.html is returned.

  7. If you're using webpack5, please see here

  8. Check whether the main app and micro-app use AMD or CommonJS. Check method: run the main app and the micro-app independently, and enter the following code in the console: (typeof exports === 'object' && typeof module === 'object') || (typeof define === 'function' && define.amd) || typeof exports === 'object',If it returns true,that it is caused by this reason, and there are mainly the following two solutions:

    • Solution 1: Modify the libraryTarget of the micro-app webpack to 'window'.
    const packageName = require('./package.json').name;
    module.exports = {
    output: {
    library: `${packageName}-[name]`,
    - libraryTarget: 'umd',
    + libraryTarget: 'window',
    jsonpFunction: `webpackJsonp_${packageName}`,
    },
    };
    • Solution 2: The micro-app is not bundle with umd, directly mount the life cycle function to the window in the entry file, refer toMicro app built without webpack.
  9. If it still not works after the steps above, this is usually due to browser compatibility issues. Try to set the webpack output.library of the broken sub app the same with your main app registration for your app, such as:

Such as here is the main configuration:

// main app
registerMicroApps([
{
name: 'brokenSubApp',
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/react',
},
]);

Set the output.library the same with main app registration:

module.exports = {
output: {
// Keep the same with the registration in main app
library: 'brokenSubApp',
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};

Application died in status NOT_MOUNTED: Target container with #container not existed after xxx mounted!

This error thrown as the container DOM does not exist after the micro app is loaded. The possible reasons are:

  1. The root id of the micro app conflicts with other DOM, and the solution is to modify the search range of the root id.

    vue micro app:

    function render(props = {}) {
    const { container } = props;
    instance = new Vue({
    router,
    store,
    render: (h) => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
    }
    export async function mount(props) {
    render(props);
    }

    react micro app:

    function render(props) {
    const { container } = props;
    ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
    }
    export async function mount(props) {
    render(props);
    }
    export async function unmount(props) {
    const { container } = props;
    ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
    }
  2. Some js of micro app use document.write, such as AMAP 1.x version, Tencent Map 2.x version.

    If it is caused by the map js, see if the upgrade can be resolved, for example, upgrade the AMAP map to version 2.x.

    If the upgrade cannot be resolved, it is recommended to put the map on the main app to load. The micro app also introduces this map js (used in run independently), but add the ignore attribute to the <script> tag:

    <script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>

    In other cases, please do not use document.write.

Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!

This error usually occurs when the main app is Vue, and the container is written on a routing page and uses the routing transition effect. Some special transition effect caused the container not to exist in the mounting process of the micro app. The solution is to use other transition effects, or remove the routing transition.

Application died in status NOT_MOUNTED: Target container with #container not existed while xxx loading!

Similar to the above error, This error thrown as the container DOM does not exist when the micro app is loaded. Generally, it is caused by incorrect calling timing of the start function, just adjust the calling timing of the start function.

How to determine the completion of the container DOM loading? The vue app can be called during the mounted life cycle, and the react app can be called during the componentDidMount life cycle.

If it still reports an error, check whether the container DOM is placed on a routing page of the main app, please refer to [How to load micro apps on a routing page of the main app](#How to load micro apps on a routing page of the main app)

[import-html-entry]: error occurs while excuting xxx script http://xxx.xxx.xxx/x.js

The first line is just an auxiliary information printed by qiankun through console.error, which is used to help users quickly know which js has an error, not a real exception. The real exception information is on the second line.

For example, such an error indicates that when qiankun was executing the http://localhost:9100/index.bundle.js of the sub application, this js itself threw an exception. The specific exception information is Uncaught TypeError: Cannot read property 'call' of undefined.

Sub-application exceptions can be attempted to be resolved through the following steps:

Check the error js for syntax errors according to the specific exception information, such as missing semicolons, dependence on uninitialized variables, etc. Whether it depends on global variables provided by the main application, but the main application is not actually initialized. Compatibility issues. The js itself of the sub-application has syntax compatibility issues in the current runtime environment.

How to load micro apps on a routing page of the main app

It must be ensured that the routing page of the main app is also loaded when the micro app is loaded.

vue + vue-router main app:

  1. When the main app registers this route, add a * to path, Note: If this route has other sub-routes, you need to register another route, just use this component.
    const routes = [
    {
    path: '/portal/*',
    name: 'portal',
    component: () => import('../views/Portal.vue'),
    },
    ];
  2. The activeRule of the micro app needs to include the route path of the main app.
    registerMicroApps([
    {
    name: 'app1',
    entry: 'http://localhost:8080',
    container: '#container',
    activeRule: '/portal/app1',
    },
    ]);
  3. Call the start function in the mounted cycle of the Portal.vue component, be careful not to call it repeatedly.
    import { start } from 'qiankun';
    export default {
    mounted() {
    if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start();
    }
    },
    };

react + react-router main app:only need to make the activeRule of the sub app include the route of the main app.

angular + angular-router main app,similar to the Vue app:

  1. The main app registers a wildcard sub route for this route, and the content is empty.

    const routes: Routes = [
    {
    path: 'portal',
    component: PortalComponent,
    children: [{ path: '**', component: EmptyComponent }],
    },
    ];
  2. The activeRule of the micro app needs to include the route path of the main app.

    registerMicroApps([
    {
    name: 'app1',
    entry: 'http://localhost:8080',
    container: '#container',
    activeRule: '/portal/app1',
    },
    ]);
  3. Call the start function in the ngAfterViewInit cycle of this routing component, be careful not to call it repeatedly.

    import { start } from 'qiankun';
    export class PortalComponent implements AfterViewInit {
    ngAfterViewInit(): void {
    if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start();
    }
    }
    }

Vue Router Error - Uncaught TypeError: Cannot redefine property: $router

If you pass { sandbox: true } to start() function, qiankun will use Proxy to isolate global window object for sub applications. When you access window.Vue in sub application's code,it will check whether the Vue property in the proxyed window object. If the property does not exist, it will look it up in the global window object and return it.

There are three lines code in the vue-router as followed, and it will access window.Vue once the vue-router module is loaded. And the window.Vue in following code is your master application's Vue.

if (inBrowser && window.Vue) {
window.Vue.use(VueRouter);
}

To solve the error, choose one of the options listed below:

  1. Use bundler to pack Vue library, instead of CDN or external module
  2. Rename Vue to other name in master application, eg: window.Vue2 = window.Vue; delete window.Vue

Why dynamic imported assets missing?

Two way to solve that:

1. With webpack live public path config

qiankun will inject a live public path variable before your sub app bootstrap, what you need is to add this code at the top of your sub app entry js:

__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;

For more details, check the webpack doc.

Runtime publicPath addresses the problem of incorrect scripts, styles, images, and other addresses for dynamically loaded in sub application.

2. With webpack static public path config

You need to set your publicPath configuration to an absolute url, and in development with webpack it might be:

{
output: {
publicPath: `//localhost:${port}`;
}
}

After the micro-app is bundled, the font files and images in the css load 404

The reason is that qiankun changed the external link style to the inline style, but the loading path of the font file and background image is a relative path.

Once the css file is packaged, you cannot modify the path of the font file and background image by dynamically modifying the publicPath.

There are mainly the following solutions:

  1. Upload all static resources such as pictures to cdn, and directly reference the address of cdn in css (recommended)

  2. Use the url-loader of webpack to package font files and images as base64 (suitable for projects with small font files and images)(recommended)

module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
use: [
{
loader: 'url-loader',
options: {},
},
],
},
],
},
};

vue-cli3 project:

module.exports = {
chainWebpack: (config) => {
config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
config.module.rule('images').use('url-loader').loader('url-loader').options({}).end();
},
};

vue-cli5 project, use the asset/inline of webpack replace url-loader:

module.exports = {
chainWebpack: (config) => {
config.module.rule('fonts').type('asset/inline').set('generator', {});
config.module.rule('images').type('asset/inline').set('generator', {});
},
};
  1. Use the file-loader of webpack to inject the full path when packaging it (suitable for projects with large font files and images)
const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath,
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
},
},
],
},
],
},
};

vue-cli3 project:

const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
chainWebpack: (config) => {
const fontRule = config.module.rule('fonts');
fontRule.uses.clear();
fontRule
.use('file-loader')
.loader('file-loader')
.options({
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
})
.end();
const imgRule = config.module.rule('images');
imgRule.uses.clear();
imgRule
.use('file-loader')
.loader('file-loader')
.options({
name: 'img/[name].[hash:8].[ext]',
publicPath,
})
.end();
},
};
  1. Combine the two schemes, convert small files to base64, and inject path prefixes for large files
const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/i,
use: [
{
loader: 'url-loader',
options: {},
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath,
},
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
use: [
{
loader: 'url-loader',
options: {},
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
},
},
},
],
},
],
},
};

vue-cli3 project:

const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
chainWebpack: (config) => {
config.module
.rule('fonts')
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096, // Less than 4kb will be packaged as base64
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
},
},
})
.end();
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096, // Less than 4kb will be packaged as base64
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath,
},
},
});
},
};
  1. The vue-cli3 project can package css into js without generating files separately (not recommended, only suitable for projects with less css)

Configuration reference vue-cli3 official website:

module.exports = {
css: {
extract: false,
},
};

Must a sub app asset support cors?

Yes it is.

Since qiankun get assets which imported by sub app via fetch, these static resources must be required to support cors.

See Enable Nginx Cors.

How to solve that micro apps loaded failed due to abnormal scripts inserted dynamically by carriers

Scripts inserted by carriers are usually marked with async to avoid loading of block micro apps. This is usually no problem, such as:

<script async src="//www.rogue.com/rogue.js"></script>

However, if some inserted scripts are not marked as async, once such scripts fail to run, the entire application will be blocked and subsequent scripts will no longer be executed. We can solve this problem in the following ways:

Use a custom getTemplate method

Filter abnormal scripts in the HTML template of the micro app through the getTemplate method implemented by yourself.

import { start } from 'qiankun';
+

qiankun

FAQ

Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry

This error thrown as qiankun could not find the exported lifecycle method from your entry js.

To solve the exception, try the following steps:

  1. check you have exported the specified lifecycles, see the doc

  2. check you have set the specified configuration with your bundler, see the doc

  3. Check the webpack of micro app whether it configured with output.globalObject or not, be sure its value was window if it had, or remove it to use default value.

  4. Check your package.json name field is unique between sub apps.

  5. Check if the entry js in the sub-app's entry HTML is the last script to load. If not, move the order to make it be the last, or manually mark the entry js as entry in the HTML, such as:

    <script src="/antd.js"></script>
    <script src="/appEntry.js" entry></script>
    <script src="https://www.google.com/analytics.js"></script>
  6. If the development environment is OK but the production environment is not, check whether the index.html and entry js of the micro app are returned normally, for example, 404.html is returned.

  7. If you're using webpack5 and not using module federation, please see here

  8. If you are using webpack5 and using module federation, you need to expose the life cycle function in the index file, and then expose the life cycle function externally in the bootstrap file.

const promise = import("index");
export const bootstrap = () => promise.then(m => m.boostrap());
export const mount = () => promise.then(m => m.mount());
export const unmount = () => promise.then(m => m.unmount());
  1. Check whether the main app and micro-app use AMD or CommonJS. Check method: run the main app and the micro-app independently, and enter the following code in the console: (typeof exports === 'object' && typeof module === 'object') || (typeof define === 'function' && define.amd) || typeof exports === 'object',If it returns true,that it is caused by this reason, and there are mainly the following two solutions:

    • Solution 1: Modify the libraryTarget of the micro-app webpack to 'window'.
    const packageName = require('./package.json').name;
    module.exports = {
    output: {
    library: `${packageName}-[name]`,
    - libraryTarget: 'umd',
    + libraryTarget: 'window',
    jsonpFunction: `webpackJsonp_${packageName}`,
    },
    };
    • Solution 2: The micro-app is not bundle with umd, directly mount the life cycle function to the window in the entry file, refer toMicro app built without webpack.
  2. If it still not works after the steps above, this is usually due to browser compatibility issues. Try to set the webpack output.library of the broken sub app the same with your main app registration for your app, such as:

Such as here is the main configuration:

// main app
registerMicroApps([
{
name: 'brokenSubApp',
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/react',
},
]);

Set the output.library the same with main app registration:

module.exports = {
output: {
// Keep the same with the registration in main app
library: 'brokenSubApp',
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};

Application died in status NOT_MOUNTED: Target container with #container not existed after xxx mounted!

This error thrown as the container DOM does not exist after the micro app is loaded. The possible reasons are:

  1. The root id of the micro app conflicts with other DOM, and the solution is to modify the search range of the root id.

    vue micro app:

    function render(props = {}) {
    const { container } = props;
    instance = new Vue({
    router,
    store,
    render: (h) => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
    }
    export async function mount(props) {
    render(props);
    }

    react micro app:

    function render(props) {
    const { container } = props;
    ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
    }
    export async function mount(props) {
    render(props);
    }
    export async function unmount(props) {
    const { container } = props;
    ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
    }
  2. Some js of micro app use document.write, such as AMAP 1.x version, Tencent Map 2.x version.

    If it is caused by the map js, see if the upgrade can be resolved, for example, upgrade the AMAP map to version 2.x.

    If the upgrade cannot be resolved, it is recommended to put the map on the main app to load. The micro app also introduces this map js (used in run independently), but add the ignore attribute to the <script> tag:

    <script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>

    In other cases, please do not use document.write.

Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!

This error usually occurs when the main app is Vue, and the container is written on a routing page and uses the routing transition effect. Some special transition effect caused the container not to exist in the mounting process of the micro app. The solution is to use other transition effects, or remove the routing transition.

Application died in status NOT_MOUNTED: Target container with #container not existed while xxx loading!

Similar to the above error, This error thrown as the container DOM does not exist when the micro app is loaded. Generally, it is caused by incorrect calling timing of the start function, just adjust the calling timing of the start function.

How to determine the completion of the container DOM loading? The vue app can be called during the mounted life cycle, and the react app can be called during the componentDidMount life cycle.

If it still reports an error, check whether the container DOM is placed on a routing page of the main app, please refer to [How to load micro apps on a routing page of the main app](#How to load micro apps on a routing page of the main app)

[import-html-entry]: error occurs while excuting xxx script http://xxx.xxx.xxx/x.js

The first line is just an auxiliary information printed by qiankun through console.error, which is used to help users quickly know which js has an error, not a real exception. The real exception information is on the second line.

For example, such an error indicates that when qiankun was executing the http://localhost:9100/index.bundle.js of the sub application, this js itself threw an exception. The specific exception information is Uncaught TypeError: Cannot read property 'call' of undefined.

Sub-application exceptions can be attempted to be resolved through the following steps:

Check the error js for syntax errors according to the specific exception information, such as missing semicolons, dependence on uninitialized variables, etc. Whether it depends on global variables provided by the main application, but the main application is not actually initialized. Compatibility issues. The js itself of the sub-application has syntax compatibility issues in the current runtime environment.

How to load micro apps on a routing page of the main app

It must be ensured that the routing page of the main app is also loaded when the micro app is loaded.

vue + vue-router main app:

  1. When the main app registers this route, add a * to path, Note: If this route has other sub-routes, you need to register another route, just use this component.
    const routes = [
    {
    path: '/portal/*',
    name: 'portal',
    component: () => import('../views/Portal.vue'),
    },
    ];
  2. The activeRule of the micro app needs to include the route path of the main app.
    registerMicroApps([
    {
    name: 'app1',
    entry: 'http://localhost:8080',
    container: '#container',
    activeRule: '/portal/app1',
    },
    ]);
  3. Call the start function in the mounted cycle of the Portal.vue component, be careful not to call it repeatedly.
    import { start } from 'qiankun';
    export default {
    mounted() {
    if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start();
    }
    },
    };

react + react-router main app:only need to make the activeRule of the sub app include the route of the main app.

angular + angular-router main app,similar to the Vue app:

  1. The main app registers a wildcard sub route for this route, and the content is empty.

    const routes: Routes = [
    {
    path: 'portal',
    component: PortalComponent,
    children: [{ path: '**', component: EmptyComponent }],
    },
    ];
  2. The activeRule of the micro app needs to include the route path of the main app.

    registerMicroApps([
    {
    name: 'app1',
    entry: 'http://localhost:8080',
    container: '#container',
    activeRule: '/portal/app1',
    },
    ]);
  3. Call the start function in the ngAfterViewInit cycle of this routing component, be careful not to call it repeatedly.

    import { start } from 'qiankun';
    export class PortalComponent implements AfterViewInit {
    ngAfterViewInit(): void {
    if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start();
    }
    }
    }

Vue Router Error - Uncaught TypeError: Cannot redefine property: $router

If you pass { sandbox: true } to start() function, qiankun will use Proxy to isolate global window object for sub applications. When you access window.Vue in sub application's code,it will check whether the Vue property in the proxyed window object. If the property does not exist, it will look it up in the global window object and return it.

There are three lines code in the vue-router as followed, and it will access window.Vue once the vue-router module is loaded. And the window.Vue in following code is your master application's Vue.

if (inBrowser && window.Vue) {
window.Vue.use(VueRouter);
}

To solve the error, choose one of the options listed below:

  1. Use bundler to pack Vue library, instead of CDN or external module
  2. Rename Vue to other name in master application, eg: window.Vue2 = window.Vue; delete window.Vue

Why dynamic imported assets missing?

Two way to solve that:

1. With webpack live public path config

qiankun will inject a live public path variable before your sub app bootstrap, what you need is to add this code at the top of your sub app entry js:

__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;

For more details, check the webpack doc.

Runtime publicPath addresses the problem of incorrect scripts, styles, images, and other addresses for dynamically loaded in sub application.

2. With webpack static public path config

You need to set your publicPath configuration to an absolute url, and in development with webpack it might be:

{
output: {
publicPath: `//localhost:${port}`;
}
}

After the micro-app is bundled, the font files and images in the css load 404

The reason is that qiankun changed the external link style to the inline style, but the loading path of the font file and background image is a relative path.

Once the css file is packaged, you cannot modify the path of the font file and background image by dynamically modifying the publicPath.

There are mainly the following solutions:

  1. Upload all static resources such as pictures to cdn, and directly reference the address of cdn in css (recommended)

  2. Use the url-loader of webpack to package font files and images as base64 (suitable for projects with small font files and images)(recommended)

module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
use: [
{
loader: 'url-loader',
options: {},
},
],
},
],
},
};

vue-cli3 project:

module.exports = {
chainWebpack: (config) => {
config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
config.module.rule('images').use('url-loader').loader('url-loader').options({}).end();
},
};

vue-cli5 project, use the asset/inline of webpack replace url-loader:

module.exports = {
chainWebpack: (config) => {
config.module.rule('fonts').type('asset/inline').set('generator', {});
config.module.rule('images').type('asset/inline').set('generator', {});
},
};
  1. Use the file-loader of webpack to inject the full path when packaging it (suitable for projects with large font files and images)
const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath,
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
},
},
],
},
],
},
};

vue-cli3 project:

const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
chainWebpack: (config) => {
const fontRule = config.module.rule('fonts');
fontRule.uses.clear();
fontRule
.use('file-loader')
.loader('file-loader')
.options({
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
})
.end();
const imgRule = config.module.rule('images');
imgRule.uses.clear();
imgRule
.use('file-loader')
.loader('file-loader')
.options({
name: 'img/[name].[hash:8].[ext]',
publicPath,
})
.end();
},
};
  1. Combine the two schemes, convert small files to base64, and inject path prefixes for large files
const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/i,
use: [
{
loader: 'url-loader',
options: {},
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath,
},
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
use: [
{
loader: 'url-loader',
options: {},
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
},
},
},
],
},
],
},
};

vue-cli3 project:

const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
module.exports = {
chainWebpack: (config) => {
config.module
.rule('fonts')
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096, // Less than 4kb will be packaged as base64
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
},
},
})
.end();
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096, // Less than 4kb will be packaged as base64
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
publicPath,
},
},
});
},
};
  1. The vue-cli3 project can package css into js without generating files separately (not recommended, only suitable for projects with less css)

Configuration reference vue-cli3 official website:

module.exports = {
css: {
extract: false,
},
};

Must a sub app asset support cors?

Yes it is.

Since qiankun get assets which imported by sub app via fetch, these static resources must be required to support cors.

See Enable Nginx Cors.

How to solve that micro apps loaded failed due to abnormal scripts inserted dynamically by carriers

Scripts inserted by carriers are usually marked with async to avoid loading of block micro apps. This is usually no problem, such as:

<script async src="//www.rogue.com/rogue.js"></script>

However, if some inserted scripts are not marked as async, once such scripts fail to run, the entire application will be blocked and subsequent scripts will no longer be executed. We can solve this problem in the following ways:

Use a custom getTemplate method

Filter abnormal scripts in the HTML template of the micro app through the getTemplate method implemented by yourself.

import { start } from 'qiankun';
start({
getTemplate(tpl) {
return tpl.replace('<script src="/to-be-replaced.js"><script>', '');
},
});

Use custom fetch method

Intercept abnormal scripts through the fetch method implemented by yourself.

import { start } from 'qiankun';
start({
async fetch(url, ...args) {
if (url === 'http://to-be-replaced.js') {
return {
async text() {
return '';
},
};
}
return window.fetch(url, ...args);
},
});

Change the response Content-type of micro app's HTML to text/plain (ultimate solution)

The principle is that the carriers can only recognize the request whose response's content-type is text/html and insert the script, and the response of the text/plain type will not be hijacked.

How to modify the content-type of the response header of the micro-application HTML request, you can google it yourself, and there is a simpler and more efficient solution:

  1. Copy an index.txt file from index.html when the micro-app is published.

  2. Change entry in the main app to a txt address, for example:

    registerMicroApps(
    [
    - { name: 'app1', entry: '//localhost:8080/index.html', container, activeRule },
    + { name: 'app1', entry: '//localhost:8080/index.txt', container, activeRule },
    ],
    );

How to guarantee the main app stylesheet isolated with sub apps?

Qiankun will isolate stylesheet between your sub apps automatically, you can manually ensure isolation between master and child applications. Such as add a prefix to all classes in the master application, and if you are using ant-design, you can follow this doc to make it works.

Example for antd:

  1. use webpack to modify antd less variable

    {
    loader: 'less-loader',
    + options: {
    + modifyVars: {
    + '@ant-prefix': 'yourPrefix',
    + },
    + javascriptEnabled: true,
    + },
    }
  2. set antd ConfigProvider

    import { ConfigProvider } from 'antd';
    @@ -75,7 +75,7 @@
    start({
    fetch(url, ...args) {
    // Enable cors mode for the specified microapp
    if (url === 'http://app.alipay.com/entry.html') {
    return window.fetch(url, {
    ...args,
    mode: 'cors',
    credentials: 'include',
    });
    }
    return window.fetch(url, ...args);
    },
    });
  3. If you load the microapp via loadMicroApp, you need to configure a custom fetch when invoking, such as:

    import { loadMicroApp } from 'qiankun';
    loadMicroApp(app, {
    fetch(url, ...args) {
    // Enable cors mode for the specified microapp
    if (url === 'http://app.alipay.com/entry.html') {
    return window.fetch(url, {
    ...args,
    mode: 'cors',
    credentials: 'include',
    });
    }
    -
    return window.fetch(url, ...args);
    },
    });
+
return window.fetch(url, ...args);
},
});
- + diff --git a/guide/getting-started/index.html b/guide/getting-started/index.html index d8fbdbb0d..606a1a7a2 100644 --- a/guide/getting-started/index.html +++ b/guide/getting-started/index.html @@ -72,7 +72,7 @@
/**
* Methods that are called each time the application is switched/unloaded,
* usually in this case we uninstall the application instance of the subapplication.
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
);
}
/**
* Optional lifecycle,just available with loadMicroApp way
*/
export async function update(props) {
console.log('update props', props);
}

As qiankun based on single-spa, you can find more documentation about the sub-application lifecycle here.

Refer to example without bundler

2. Config Sub App Bundler

In addition to exposing the corresponding life-cycle hooks in the code, in order for the main application to correctly identify some of the information exposed by the sub-application, the sub-application bundler needs to add the following configuration:

webpack:

If using Webpack v5:

const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${packageName}`,
},
};

If using Webpack v4:

const packageName = require('./package.json').name;
-
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};

You can check the configuration description from webpack doc

+
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};

You can check the configuration description from webpack doc

- + diff --git a/guide/index.html b/guide/index.html index 88b8e88b4..d6f490c0d 100644 --- a/guide/index.html +++ b/guide/index.html @@ -65,7 +65,7 @@ Introduction - qiankun -

Introduction

Qiankun is an implementation of Micro Frontends, which based on single-spa. It aims to make it easier and painless to build a production-ready microfront-end architecture system.

Qiankun hatched from Ant Financial’s unified front-end platform for cloud products based on micro-frontends architecture. After full testing and polishing of a number of online applications, we extracted its micro-frontends kernel and open sourced it. We hope to help the systems who has the same requirement more convenient to build its own micro-frontends application in the community. At the same time, with the help of community, qiankun will be polished and improved.

At present qiankun has served more than 2000 online applications inside Ant, and it is definitely trustworthy in terms of ease of use and completeness.

What Are Micro FrontEnds

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends

Micro Frontends architecture has the following core values:

  • Technology Agnostic

    The main framework does not restrict access to the technology stack of the application, and the sub-applications have full autonomy.

  • Independent Development and Deployment

    The sub application repo is independent, and the frontend and backend can be independently developed. After deployment, the main framework can be updated automatically.

  • Incremental Upgrade

    In the face of various complex scenarios, it is often difficult for us to upgrade or refactor the entire technology stack of an existing system. Micro frontends is a very good method and strategy for implementing progressive refactoring.

  • Isolated Runtime

    State is isolated between each subapplication and no shared runtime state.

The micro-frontends architecture is designed to solve the application of a single application in a relatively long time span. As a result of the increase in the number of people and teams involved, it has evolved from a common application to a Frontend Monolith then becomes unmaintainable. Such a problem is especially common in enterprise web applications.

For more related introductions about micro frontends, I recommend that you check out these articles:

Core Design Philosophy Of qiankun

  • 🥄 Simple

    Since the main application sub-applications can be independent of the technology stack, qiankun is just a jQuery-like library for users. You need to call several qiankun APIs to complete the micro frontends transformation of your application. At the same time, due to the design of qiankun's HTML entry and sandbox, accessing sub-applications is as simple as using an iframe.

  • 🍡 Decoupling/Technology Agnostic

    As the core goal of the micro frontends is to disassemble the monolithic application into a number of loosely coupled micro applications that can be autonomous, all the designs of qiankun are follow this principle, such as HTML Entry, sandbox, and communicating mechanism between applications. Only in this way can we ensure that sub-applications truly have the ability to develop and run independently.

How Does Qiankun Works

TODO

Why Not Iframe

Check this article Why Not Iframe

Features

  • 📦 Based On single-spa , provide a more out-of-box APIs.
  • 📱 Technology Agnostic,any javascript framework can use/integrate, whether React/Vue/Angular/JQuery or the others.
  • 💪 HTML Entry access mode, allows you to access the son as simple application like use the iframe.
  • 🛡 Style Isolation, make sure styles don't interfere with each other.
  • 🧳 JS Sandbox, ensure that global variables/events do not conflict between sub-applications.
  • Prefetch Assets, prefetch unopened sub-application assets during the browser idle time to speed up the sub-application opening speed.
  • 🔌 Umi Plugin, @umijs/plugin-qiankun is provided for umi applications to switch to a micro frontends architecture system with one line code.
+

Introduction

Qiankun is an implementation of Micro Frontends, which based on single-spa. It aims to make it easier and painless to build a production-ready microfront-end architecture system.

Qiankun hatched from Ant Financial’s unified front-end platform for cloud products based on micro-frontends architecture. After full testing and polishing of a number of online applications, we extracted its micro-frontends kernel and open sourced it. We hope to help the systems who has the same requirement more convenient to build its own micro-frontends application in the community. At the same time, with the help of community, qiankun will be polished and improved.

At present qiankun has served more than 2000 online applications inside Ant, and it is definitely trustworthy in terms of ease of use and completeness.

What Are Micro FrontEnds

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends

Micro Frontends architecture has the following core values:

  • Technology Agnostic

    The main framework does not restrict access to the technology stack of the application, and the sub-applications have full autonomy.

  • Independent Development and Deployment

    The sub application repo is independent, and the frontend and backend can be independently developed. After deployment, the main framework can be updated automatically.

  • Incremental Upgrade

    In the face of various complex scenarios, it is often difficult for us to upgrade or refactor the entire technology stack of an existing system. Micro frontends is a very good method and strategy for implementing progressive refactoring.

  • Isolated Runtime

    State is isolated between each subapplication and no shared runtime state.

The micro-frontends architecture is designed to solve the application of a single application in a relatively long time span. As a result of the increase in the number of people and teams involved, it has evolved from a common application to a Frontend Monolith then becomes unmaintainable. Such a problem is especially common in enterprise web applications.

For more related introductions about micro frontends, I recommend that you check out these articles:

Core Design Philosophy Of qiankun

  • 🥄 Simple

    Since the main application sub-applications can be independent of the technology stack, qiankun is just a jQuery-like library for users. You need to call several qiankun APIs to complete the micro frontends transformation of your application. At the same time, due to the design of qiankun's HTML entry and sandbox, accessing sub-applications is as simple as using an iframe.

  • 🍡 Decoupling/Technology Agnostic

    As the core goal of the micro frontends is to disassemble the monolithic application into a number of loosely coupled micro applications that can be autonomous, all the designs of qiankun are follow this principle, such as HTML Entry, sandbox, and communicating mechanism between applications. Only in this way can we ensure that sub-applications truly have the ability to develop and run independently.

How Does Qiankun Works

TODO

Why Not Iframe

Check this article Why Not Iframe

Features

  • 📦 Based On single-spa , provide a more out-of-box APIs.
  • 📱 Technology Agnostic,any javascript framework can use/integrate, whether React/Vue/Angular/JQuery or the others.
  • 💪 HTML Entry access mode, allows you to access the son as simple application like use the iframe.
  • 🛡 Style Isolation, make sure styles don't interfere with each other.
  • 🧳 JS Sandbox, ensure that global variables/events do not conflict between sub-applications.
  • Prefetch Assets, prefetch unopened sub-application assets during the browser idle time to speed up the sub-application opening speed.
  • 🔌 Umi Plugin, @umijs/plugin-qiankun is provided for umi applications to switch to a micro frontends architecture system with one line code.
- + diff --git a/guide/tutorial/index.html b/guide/tutorial/index.html index c87f67cad..89934f878 100644 --- a/guide/tutorial/index.html +++ b/guide/tutorial/index.html @@ -72,7 +72,7 @@
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
export async function mount(props) {
console.log('[react16] props from main framework', props);
render(props);
}
export async function unmount(props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

It's important, When mount a sub-application through ReactDOM.render, need to ensure each sub-application load with a new router instance.

  1. Modify webpack configuration

    Install the plugin @rescripts/cli, of course, you can also choose other plugins, 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';
    +
    module.exports = {
    webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal
    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;
    @@ -81,13 +81,13 @@
    let router = null;
    let instance = null;
    function render(props = {}) {
    const { container } = props;
    router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
    mode: 'history',
    routes,
    });
    instance = new Vue({
    router,
    store,
    render: (h) => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
    }
    // when run independently
    if (!window.__POWERED_BY_QIANKUN__) {
    render();
    }
    -
    export async function bootstrap() {
    console.log('[vue] vue app bootstraped');
    }
    export async function mount(props) {
    console.log('[vue] props from main framework', props);
    render(props);
    }
    export async function unmount() {
    instance.$destroy();
    instance.$el.innerHTML = '';
    instance = null;
    router = null;
    }
  2. Modify webpack configuration(vue.config.js):

    const { name } = require('./package');
    module.exports = {
    devServer: {
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    configureWebpack: {
    output: {
    library: `${name}-[name]`,
    libraryTarget: 'umd', // bundle the micro app into umd library format
    jsonpFunction: `webpackJsonp_${name}`,
    },
    },
    };

Angular micro app

Take the angular 9 project generated by Angular-cli 9 as an example, other versions of angular will be added later.

  1. Add the file public-path.js in the src directory with the content:

    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  2. Set the base of history mode routing, src/app/app-routing.module.ts file:

    + import { APP_BASE_HREF } from '@angular/common';
    @NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
    // @ts-ignore
    + providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]
    })
  3. Modify the entry file, src/main.ts file:

    import './public-path';
    import { enableProdMode, NgModuleRef } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';
    +
    export async function bootstrap() {
    console.log('[vue] vue app bootstraped');
    }
    export async function mount(props) {
    console.log('[vue] props from main framework', props);
    render(props);
    }
    export async function unmount() {
    instance.$destroy();
    instance.$el.innerHTML = '';
    instance = null;
    router = null;
    }
  4. Modify webpack configuration(vue.config.js):

    const { name } = require('./package');
    module.exports = {
    devServer: {
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    configureWebpack: {
    output: {
    library: `${name}-[name]`,
    libraryTarget: 'umd', // bundle the micro app into umd library format
    jsonpFunction: `webpackJsonp_${name}`, // // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal
    },
    },
    };

Angular micro app

Take the angular 9 project generated by Angular-cli 9 as an example, other versions of angular will be added later.

  1. Add the file public-path.js in the src directory with the content:

    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  2. Set the base of history mode routing, src/app/app-routing.module.ts file:

    + import { APP_BASE_HREF } from '@angular/common';
    @NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
    // @ts-ignore
    + providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]
    })
  3. Modify the entry file, src/main.ts file:

    import './public-path';
    import { enableProdMode, NgModuleRef } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';
    if (environment.production) {
    enableProdMode();
    }
    let app: void | NgModuleRef<AppModule>;
    async function render() {
    app = await platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) => console.error(err));
    }
    if (!(window as any).__POWERED_BY_QIANKUN__) {
    render();
    }
    export async function bootstrap(props: Object) {
    console.log(props);
    }
    export async function mount(props: Object) {
    render();
    }
    -
    export async function unmount(props: Object) {
    console.log(props);
    // @ts-ignore
    app.destroy();
    }
  4. Modify webpack bundling configuration

    First install the @angular-builders/custom-webpack plugin. Note: Angular 9 project can only install 9.x version, angular 10 project can install the latest version.

    npm i @angular-builders/custom-webpack@9.2.0 -D

    Add custom-webpack.config.js to the root directory with the content:

    const appName = require('./package.json').name;
    module.exports = {
    devServer: {
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    output: {
    library: `${appName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${appName}`,
    },
    };

    Modify angular.json, change the values of [packageName]> architect> build> builder and [packageName]> architect> serve> builder to the plugins we installed, and add our webpack's configuration file to [ packageName]> architect> build> options.

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
    "options": {
    + "customWebpackConfig": {
    + "path": "./custom-webpack.config.js"
    + }
    }
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/custom-webpack:dev-server",
  5. Solve the problem of zone.js

    Import zone.js in main app, it needs to be imported before import qiankun.

    Delete the code of import zone.js in the src/polyfills.ts of the micro app.

    - import 'zone.js/dist/zone';

    Add the following content to the <head> tag in the src/index.html of the micro app, which is used when the micro app is accessed independently.

    <!-- Other CDN/local packages can also be used -->
    <script src="https://unpkg.com/zone.js" ignore></script>
  6. Fix ng build comand's error report, modify tsconfig.json file, referenceissues/431.

    - "target": "es2015",
    + "target": "es5",
    + "typeRoots": [
    + "node_modules/@types"
    + ],
  7. In order to prevent the conflict of <app-root></app-root> when the main app or other micro apps are also angular, it is recommended to add a unique id to <app-root>, such as Say the current app name.

    src/index.html :

    - <app-root></app-root>
    + <app-root id="angular9"></app-root>

    src/app/app.component.ts :

    - selector: 'app-root',
    + selector: '#angular9 app-root',

Of course, you can also choose to use the single-spa-angular plugin, refer to single-spa-angular official websiteangular demo

supplement)The angular7 has the same steps as angular9 except for step 4. The steps for angular7 to modify the webpack configuration are as follows:

In addition to installing the 7.x version of angular-builders/custom-webpack, you also need to install angular-builders/dev-server.

npm i @angular-builders/custom-webpack@7 -D
npm i @angular-builders/dev-server -D

Add custom-webpack.config.js to the root directory, same as above.

Modify angular.json, [packageName] > architect > build > builder is the same as Angular9, and [packageName] > architect > serve > builder is different from Angular9.

- "builder": "@angular-devkit/build-angular:browser",
+ "builder": "@angular-builders/custom-webpack:browser",
"options": {
+ "customWebpackConfig": {
+ "path": "./custom-webpack.config.js"
+ }
}
- "builder": "@angular-devkit/build-angular:dev-server",
+ "builder": "@angular-builders/dev-server:generic",

Micro app built without webpack

Some apps that are not built by webpack, such as jQuery app, jsp app, can be handled according to this.

Before modify, please make sure that the resources such as pictures, audio and video in your project can be loaded normally. If the addresses of these resources are all full paths (for example, https://qiankun.umijs.org/logo.png), there is no problem. If they are all relative paths, you need to upload these resources to the server first and reference the full path.

The only change is that we need to declare a script tag, to export the lifecycles

example:

  1. declare entry script

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Purehtml Example</title>
    </head>
    <body>
    <div>
    Purehtml Example
    </div>
    </body>
    + <script src="//yourhost/entry.js" entry></script>
    </html>
  2. export lifecycles in the entry

    const render = ($) => {
    $('#purehtml-container').html('Hello, render with jQuery');
    return Promise.resolve();
    };
    -
    ((global) => {
    global['purehtml'] = {
    bootstrap: () => {
    console.log('purehtml bootstrap');
    return Promise.resolve();
    },
    mount: () => {
    console.log('purehtml mount');
    return render($);
    },
    unmount: () => {
    console.log('purehtml unmount');
    return Promise.resolve();
    },
    };
    })(window);

refer to the purehtml examples

At the same time, the subApp must support the CORS

umi-qiankun app

For the tutorial of umi-qiankun, please go to umi official website and umi-qiankun official demo

+
export async function unmount(props: Object) {
console.log(props);
// @ts-ignore
app.destroy();
}
  • Modify webpack bundling configuration

    First install the @angular-builders/custom-webpack plugin. Note: Angular 9 project can only install 9.x version, angular 10 project can install the latest version.

    npm i @angular-builders/custom-webpack@9.2.0 -D

    Add custom-webpack.config.js to the root directory with the content:

    const appName = require('./package.json').name;
    module.exports = {
    devServer: {
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    output: {
    library: `${appName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${appName}`, // // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal
    },
    };

    Modify angular.json, change the values of [packageName]> architect> build> builder and [packageName]> architect> serve> builder to the plugins we installed, and add our webpack's configuration file to [ packageName]> architect> build> options.

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
    "options": {
    + "customWebpackConfig": {
    + "path": "./custom-webpack.config.js"
    + }
    }
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/custom-webpack:dev-server",
  • Solve the problem of zone.js

    Import zone.js in main app, it needs to be imported before import qiankun.

    Delete the code of import zone.js in the src/polyfills.ts of the micro app.

    - import 'zone.js/dist/zone';

    Add the following content to the <head> tag in the src/index.html of the micro app, which is used when the micro app is accessed independently.

    <!-- Other CDN/local packages can also be used -->
    <script src="https://unpkg.com/zone.js" ignore></script>
  • Fix ng build comand's error report, modify tsconfig.json file, referenceissues/431.

    - "target": "es2015",
    + "target": "es5",
    + "typeRoots": [
    + "node_modules/@types"
    + ],
  • In order to prevent the conflict of <app-root></app-root> when the main app or other micro apps are also angular, it is recommended to add a unique id to <app-root>, such as Say the current app name.

    src/index.html :

    - <app-root></app-root>
    + <app-root id="angular9"></app-root>

    src/app/app.component.ts :

    - selector: 'app-root',
    + selector: '#angular9 app-root',
  • Of course, you can also choose to use the single-spa-angular plugin, refer to single-spa-angular official websiteangular demo

    supplement)The angular7 has the same steps as angular9 except for step 4. The steps for angular7 to modify the webpack configuration are as follows:

    In addition to installing the 7.x version of angular-builders/custom-webpack, you also need to install angular-builders/dev-server.

    npm i @angular-builders/custom-webpack@7 -D
    npm i @angular-builders/dev-server -D

    Add custom-webpack.config.js to the root directory, same as above.

    Modify angular.json, [packageName] > architect > build > builder is the same as Angular9, and [packageName] > architect > serve > builder is different from Angular9.

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
    "options": {
    + "customWebpackConfig": {
    + "path": "./custom-webpack.config.js"
    + }
    }
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/dev-server:generic",

    Micro app built without webpack

    Some apps that are not built by webpack, such as jQuery app, jsp app, can be handled according to this.

    Before modify, please make sure that the resources such as pictures, audio and video in your project can be loaded normally. If the addresses of these resources are all full paths (for example, https://qiankun.umijs.org/logo.png), there is no problem. If they are all relative paths, you need to upload these resources to the server first and reference the full path.

    The only change is that we need to declare a script tag, to export the lifecycles

    example:

    1. declare entry script

      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Purehtml Example</title>
      </head>
      <body>
      <div>
      Purehtml Example
      </div>
      </body>
      + <script src="//yourhost/entry.js" entry></script>
      </html>
    2. export lifecycles in the entry

      const render = ($) => {
      $('#purehtml-container').html('Hello, render with jQuery');
      return Promise.resolve();
      };
      +
      ((global) => {
      global['purehtml'] = {
      bootstrap: () => {
      console.log('purehtml bootstrap');
      return Promise.resolve();
      },
      mount: () => {
      console.log('purehtml mount');
      return render($);
      },
      unmount: () => {
      console.log('purehtml unmount');
      return Promise.resolve();
      },
      };
      })(window);

    refer to the purehtml examples

    At the same time, the subApp must support the CORS

    umi-qiankun app

    For the tutorial of umi-qiankun, please go to umi official website and umi-qiankun official demo

    - + diff --git a/index.html b/index.html index b45cb6ea2..72f5104a3 100644 --- a/index.html +++ b/index.html @@ -99,6 +99,6 @@ } })(); - + diff --git a/umi.78e90449.js b/umi.78e90449.js new file mode 100644 index 000000000..fad38ccf3 --- /dev/null +++ b/umi.78e90449.js @@ -0,0 +1 @@ +(function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/qiankun/",n(n.s=0)})({"++zV":function(e,t,n){var r=n("I+eb"),a=n("eDxR"),o=n("glrk"),i=a.toKey,l=a.set;r({target:"Reflect",stat:!0},{defineMetadata:function(e,t,n){var r=arguments.length<4?void 0:i(arguments[3]);l(e,t,o(n),r)}})},"+2oP":function(e,t,n){"use strict";var r=n("I+eb"),a=n("hh1v"),o=n("6LWA"),i=n("I8vh"),l=n("UMSQ"),c=n("/GqU"),u=n("hBjN"),s=n("tiKp"),p=n("Hd5f"),d=n("rkAj"),f=p("slice"),m=d("slice",{ACCESSORS:!0,0:0,1:2}),h=s("species"),g=[].slice,v=Math.max;r({target:"Array",proto:!0,forced:!f||!m},{slice:function(e,t){var n,r,s,p=c(this),d=l(p.length),f=i(e,d),m=i(void 0===t?d:t,d);if(o(p)&&(n=p.constructor,"function"!=typeof n||n!==Array&&!o(n.prototype)?a(n)&&(n=n[h],null===n&&(n=void 0)):n=void 0,n===Array||void 0===n))return g.call(p,f,m);for(r=new(void 0===n?Array:n)(v(m-f,0)),s=0;f=k},l=function(){},t.unstable_forceFrameRate=function(e){0>e||125>>1,a=e[r];if(!(void 0!==a&&0T(i,n))void 0!==c&&0>T(c,i)?(e[r]=c,e[l]=n,r=l):(e[r]=i,e[o]=n,r=o);else{if(!(void 0!==c&&0>T(c,n)))break e;e[r]=c,e[l]=n,r=l}}}return t}return null}function T(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var _=[],I=[],P=1,R=null,C=3,N=!1,M=!1,L=!1;function D(e){for(var t=A(I);null!==t;){if(null===t.callback)j(I);else{if(!(t.startTime<=e))break;j(I),t.sortIndex=t.expirationTime,S(_,t)}t=A(I)}}function F(e){if(L=!1,D(e),!M)if(null!==A(_))M=!0,r(U);else{var t=A(I);null!==t&&a(F,t.startTime-e)}}function U(e,n){M=!1,L&&(L=!1,o()),N=!0;var r=C;try{for(D(n),R=A(_);null!==R&&(!(R.expirationTime>n)||e&&!i());){var l=R.callback;if(null!==l){R.callback=null,C=R.priorityLevel;var c=l(R.expirationTime<=n);n=t.unstable_now(),"function"===typeof c?R.callback=c:R===A(_)&&j(_),D(n)}else j(_);R=A(_)}if(null!==R)var u=!0;else{var s=A(I);null!==s&&a(F,s.startTime-n),u=!1}return u}finally{R=null,C=r,N=!1}}function B(e){switch(e){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var q=l;t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){M||N||(M=!0,r(U))},t.unstable_getCurrentPriorityLevel=function(){return C},t.unstable_getFirstCallbackNode=function(){return A(_)},t.unstable_next=function(e){switch(C){case 1:case 2:case 3:var t=3;break;default:t=C}var n=C;C=t;try{return e()}finally{C=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=q,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=C;C=e;try{return t()}finally{C=n}},t.unstable_scheduleCallback=function(e,n,i){var l=t.unstable_now();if("object"===typeof i&&null!==i){var c=i.delay;c="number"===typeof c&&0l?(e.sortIndex=c,S(I,e),null===A(_)&&e===A(I)&&(L?o():L=!0,a(F,c-l))):(e.sortIndex=i,S(_,e),M||N||(M=!0,r(U))),e},t.unstable_shouldYield=function(){var e=t.unstable_now();D(e);var n=A(_);return n!==R&&null!==R&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime2?a.a.createElement("select",{value:c,onChange:function(e){return i["a"].push(p(e.target.value))}},u.map((function(e){return a.a.createElement("option",{value:e.name,key:e.name},e.label)}))):a.a.createElement(o["Link"],{to:p(s.name)},s.label)):null}),c=l,u=(n("fVI1"),function(e){var t=e.onMobileMenuClick,n=e.navPrefix,i=e.location,l=e.darkPrefix,u=Object(r["useContext"])(o["context"]),s=u.base,p=u.config,d=p.mode,f=p.title,m=p.logo,h=u.nav;return a.a.createElement("div",{className:"__dumi-default-navbar","data-mode":d},a.a.createElement("button",{className:"__dumi-default-navbar-toggle",onClick:t}),a.a.createElement(o["Link"],{className:"__dumi-default-navbar-logo",style:{backgroundImage:m&&"url('".concat(m,"')")},to:s,"data-plaintext":!1===m||void 0},f),a.a.createElement("nav",null,n,h.map((function(e){var t,n=Boolean(null===(t=e.children)||void 0===t?void 0:t.length)&&a.a.createElement("ul",null,e.children.map((function(e){return a.a.createElement("li",{key:e.path},a.a.createElement(o["NavLink"],{to:e.path},e.title))})));return a.a.createElement("span",{key:e.title||e.path},e.path?a.a.createElement(o["NavLink"],{to:e.path,key:e.path},e.title):e.title,n)})),a.a.createElement("div",{className:"__dumi-default-navbar-tool"},a.a.createElement(c,{location:i}),l)))}),s=u,p=(n("hJnp"),["slugs"]);function d(){return d=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}function m(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r=0||(a[n]=e[n]);return a}var h=function(e){var t=e.slugs,n=f(e,p);return a.a.createElement("ul",d({role:"slug-list"},n),t.filter((function(e){var t=e.depth;return t>1&&t<4})).map((function(e){return a.a.createElement("li",{key:e.heading,title:e.value,"data-depth":e.depth},a.a.createElement(o["AnchorLink"],{to:"#".concat(e.heading)},a.a.createElement("span",null,e.value)))})))},g=h,v=(n("Mpie"),function(e){var t=e.mobileMenuCollapsed,n=e.location,i=e.darkPrefix,l=Object(r["useContext"])(o["context"]),u=l.config,s=u.logo,p=u.title,d=u.description,f=u.mode,m=u.repository.url,h=l.menu,v=l.nav,b=l.base,y=l.meta,E=Boolean((y.hero||y.features||y.gapless)&&"site"===f)||!1===y.sidemenu||void 0;return a.a.createElement("div",{className:"__dumi-default-menu","data-mode":f,"data-hidden":E,"data-mobile-show":!t||void 0},a.a.createElement("div",{className:"__dumi-default-menu-inner"},a.a.createElement("div",{className:"__dumi-default-menu-header"},a.a.createElement(o["Link"],{to:b,className:"__dumi-default-menu-logo",style:{backgroundImage:s&&"url('".concat(s,"')")}}),a.a.createElement("h1",null,p),a.a.createElement("p",null,d),/github\.com/.test(m)&&"doc"===f&&a.a.createElement("p",null,a.a.createElement("object",{type:"image/svg+xml",data:"https://img.shields.io/github/stars".concat(m.match(/((\/[^\/]+){2})$/)[1],"?style=social")}))),a.a.createElement("div",{className:"__dumi-default-menu-mobile-area"},!!v.length&&a.a.createElement("ul",{className:"__dumi-default-menu-nav-list"},v.map((function(e){var t,n=Boolean(null===(t=e.children)||void 0===t?void 0:t.length)&&a.a.createElement("ul",null,e.children.map((function(e){return a.a.createElement("li",{key:e.path||e.title},a.a.createElement(o["NavLink"],{to:e.path},e.title))})));return a.a.createElement("li",{key:e.path||e.title},e.path?a.a.createElement(o["NavLink"],{to:e.path},e.title):e.title,n)}))),a.a.createElement(c,{location:n}),i),a.a.createElement("ul",{className:"__dumi-default-menu-list"},!E&&h.map((function(e){var t,r=Boolean(null===(t=y.slugs)||void 0===t?void 0:t.length),i=e.children&&Boolean(e.children.length),l="menu"===y.toc&&!i&&r&&e.path===n.pathname.replace(/([^^])\/$/,"$1"),c=i?e.children.map((function(e){return e.path})):[e.path,n.pathname.startsWith("".concat(e.path,"/"))&&y.title===e.title?n.pathname:null];return a.a.createElement("li",{key:e.path||e.title},a.a.createElement(o["NavLink"],{to:e.path,isActive:function(){return c.includes(n.pathname)}},e.title),Boolean(e.children&&e.children.length)&&a.a.createElement("ul",null,e.children.map((function(e){return a.a.createElement("li",{key:e.path},a.a.createElement(o["NavLink"],{to:e.path,exact:!0},a.a.createElement("span",null,e.title)),Boolean("menu"===y.toc&&"undefined"!==typeof window&&e.path===n.pathname&&r)&&a.a.createElement(g,{slugs:y.slugs}))}))),l&&a.a.createElement(g,{slugs:y.slugs}))})))))}),b=v;n("AK2Z");function y(){return y=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&u.map((function(e){var t;return a.a.createElement("li",{key:e.path,onClick:function(){return i("")}},a.a.createElement(o["AnchorLink"],{to:e.path},(null===(t=e.parent)||void 0===t?void 0:t.title)&&a.a.createElement("span",null,e.parent.title),A(n,e.title)))})),0===u.length&&n&&a.a.createElement("li",{style:{textAlign:"center"}},f)))};n("Zkgb");function T(e,t){return C(e)||R(e,t)||I(e,t)||_()}function _(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function I(e,t){if(e){if("string"===typeof e)return P(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?P(e,t):void 0}}function P(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?arguments[1]:void 0,3);return c(n,(function(e,n){if(r(n,e,t))return c.stop(n)}),void 0,!0,!0).result}})},"0rvr":function(e,t,n){var r=n("glrk"),a=n("O741");e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,e.call(n,[]),t=n instanceof Array}catch(o){}return function(n,o){return r(n),a(o),t?e.call(n,o):n.__proto__=o,n}}():void 0)},"14Sl":function(e,t,n){"use strict";n("rB9j");var r=n("busE"),a=n("0Dky"),o=n("tiKp"),i=n("kmMV"),l=n("kRJp"),c=o("species"),u=!a((function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")})),s=function(){return"$0"==="a".replace(/./,"$0")}(),p=o("replace"),d=function(){return!!/./[p]&&""===/./[p]("a","$0")}(),f=!a((function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2!==n.length||"a"!==n[0]||"b"!==n[1]}));e.exports=function(e,t,n,p){var m=o(e),h=!a((function(){var t={};return t[m]=function(){return 7},7!=""[e](t)})),g=h&&!a((function(){var t=!1,n=/a/;return"split"===e&&(n={},n.constructor={},n.constructor[c]=function(){return n},n.flags="",n[m]=/./[m]),n.exec=function(){return t=!0,null},n[m](""),!t}));if(!h||!g||"replace"===e&&(!u||!s||d)||"split"===e&&!f){var v=/./[m],b=n(m,""[e],(function(e,t,n,r,a){return t.exec===i?h&&!a?{done:!0,value:v.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),{REPLACE_KEEPS_$0:s,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:d}),y=b[0],E=b[1];r(String.prototype,e,y),r(RegExp.prototype,m,2==t?function(e,t){return E.call(e,this,t)}:function(e){return E.call(e,this)})}p&&l(RegExp.prototype[m],"sham",!0)}},"16Al":function(e,t,n){"use strict";var r=n("WbBG");function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},"17x9":function(e,t,n){e.exports=n("16Al")()},"1E5z":function(e,t,n){var r=n("m/L8").f,a=n("UTVS"),o=n("tiKp"),i=o("toStringTag");e.exports=function(e,t,n){e&&!a(e=n?e:e.prototype,i)&&r(e,i,{configurable:!0,value:t})}},"1OyB":function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}n.d(t,"a",(function(){return r}))},"1Y/n":function(e,t,n){var r=n("HAuM"),a=n("ewvW"),o=n("RK3t"),i=n("UMSQ"),l=function(e){return function(t,n,l,c){r(n);var u=a(t),s=o(u),p=i(u.length),d=e?p-1:0,f=e?-1:1;if(l<2)while(1){if(d in s){c=s[d],d+=f;break}if(d+=f,e?d<0:p<=d)throw TypeError("Reduce of empty array with no initial value")}for(;e?d>=0:p>d;d+=f)d in s&&(c=n(c,s[d],d,u));return c}};e.exports={left:l(!1),right:l(!0)}},"1kQv":function(e,t,n){var r=n("I+eb"),a=n("qY7S");r({target:"Set",stat:!0},{from:a})},"25BE":function(e,t,n){"use strict";function r(e){if("undefined"!==typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}n.d(t,"a",(function(){return r}))},"27RR":function(e,t,n){var r=n("I+eb"),a=n("g6v/"),o=n("Vu81"),i=n("/GqU"),l=n("Bs8V"),c=n("hBjN");r({target:"Object",stat:!0,sham:!a},{getOwnPropertyDescriptors:function(e){var t,n,r=i(e),a=l.f,u=o(r),s={},p=0;while(u.length>p)n=a(r,t=u[p++]),void 0!==n&&c(s,t,n);return s}})},"2B1R":function(e,t,n){"use strict";var r=n("I+eb"),a=n("tycR").map,o=n("Hd5f"),i=n("rkAj"),l=o("map"),c=i("map");r({target:"Array",proto:!0,forced:!l||!c},{map:function(e){return a(this,e,arguments.length>1?arguments[1]:void 0)}})},"2N97":function(e,t,n){"use strict";var r=n("xbqb")["default"],a=n("Lw8S")["default"];function o(){var e=n("q1tI");return o=function(){return e},e}function i(e,t){return p(e)||s(e,t)||c(e,t)||l()}function l(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function c(e,t){if(e){if("string"===typeof e)return u(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(e,t):void 0}}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n{e.demos;return a.a.createElement(a.a.Fragment,null,a.a.createElement("div",{className:"markdown"},a.a.createElement("h2",{id:"-installation"},a.a.createElement(o["AnchorLink"],{to:"#-installation","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"\ud83d\udce6 Installation"),a.a.createElement(i["a"],{code:"$ yarn add qiankun # or npm i qiankun -S",lang:"shell"}),a.a.createElement("h2",{id:"-getting-started"},a.a.createElement(o["AnchorLink"],{to:"#-getting-started","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"\ud83d\udd28 Getting Started"),a.a.createElement(i["a"],{code:"import { loadMicroApp } from 'qiankun';\n\n// load micro app\nloadMicroApp({\n name: 'reactApp',\n entry: '//localhost:7100',\n container: '#container',\n props: {\n slogan: 'Hello Qiankun',\n },\n});",lang:"tsx"}),a.a.createElement("p",null,"See details\uff1a",a.a.createElement(o["Link"],{to:"/guide/getting-started"},"Getting Started")),a.a.createElement("h2",{id:"-community"},a.a.createElement(o["AnchorLink"],{to:"#-community","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"\ud83d\udc6c Community"),a.a.createElement(l["a"],null,a.a.createElement("thead",null,a.a.createElement("tr",null,a.a.createElement("th",null,"Github Discussions"),a.a.createElement("th",null,"DingTalk Group"),a.a.createElement("th",null,"WeChat Group"))),a.a.createElement("tbody",null,a.a.createElement("tr",null,a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"https://github.com/umijs/qiankun/discussions"},"qiankun discussions")),a.a.createElement("td",null,a.a.createElement("img",{src:"https://mdn.alipayobjects.com/huamei_zvchwx/afts/img/A*GG8zTJaUnTAAAAAAAAAAAAAADuWEAQ/original",width:150})),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"https://github.com/umijs/qiankun/discussions/2343"},"view group QR code")))))))}));t["default"]=e=>{var t=a.a.useContext(o["context"]),n=t.demos;return a.a.useEffect((()=>{var t;null!==e&&void 0!==e&&null!==(t=e.location)&&void 0!==t&&t.hash&&o["AnchorLink"].scrollToAnchor(decodeURIComponent(e.location.hash.slice(1)))}),[]),a.a.createElement(c,{demos:n})}},"2mql":function(e,t,n){"use strict";var r=n("TOwV"),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},c={};function u(e){return r.isMemo(e)?l:c[e["$$typeof"]]||a}c[r.ForwardRef]=i,c[r.Memo]=l;var s=Object.defineProperty,p=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,f=Object.getOwnPropertyDescriptor,m=Object.getPrototypeOf,h=Object.prototype;function g(e,t,n){if("string"!==typeof t){if(h){var r=m(t);r&&r!==h&&g(e,r,n)}var a=p(t);d&&(a=a.concat(d(t)));for(var i=u(e),l=u(t),c=0;ce.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?arguments[1]:void 0,3),a=new(u(t,o("Set"))),d=l(a.add);return p(n,(function(e){r(e,e,t)&&d.call(a,e)}),void 0,!1,!0),a}})},"49+q":function(e,t,n){"use strict";var r=n("I+eb"),a=n("xDBR"),o=n("fXLg");r({target:"Set",proto:!0,real:!0,forced:a},{addAll:function(){return o.apply(this,arguments)}})},"4Brf":function(e,t,n){"use strict";var r=n("I+eb"),a=n("g6v/"),o=n("2oRo"),i=n("UTVS"),l=n("hh1v"),c=n("m/L8").f,u=n("6JNq"),s=o.Symbol;if(a&&"function"==typeof s&&(!("description"in s.prototype)||void 0!==s().description)){var p={},d=function(){var e=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),t=this instanceof d?new s(e):void 0===e?s():s(e);return""===e&&(p[t]=!0),t};u(d,s);var f=d.prototype=s.prototype;f.constructor=d;var m=f.toString,h="Symbol(test)"==String(s("test")),g=/^Symbol\((.*)\)[^)]+$/;c(f,"description",{configurable:!0,get:function(){var e=l(this)?this.valueOf():this,t=m.call(e);if(i(p,e))return"";var n=h?t.slice(7,-1):t.replace(g,"$1");return""===n?void 0:n}}),r({global:!0,forced:!0},{Symbol:d})}},"4IlW":function(e,t,n){"use strict";var r={MAC_ENTER:3,BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,QUESTION_MARK:63,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,NUMLOCK:144,SEMICOLON:186,DASH:189,EQUALS:187,COMMA:188,PERIOD:190,SLASH:191,APOSTROPHE:192,SINGLE_QUOTE:222,OPEN_SQUARE_BRACKET:219,BACKSLASH:220,CLOSE_SQUARE_BRACKET:221,WIN_KEY:224,MAC_FF_META:224,WIN_IME:229,isTextModifyingKeyEvent:function(e){var t=e.keyCode;if(e.altKey&&!e.ctrlKey||e.metaKey||t>=r.F1&&t<=r.F12)return!1;switch(t){case r.ALT:case r.CAPS_LOCK:case r.CONTEXT_MENU:case r.CTRL:case r.DOWN:case r.END:case r.ESC:case r.HOME:case r.INSERT:case r.LEFT:case r.MAC_FF_META:case r.META:case r.NUMLOCK:case r.NUM_CENTER:case r.PAGE_DOWN:case r.PAGE_UP:case r.PAUSE:case r.PRINT_SCREEN:case r.RIGHT:case r.SHIFT:case r.UP:case r.WIN_KEY:case r.WIN_KEY_RIGHT:return!1;default:return!0}},isCharacterKey:function(e){if(e>=r.ZERO&&e<=r.NINE)return!0;if(e>=r.NUM_ZERO&&e<=r.NUM_MULTIPLY)return!0;if(e>=r.A&&e<=r.Z)return!0;if(-1!==window.navigator.userAgent.indexOf("WebKit")&&0===e)return!0;switch(e){case r.SPACE:case r.QUESTION_MARK:case r.NUM_PLUS:case r.NUM_MINUS:case r.NUM_PERIOD:case r.NUM_DIVISION:case r.SEMICOLON:case r.DASH:case r.EQUALS:case r.COMMA:case r.PERIOD:case r.SLASH:case r.APOSTROPHE:case r.SINGLE_QUOTE:case r.OPEN_SQUARE_BRACKET:case r.BACKSLASH:case r.CLOSE_SQUARE_BRACKET:return!0;default:return!1}}};t["a"]=r},"4WOD":function(e,t,n){var r=n("UTVS"),a=n("ewvW"),o=n("93I0"),i=n("4Xet"),l=o("IE_PROTO"),c=Object.prototype;e.exports=i?Object.getPrototypeOf:function(e){return e=a(e),r(e,l)?e[l]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?c:null}},"4XaG":function(e,t,n){var r=n("dG/n");r("observable")},"4Xet":function(e,t,n){var r=n("0Dky");e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},"4l63":function(e,t,n){var r=n("I+eb"),a=n("wg0c");r({global:!0,forced:parseInt!=a},{parseInt:a})},"4mDm":function(e,t,n){"use strict";var r=n("/GqU"),a=n("RNIs"),o=n("P4y1"),i=n("afO8"),l=n("fdAy"),c="Array Iterator",u=i.set,s=i.getterFor(c);e.exports=l(Array,"Array",(function(e,t){u(this,{type:c,target:r(e),index:0,kind:t})}),(function(){var e=s(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values"),o.Arguments=o.Array,a("keys"),a("values"),a("entries")},"4oU/":function(e,t,n){var r=n("2oRo"),a=r.isFinite;e.exports=Number.isFinite||function(e){return"number"==typeof e&&a(e)}},"4syw":function(e,t,n){var r=n("busE");e.exports=function(e,t,n){for(var a in t)r(e,a,t[a],n);return e}},5921:function(e,t,n){var r=n("I+eb"),a=n("P940");r({target:"Map",stat:!0},{of:a})},"5JV0":function(e,t,n){"use strict";var r=n("I+eb"),a=n("xDBR"),o=n("glrk"),i=n("WGBp"),l=n("ImZN");r({target:"Set",proto:!0,real:!0,forced:a},{join:function(e){var t=o(this),n=i(t),r=void 0===e?",":String(e),a=[];return l(n,a.push,a,!1,!0),a.join(r)}})},"5Tg+":function(e,t,n){var r=n("tiKp");t.f=r},"5Yz+":function(e,t,n){"use strict";var r=n("/GqU"),a=n("ppGB"),o=n("UMSQ"),i=n("pkCn"),l=n("rkAj"),c=Math.min,u=[].lastIndexOf,s=!!u&&1/[1].lastIndexOf(1,-0)<0,p=i("lastIndexOf"),d=l("indexOf",{ACCESSORS:!0,1:0}),f=s||!p||!d;e.exports=f?function(e){if(s)return u.apply(this,arguments)||0;var t=r(this),n=o(t.length),i=n-1;for(arguments.length>1&&(i=c(i,a(arguments[1]))),i<0&&(i=n+i);i>=0;i--)if(i in t&&t[i]===e)return i||0;return-1}:u},"5mdu":function(e,t){e.exports=function(e){try{return{error:!1,value:e()}}catch(t){return{error:!0,value:t}}}},"5r1n":function(e,t,n){var r=n("I+eb"),a=n("eDxR"),o=n("glrk"),i=a.get,l=a.toKey;r({target:"Reflect",stat:!0},{getOwnMetadata:function(e,t){var n=arguments.length<3?void 0:l(arguments[2]);return i(e,o(t),n)}})},"5s+n":function(e,t,n){"use strict";var r,a,o,i,l=n("I+eb"),c=n("xDBR"),u=n("2oRo"),s=n("0GbY"),p=n("/qmn"),d=n("busE"),f=n("4syw"),m=n("1E5z"),h=n("JiZb"),g=n("hh1v"),v=n("HAuM"),b=n("GarU"),y=n("xrYK"),E=n("iSVu"),w=n("ImZN"),k=n("HH4o"),x=n("SEBh"),O=n("LPSS").set,S=n("tXUg"),A=n("zfnd"),j=n("RN6c"),T=n("8GlL"),_=n("5mdu"),I=n("afO8"),P=n("lMq5"),R=n("tiKp"),C=n("LQDL"),N=R("species"),M="Promise",L=I.get,D=I.set,F=I.getterFor(M),U=p,B=u.TypeError,q=u.document,z=u.process,W=s("fetch"),H=T.f,G=H,V="process"==y(z),$=!!(q&&q.createEvent&&u.dispatchEvent),K="unhandledrejection",Y="rejectionhandled",Q=0,Z=1,J=2,X=1,ee=2,te=P(M,(function(){var e=E(U)!==String(U);if(!e){if(66===C)return!0;if(!V&&"function"!=typeof PromiseRejectionEvent)return!0}if(c&&!U.prototype["finally"])return!0;if(C>=51&&/native code/.test(U))return!1;var t=U.resolve(1),n=function(e){e((function(){}),(function(){}))},r=t.constructor={};return r[N]=n,!(t.then((function(){}))instanceof n)})),ne=te||!k((function(e){U.all(e)["catch"]((function(){}))})),re=function(e){var t;return!(!g(e)||"function"!=typeof(t=e.then))&&t},ae=function(e,t,n){if(!t.notified){t.notified=!0;var r=t.reactions;S((function(){var a=t.value,o=t.state==Z,i=0;while(r.length>i){var l,c,u,s=r[i++],p=o?s.ok:s.fail,d=s.resolve,f=s.reject,m=s.domain;try{p?(o||(t.rejection===ee&&ce(e,t),t.rejection=X),!0===p?l=a:(m&&m.enter(),l=p(a),m&&(m.exit(),u=!0)),l===s.promise?f(B("Promise-chain cycle")):(c=re(l))?c.call(l,d,f):d(l)):f(a)}catch(h){m&&!u&&m.exit(),f(h)}}t.reactions=[],t.notified=!1,n&&!t.rejection&&ie(e,t)}))}},oe=function(e,t,n){var r,a;$?(r=q.createEvent("Event"),r.promise=t,r.reason=n,r.initEvent(e,!1,!0),u.dispatchEvent(r)):r={promise:t,reason:n},(a=u["on"+e])?a(r):e===K&&j("Unhandled promise rejection",n)},ie=function(e,t){O.call(u,(function(){var n,r=t.value,a=le(t);if(a&&(n=_((function(){V?z.emit("unhandledRejection",r,e):oe(K,e,r)})),t.rejection=V||le(t)?ee:X,n.error))throw n.value}))},le=function(e){return e.rejection!==X&&!e.parent},ce=function(e,t){O.call(u,(function(){V?z.emit("rejectionHandled",e):oe(Y,e,t.value)}))},ue=function(e,t,n,r){return function(a){e(t,n,a,r)}},se=function(e,t,n,r){t.done||(t.done=!0,r&&(t=r),t.value=n,t.state=J,ae(e,t,!0))},pe=function(e,t,n,r){if(!t.done){t.done=!0,r&&(t=r);try{if(e===n)throw B("Promise can't be resolved itself");var a=re(n);a?S((function(){var r={done:!1};try{a.call(n,ue(pe,e,r,t),ue(se,e,r,t))}catch(o){se(e,r,o,t)}})):(t.value=n,t.state=Z,ae(e,t,!1))}catch(o){se(e,{done:!1},o,t)}}};te&&(U=function(e){b(this,U,M),v(e),r.call(this);var t=L(this);try{e(ue(pe,this,t),ue(se,this,t))}catch(n){se(this,t,n)}},r=function(e){D(this,{type:M,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:Q,value:void 0})},r.prototype=f(U.prototype,{then:function(e,t){var n=F(this),r=H(x(this,U));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=V?z.domain:void 0,n.parent=!0,n.reactions.push(r),n.state!=Q&&ae(this,n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),a=function(){var e=new r,t=L(e);this.promise=e,this.resolve=ue(pe,e,t),this.reject=ue(se,e,t)},T.f=H=function(e){return e===U||e===o?new a(e):G(e)},c||"function"!=typeof p||(i=p.prototype.then,d(p.prototype,"then",(function(e,t){var n=this;return new U((function(e,t){i.call(n,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof W&&l({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return A(U,W.apply(u,arguments))}}))),l({global:!0,wrap:!0,forced:te},{Promise:U}),m(U,M,!1,!0),h(M),o=s(M),l({target:M,stat:!0,forced:te},{reject:function(e){var t=H(this);return t.reject.call(void 0,e),t.promise}}),l({target:M,stat:!0,forced:c||te},{resolve:function(e){return A(c&&this===o?U:this,e)}}),l({target:M,stat:!0,forced:ne},{all:function(e){var t=this,n=H(t),r=n.resolve,a=n.reject,o=_((function(){var n=v(t.resolve),o=[],i=0,l=1;w(e,(function(e){var c=i++,u=!1;o.push(void 0),l++,n.call(t,e).then((function(e){u||(u=!0,o[c]=e,--l||r(o))}),a)})),--l||r(o)}));return o.error&&a(o.value),n.promise},race:function(e){var t=this,n=H(t),r=n.reject,a=_((function(){var a=v(t.resolve);w(e,(function(e){a.call(t,e).then(n.resolve,r)}))}));return a.error&&r(a.value),n.promise}})},"5wUe":function(e,t,n){var r=n("Q9SF"),a=n("MIOZ"),o=n("mGKP"),i=n("h0XC");function l(e,t){return r(e)||a(e,t)||o(e,t)||i()}e.exports=l,e.exports.__esModule=!0,e.exports["default"]=e.exports},"5xtp":function(e,t,n){"use strict";var r=n("I+eb"),a=n("g6v/"),o=n("6x0u"),i=n("ewvW"),l=n("HAuM"),c=n("m/L8");a&&r({target:"Object",proto:!0,forced:o},{__defineSetter__:function(e,t){c.f(i(this),e,{set:l(t),enumerable:!0,configurable:!0})}})},"66V8":function(e,t,n){"use strict";var r=n("I+eb"),a=n("g6v/"),o=n("4WOD"),i=n("0rvr"),l=n("fHMY"),c=n("m/L8"),u=n("XGwC"),s=n("ImZN"),p=n("kRJp"),d=n("afO8"),f=d.set,m=d.getterFor("AggregateError"),h=function(e,t){var n=this;if(!(n instanceof h))return new h(e,t);i&&(n=i(new Error(t),o(n)));var r=[];return s(e,r.push,r),a?f(n,{errors:r,type:"AggregateError"}):n.errors=r,void 0!==t&&p(n,"message",String(t)),n};h.prototype=l(Error.prototype,{constructor:u(5,h),message:u(5,""),name:u(5,"AggregateError")}),a&&c.f(h.prototype,"errors",{get:function(){return m(this).errors},configurable:!0}),r({global:!0},{AggregateError:h})},"67WC":function(e,t,n){"use strict";var r,a=n("qYE9"),o=n("g6v/"),i=n("2oRo"),l=n("hh1v"),c=n("UTVS"),u=n("9d/t"),s=n("kRJp"),p=n("busE"),d=n("m/L8").f,f=n("4WOD"),m=n("0rvr"),h=n("tiKp"),g=n("kOOl"),v=i.Int8Array,b=v&&v.prototype,y=i.Uint8ClampedArray,E=y&&y.prototype,w=v&&f(v),k=b&&f(b),x=Object.prototype,O=x.isPrototypeOf,S=h("toStringTag"),A=g("TYPED_ARRAY_TAG"),j=a&&!!m&&"Opera"!==u(i.opera),T=!1,_={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},I=function(e){var t=u(e);return"DataView"===t||c(_,t)},P=function(e){return l(e)&&c(_,u(e))},R=function(e){if(P(e))return e;throw TypeError("Target is not a typed array")},C=function(e){if(m){if(O.call(w,e))return e}else for(var t in _)if(c(_,r)){var n=i[t];if(n&&(e===n||O.call(n,e)))return e}throw TypeError("Target is not a typed array constructor")},N=function(e,t,n){if(o){if(n)for(var r in _){var a=i[r];a&&c(a.prototype,e)&&delete a.prototype[e]}k[e]&&!n||p(k,e,n?t:j&&b[e]||t)}},M=function(e,t,n){var r,a;if(o){if(m){if(n)for(r in _)a=i[r],a&&c(a,e)&&delete a[e];if(w[e]&&!n)return;try{return p(w,e,n?t:j&&v[e]||t)}catch(l){}}for(r in _)a=i[r],!a||a[e]&&!n||p(a,e,t)}};for(r in _)i[r]||(j=!1);if((!j||"function"!=typeof w||w===Function.prototype)&&(w=function(){throw TypeError("Incorrect invocation")},j))for(r in _)i[r]&&m(i[r],w);if((!j||!k||k===x)&&(k=w.prototype,j))for(r in _)i[r]&&m(i[r].prototype,k);if(j&&f(E)!==k&&m(E,k),o&&!c(k,S))for(r in T=!0,d(k,S,{get:function(){return l(this)?this[A]:void 0}}),_)i[r]&&s(i[r],A,r);e.exports={NATIVE_ARRAY_BUFFER_VIEWS:j,TYPED_ARRAY_TAG:T&&A,aTypedArray:R,aTypedArrayConstructor:C,exportTypedArrayMethod:N,exportTypedArrayStaticMethod:M,isView:I,isTypedArray:P,TypedArray:w,TypedArrayPrototype:k}},"6JNq":function(e,t,n){var r=n("UTVS"),a=n("Vu81"),o=n("Bs8V"),i=n("m/L8");e.exports=function(e,t){for(var n=a(t),l=i.f,c=o.f,u=0;u>>8,n[2*r+1]=i%256}return n},decompressFromUint8Array:function(t){if(null===t||void 0===t)return o.decompress(t);for(var n=new Array(t.length/2),r=0,a=n.length;r>=1}else{for(a=1,r=0;r>=1}p--,0==p&&(p=Math.pow(2,f),f++),delete l[s]}else for(a=i[s],r=0;r>=1;p--,0==p&&(p=Math.pow(2,f),f++),i[u]=d++,s=String(c)}if(""!==s){if(Object.prototype.hasOwnProperty.call(l,s)){if(s.charCodeAt(0)<256){for(r=0;r>=1}else{for(a=1,r=0;r>=1}p--,0==p&&(p=Math.pow(2,f),f++),delete l[s]}else for(a=i[s],r=0;r>=1;p--,0==p&&(p=Math.pow(2,f),f++)}for(a=2,r=0;r>=1;while(1){if(h<<=1,g==t-1){m.push(n(h));break}g++}return m.join("")},decompress:function(e){return null==e?"":""==e?null:o._decompress(e.length,32768,(function(t){return e.charCodeAt(t)}))},_decompress:function(t,n,r){var a,o,i,l,c,u,s,p=[],d=4,f=4,m=3,h="",g=[],v={val:r(0),position:n,index:1};for(a=0;a<3;a+=1)p[a]=a;i=0,c=Math.pow(2,2),u=1;while(u!=c)l=v.val&v.position,v.position>>=1,0==v.position&&(v.position=n,v.val=r(v.index++)),i|=(l>0?1:0)*u,u<<=1;switch(i){case 0:i=0,c=Math.pow(2,8),u=1;while(u!=c)l=v.val&v.position,v.position>>=1,0==v.position&&(v.position=n,v.val=r(v.index++)),i|=(l>0?1:0)*u,u<<=1;s=e(i);break;case 1:i=0,c=Math.pow(2,16),u=1;while(u!=c)l=v.val&v.position,v.position>>=1,0==v.position&&(v.position=n,v.val=r(v.index++)),i|=(l>0?1:0)*u,u<<=1;s=e(i);break;case 2:return""}p[3]=s,o=s,g.push(s);while(1){if(v.index>t)return"";i=0,c=Math.pow(2,m),u=1;while(u!=c)l=v.val&v.position,v.position>>=1,0==v.position&&(v.position=n,v.val=r(v.index++)),i|=(l>0?1:0)*u,u<<=1;switch(s=i){case 0:i=0,c=Math.pow(2,8),u=1;while(u!=c)l=v.val&v.position,v.position>>=1,0==v.position&&(v.position=n,v.val=r(v.index++)),i|=(l>0?1:0)*u,u<<=1;p[f++]=e(i),s=f-1,d--;break;case 1:i=0,c=Math.pow(2,16),u=1;while(u!=c)l=v.val&v.position,v.position>>=1,0==v.position&&(v.position=n,v.val=r(v.index++)),i|=(l>0?1:0)*u,u<<=1;p[f++]=e(i),s=f-1,d--;break;case 2:return g.join("")}if(0==d&&(d=Math.pow(2,m),m++),p[s])h=p[s];else{if(s!==f)return null;h=o+o.charAt(0)}g.push(h),p[f++]=o+h.charAt(0),d--,o=h,0==d&&(d=Math.pow(2,m),m++)}}};return o}();r=function(){return a}.call(t,n,t,e),void 0===r||(e.exports=r)},"7+kd":function(e,t,n){var r=n("dG/n");r("isConcatSpreadable")},"7+zs":function(e,t,n){var r=n("kRJp"),a=n("UesL"),o=n("tiKp"),i=o("toPrimitive"),l=Date.prototype;i in l||r(l,i,a)},"702D":function(e,t,n){var r=n("I+eb"),a=n("qY7S");r({target:"WeakMap",stat:!0},{from:a})},"7JcK":function(e,t,n){"use strict";var r=n("67WC"),a=n("iqeF"),o=r.aTypedArrayConstructor,i=r.exportTypedArrayStaticMethod;i("of",(function(){var e=0,t=arguments.length,n=new(o(this))(t);while(t>e)n[e]=arguments[e++];return n}),a)},"7sf/":function(e,t,n){"use strict";function r(){var e=n("q1tI");return r=function(){return e},e}function a(){var e=o(n("6xEa"));return a=function(){return e},e}function o(e){return e&&e.__esModule?e:{default:e}}function i(e,t){return p(e)||s(e,t)||c(e,t)||l()}function l(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function c(e,t){if(e){if("string"===typeof e)return u(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(e,t):void 0}}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:2;t();var o=Object(K["a"])((function(){a<=1?r({isCanceled:function(){return o!==e.current}}):n(r,a-1)}));e.current=o}return p["useEffect"]((function(){return function(){t()}}),[]),[n,t]},Q=[A,j,T,_],Z=[A,I],J=!1,X=!0;function ee(e){return e===T||e===_}var te=function(e,t,n){var r=Object(E["a"])(S),a=Object(o["a"])(r,2),i=a[0],l=a[1],c=Y(),u=Object(o["a"])(c,2),s=u[0],d=u[1];function f(){l(A,!0)}var m=t?Z:Q;return $((function(){if(i!==S&&i!==_){var e=m.indexOf(i),t=m[e+1],r=n(i);r===J?l(t,!0):t&&s((function(e){function n(){e.isCanceled()||l(t,!0)}!0===r?n():Promise.resolve(r).then(n)}))}}),[e,i]),p["useEffect"]((function(){return function(){d()}}),[]),[f,i]};function ne(e,t,n,i){var l=i.motionEnter,c=void 0===l||l,u=i.motionAppear,s=void 0===u||u,d=i.motionLeave,f=void 0===d||d,m=i.motionDeadline,h=i.motionLeaveImmediately,g=i.onAppearPrepare,v=i.onEnterPrepare,b=i.onLeavePrepare,y=i.onAppearStart,S=i.onEnterStart,_=i.onLeaveStart,P=i.onAppearActive,R=i.onEnterActive,C=i.onLeaveActive,N=i.onAppearEnd,M=i.onEnterEnd,L=i.onLeaveEnd,D=i.onVisibleChanged,F=Object(E["a"])(),U=Object(o["a"])(F,2),B=U[0],q=U[1],z=Object(E["a"])(w),W=Object(o["a"])(z,2),H=W[0],V=W[1],K=Object(E["a"])(null),Y=Object(o["a"])(K,2),Q=Y[0],Z=Y[1],ne=Object(p["useRef"])(!1),re=Object(p["useRef"])(null);function ae(){return n()}var oe=Object(p["useRef"])(!1);function ie(){V(w,!0),Z(null,!0)}function le(e){var t=ae();if(!e||e.deadline||e.target===t){var n,r=oe.current;H===k&&r?n=null===N||void 0===N?void 0:N(t,e):H===x&&r?n=null===M||void 0===M?void 0:M(t,e):H===O&&r&&(n=null===L||void 0===L?void 0:L(t,e)),H!==w&&r&&!1!==n&&ie()}}var ce=G(le),ue=Object(o["a"])(ce,1),se=ue[0],pe=function(e){var t,n,a;switch(e){case k:return t={},Object(r["a"])(t,A,g),Object(r["a"])(t,j,y),Object(r["a"])(t,T,P),t;case x:return n={},Object(r["a"])(n,A,v),Object(r["a"])(n,j,S),Object(r["a"])(n,T,R),n;case O:return a={},Object(r["a"])(a,A,b),Object(r["a"])(a,j,_),Object(r["a"])(a,T,C),a;default:return{}}},de=p["useMemo"]((function(){return pe(H)}),[H]),fe=te(H,!e,(function(e){if(e===A){var t=de[A];return t?t(ae()):J}var n;ge in de&&Z((null===(n=de[ge])||void 0===n?void 0:n.call(de,ae(),null))||null);return ge===T&&(se(ae()),m>0&&(clearTimeout(re.current),re.current=setTimeout((function(){le({deadline:!0})}),m))),ge===I&&ie(),X})),me=Object(o["a"])(fe,2),he=me[0],ge=me[1],ve=ee(ge);oe.current=ve,$((function(){q(t);var n,r=ne.current;ne.current=!0,!r&&t&&s&&(n=k),r&&t&&c&&(n=x),(r&&!t&&f||!r&&h&&!t&&f)&&(n=O);var a=pe(n);n&&(e||a[A])?(V(n),he()):V(w)}),[t]),Object(p["useEffect"])((function(){(H===k&&!s||H===x&&!c||H===O&&!f)&&V(w)}),[s,c,f]),Object(p["useEffect"])((function(){return function(){ne.current=!1,clearTimeout(re.current)}}),[]);var be=p["useRef"](!1);Object(p["useEffect"])((function(){B&&(be.current=!0),void 0!==B&&H===w&&((be.current||B)&&(null===D||void 0===D||D(B)),be.current=!0)}),[B,H]);var ye=Q;return de[A]&&ge===j&&(ye=Object(a["a"])({transition:"none"},ye)),[H,ge,ye,null!==B&&void 0!==B?B:t]}function re(e){var t=e;function n(e,n){return!(!e.motionName||!t||!1===n)}"object"===Object(i["a"])(e)&&(t=e.transitionSupport);var l=p["forwardRef"]((function(e,t){var i=e.visible,l=void 0===i||i,d=e.removeOnLeave,m=void 0===d||d,h=e.forceRender,g=e.children,v=e.motionName,b=e.leavedClassName,E=e.eventProps,k=p["useContext"](f),x=k.motion,O=n(e,x),S=Object(p["useRef"])(),T=Object(p["useRef"])();function _(){try{return S.current instanceof HTMLElement?S.current:Object(u["a"])(T.current)}catch(e){return null}}var I=ne(O,l,_,e),P=Object(o["a"])(I,4),R=P[0],C=P[1],N=P[2],M=P[3],L=p["useRef"](M);M&&(L.current=!0);var D,F=p["useCallback"]((function(e){S.current=e,Object(s["b"])(t,e)}),[t]),U=Object(a["a"])(Object(a["a"])({},E),{},{visible:l});if(g)if(R===w)D=M?g(Object(a["a"])({},U),F):!m&&L.current&&b?g(Object(a["a"])(Object(a["a"])({},U),{},{className:b}),F):h||!m&&!b?g(Object(a["a"])(Object(a["a"])({},U),{},{style:{display:"none"}}),F):null;else{var B,q;C===A?q="prepare":ee(C)?q="active":C===j&&(q="start");var z=H(v,"".concat(R,"-").concat(q));D=g(Object(a["a"])(Object(a["a"])({},U),{},{className:c()(H(v,R),(B={},Object(r["a"])(B,z,z&&q),Object(r["a"])(B,v,"string"===typeof v),B)),style:N}),F)}else D=null;if(p["isValidElement"](D)&&Object(s["c"])(D)){var W=D,G=W.ref;G||(D=p["cloneElement"](D,{ref:F}))}return p["createElement"](y,{ref:T},D)}));return l.displayName="CSSMotion",l}var ae=re(q),oe=n("wx14"),ie=n("JX7q"),le="add",ce="keep",ue="remove",se="removed";function pe(e){var t;return t=e&&"object"===Object(i["a"])(e)&&"key"in e?e:{key:e},Object(a["a"])(Object(a["a"])({},t),{},{key:String(t.key)})}function de(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.map(pe)}function fe(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=[],r=0,o=t.length,i=de(e),l=de(t);i.forEach((function(e){for(var t=!1,i=r;i1}));return u.forEach((function(e){n=n.filter((function(t){var n=t.key,r=t.status;return n!==e||r!==ue})),n.forEach((function(t){t.key===e&&(t.status=ce)}))})),n}var me=["component","children","onVisibleChanged","onAllRemoved"],he=["status"],ge=["eventProps","visible","children","motionName","motionAppear","motionEnter","motionLeave","motionLeaveImmediately","motionDeadline","removeOnLeave","leavedClassName","onAppearPrepare","onAppearStart","onAppearActive","onAppearEnd","onEnterStart","onEnterActive","onEnterEnd","onLeaveStart","onLeaveActive","onLeaveEnd"];function ve(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:ae,n=function(e){Object(g["a"])(o,e);var n=Object(v["a"])(o);function o(){var e;Object(m["a"])(this,o);for(var t=arguments.length,i=new Array(t),l=0;l2?arguments[2]:void 0)(e,n);return n.set(e,t(l,e,n)),n}})},"9N29":function(e,t,n){"use strict";var r=n("I+eb"),a=n("1Y/n").right,o=n("pkCn"),i=n("rkAj"),l=o("reduceRight"),c=i("reduce",{1:0});r({target:"Array",proto:!0,forced:!l||!c},{reduceRight:function(e){return a(this,e,arguments.length,arguments.length>1?arguments[1]:void 0)}})},"9R94":function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=!0,a="Invariant failed";function o(e,t){if(!e){if(r)throw new Error(a);var n="function"===typeof t?t():t,o=n?"".concat(a,": ").concat(n):a;throw new Error(o)}}},"9d/t":function(e,t,n){var r=n("AO7/"),a=n("xrYK"),o=n("tiKp"),i=o("toStringTag"),l="Arguments"==a(function(){return arguments}()),c=function(e,t){try{return e[t]}catch(n){}};e.exports=r?a:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=c(t=Object(e),i))?n:l?a(t):"Object"==(r=a(t))&&"function"==typeof t.callee?"Arguments":r}},"9kvl":function(e,t,n){"use strict";n.d(t,"a",(function(){return r["b"]}));var r=n("FfOG");n("bCY9"),n("vpkV")},"9xmf":function(e,t,n){var r=n("EdiO");function a(e){if(Array.isArray(e))return r(e)}e.exports=a,e.exports.__esModule=!0,e.exports["default"]=e.exports},A2ZE:function(e,t,n){var r=n("HAuM");e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,a){return e.call(t,n,r,a)}}return function(){return e.apply(t,arguments)}}},AK2Z:function(e,t,n){},"AO7/":function(e,t,n){var r=n("tiKp"),a=r("toStringTag"),o={};o[a]="z",e.exports="[object z]"===String(o)},AQPS:function(e,t,n){},AVoK:function(e,t,n){"use strict";var r=n("I+eb"),a=n("xDBR"),o=n("Cg3G");r({target:"Set",proto:!0,real:!0,forced:a},{deleteAll:function(){return o.apply(this,arguments)}})},AqCL:function(e,t){e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},AwgR:function(e,t,n){var r=n("I+eb"),a=n("eDxR"),o=n("glrk"),i=a.has,l=a.toKey;r({target:"Reflect",stat:!0},{hasOwnMetadata:function(e,t){var n=arguments.length<3?void 0:l(arguments[2]);return i(e,o(t),n)}})},B6y2:function(e,t,n){var r=n("I+eb"),a=n("b1O7").values;r({target:"Object",stat:!0},{values:function(e){return a(e)}})},BGb9:function(e,t,n){"use strict";var r=n("I+eb"),a=n("xDBR"),o=n("0GbY"),i=n("glrk"),l=n("HAuM"),c=n("SEBh"),u=n("ImZN");r({target:"Set",proto:!0,real:!0,forced:a},{union:function(e){var t=i(this),n=new(c(t,o("Set")))(t);return u(e,l(n.add),n),n}})},BIHw:function(e,t,n){"use strict";var r=n("I+eb"),a=n("or9q"),o=n("ewvW"),i=n("UMSQ"),l=n("ppGB"),c=n("ZfDv");r({target:"Array",proto:!0},{flat:function(){var e=arguments.length?arguments[0]:void 0,t=o(this),n=i(t.length),r=c(t,0);return r.length=a(r,t,t,n,0,void 0===e?1:l(e)),r}})},BTho:function(e,t,n){"use strict";var r=n("HAuM"),a=n("hh1v"),o=[].slice,i={},l=function(e,t,n){if(!(t in i)){for(var r=[],a=0;a1?arguments[1]:void 0,3),a=new(u(t,o("Map"))),d=l(a.set);return p(n,(function(e,n){d.call(a,e,r(n,e,t))}),void 0,!0,!0),a}})},Cg3G:function(e,t,n){"use strict";var r=n("glrk"),a=n("HAuM");e.exports=function(){for(var e,t=r(this),n=a(t["delete"]),o=!0,i=0,l=arguments.length;ic&&(u=u.slice(0,c)),e?s+u:u+s)}};e.exports={start:l(!1),end:l(!0)}},DPsx:function(e,t,n){var r=n("g6v/"),a=n("0Dky"),o=n("zBJ4");e.exports=!r&&!a((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},DSFK:function(e,t,n){"use strict";function r(e){if(Array.isArray(e))return e}n.d(t,"a",(function(){return r}))},DTth:function(e,t,n){var r=n("0Dky"),a=n("tiKp"),o=n("xDBR"),i=a("iterator");e.exports=!r((function(){var e=new URL("b?a=1&b=2&c=3","http://a"),t=e.searchParams,n="";return e.pathname="c%20d",t.forEach((function(e,r){t["delete"]("b"),n+=r+e})),o&&!e.toJSON||!t.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==t.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!t[i]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://\u0442\u0435\u0441\u0442").host||"#%D0%B1"!==new URL("http://a#\u0431").hash||"a1c3"!==n||"x"!==new URL("http://x",void 0).host}))},DhMN:function(e,t,n){n("ofBz")},DrvE:function(e,t,n){"use strict";var r=n("I+eb"),a=n("HAuM"),o=n("0GbY"),i=n("8GlL"),l=n("5mdu"),c=n("ImZN"),u="No one promise resolved";r({target:"Promise",stat:!0},{any:function(e){var t=this,n=i.f(t),r=n.resolve,s=n.reject,p=l((function(){var n=a(t.resolve),i=[],l=0,p=1,d=!1;c(e,(function(e){var a=l++,c=!1;i.push(void 0),p++,n.call(t,e).then((function(e){c||d||(d=!0,r(e))}),(function(e){c||d||(c=!0,i[a]=e,--p||s(new(o("AggregateError"))(i,u)))}))})),--p||s(new(o("AggregateError"))(i,u))}));return p.error&&s(p.value),n.promise}})},E9XD:function(e,t,n){"use strict";var r=n("I+eb"),a=n("1Y/n").left,o=n("pkCn"),i=n("rkAj"),l=o("reduce"),c=i("reduce",{1:0});r({target:"Array",proto:!0,forced:!l||!c},{reduce:function(e){return a(this,e,arguments.length,arguments.length>1?arguments[1]:void 0)}})},"EDT/":function(e,t,n){var r=n("I+eb"),a=n("p5mE"),o=n("0GbY");r({global:!0},{compositeSymbol:function(){return 1===arguments.length&&"string"===typeof arguments[0]?o("Symbol")["for"](arguments[0]):a.apply(null,arguments).get("symbol",o("Symbol"))}})},ENF9:function(e,t,n){"use strict";var r,a=n("2oRo"),o=n("4syw"),i=n("8YOa"),l=n("bWFh"),c=n("rKzb"),u=n("hh1v"),s=n("afO8").enforce,p=n("f5p1"),d=!a.ActiveXObject&&"ActiveXObject"in a,f=Object.isExtensible,m=function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}},h=e.exports=l("WeakMap",m,c);if(p&&d){r=c.getConstructor(m,"WeakMap",!0),i.REQUIRED=!0;var g=h.prototype,v=g["delete"],b=g.has,y=g.get,E=g.set;o(g,{delete:function(e){if(u(e)&&!f(e)){var t=s(this);return t.frozen||(t.frozen=new r),v.call(this,e)||t.frozen["delete"](e)}return v.call(this,e)},has:function(e){if(u(e)&&!f(e)){var t=s(this);return t.frozen||(t.frozen=new r),b.call(this,e)||t.frozen.has(e)}return b.call(this,e)},get:function(e){if(u(e)&&!f(e)){var t=s(this);return t.frozen||(t.frozen=new r),b.call(this,e)?y.call(this,e):t.frozen.get(e)}return y.call(this,e)},set:function(e,t){if(u(e)&&!f(e)){var n=s(this);n.frozen||(n.frozen=new r),b.call(this,e)?E.call(this,e,t):n.frozen.set(e,t)}else E.call(this,e,t);return this}})}},"ET+V":function(e,t,n){"use strict";n.r(t);var r=n("q1tI"),a=n.n(r),o=n("dEAq"),i=n("H1Ra"),l=n("dMo/"),c=a.a.memo((e=>{e.demos;return a.a.createElement(a.a.Fragment,null,a.a.createElement("div",{className:"markdown"},a.a.createElement("h1",{id:"tutorial"},a.a.createElement(o["AnchorLink"],{to:"#tutorial","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"Tutorial"),a.a.createElement("h2",{id:"how-to-choose-the-routing-mode-of-micro-app"},a.a.createElement(o["AnchorLink"],{to:"#how-to-choose-the-routing-mode-of-micro-app","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"How to choose the routing mode of micro app"),a.a.createElement("p",null,"The three routes ",a.a.createElement("code",null,"react-router"),", ",a.a.createElement("code",null,"angular-router"),", and ",a.a.createElement("code",null,"vue-router")," all support the ",a.a.createElement("code",null,"hash")," and ",a.a.createElement("code",null,"history")," modes. The different modes used by micro apps are slightly different in ",a.a.createElement("code",null,"qiankun"),"."),a.a.createElement("h3",{id:"activerule-uses-locationpathname-to-distinguish-micro-apps"},a.a.createElement(o["AnchorLink"],{to:"#activerule-uses-locationpathname-to-distinguish-micro-apps","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),a.a.createElement("code",null,"activeRule")," uses ",a.a.createElement("code",null,"location.pathname")," to distinguish micro apps"),a.a.createElement("p",null,"When the main app uses ",a.a.createElement("code",null,"location.pathname")," to distinguish micro apps, micro apps can be in ",a.a.createElement("code",null,"hash")," and ",a.a.createElement("code",null,"history")," modes."),a.a.createElement("p",null,"When registering micro apps, ",a.a.createElement("code",null,"activeRule")," needs to be written like this:"),a.a.createElement(i["a"],{code:"registerMicroApps([\n {\n name: 'app',\n entry: 'http://localhost:8080',\n container: '#container',\n activeRule: '/app',\n },\n]);",lang:"js"}),a.a.createElement("ol",null,a.a.createElement("li",null,a.a.createElement("p",null,"When the micro app is in ",a.a.createElement("code",null,"history")," mode, just set the route ",a.a.createElement("code",null,"base"),".")),a.a.createElement("li",null,a.a.createElement("p",null,"When the micro app is in the ",a.a.createElement("code",null,"hash")," mode, the performance of the three routes is inconsistent"),a.a.createElement(l["a"],null,a.a.createElement("thead",null,a.a.createElement("tr",null,a.a.createElement("th",null,"routing"),a.a.createElement("th",null,"main app jump ",a.a.createElement("code",null,"/app/#/about")),a.a.createElement("th",null,"special configuration"))),a.a.createElement("tbody",null,a.a.createElement("tr",null,a.a.createElement("td",null,"vue-router"),a.a.createElement("td",null,"Response ",a.a.createElement("code",null,"about")," routing"),a.a.createElement("td",null,"none")),a.a.createElement("tr",null,a.a.createElement("td",null,"react-router"),a.a.createElement("td",null,"not responding ",a.a.createElement("code",null,"about")," routing"),a.a.createElement("td",null,"none")),a.a.createElement("tr",null,a.a.createElement("td",null,"angular-router"),a.a.createElement("td",null,"Response ",a.a.createElement("code",null,"about")," routing"),a.a.createElement("td",null,"need to set ",a.a.createElement("code",null,"--base-href"))))),a.a.createElement("p",null,a.a.createElement("code",null,"Angular")," app set ",a.a.createElement("code",null,"--base-href")," in ",a.a.createElement("code",null,"package.json"),":"),a.a.createElement(i["a"],{code:'- "start": "ng serve",\n+ "start": "ng serve --base-href /angular9",\n- "build": "ng build",\n+ "build": "ng build --base-href /angular9",',lang:"diff"}),a.a.createElement("p",null,"After bundled and deployed, the ",a.a.createElement("code",null,"angular")," micro app can be accessed by the main app, but the lazy-loaded route during independent access will report an error and the path is incorrect. There are two solutions:"),a.a.createElement("ul",null,a.a.createElement("li",null,a.a.createElement("p",null,"Solution 1: Modify ",a.a.createElement("code",null,"public-path.js")," to:"),a.a.createElement(i["a"],{code:"__webpack_public_path__ = window.__POWERED_BY_QIANKUN__\n ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__\n : `http://${ip}:${port}/`; // Fill in your actual deployment address",lang:"js"})),a.a.createElement("li",null,a.a.createElement("p",null,"Solution 2: Modify the bundling command and deploy the micro app in the ",a.a.createElement("code",null,"angular9")," directory:"),a.a.createElement(i["a"],{code:'- "build": "ng build",\n+ "build": "ng build --base-href /angular9 --deploy-url /angular9/",',lang:"diff"}))))),a.a.createElement("h3",{id:"activerule-uses-hash-to-distinguish-micro-apps"},a.a.createElement(o["AnchorLink"],{to:"#activerule-uses-hash-to-distinguish-micro-apps","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"activeRule uses hash to distinguish micro apps"),a.a.createElement("p",null,"When the micro apps are all in the ",a.a.createElement("code",null,"hash")," mode, ",a.a.createElement("code",null,"hash")," can be used to distinguish the micro apps, and the routing mode of the main app is not limited."),a.a.createElement("p",null,"When registering micro apps, ",a.a.createElement("code",null,"activeRule")," needs to be written like this:"),a.a.createElement(i["a"],{code:"const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);\nregisterMicroApps([\n {\n name: 'app-hash',\n entry: 'http://localhost:8080',\n container: '#container',\n activeRule: getActiveRule('#/app-hash'),\n // Here you can also write `activeRule:'#/app-hash'` directly,\n // but if the main app is in history mode or the main app is deployed in a non-root directory, this writing will not take effect.\n },\n]);",lang:"js"}),a.a.createElement("p",null,"The ",a.a.createElement("code",null,"react-router")," and ",a.a.createElement("code",null,"angular-router")," micro-apps need to set the value of ",a.a.createElement("code",null,"activeRule")," to the route's ",a.a.createElement("code",null,"base"),", written in the same way as ",a.a.createElement("code",null,"history"),"."),a.a.createElement("p",null,"In the ",a.a.createElement("code",null,"hash")," mode of the ",a.a.createElement("code",null,"vue-router")," app, the ",a.a.createElement("code",null,"base")," for routing is not supported. You need to create an additional empty routing page and use all other routes as its children:"),a.a.createElement(i["a"],{code:"const routes = [\n {\n path: '/app-vue-hash',\n name: 'Home',\n component: Home,\n children: [\n // All other routes are written here\n ],\n },\n];",lang:"js"}),a.a.createElement("h3",{id:"when-there-are-multiple-micro-apps-at-the-same-time"},a.a.createElement(o["AnchorLink"],{to:"#when-there-are-multiple-micro-apps-at-the-same-time","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"When there are multiple micro apps at the same time"),a.a.createElement("p",null,"If a page displays multiple micro apps at the same time, you need to use ",a.a.createElement("code",null,"loadMicroApp")," to load them."),a.a.createElement("p",null,"If these micro apps have routing jump requirements, to ensure that these routes do not interfere with each other, you need to use the ",a.a.createElement("code",null,"momery")," routing. ",a.a.createElement("code",null,"vue-router")," uses the ",a.a.createElement("code",null,"abstract")," mode, ",a.a.createElement("code",null,"react-router")," uses the ",a.a.createElement("code",null,"memory history")," mode, and ",a.a.createElement("code",null,"angular-router")," does not support it."),a.a.createElement("h2",{id:"how-to-deploy"},a.a.createElement(o["AnchorLink"],{to:"#how-to-deploy","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"How to deploy"),a.a.createElement("p",null,a.a.createElement("strong",null,"Recommendation"),": The main app and micro apps are developed and deployed independently, that is, they belong to different git repositories and services."),a.a.createElement("h3",{id:"scenario-1-the-main-app-and-micro-apps-are-deployed-to-the-same-server-the-same-ip-and-port"},a.a.createElement(o["AnchorLink"],{to:"#scenario-1-the-main-app-and-micro-apps-are-deployed-to-the-same-server-the-same-ip-and-port","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"Scenario 1: The main app and micro apps are deployed to the same server (the same IP and port)"),a.a.createElement("p",null,"If the number of servers is limited, or cannot be cross-domain and other reasons, the main app and micro apps need to be deployed together."),a.a.createElement("p",null,"The usual practice is to deploy the main app in the first-level directory and the micro apps in the second/third-level directory."),a.a.createElement("p",null,"If you want to deploy a micro app in a non-root directory, you need to do two things before bundling the micro app:"),a.a.createElement("ol",null,a.a.createElement("li",null,a.a.createElement("p",null,"You must configure the ",a.a.createElement("code",null,"publicPath")," when building ",a.a.createElement("code",null,"webpack")," as the ",a.a.createElement("strong",null,"directory name"),". For more information, please see ",a.a.createElement(o["Link"],{to:"https://webpack.js.org/configuration/output/#outputpublicpath"},"webpack official instructions")," \u548c ",a.a.createElement(o["Link"],{to:"https://cli.vuejs.org/config/#publicpath"},"vue-cli3 official instructions"),".")),a.a.createElement("li",null,a.a.createElement("p",null,"The micro app of the ",a.a.createElement("code",null,"history")," route needs to set the ",a.a.createElement("code",null,"base"),", the value is ",a.a.createElement("strong",null,"directory name"),", which is used when the micro app is accessed independently."))),a.a.createElement("p",null,"Pay attention to three points after deployment:"),a.a.createElement("ol",null,a.a.createElement("li",null,a.a.createElement("code",null,"activeRule")," cannot be the same as ",a.a.createElement("strong",null,"the real access path of the micro app"),", otherwise it will directly become the micro app page when refreshed on the main app page."),a.a.createElement("li",null,"The real access path of the micro app is the ",a.a.createElement("code",null,"entry")," of the micro app, and the ",a.a.createElement("code",null,"entry")," can be a relative path."),a.a.createElement("li",null,"The ",a.a.createElement("code",null,"/")," at the end of the ",a.a.createElement("code",null,"entry")," path of the micro app cannot be omitted, otherwise the ",a.a.createElement("code",null,"publicPath")," will be set incorrectly. For example, if the access path of the child item is ",a.a.createElement("code",null,"http://localhost:8080/app1"),", then ",a.a.createElement("code",null,"entry")," It is ",a.a.createElement("code",null,"http://localhost:8080/app1/"),".")),a.a.createElement("p",null,"There are two specific deployment methods, choose one of them."),a.a.createElement("h4",{id:"solution-1-all-micro-apps-are-placed-in-a-folder-with-a-special-name-that-do-not-have-the-same-name-as-micro-apps-recommended"},a.a.createElement(o["AnchorLink"],{to:"#solution-1-all-micro-apps-are-placed-in-a-folder-with-a-special-name-that-do-not-have-the-same-name-as-micro-apps-recommended","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"Solution 1: All micro apps are placed in a folder with a special name that do not have the same name as micro apps (",a.a.createElement("strong",null,"recommended"),")"),a.a.createElement("p",null,"Suppose we have a main app and 6 micro apps ( respectively ",a.a.createElement("code",null,"vue-hash"),", ",a.a.createElement("code",null,"vue-history"),", ",a.a.createElement("code",null,"react-hash"),", ",a.a.createElement("code",null,"react-history"),", ",a.a.createElement("code",null,"angular-hash"),", ",a.a.createElement("code",null,"angular-history"),") And place it as follows after bundling:"),a.a.createElement(i["a"],{code:"\u2514\u2500\u2500 html/ # root folder\n |\n \u251c\u2500\u2500 child/ # the folder of all micro apps\n | \u251c\u2500\u2500 vue-hash/ # the folder of the micro app `vue-hash`\n | \u251c\u2500\u2500 vue-history/ # the folder of the micro app `vue-history`\n | \u251c\u2500\u2500 react-hash/ # the folder of the micro app `react-hash`\n | \u251c\u2500\u2500 react-history/ # the folder of the micro app `react-history`\n | \u251c\u2500\u2500 angular-hash/ # the folder of the micro app `angular-hash`\n | \u251c\u2500\u2500 angular-history/ # the folder of the micro app `angular-history`\n \u251c\u2500\u2500 index.html # index.html of the main app\n \u251c\u2500\u2500 css/ # the css folder of the main app\n \u251c\u2500\u2500 js/ # the js folder of the main app",lang:"unknown"}),a.a.createElement("p",null,"At this time, you need to set the ",a.a.createElement("code",null,"publicPath")," and the route ",a.a.createElement("code",null,"base")," of the ",a.a.createElement("code",null,"history")," mode when the micro app is built, and then bundle them into the corresponding directory."),a.a.createElement(l["a"],null,a.a.createElement("thead",null,a.a.createElement("tr",null,a.a.createElement("th",null,"app"),a.a.createElement("th",null,"routing base"),a.a.createElement("th",null,"publicPath"),a.a.createElement("th",null,"real access path"))),a.a.createElement("tbody",null,a.a.createElement("tr",null,a.a.createElement("td",null,"vue-hash"),a.a.createElement("td",null,"none"),a.a.createElement("td",null,"/child/vue-hash/"),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"http://localhost:8080/child/vue-hash/"},"http://localhost:8080/child/vue-hash/"))),a.a.createElement("tr",null,a.a.createElement("td",null,"vue-history"),a.a.createElement("td",null,"/child/vue-history/"),a.a.createElement("td",null,"/child/vue-history/"),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"http://localhost:8080/child/vue-history/"},"http://localhost:8080/child/vue-history/"))),a.a.createElement("tr",null,a.a.createElement("td",null,"react-hash"),a.a.createElement("td",null,"none"),a.a.createElement("td",null,"/child/react-hash/"),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"http://localhost:8080/child/react-hash/"},"http://localhost:8080/child/react-hash/"))),a.a.createElement("tr",null,a.a.createElement("td",null,"react-history"),a.a.createElement("td",null,"/child/react-history/"),a.a.createElement("td",null,"/child/react-history/"),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"http://localhost:8080/child/react-history/"},"http://localhost:8080/child/react-history/"))),a.a.createElement("tr",null,a.a.createElement("td",null,"angular-hash"),a.a.createElement("td",null,"none"),a.a.createElement("td",null,"/child/angular-hash/"),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"http://localhost:8080/child/angular-hash/"},"http://localhost:8080/child/angular-hash/"))),a.a.createElement("tr",null,a.a.createElement("td",null,"angular-history"),a.a.createElement("td",null,"/child/angular-history/"),a.a.createElement("td",null,"/child/angular-history/"),a.a.createElement("td",null,a.a.createElement(o["Link"],{to:"http://localhost:8080/child/angular-history/"},"http://localhost:8080/child/angular-history/"))))),a.a.createElement("ul",null,a.a.createElement("li",null,a.a.createElement("p",null,a.a.createElement("code",null,"vue-history")," micro app"),a.a.createElement("p",null,"Routing's base configuration:"),a.a.createElement(i["a"],{code:"base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/',",lang:"js"}),a.a.createElement("p",null,"Webpack's publicPath configuration\uff08",a.a.createElement("code",null,"vue.config.js"),"\uff09:"),a.a.createElement(i["a"],{code:"module.exports = {\n publicPath: '/child/vue-history/',\n};",lang:"js"})),a.a.createElement("li",null,a.a.createElement("p",null,a.a.createElement("code",null,"react-history")," micro app"),a.a.createElement("p",null,"Routing's base configuration:"),a.a.createElement(i["a"],{code:"",lang:"html"}),a.a.createElement("p",null,"Webpack's publicPath configuration:"),a.a.createElement(i["a"],{code:"module.exports = {\n output: {\n publicPath: '/child/react-history/',\n },\n};",lang:"js"})),a.a.createElement("li",null,a.a.createElement("p",null,a.a.createElement("code",null,"angular-history")," micro app"),a.a.createElement("p",null,"Routing's base configuration:"),a.a.createElement(i["a"],{code:"providers: [\n {\n provide: APP_BASE_HREF,\n useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/',\n },\n];",lang:"js"}),a.a.createElement("p",null,"The ",a.a.createElement("code",null,"publicPath")," of webpack is set by ",a.a.createElement("code",null,"deploy-url"),", modify ",a.a.createElement("code",null,"package.json"),":"),a.a.createElement(i["a"],{code:'- "build": "ng build",\n+ "build": "ng build --deploy-url /child/angular-history/",',lang:"diff"}))),a.a.createElement("p",null,"Then the ",a.a.createElement("code",null,"registerMicroApps")," function at this time is like this (you need to ensure that ",a.a.createElement("code",null,"activeRule")," and ",a.a.createElement("code",null,"entry")," are different):"),a.a.createElement(i["a"],{code:"registerMicroApps([\n {\n name: 'app-vue-hash',\n entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/\n container: '#container',\n activeRule: '/app-vue-hash',\n },\n {\n name: 'app-vue-history',\n entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/\n container: '#container',\n activeRule: '/app-vue-history',\n },\n // angular and react same as above\n],",lang:"js"}),a.a.createElement("p",null,"So far, the main app and the micro apps can run normally, but the main app and the ",a.a.createElement("code",null,"vue-history"),", ",a.a.createElement("code",null,"react-history"),", and ",a.a.createElement("code",null,"angular-history")," micro apps are ",a.a.createElement("code",null,"history")," routes. The problem of refreshing 404 needs to be solved. nginx` also needs to be configured:"),a.a.createElement(i["a"],{code:"server {\n listen 8080;\n server_name localhost;\n\n location / {\n root html;\n index index.html index.htm;\n try_files $uri $uri/ /index.html;\n }\n\n location /child/vue-history {\n root html;\n index index.html index.htm;\n try_files $uri $uri/ /child/vue-history/index.html;\n }\n # The configuration of angular-history and react-history is the same as above\n}",lang:"conf"}),a.a.createElement("h4",{id:"solution-2-place-the-micro-apps-directly-in-the-secondary-directory-but-set-a-special-activerule"},a.a.createElement(o["AnchorLink"],{to:"#solution-2-place-the-micro-apps-directly-in-the-secondary-directory-but-set-a-special-activerule","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"Solution 2: Place the micro apps directly in the secondary directory, but set a special ",a.a.createElement("code",null,"activeRule")),a.a.createElement(i["a"],{code:"\u2514\u2500\u2500 html/ # root folder\n |\n \u251c\u2500\u2500 vue-hash/ # the folder of the micro app `vue-hash`\n \u251c\u2500\u2500 vue-history/ # the folder of the micro app `vue-history`\n \u251c\u2500\u2500 react-hash/ # the folder of the micro app `react-hash`\n \u251c\u2500\u2500 react-history/ # the folder of the micro app `react-history`\n \u251c\u2500\u2500 angular-hash/ # the folder of the micro app `angular-hash`\n \u251c\u2500\u2500 angular-history/ # the folder of the micro app `angular-history`\n \u251c\u2500\u2500 index.html # index.html of the main app\n \u251c\u2500\u2500 css/ # the css folder of the main app\n \u251c\u2500\u2500 js/ # the js folder of the main app",lang:"unknown"}),a.a.createElement("p",null,"The basic operation is the same as above, just make sure that ",a.a.createElement("code",null,"activeRule")," is different from the storage path name of the micro app."),a.a.createElement("h3",{id:"scenario-2-the-main-app-and-micro-apps-are-deployed-on-different-servers-and-accessed-through-nginx-proxy"},a.a.createElement(o["AnchorLink"],{to:"#scenario-2-the-main-app-and-micro-apps-are-deployed-on-different-servers-and-accessed-through-nginx-proxy","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"Scenario 2: The main app and micro apps are deployed on different servers and accessed through Nginx proxy"),a.a.createElement("p",null,"This is generally done because ",a.a.createElement("strong",null,"the main app is not allowed to access micro apps across domains"),'. The practice is to forward all requests for a special path on the main app server to the micro app server, that is, a "micro app deployed on the main app server" effect is achieved through the proxy.'),a.a.createElement("p",null,"For example, the main app is on the A server, and the micro app is on the B server. The path ",a.a.createElement("code",null,"/app1")," is used to distinguish the micro app, that is, all requests starting with ",a.a.createElement("code",null,"/app1")," on the A server are forwarded to the B server."),a.a.createElement("p",null,"the ",a.a.createElement("code",null,"Nginx")," proxy configuration of the main app is\uff1a"),a.a.createElement(i["a"],{code:"/app1/ {\n proxy_pass http://www.b.com/app1/;\n proxy_set_header Host $host:$server_port;\n}",lang:"unknown"}),a.a.createElement("p",null,"When the main app registers micro apps, ",a.a.createElement("code",null,"entry")," can be a relative path, and ",a.a.createElement("code",null,"activeRule")," cannot be the same as ",a.a.createElement("code",null,"entry")," (otherwise the main app page refreshes and becomes a micro app):"),a.a.createElement(i["a"],{code:"registerMicroApps([\n {\n name: 'app1',\n entry: '/app1/', // http://localhost:8080/app1/\n container: '#container',\n activeRule: '/child-app1',\n },\n],",lang:"js"}),a.a.createElement("p",null,"For micro apps bundled by ",a.a.createElement("code",null,"webpack"),", the ",a.a.createElement("code",null,"publicPath")," bundled by the micro app's ",a.a.createElement("code",null,"webpack")," needs to be configured as ",a.a.createElement("code",null,"/app1/"),", otherwise the micro app's ",a.a.createElement("code",null,"index.html")," can be requested correctly, But the path of ",a.a.createElement("code",null,"js/css")," in the micro app's ",a.a.createElement("code",null,"index.html")," will not carry ",a.a.createElement("code",null,"/app1/"),"."),a.a.createElement(i["a"],{code:"module.exports = {\n output: {\n publicPath: `/app1/`,\n },\n};",lang:"js"}),a.a.createElement("p",null,"After adding ",a.a.createElement("code",null,"/app1/")," to the ",a.a.createElement("code",null,"publicPath")," of the micro app, it must be deployed in the ",a.a.createElement("code",null,"/app1")," directory, otherwise it cannot be accessed independently."),a.a.createElement("p",null,"In addition, if you don't want the micro app to be accessed independently through the proxy path, you can judge based on some information requested. The requesting micro app in the main app is requested with ",a.a.createElement("code",null,"fetch"),", which can include parameters and ",a.a.createElement("code",null,"cookie"),". For example, judge by request header parameters:"),a.a.createElement(i["a"],{code:'if ($http_custom_referer != "main") {\n rewrite /index /404.html;\n}',lang:"js"}),a.a.createElement("h2",{id:"upgrade-from-1x-version-to-2x-version"},a.a.createElement(o["AnchorLink"],{to:"#upgrade-from-1x-version-to-2x-version","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"Upgrade from 1.x version to 2.x version"),a.a.createElement("p",null,"The micro apps does not need to be changed, and the main app needs to be adjusted."),a.a.createElement("p",null,"The basic modification of ",a.a.createElement("code",null,"registerMicroApps")," function is as follows:"),a.a.createElement("ol",null,a.a.createElement("li",null,"Remove the ",a.a.createElement("code",null,"render")," parameter and only need to provide the container."),a.a.createElement("li",null,"Add the ",a.a.createElement("code",null,"loader")," parameter to display the ",a.a.createElement("code",null,"loading")," status. Originally, the ",a.a.createElement("code",null,"loading")," status was provided to the ",a.a.createElement("code",null,"render")," parameter."),a.a.createElement("li",null,"The ",a.a.createElement("code",null,"activeRule")," parameter can be abbreviated as ",a.a.createElement("code",null,"/app"),", which is compatible with the previous function writing."),a.a.createElement("li",null,"The ",a.a.createElement("code",null,"RegisterMicroAppsOpts")," parameter is removed and placed in the parameter of the ",a.a.createElement("code",null,"start")," function.")),a.a.createElement("p",null,"The basic modification of the ",a.a.createElement("code",null,"start")," function is as follows:"),a.a.createElement("ol",null,a.a.createElement("li",null,"The ",a.a.createElement("code",null,"jsSandbox")," configuration has been removed and changed to ",a.a.createElement("code",null,"sandbox"),", and the optional values have also been modified."),a.a.createElement("li",null,"Added ",a.a.createElement("code",null,"getPublicPath")," and ",a.a.createElement("code",null,"getTemplate")," to replace ",a.a.createElement("code",null,"RegisterMicroAppsOpts"),"."))))}));t["default"]=e=>{var t=a.a.useContext(o["context"]),n=t.demos;return a.a.useEffect((()=>{var t;null!==e&&void 0!==e&&null!==(t=e.location)&&void 0!==t&&t.hash&&o["AnchorLink"].scrollToAnchor(decodeURIComponent(e.location.hash.slice(1)))}),[]),a.a.createElement(c,{demos:n})}},EUja:function(e,t,n){"use strict";var r=n("ppGB"),a=n("HYAF");e.exports="".repeat||function(e){var t=String(a(this)),n="",o=r(e);if(o<0||o==1/0)throw RangeError("Wrong number of repetitions");for(;o>0;(o>>>=1)&&(t+=t))1&o&&(n+=t);return n}},EdiO:function(e,t){function n(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1||"".split(/.?/).length?function(e,n){var r=String(i(this)),o=void 0===n?h:n>>>0;if(0===o)return[];if(void 0===e)return[r];if(!a(e))return t.call(r,e,o);var l,c,u,s=[],d=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(e.sticky?"y":""),m=0,g=new RegExp(e.source,d+"g");while(l=p.call(g,r)){if(c=g.lastIndex,c>m&&(s.push(r.slice(m,l.index)),l.length>1&&l.index=o))break;g.lastIndex===l.index&&g.lastIndex++}return m===r.length?!u&&g.test("")||s.push(""):s.push(r.slice(m)),s.length>o?s.slice(0,o):s}:"0".split(void 0,0).length?function(e,n){return void 0===e&&0===n?[]:t.call(this,e,n)}:t,[function(t,n){var a=i(this),o=void 0==t?void 0:t[e];return void 0!==o?o.call(t,a,n):r.call(String(a),t,n)},function(e,a){var i=n(r,e,this,a,r!==t);if(i.done)return i.value;var p=o(e),d=String(this),f=l(p,RegExp),v=p.unicode,b=(p.ignoreCase?"i":"")+(p.multiline?"m":"")+(p.unicode?"u":"")+(g?"y":"g"),y=new f(g?p:"^(?:"+p.source+")",b),E=void 0===a?h:a>>>0;if(0===E)return[];if(0===d.length)return null===s(y,d)?[d]:[];var w=0,k=0,x=[];while(k1?arguments[1]:void 0)}},FDzp:function(e,t,n){var r=n("dOgj");r("Int32",(function(e){return function(t,n,r){return e(this,t,n,r)}}))},FMNM:function(e,t,n){var r=n("xrYK"),a=n("kmMV");e.exports=function(e,t){var n=e.exec;if("function"===typeof n){var o=n.call(e,t);if("object"!==typeof o)throw TypeError("RegExp exec method returned something other than an Object or null");return o}if("RegExp"!==r(e))throw TypeError("RegExp#exec called on incompatible receiver");return a.call(e,t)}},FPCf:function(e,t,n){"use strict";n.r(t);var r=n("q1tI"),a=n.n(r),o=n("dEAq"),i=n("M/Q6"),l=n("H1Ra"),c=a.a.memo((e=>{e.demos;return a.a.createElement(a.a.Fragment,null,a.a.createElement("div",{className:"markdown"},a.a.createElement("h1",{id:"\u5e38\u89c1\u95ee\u9898"},a.a.createElement(o["AnchorLink"],{to:"#\u5e38\u89c1\u95ee\u9898","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),"\u5e38\u89c1\u95ee\u9898"),a.a.createElement("h2",{id:"application-died-in-status-loading_source_code-you-need-to-export-the-functional-lifecycles-in-xxx-entry"},a.a.createElement(o["AnchorLink"],{to:"#application-died-in-status-loading_source_code-you-need-to-export-the-functional-lifecycles-in-xxx-entry","aria-hidden":"true",tabIndex:-1},a.a.createElement("span",{className:"icon icon-link"})),a.a.createElement("code",null,"Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry")),a.a.createElement("p",null,"qiankun \u629b\u51fa\u8fd9\u4e2a\u9519\u8bef\u662f\u56e0\u4e3a\u65e0\u6cd5\u4ece\u5fae\u5e94\u7528\u7684 entry js \u4e2d\u8bc6\u522b\u51fa\u5176\u5bfc\u51fa\u7684\u751f\u547d\u5468\u671f\u94a9\u5b50\u3002"),a.a.createElement("p",null,"\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u51e0\u4e2a\u6b65\u9aa4\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff1a"),a.a.createElement("ol",null,a.a.createElement("li",null,a.a.createElement("p",null,"\u68c0\u67e5\u5fae\u5e94\u7528\u662f\u5426\u5df2\u7ecf\u5bfc\u51fa\u76f8\u5e94\u7684\u751f\u547d\u5468\u671f\u94a9\u5b50\uff0c\u53c2\u8003",a.a.createElement(o["AnchorLink"],{to:"/zh/guide/getting-started#1-%E5%AF%BC%E5%87%BA%E7%9B%B8%E5%BA%94%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90"},"\u6587\u6863"),"\u3002")),a.a.createElement("li",null,a.a.createElement("p",null,"\u68c0\u67e5\u5fae\u5e94\u7528\u7684 webpack \u662f\u5426\u589e\u52a0\u4e86\u6307\u5b9a\u7684\u914d\u7f6e\uff0c\u53c2\u8003",a.a.createElement(o["AnchorLink"],{to:"/zh/guide/getting-started#2-%E9%85%8D%E7%BD%AE%E5%BE%AE%E5%BA%94%E7%94%A8%E7%9A%84%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7"},"\u6587\u6863"),"\u3002")),a.a.createElement("li",null,a.a.createElement("p",null,"\u68c0\u67e5\u5fae\u5e94\u7528\u7684 webpack \u662f\u5426\u914d\u7f6e\u4e86 ",a.a.createElement("code",null,"output.globalObject")," \u7684\u503c\uff0c\u5982\u679c\u6709\u8bf7\u786e\u4fdd\u5176\u503c\u4e3a ",a.a.createElement("code",null,"window"),"\uff0c\u6216\u8005\u79fb\u9664\u8be5\u914d\u7f6e\u4ece\u800c\u4f7f\u7528\u9ed8\u8ba4\u503c\u3002")),a.a.createElement("li",null,a.a.createElement("p",null,"\u68c0\u67e5\u5fae\u5e94\u7528\u7684 ",a.a.createElement("code",null,"package.json")," \u4e2d\u7684 ",a.a.createElement("code",null,"name")," \u5b57\u6bb5\u662f\u5426\u662f\u5fae\u5e94\u7528\u4e2d\u552f\u4e00\u7684\u3002")),a.a.createElement("li",null,a.a.createElement("p",null,"\u68c0\u67e5\u5fae\u5e94\u7528\u7684 entry html \u4e2d\u5165\u53e3\u7684 js \u662f\u4e0d\u662f\u6700\u540e\u4e00\u4e2a\u52a0\u8f7d\u7684\u811a\u672c\u3002\u5982\u679c\u4e0d\u662f\uff0c\u9700\u8981\u79fb\u52a8\u987a\u5e8f\u5c06\u5176\u53d8\u6210\u6700\u540e\u4e00\u4e2a\u52a0\u8f7d\u7684 js\uff0c\u6216\u8005\u5728 html \u4e2d\u5c06\u5165\u53e3 js \u624b\u52a8\u6807\u8bb0\u4e3a ",a.a.createElement("code",null,"entry"),"\uff0c\u5982\uff1a"),a.a.createElement(l["a"],{code:' - + diff --git a/zh/cookbook/index.html b/zh/cookbook/index.html index 18b034bb2..dd254e02c 100644 --- a/zh/cookbook/index.html +++ b/zh/cookbook/index.html @@ -67,7 +67,7 @@

    入门教程

    微应用的路由模式如何选择

    react-routerangular-routervue-router 这三种路由,都支持 hashhistory 模式,微应用使用不同的模式在 qiankun 中略有差别。

    activeRule 使用 location.pathname 区分微应用

    主应用使用 location.pathname 来区分微应用时,微应用可以是 hashhistory 模式。

    注册微应用时 activeRule 这样写即可:

    registerMicroApps([
    {
    name: 'app',
    entry: 'http://localhost:8080',
    container: '#container',
    activeRule: '/app',
    },
    ]);
    1. 当微应用是 history 模式时,设置路由 base 即可

    2. 当微应用是 hash 模式时,三种路由的表现不一致

      路由主应用跳转/app/#/about特殊配置
      vue-router响应 about 路由
      react-router不响应 about 路由
      angular-router响应 about 路由需要设置 --base-href

      angular 应用在 package.json 里面设置 --base-href

      - "start": "ng serve",
      + "start": "ng serve --base-href /angular9",
      - "build": "ng build",
      + "build": "ng build --base-href /angular9",

      打包部署后,angular 微应用可以被主应用访问。但是独立访问时,懒加载的路由会报错,路径不正确。这里有两个解决办法:

      • 方法 1:修改 public-path.js 为:

        __webpack_public_path__ = window.__POWERED_BY_QIANKUN__
        ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
        : `http://${ip}:${port}/`; // 填写你的实际部署地址
      • 方法 2:修改打包命令,并且将微应用部署在 angular9 目录:

        - "build": "ng build",
        + "build": "ng build --base-href /angular9 --deploy-url /angular9/",

    activeRule 使用 location.hash 区分微应用

    当微应用都是 hash 模式时可以使用 hash 区分微应用,主应用的路由模式不限。

    注册微应用时 activeRule 需要这样写:

    const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
    registerMicroApps([
    {
    name: 'app-hash',
    entry: 'http://localhost:8080',
    container: '#container',
    activeRule: getActiveRule('#/app-hash'),
    // 这里也可以直接写 activeRule: '#/app-hash',但是如果主应用是 history 模式或者主应用部署在非根目录,这样写不会生效。
    },
    ]);

    react-routerangular-router 微应用需要设置 activeRule 的值为路由的 base ,写法同 history 模式。

    vue-routerhash 模式下不支持设置路由的 base,需要额外新建一个空的路由页面,将其他所有路由都作为它的 children

    const routes = [
    {
    path: '/app-vue-hash',
    name: 'Home',
    component: Home,
    children: [
    // 其他的路由都写到这里
    ],
    },
    ];

    同时存在多个微应用时

    如果一个页面同时展示多个微应用,需要使用 loadMicroApp 来加载。

    如果这些微应用都有路由跳转的需求,要保证这些路由能互不干扰,需要使用 momery 路由。vue-router 使用 abstract 模式,react-router 使用 memory history 模式,angular-router 不支持。

    如何部署

    建议:主应用和微应用都是独立开发和部署,即它们都属于不同的仓库和服务。

    场景 1:主应用和微应用部署到同一个服务器(同一个 IP 和端口)

    如果服务器数量有限,或不能跨域等原因需要把主应用和微应用部署到一起。

    通常的做法是主应用部署在一级目录,微应用部署在二/三级目录。

    微应用想部署在非根目录,在微应用打包之前需要做两件事:

    1. 必须配置 webpack 构建时的 publicPath目录名称,更多信息请看 webpack 官方说明vue-cli3 的官方说明

    2. history 路由的微应用需要设置 base ,值为目录名称,用于独立访问时使用。

    部署之后注意三点:

    1. activeRule 不能和微应用的真实访问路径一样,否则在主应用页面刷新会直接变成微应用页面。
    2. 微应用的真实访问路径就是微应用的 entryentry 可以为相对路径。
    3. 微应用的 entry 路径最后面的 / 不可省略,否则 publicPath 会设置错误,例如子项的访问路径是 http://localhost:8080/app1,那么 entry 就是 http://localhost:8080/app1/

    具体的部署有以下两种方式,选择其一即可。

    方案 1:微应用都放在在一个特殊名称(不会和微应用重名)的文件夹下(建议使用

    假设我们有一个主应用和 6 个微应用(分别为 vue-hashvue-historyreact-hashreact-historyangular-hashangular-history ),打包后如下放置:

    └── html/ # 根文件夹
    |
    ├── child/ # 存放所有微应用的文件夹
    | ├── vue-hash/ # 存放微应用 vue-hash 的文件夹
    | ├── vue-history/ # 存放微应用 vue-history 的文件夹
    | ├── react-hash/ # 存放微应用 react-hash 的文件夹
    | ├── react-history/ # 存放微应用 react-history 的文件夹
    | ├── angular-hash/ # 存放微应用 angular-hash 的文件夹
    | ├── angular-history/ # 存放微应用 angular-history 的文件夹
    ├── index.html # 主应用的index.html
    ├── css/ # 主应用的css文件夹
    ├── js/ # 主应用的js文件夹

    此时需要设置微应用构建时的 publicPathhistory 模式的路由 base,然后才能打包放到对应的目录里。

    项目路由 basepublicPath真实访问路径
    vue-hash/child/vue-hash/http://localhost:8080/child/vue-hash/
    vue-history/child/vue-history//child/vue-history/http://localhost:8080/child/vue-history/
    react-hash/child/react-hash/http://localhost:8080/child/react-hash/
    react-history/child/react-history//child/react-history/http://localhost:8080/child/react-history/
    angular-hash/child/angular-hash/http://localhost:8080/child/angular-hash/
    angular-history/child/angular-history//child/angular-history/http://localhost:8080/child/angular-history/
    • vue-history 微应用

      路由设置:

      base: window.__POWERED_BY_QIANKUN__ ? '/app-vue-history/' : '/child/vue-history/',

      webpack 打包 publicPath 配置(vue.config.js):

      module.exports = {
      publicPath: '/child/vue-history/',
      };
    • react-history 微应用

      路由设置:

      <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react-history' : '/child/react-history/'}>

      webpack 打包 publicPath 配置:

      module.exports = {
      output: {
      publicPath: '/child/react-history/',
      },
      };
    • angular-history 微应用

      路由设置:

      providers: [
      {
      provide: APP_BASE_HREF,
      useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular-history/' : '/child/angular-history/',
      },
      ];

      webpack 打包的 publicPath 通过 deploy-url 来修改,修改 package.json

      - "build": "ng build",
      + "build": "ng build --deploy-url /child/angular-history/",

    那么此时的注册函数是这样的(需要保证 activeRuleentry 不同):

    registerMicroApps([
    {
    name: 'app-vue-hash',
    entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/
    container: '#container',
    activeRule: '/app-vue-hash',
    },
    {
    name: 'app-vue-history',
    entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/
    container: '#container',
    activeRule: '/app-vue-history',
    },
    // angular 和 react 同上
    ],

    至此主应用已经和微应用都能跑起来了,但是主应用和 vue-historyreact-historyangular-history 微应用是 history 路由,需要解决刷新 404 的问题,nginx 还需要配置一下:

    server {
    listen 8080;
    server_name localhost;
    location / {
    root html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
    }
    -
    location /child/vue-history {
    root html;
    index index.html index.htm;
    try_files $uri $uri/ /child/vue-history/index.html;
    }
    # angular 和 react 的history 配置同上
    }

    方案 2:微应用直接放在二级目录,但是设置特殊的 activeRule

    └── html/ # 根文件夹
    |
    ├── vue-hash/ # 存放微应用 vue-hash 的文件夹
    ├── vue-history/ # 存放微应用 vue-history 的文件夹
    ├── react-hash/ # 存放微应用 react-hash 的文件夹
    ├── react-history/ # 存放微应用 react-history 的文件夹
    ├── angular-hash/ # 存放微应用 angular-hash 的文件夹
    ├── angular-history/ # 存放微应用 angular-history 的文件夹
    ├── index.html # 主应用的index.html
    ├── css/ # 主应用的css文件夹
    ├── js/ # 主应用的js文件夹

    基本操作和上面是一样的,只要保证 activeRule 和微应用的存放路径名不一样即可。

    场景 2:主应用和微应用部署在不同的服务器,使用 Nginx 代理访问

    一般这么做是因为不允许主应用跨域访问微应用,做法就是将主应用服务器上一个特殊路径的请求全部转发到微应用的服务器上,即通过代理实现“微应用部署在主应用服务器上”的效果。

    例如,主应用在 A 服务器,微应用在 B 服务器,使用路径 /app1 来区分微应用,即 A 服务器上所有 /app1 开头的请求都转发到 B 服务器上。

    此时主应用的 Nginx 代理配置为:

    /app1/ {
    proxy_pass http://www.b.com/app1/;
    proxy_set_header Host $host:$server_port;
    }

    主应用注册微应用时,entry 可以为相对路径,activeRule 不可以和 entry 一样(否则主应用页面刷新就变成微应用):

    registerMicroApps([
    {
    name: 'app1',
    entry: '/app1/', // http://localhost:8080/app1/
    container: '#container',
    activeRule: '/child-app1',
    },
    ],

    对于 webpack 构建的微应用,微应用的 webpack 打包的 publicPath 需要配置成 /app1/,否则微应用的 index.html 能正确请求,但是微应用 index.html 里面的 js/css 路径不会带上 /app1/

    module.exports = {
    output: {
    publicPath: `/app1/`,
    },
    };

    微应用打包的 publicPath 加上 /app1/ 之后,必须部署在 /app1 目录,否则无法独立访问。

    另外,如果不想微应用通过代理路径被独立访问,可以根据请求的一些信息判断下,主应用中请求微应用是用 fetch 请求的,可以带参数和 cookie。例如通过请求头参数判断:

    if ($http_custom_referer != "main") {
    rewrite /index /404.html;
    }

    从 1.x 版本升级到 2.x 版本

    微应用无需改动,主应用需要做一些调整。

    registerMicroApps 函数基本修改如下:

    1. 去掉 render 参数,只需要提供容器 container 即可。
    2. 增加 loader 参数,用于展示 loading 状态,原本 loading 状态是提供给 render 参数的。
    3. activeRule 参数可以简写为 /app,兼容之前的函数写法。
    4. RegisterMicroAppsOpts 参数去掉了,放在了 start 函数的参数里面。

    start 函数基本修改如下:

    1. jsSandbox 配置去掉,改为 sandbox ,可选值也修改了。
    2. 新增了 getPublicPathgetTemplate ,用于替代RegisterMicroAppsOpts
    +
    location /child/vue-history {
    root html;
    index index.html index.htm;
    try_files $uri $uri/ /child/vue-history/index.html;
    }
    # angular 和 react 的history 配置同上
    }

    方案 2:微应用直接放在二级目录,但是设置特殊的 activeRule

    └── html/ # 根文件夹
    |
    ├── vue-hash/ # 存放微应用 vue-hash 的文件夹
    ├── vue-history/ # 存放微应用 vue-history 的文件夹
    ├── react-hash/ # 存放微应用 react-hash 的文件夹
    ├── react-history/ # 存放微应用 react-history 的文件夹
    ├── angular-hash/ # 存放微应用 angular-hash 的文件夹
    ├── angular-history/ # 存放微应用 angular-history 的文件夹
    ├── index.html # 主应用的index.html
    ├── css/ # 主应用的css文件夹
    ├── js/ # 主应用的js文件夹

    基本操作和上面是一样的,只要保证 activeRule 和微应用的存放路径名不一样即可。

    场景 2:主应用和微应用部署在不同的服务器,使用 Nginx 代理访问

    一般这么做是因为不允许主应用跨域访问微应用,做法就是将主应用服务器上一个特殊路径的请求全部转发到微应用的服务器上,即通过代理实现“微应用部署在主应用服务器上”的效果。

    例如,主应用在 A 服务器,微应用在 B 服务器,使用路径 /app1 来区分微应用,即 A 服务器上所有 /app1 开头的请求都转发到 B 服务器上。

    此时主应用的 Nginx 代理配置为:

    /app1/ {
    proxy_pass http://www.b.com/app1/;
    proxy_set_header Host $host:$server_port;
    }

    主应用注册微应用时,entry 可以为相对路径,activeRule 不可以和 entry 一样(否则主应用页面刷新就变成微应用):

    registerMicroApps([
    {
    name: 'app1',
    entry: '/app1/', // http://localhost:8080/app1/
    container: '#container',
    activeRule: '/child-app1',
    },
    ],

    对于 webpack 构建的微应用,微应用的 webpack 打包的 publicPath 需要配置成 /app1/,否则微应用的 index.html 能正确请求,但是微应用 index.html 里面的 js/css 路径不会带上 /app1/

    module.exports = {
    output: {
    publicPath: `/app1/`,
    },
    };

    微应用打包的 publicPath 加上 /app1/ 之后,必须部署在 /app1 目录,否则无法独立访问。

    另外,如果不想微应用通过代理路径被独立访问,可以根据请求的一些信息判断下,主应用中请求微应用是用 fetch 请求的,可以带参数和 cookie。例如通过请求头参数判断:

    if ($http_custom_referer != "main") {
    rewrite /index /404.html;
    }

    从 1.x 版本升级到 2.x 版本

    微应用无需改动,主应用需要做一些调整。

    registerMicroApps 函数基本修改如下:

    1. 去掉 render 参数,只需要提供容器 container 即可。
    2. 增加 loader 参数,用于展示 loading 状态,原本 loading 状态是提供给 render 参数的。
    3. activeRule 参数可以简写为 /app,兼容之前的函数写法。
    4. RegisterMicroAppsOpts 参数去掉了,放在了 start 函数的参数里面。

    start 函数基本修改如下:

    1. jsSandbox 配置去掉,改为 sandbox ,可选值也修改了。
    2. 新增了 getPublicPathgetTemplate ,用于替代RegisterMicroAppsOpts
    - + diff --git a/zh/faq/index.html b/zh/faq/index.html index b792b040b..4476598b2 100644 --- a/zh/faq/index.html +++ b/zh/faq/index.html @@ -65,7 +65,7 @@ 常见问题 - qiankun -

    qiankun

    常见问题

    Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry

    qiankun 抛出这个错误是因为无法从微应用的 entry js 中识别出其导出的生命周期钩子。

    可以通过以下几个步骤解决这个问题:

    1. 检查微应用是否已经导出相应的生命周期钩子,参考文档

    2. 检查微应用的 webpack 是否增加了指定的配置,参考文档

    3. 检查微应用的 webpack 是否配置了 output.globalObject 的值,如果有请确保其值为 window,或者移除该配置从而使用默认值。

    4. 检查微应用的 package.json 中的 name 字段是否是微应用中唯一的。

    5. 检查微应用的 entry html 中入口的 js 是不是最后一个加载的脚本。如果不是,需要移动顺序将其变成最后一个加载的 js,或者在 html 中将入口 js 手动标记为 entry,如:

      <script src="/antd.js"></script>
      <script src="/appEntry.js" entry></script>
      <script src="https://www.google.com/analytics.js"></script>
    6. 如果开发环境可以,生产环境不行,检查微应用的 index.htmlentry js 是否正常返回,比如说返回了 404.html

    7. 如果你正在使用 webpack5,请看这个 issues

    8. 检查主应用和微应用是否使用了 AMD 或 CommonJS 模块化。检查方法:单独运行微应用和主应用,在控制台输入如下代码:(typeof exports === 'object' && typeof module === 'object') || (typeof define === 'function' && define.amd) || typeof exports === 'object',如果返回 true,则说明是这种情况,主要有以下两个解决办法:

      • 解决办法1:修改微应用 webpacklibraryTarget'window'
      const packageName = require('./package.json').name;
      module.exports = {
      output: {
      library: `${packageName}-[name]`,
      - libraryTarget: 'umd',
      + libraryTarget: 'window',
      jsonpFunction: `webpackJsonp_${packageName}`,
      },
      };
    9. 如果在上述步骤完成后仍有问题,通常说明是浏览器兼容性问题导致的。可以尝试 将有问题的微应用的 webpack output.library 配置成跟主应用中注册的 name 字段一致,如:

    假如主应用配置是这样的:

    // 主应用
    registerMicroApps([
    {
    name: 'brokenSubApp',
    entry: '//localhost:7100',
    container: '#yourContainer',
    activeRule: '/react',
    },
    ]);

    将微应用的 output.library 改为跟主应用中注册的一致:

    module.exports = {
    output: {
    // 这里改成跟主应用中注册的一致
    library: 'brokenSubApp',
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
    },
    };

    Application died in status NOT_MOUNTED: Target container with #container not existed after xxx mounted!

    qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在了。可能的原因有:

    1. 微应用的根 id 与其他 DOM 冲突。解决办法是:修改根 id 的查找范围。

      vue 微应用:

      function render(props = {}) {
      const { container } = props;
      instance = new Vue({
      router,
      store,
      render: (h) => h(App),
      }).$mount(container ? container.querySelector('#app') : '#app');
      }
      export async function mount(props) {
      render(props);
      }

      react 微应用:

      function render(props) {
      const { container } = props;
      ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
      }
      export async function mount(props) {
      render(props);
      }
      export async function unmount(props) {
      const { container } = props;
      ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
      }
    2. 微应用的某些 js 里面使用了 document.write,比如高德地图 1.x 版本,腾讯地图 2.x 版本。

      如果是地图 js 导致的,先看看升级能否解决,比如说高德地图升级到 2.x 版本即可。

      如果升级无法解决,建议将地图放到主应用加载,微应用也引入这个地图 js(独立运行时使用),但是给 <script> 标签加上 ignore 属性:

      <script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>

      如果是其他的情况,请不要使用 document.write

    Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!

    这个报错通常出现在主应用为 vue 时,容器写在了路由页面并且使用了路由过渡效果,一些特殊的过渡效果会导致微应用在 mounting 的过程中容器不存在,解决办法就是换成其他的过渡效果,或者去掉路由过渡。

    Application died in status NOT_MOUNTED: Target container with #container not existed while xxx loading!

    与上面的报错类似,这个报错是因为微应用加载时容器 DOM 不存在。一般是因为 start 函数调用时机不正确导致的,调整 start 函数调用时机即可。

    如何判断容器 DOM 加载完成?vue 应用可以在 mounted 生命周期调用,react 应用可以在 componentDidMount 生命周期调用。

    如果仍然报错,检查容器 DOM 是否放在了主应用的某个路由页面,请参考如何在主应用的某个路由页面加载微应用

    [import-html-entry]: error occurs while excuting xxx script http://xxx.xxx.xxx/x.js

    其中第一行只是 qiankun 通过 console.error 打印出来的一个辅助信息,目的是帮助用户更快的知道是哪个 js 报错了,并不是真的异常。真正的异常信息在第二行。

    比如上图这样一个报错,指的是 qiankun 在执行子应用的 http://localhost:9100/index.bundle.js 时,这个 js 本身抛异常了。而具体的异常信息就是 Uncaught TypeError: Cannot read property 'call' of undefined.

    子应用本身的异常,可以尝试通过以下步骤排查解决:

    1. 根据具体的异常信息,检查报错的 js 是否有语法错误,比如少了分号、依赖了未初始化的变量等。
    2. 是否依赖了主应用提供的全局变量,但实际主应用并未初始化。
    3. 兼容性问题。子应用这个 js 本身在当前运行环境存在语法兼容性问题。

    如何在主应用的某个路由页面加载微应用

    必须保证微应用加载时主应用这个路由页面也加载了。

    vue + vue-router 技术栈的主应用:

    1. 主应用注册这个路由时给 path 加一个 *注意:如果这个路由有其他子路由,需要另外注册一个路由,仍然使用这个组件即可
      const routes = [
      {
      path: '/portal/*',
      name: 'portal',
      component: () => import('../views/Portal.vue'),
      },
      ];
    2. 微应用的 activeRule 需要包含主应用的这个路由 path
      registerMicroApps([
      {
      name: 'app1',
      entry: 'http://localhost:8080',
      container: '#container',
      activeRule: '/portal/app1',
      },
      ]);
    3. Portal.vue 这个组件的 mounted 周期调用 start 函数,注意不要重复调用
      import { start } from 'qiankun';
      export default {
      mounted() {
      if (!window.qiankunStarted) {
      window.qiankunStarted = true;
      start();
      }
      },
      };

    react + react-router 技术栈的主应用:只需要让微应用的 activeRule 包含主应用的这个路由即可。

    angular + angular-router 技术栈的主应用,与 vue 项目类似:

    1. 主应用给这个路由注册一个通配符的子路由,内容为空。

      const routes: Routes = [
      {
      path: 'portal',
      component: PortalComponent,
      children: [{ path: '**', component: EmptyComponent }],
      },
      ];
    2. 微应用的 activeRule 需要包含主应用的这个路由 path

      registerMicroApps([
      {
      name: 'app1',
      entry: 'http://localhost:8080',
      container: '#container',
      activeRule: '/portal/app1',
      },
      ]);
    3. 在这个路由组件的 ngAfterViewInit 周期调用 start 函数,注意不要重复调用

      import { start } from 'qiankun';
      export class PortalComponent implements AfterViewInit {
      ngAfterViewInit(): void {
      if (!window.qiankunStarted) {
      window.qiankunStarted = true;
      start();
      }
      }
      }

    Vue Router 报错 Uncaught TypeError: Cannot redefine property: $router

    qiankun 中的代码使用 Proxy 去代理父页面的 window,来实现的沙箱,在微应用中访问 window.Vue 时,会先在自己的 window 里查找有没有 Vue 属性,如果没有就去父应用里查找。

    在 VueRouter 的代码里有这样三行代码,会在模块加载的时候就访问 window.Vue 这个变量,微应用中报这个错,一般是由于父应用中的 Vue 挂载到了父应用的 window 对象上了。

    if (inBrowser && window.Vue) {
    window.Vue.use(VueRouter);
    }

    可以从以下方式中选择一种来解决问题:

    1. 在主应用中不使用 CDN 等 external 的方式来加载 Vue 框架,使用前端打包软件来加载模块
    2. 在主应用中,将 window.Vue 变量改个名称,例如 window.Vue2 = window.Vue; delete window.Vue

    为什么微应用加载的资源会 404?

    原因是 webpack 加载资源时未使用正确的 publicPath

    可以通过以下两个方式解决这个问题:

    a. 使用 webpack 运行时 publicPath 配置

    qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加如下代码:

    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;

    关于运行时 publicPath 的技术细节,可以参考 webpack 文档

    runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。

    b. 使用 webpack 静态 publicPath 配置

    你需要将你的 webpack publicPath 配置设置成一个绝对地址的 url,比如在开发环境可能是:

    {
    output: {
    publicPath: `//localhost:${port}`,
    }
    }

    微应用打包之后 css 中的字体文件和图片加载 404

    原因是 qiankun 将外链样式改成了内联样式,但是字体文件和背景图片的加载路径是相对路径。

    css 文件一旦打包完成,就无法通过动态修改 publicPath 来修正其中的字体文件和背景图片的路径。

    主要有以下几个解决方案:

    1. 所有图片等静态资源上传至 cdncss 中直接引用 cdn 地址(推荐

    2. 借助 webpackurl-loader 将字体文件和图片打包成 base64(适用于字体文件和图片体积小的项目)(推荐

    module.exports = {
    module: {
    rules: [
    {
    test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
    use: [
    {
    loader: 'url-loader',
    options: {},
    },
    ],
    },
    ],
    },
    };

    vue-cli3 项目写法:

    module.exports = {
    chainWebpack: (config) => {
    config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
    config.module.rule('images').use('url-loader').loader('url-loader').options({}).end();
    },
    };

    vue-cli5 项目,使用 asset/inline 替代 url-loader,写法:

    module.exports = {
    chainWebpack: (config) => {
    config.module.rule('fonts').type('asset/inline').set('generator', {});
    config.module.rule('images').type('asset/inline').set('generator', {});
    },
    };
    1. 借助 webpackfile-loader ,在打包时给其注入完整路径(适用于字体文件和图片体积比较大的项目)
    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    module: {
    rules: [
    {
    test: /\.(png|jpe?g|gif|webp)$/i,
    use: [
    {
    loader: 'file-loader',
    options: {
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    ],
    },
    {
    test: /\.(woff2?|eot|ttf|otf)$/i,
    use: [
    {
    loader: 'file-loader',
    options: {
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    ],
    },
    ],
    },
    };

    vue-cli3 项目写法:

    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    chainWebpack: (config) => {
    const fontRule = config.module.rule('fonts');
    fontRule.uses.clear();
    fontRule
    .use('file-loader')
    .loader('file-loader')
    .options({
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    })
    .end();
    const imgRule = config.module.rule('images');
    imgRule.uses.clear();
    imgRule
    .use('file-loader')
    .loader('file-loader')
    .options({
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    })
    .end();
    },
    };
    1. 将两种方案结合起来,小文件转 base64 ,大文件注入路径前缀
    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    module: {
    rules: [
    {
    test: /\.(png|jpe?g|gif|webp)$/i,
    use: [
    {
    loader: 'url-loader',
    options: {},
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    },
    ],
    },
    {
    test: /\.(woff2?|eot|ttf|otf)$/i,
    use: [
    {
    loader: 'url-loader',
    options: {},
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    },
    ],
    },
    ],
    },
    };

    vue-cli3 项目写法:

    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    chainWebpack: (config) => {
    config.module
    .rule('fonts')
    .use('url-loader')
    .loader('url-loader')
    .options({
    limit: 4096, // 小于4kb将会被打包成 base64
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    })
    .end();
    config.module
    .rule('images')
    .use('url-loader')
    .loader('url-loader')
    .options({
    limit: 4096, // 小于4kb将会被打包成 base64
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    });
    },
    };
    1. vue-cli3 项目可以将 css 打包到 js里面,不单独生成文件(不推荐,仅适用于 css 较少的项目)

    配置参考 vue-cli3 官网:

    module.exports = {
    css: {
    extract: false,
    },
    };

    微应用静态资源一定要支持跨域吗?

    是的。

    由于 qiankun 是通过 fetch 去获取微应用的引入的静态资源的,所以必须要求这些静态资源支持跨域

    如果是自己的脚本,可以通过开发服务端跨域来支持。如果是三方脚本且无法为其添加跨域头,可以将脚本拖到本地,由自己的服务器 serve 来支持跨域。

    参考:Nginx 跨域配置

    如何解决由于运营商动态插入的脚本加载异常导致微应用加载失败的问题

    运营商插入的脚本通常会用 async 标记从而避免 block 微应用的加载,这种通常没问题,如:

    <script async src="//www.rogue.com/rogue.js"></script>

    但如果有些插入的脚本不是被标记成 async 的,这类脚本一旦运行失败,将会导致整个应用被 block 且后续的脚本也不再执行。我们可以通过以下几个方式来解决这个问题:

    使用自定义的 getTemplate 方法

    通过自己实现的 getTemplate 方法过滤微应用 HTML 模板中的异常脚本

    import { start } from 'qiankun';
    +

    qiankun

    常见问题

    Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry

    qiankun 抛出这个错误是因为无法从微应用的 entry js 中识别出其导出的生命周期钩子。

    可以通过以下几个步骤解决这个问题:

    1. 检查微应用是否已经导出相应的生命周期钩子,参考文档

    2. 检查微应用的 webpack 是否增加了指定的配置,参考文档

    3. 检查微应用的 webpack 是否配置了 output.globalObject 的值,如果有请确保其值为 window,或者移除该配置从而使用默认值。

    4. 检查微应用的 package.json 中的 name 字段是否是微应用中唯一的。

    5. 检查微应用的 entry html 中入口的 js 是不是最后一个加载的脚本。如果不是,需要移动顺序将其变成最后一个加载的 js,或者在 html 中将入口 js 手动标记为 entry,如:

      <script src="/antd.js"></script>
      <script src="/appEntry.js" entry></script>
      <script src="https://www.google.com/analytics.js"></script>
    6. 如果开发环境可以,生产环境不行,检查微应用的 index.htmlentry js 是否正常返回,比如说返回了 404.html

    7. 如果你正在使用 webpack5,但没用使用模块联邦,请看这个 issues

    8. 如果你正在使用 webpack5,并且使用了使用模块联邦。需要在 index 文件中暴露生命周期函数,然后在 bootstrap 文件向外暴露生命周期函数。

    const promise = import("index");
    export const bootstrap = () => promise.then(m => m.boostrap());
    export const mount = () => promise.then(m => m.mount());
    export const unmount = () => promise.then(m => m.unmount());
    1. 检查主应用和微应用是否使用了 AMD 或 CommonJS 模块化。检查方法:单独运行微应用和主应用,在控制台输入如下代码:(typeof exports === 'object' && typeof module === 'object') || (typeof define === 'function' && define.amd) || typeof exports === 'object',如果返回 true,则说明是这种情况,主要有以下两个解决办法:

      • 解决办法1:修改微应用 webpacklibraryTarget'window'
      const packageName = require('./package.json').name;
      module.exports = {
      output: {
      library: `${packageName}-[name]`,
      - libraryTarget: 'umd',
      + libraryTarget: 'window',
      jsonpFunction: `webpackJsonp_${packageName}`,
      },
      };
    2. 如果在上述步骤完成后仍有问题,通常说明是浏览器兼容性问题导致的。可以尝试 将有问题的微应用的 webpack output.library 配置成跟主应用中注册的 name 字段一致,如:

    假如主应用配置是这样的:

    // 主应用
    registerMicroApps([
    {
    name: 'brokenSubApp',
    entry: '//localhost:7100',
    container: '#yourContainer',
    activeRule: '/react',
    },
    ]);

    将微应用的 output.library 改为跟主应用中注册的一致:

    module.exports = {
    output: {
    // 这里改成跟主应用中注册的一致
    library: 'brokenSubApp',
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
    },
    };

    Application died in status NOT_MOUNTED: Target container with #container not existed after xxx mounted!

    qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在了。可能的原因有:

    1. 微应用的根 id 与其他 DOM 冲突。解决办法是:修改根 id 的查找范围。

      vue 微应用:

      function render(props = {}) {
      const { container } = props;
      instance = new Vue({
      router,
      store,
      render: (h) => h(App),
      }).$mount(container ? container.querySelector('#app') : '#app');
      }
      export async function mount(props) {
      render(props);
      }

      react 微应用:

      function render(props) {
      const { container } = props;
      ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
      }
      export async function mount(props) {
      render(props);
      }
      export async function unmount(props) {
      const { container } = props;
      ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
      }
    2. 微应用的某些 js 里面使用了 document.write,比如高德地图 1.x 版本,腾讯地图 2.x 版本。

      如果是地图 js 导致的,先看看升级能否解决,比如说高德地图升级到 2.x 版本即可。

      如果升级无法解决,建议将地图放到主应用加载,微应用也引入这个地图 js(独立运行时使用),但是给 <script> 标签加上 ignore 属性:

      <script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>

      如果是其他的情况,请不要使用 document.write

    Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!

    这个报错通常出现在主应用为 vue 时,容器写在了路由页面并且使用了路由过渡效果,一些特殊的过渡效果会导致微应用在 mounting 的过程中容器不存在,解决办法就是换成其他的过渡效果,或者去掉路由过渡。

    Application died in status NOT_MOUNTED: Target container with #container not existed while xxx loading!

    与上面的报错类似,这个报错是因为微应用加载时容器 DOM 不存在。一般是因为 start 函数调用时机不正确导致的,调整 start 函数调用时机即可。

    如何判断容器 DOM 加载完成?vue 应用可以在 mounted 生命周期调用,react 应用可以在 componentDidMount 生命周期调用。

    如果仍然报错,检查容器 DOM 是否放在了主应用的某个路由页面,请参考如何在主应用的某个路由页面加载微应用

    [import-html-entry]: error occurs while excuting xxx script http://xxx.xxx.xxx/x.js

    其中第一行只是 qiankun 通过 console.error 打印出来的一个辅助信息,目的是帮助用户更快的知道是哪个 js 报错了,并不是真的异常。真正的异常信息在第二行。

    比如上图这样一个报错,指的是 qiankun 在执行子应用的 http://localhost:9100/index.bundle.js 时,这个 js 本身抛异常了。而具体的异常信息就是 Uncaught TypeError: Cannot read property 'call' of undefined.

    子应用本身的异常,可以尝试通过以下步骤排查解决:

    1. 根据具体的异常信息,检查报错的 js 是否有语法错误,比如少了分号、依赖了未初始化的变量等。
    2. 是否依赖了主应用提供的全局变量,但实际主应用并未初始化。
    3. 兼容性问题。子应用这个 js 本身在当前运行环境存在语法兼容性问题。

    如何在主应用的某个路由页面加载微应用

    必须保证微应用加载时主应用这个路由页面也加载了。

    vue + vue-router 技术栈的主应用:

    1. 主应用注册这个路由时给 path 加一个 *注意:如果这个路由有其他子路由,需要另外注册一个路由,仍然使用这个组件即可
      const routes = [
      {
      path: '/portal/*',
      name: 'portal',
      component: () => import('../views/Portal.vue'),
      },
      ];
    2. 微应用的 activeRule 需要包含主应用的这个路由 path
      registerMicroApps([
      {
      name: 'app1',
      entry: 'http://localhost:8080',
      container: '#container',
      activeRule: '/portal/app1',
      },
      ]);
    3. Portal.vue 这个组件的 mounted 周期调用 start 函数,注意不要重复调用
      import { start } from 'qiankun';
      export default {
      mounted() {
      if (!window.qiankunStarted) {
      window.qiankunStarted = true;
      start();
      }
      },
      };

    react + react-router 技术栈的主应用:只需要让微应用的 activeRule 包含主应用的这个路由即可。

    angular + angular-router 技术栈的主应用,与 vue 项目类似:

    1. 主应用给这个路由注册一个通配符的子路由,内容为空。

      const routes: Routes = [
      {
      path: 'portal',
      component: PortalComponent,
      children: [{ path: '**', component: EmptyComponent }],
      },
      ];
    2. 微应用的 activeRule 需要包含主应用的这个路由 path

      registerMicroApps([
      {
      name: 'app1',
      entry: 'http://localhost:8080',
      container: '#container',
      activeRule: '/portal/app1',
      },
      ]);
    3. 在这个路由组件的 ngAfterViewInit 周期调用 start 函数,注意不要重复调用

      import { start } from 'qiankun';
      export class PortalComponent implements AfterViewInit {
      ngAfterViewInit(): void {
      if (!window.qiankunStarted) {
      window.qiankunStarted = true;
      start();
      }
      }
      }

    Vue Router 报错 Uncaught TypeError: Cannot redefine property: $router

    qiankun 中的代码使用 Proxy 去代理父页面的 window,来实现的沙箱,在微应用中访问 window.Vue 时,会先在自己的 window 里查找有没有 Vue 属性,如果没有就去父应用里查找。

    在 VueRouter 的代码里有这样三行代码,会在模块加载的时候就访问 window.Vue 这个变量,微应用中报这个错,一般是由于父应用中的 Vue 挂载到了父应用的 window 对象上了。

    if (inBrowser && window.Vue) {
    window.Vue.use(VueRouter);
    }

    可以从以下方式中选择一种来解决问题:

    1. 在主应用中不使用 CDN 等 external 的方式来加载 Vue 框架,使用前端打包软件来加载模块
    2. 在主应用中,将 window.Vue 变量改个名称,例如 window.Vue2 = window.Vue; delete window.Vue

    为什么微应用加载的资源会 404?

    原因是 webpack 加载资源时未使用正确的 publicPath

    可以通过以下两个方式解决这个问题:

    a. 使用 webpack 运行时 publicPath 配置

    qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加如下代码:

    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;

    关于运行时 publicPath 的技术细节,可以参考 webpack 文档

    runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。

    b. 使用 webpack 静态 publicPath 配置

    你需要将你的 webpack publicPath 配置设置成一个绝对地址的 url,比如在开发环境可能是:

    {
    output: {
    publicPath: `//localhost:${port}`,
    }
    }

    微应用打包之后 css 中的字体文件和图片加载 404

    原因是 qiankun 将外链样式改成了内联样式,但是字体文件和背景图片的加载路径是相对路径。

    css 文件一旦打包完成,就无法通过动态修改 publicPath 来修正其中的字体文件和背景图片的路径。

    主要有以下几个解决方案:

    1. 所有图片等静态资源上传至 cdncss 中直接引用 cdn 地址(推荐

    2. 借助 webpackurl-loader 将字体文件和图片打包成 base64(适用于字体文件和图片体积小的项目)(推荐

    module.exports = {
    module: {
    rules: [
    {
    test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
    use: [
    {
    loader: 'url-loader',
    options: {},
    },
    ],
    },
    ],
    },
    };

    vue-cli3 项目写法:

    module.exports = {
    chainWebpack: (config) => {
    config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
    config.module.rule('images').use('url-loader').loader('url-loader').options({}).end();
    },
    };

    vue-cli5 项目,使用 asset/inline 替代 url-loader,写法:

    module.exports = {
    chainWebpack: (config) => {
    config.module.rule('fonts').type('asset/inline').set('generator', {});
    config.module.rule('images').type('asset/inline').set('generator', {});
    },
    };
    1. 借助 webpackfile-loader ,在打包时给其注入完整路径(适用于字体文件和图片体积比较大的项目)
    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    module: {
    rules: [
    {
    test: /\.(png|jpe?g|gif|webp)$/i,
    use: [
    {
    loader: 'file-loader',
    options: {
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    ],
    },
    {
    test: /\.(woff2?|eot|ttf|otf)$/i,
    use: [
    {
    loader: 'file-loader',
    options: {
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    ],
    },
    ],
    },
    };

    vue-cli3 项目写法:

    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    chainWebpack: (config) => {
    const fontRule = config.module.rule('fonts');
    fontRule.uses.clear();
    fontRule
    .use('file-loader')
    .loader('file-loader')
    .options({
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    })
    .end();
    const imgRule = config.module.rule('images');
    imgRule.uses.clear();
    imgRule
    .use('file-loader')
    .loader('file-loader')
    .options({
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    })
    .end();
    },
    };
    1. 将两种方案结合起来,小文件转 base64 ,大文件注入路径前缀
    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    module: {
    rules: [
    {
    test: /\.(png|jpe?g|gif|webp)$/i,
    use: [
    {
    loader: 'url-loader',
    options: {},
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    },
    ],
    },
    {
    test: /\.(woff2?|eot|ttf|otf)$/i,
    use: [
    {
    loader: 'url-loader',
    options: {},
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    },
    ],
    },
    ],
    },
    };

    vue-cli3 项目写法:

    const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`;
    module.exports = {
    chainWebpack: (config) => {
    config.module
    .rule('fonts')
    .use('url-loader')
    .loader('url-loader')
    .options({
    limit: 4096, // 小于4kb将会被打包成 base64
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'fonts/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    })
    .end();
    config.module
    .rule('images')
    .use('url-loader')
    .loader('url-loader')
    .options({
    limit: 4096, // 小于4kb将会被打包成 base64
    fallback: {
    loader: 'file-loader',
    options: {
    name: 'img/[name].[hash:8].[ext]',
    publicPath,
    },
    },
    });
    },
    };
    1. vue-cli3 项目可以将 css 打包到 js里面,不单独生成文件(不推荐,仅适用于 css 较少的项目)

    配置参考 vue-cli3 官网:

    module.exports = {
    css: {
    extract: false,
    },
    };

    微应用静态资源一定要支持跨域吗?

    是的。

    由于 qiankun 是通过 fetch 去获取微应用的引入的静态资源的,所以必须要求这些静态资源支持跨域

    如果是自己的脚本,可以通过开发服务端跨域来支持。如果是三方脚本且无法为其添加跨域头,可以将脚本拖到本地,由自己的服务器 serve 来支持跨域。

    参考:Nginx 跨域配置

    如何解决由于运营商动态插入的脚本加载异常导致微应用加载失败的问题

    运营商插入的脚本通常会用 async 标记从而避免 block 微应用的加载,这种通常没问题,如:

    <script async src="//www.rogue.com/rogue.js"></script>

    但如果有些插入的脚本不是被标记成 async 的,这类脚本一旦运行失败,将会导致整个应用被 block 且后续的脚本也不再执行。我们可以通过以下几个方式来解决这个问题:

    使用自定义的 getTemplate 方法

    通过自己实现的 getTemplate 方法过滤微应用 HTML 模板中的异常脚本

    import { start } from 'qiankun';
    start({
    getTemplate(tpl) {
    return tpl.replace('<script src="/to-be-replaced.js"><script>', '');
    },
    });

    使用自定义的 fetch 方法

    通过自己实现的 fetch 方法拦截有问题的脚本

    import { start } from 'qiankun';
    start({
    async fetch(url, ...args) {
    if (url === 'http://to-be-replaced.js') {
    return {
    async text() {
    return '';
    },
    };
    }
    return window.fetch(url, ...args);
    },
    });

    将微应用的 HTML 的 response content-type 改为 text/plain(终极方案)

    原理是运营商只能识别 response content-type 为 text/html 的请求并插入脚本,text/plain 类型的响应则不会被劫持。

    修改微应用 HTML 的 content-type 方法可以自行 google,也有一个更简单高效的方案:

    1. 微应用发布时从 index.html 复制出一个 index.txt 文件出来

    2. 将主应用中的 entry 改为 txt 地址,如:

      registerMicroApps(
      [
      - { name: 'app1', entry: '//localhost:8080/index.html', container, activeRule },
      + { name: 'app1', entry: '//localhost:8080/index.txt', container, activeRule },
      ],
      );

    如何确保主应用跟微应用之间的样式隔离

    qiankun 将会自动隔离微应用之间的样式(开启沙箱的情况下),你可以通过手动的方式确保主应用与微应用之间的样式隔离。比如给主应用的所有样式添加一个前缀,或者假如你使用了 ant-design 这样的组件库,你可以通过这篇文档中的配置方式给主应用样式自动添加指定的前缀。

    以 antd 为例:

    1. 配置 webpack 修改 less 变量

      {
      loader: 'less-loader',
      + options: {
      + modifyVars: {
      + '@ant-prefix': 'yourPrefix',
      + },
      + javascriptEnabled: true,
      + },
      }
    2. 配置 antd ConfigProvider

      import { ConfigProvider } from 'antd';
      @@ -75,7 +75,7 @@
      start({
      fetch(url, ...args) {
      // 给指定的微应用 entry 开启跨域请求
      if (url === 'http://app.alipay.com/entry.html') {
      return window.fetch(url, {
      ...args,
      mode: 'cors',
      credentials: 'include',
      });
      }
      return window.fetch(url, ...args);
      },
      });
    3. 如果你是通过 loadMicroApp 加载微应用的,你需要在调用时配置自定义 fetch,如:

      import { loadMicroApp } from 'qiankun';
      loadMicroApp(app, {
      fetch(url, ...args) {
      // 给指定的微应用 entry 开启跨域请求
      if (url === 'http://app.alipay.com/entry.html') {
      return window.fetch(url, {
      ...args,
      mode: 'cors',
      credentials: 'include',
      });
      }
      -
      return window.fetch(url, ...args);
      },
      });
    4. 如果你是通过 umi plugin 来使用 qiankun 的,那么你只需要给对应的微应用开启 credentials 配置即可:

      export default {
      qiankun: {
      master: {
      apps: [
      {
      name: 'app',
      entry: '//app.alipay.com/entry.html',
      + credentials: true,
      }
      ]
      }
      }
      }
    +
    return window.fetch(url, ...args);
    },
    });
  • 如果你是通过 umi plugin 来使用 qiankun 的,那么你只需要给对应的微应用开启 credentials 配置即可:

    export default {
    qiankun: {
    master: {
    apps: [
    {
    name: 'app',
    entry: '//app.alipay.com/entry.html',
    + credentials: true,
    }
    ]
    }
    }
    }
  • - + diff --git a/zh/guide/getting-started/index.html b/zh/guide/getting-started/index.html index 68b6346c0..7ab2b6f4d 100644 --- a/zh/guide/getting-started/index.html +++ b/zh/guide/getting-started/index.html @@ -71,9 +71,9 @@
    loadMicroApp({
    name: 'app',
    entry: '//localhost:7100',
    container: '#yourContainer',
    });

    微应用

    微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。

    1. 导出相应的生命周期钩子

    微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用。

    /**
    * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
    * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
    */
    export async function bootstrap() {
    console.log('react app bootstraped');
    }
    /**
    * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
    */
    export async function mount(props) {
    ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
    }
    /**
    * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
    */
    export async function unmount(props) {
    ReactDOM.unmountComponentAtNode(
    props.container ? props.container.querySelector('#root') : document.getElementById('root'),
    );
    }
    -
    /**
    * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
    */
    export async function update(props) {
    console.log('update props', props);
    }

    qiankun 基于 single-spa,所以你可以在这里找到更多关于微应用生命周期相关的文档说明。

    无 webpack 等构建工具的应用接入方式请见这里

    2. 配置微应用的打包工具

    除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

    webpack:

    如果你的项目使用的是 Webpack v5:

    const packageName = require('./package.json').name;
    -
    module.exports = {
    output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    chunkLoadingGlobal: `webpackJsonp_${packageName}`,
    },
    };

    Webpack v4:

    const packageName = require('./package.json').name;
    -
    module.exports = {
    output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
    },
    };

    相关配置介绍可以查看 webpack 相关文档

    +
    /**
    * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
    */
    export async function update(props) {
    console.log('update props', props);
    }

    qiankun 基于 single-spa,所以你可以在这里找到更多关于微应用生命周期相关的文档说明。

    无 webpack 等构建工具的应用接入方式请见这里

    2. 配置微应用的打包工具

    除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

    webpack:

    webpack v5:

    const packageName = require('./package.json').name;
    +
    module.exports = {
    output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    chunkLoadingGlobal: `webpackJsonp_${packageName}`,
    },
    };

    webpack v4:

    const packageName = require('./package.json').name;
    +
    module.exports = {
    output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
    },
    };

    相关配置介绍可以查看 webpack 相关文档

    - + diff --git a/zh/guide/index.html b/zh/guide/index.html index 5e69a3ef0..1c9046485 100644 --- a/zh/guide/index.html +++ b/zh/guide/index.html @@ -65,7 +65,7 @@ 介绍 - qiankun -

    介绍

    qiankun 是一个基于 single-spa微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

    qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。

    目前 qiankun 已在蚂蚁内部服务了超过 2000+ 线上应用,在易用性及完备性上,绝对是值得信赖的。

    什么是微前端

    Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends

    微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

    微前端架构具备以下几个核心价值:

    • 技术栈无关
      主框架不限制接入应用的技术栈,微应用具备完全自主权

    • 独立开发、独立部署
      微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

    • 增量升级

      在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

    • 独立运行时
      每个微应用之间状态隔离,运行时状态不共享

    微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

    更多关于微前端的相关介绍,推荐大家可以去看这几篇文章:

    qiankun 的核心设计理念

    • 🥄 简单

      由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。

    • 🍡 解耦/技术栈无关

      微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。

    它是如何工作的

    TODO

    为什么不是 iframe

    看这里 Why Not Iframe

    特性

    • 📦 基于 single-spa 封装,提供了更加开箱即用的 API。
    • 📱 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
    • 💪 HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
    • 🛡​ 样式隔离,确保微应用之间样式互相不干扰。
    • 🧳 JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
    • ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
    • 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
    +

    介绍

    qiankun 是一个基于 single-spa微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

    qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。

    目前 qiankun 已在蚂蚁内部服务了超过 2000+ 线上应用,在易用性及完备性上,绝对是值得信赖的。

    什么是微前端

    Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends

    微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

    微前端架构具备以下几个核心价值:

    • 技术栈无关
      主框架不限制接入应用的技术栈,微应用具备完全自主权

    • 独立开发、独立部署
      微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

    • 增量升级

      在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

    • 独立运行时
      每个微应用之间状态隔离,运行时状态不共享

    微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

    更多关于微前端的相关介绍,推荐大家可以去看这几篇文章:

    qiankun 的核心设计理念

    • 🥄 简单

      由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。

    • 🍡 解耦/技术栈无关

      微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。

    它是如何工作的

    TODO

    为什么不是 iframe

    看这里 Why Not Iframe

    特性

    • 📦 基于 single-spa 封装,提供了更加开箱即用的 API。
    • 📱 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
    • 💪 HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
    • 🛡​ 样式隔离,确保微应用之间样式互相不干扰。
    • 🧳 JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
    • ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
    • 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
    - + diff --git a/zh/guide/tutorial/index.html b/zh/guide/tutorial/index.html index bf9e9a058..41765d89c 100644 --- a/zh/guide/tutorial/index.html +++ b/zh/guide/tutorial/index.html @@ -72,7 +72,7 @@
    export async function bootstrap() {
    console.log('[react16] react app bootstraped');
    }
    export async function mount(props) {
    console.log('[react16] props from main framework', props);
    render(props);
    }
    export async function unmount(props) {
    const { container } = props;
    ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
    }

    这里需要特别注意的是,通过 ReactDOM.render 挂载子应用时,需要保证每次子应用加载都应使用一个新的路由实例。

    1. 修改 webpack 配置

      安装插件 @rescripts/cli,当然也可以选择其他的插件,例如 react-app-rewired

      npm i -D @rescripts/cli

      根目录新增 .rescriptsrc.js

      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';
      +
      module.exports = {
      webpack: (config) => {
      config.output.library = `${name}-[name]`;
      config.output.libraryTarget = 'umd';
      // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
      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;
      @@ -81,13 +81,13 @@
      let router = null;
      let instance = null;
      function render(props = {}) {
      const { container } = props;
      router = new VueRouter({
      base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
      mode: 'history',
      routes,
      });
      instance = new Vue({
      router,
      store,
      render: (h) => h(App),
      }).$mount(container ? container.querySelector('#app') : '#app');
      }
      // 独立运行时
      if (!window.__POWERED_BY_QIANKUN__) {
      render();
      }
      -
      export async function bootstrap() {
      console.log('[vue] vue app bootstraped');
      }
      export async function mount(props) {
      console.log('[vue] props from main framework', props);
      render(props);
      }
      export async function unmount() {
      instance.$destroy();
      instance.$el.innerHTML = '';
      instance = null;
      router = null;
      }
    2. 打包配置修改(vue.config.js):

      const { name } = require('./package');
      module.exports = {
      devServer: {
      headers: {
      'Access-Control-Allow-Origin': '*',
      },
      },
      configureWebpack: {
      output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
      },
      },
      };

    Angular 微应用

    Angular-cli 9 生成的 angular 9 项目为例,其他版本的 angular 后续会逐渐补充。

    1. src 目录新增 public-path.js 文件,内容为:

      if (window.__POWERED_BY_QIANKUN__) {
      // eslint-disable-next-line no-undef
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
      }
    2. 设置 history 模式路由的 basesrc/app/app-routing.module.ts 文件:

      + import { APP_BASE_HREF } from '@angular/common';
      @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
      // @ts-ignore
      + providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]
      })
    3. 修改入口文件,src/main.ts 文件。

      import './public-path';
      import { enableProdMode, NgModuleRef } from '@angular/core';
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
      import { AppModule } from './app/app.module';
      import { environment } from './environments/environment';
      +
      export async function bootstrap() {
      console.log('[vue] vue app bootstraped');
      }
      export async function mount(props) {
      console.log('[vue] props from main framework', props);
      render(props);
      }
      export async function unmount() {
      instance.$destroy();
      instance.$el.innerHTML = '';
      instance = null;
      router = null;
      }
    4. 打包配置修改(vue.config.js):

      const { name } = require('./package');
      module.exports = {
      devServer: {
      headers: {
      'Access-Control-Allow-Origin': '*',
      },
      },
      configureWebpack: {
      output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
      },
      },
      };

    Angular 微应用

    Angular-cli 9 生成的 angular 9 项目为例,其他版本的 angular 后续会逐渐补充。

    1. src 目录新增 public-path.js 文件,内容为:

      if (window.__POWERED_BY_QIANKUN__) {
      // eslint-disable-next-line no-undef
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
      }
    2. 设置 history 模式路由的 basesrc/app/app-routing.module.ts 文件:

      + import { APP_BASE_HREF } from '@angular/common';
      @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
      // @ts-ignore
      + providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]
      })
    3. 修改入口文件,src/main.ts 文件。

      import './public-path';
      import { enableProdMode, NgModuleRef } from '@angular/core';
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
      import { AppModule } from './app/app.module';
      import { environment } from './environments/environment';
      if (environment.production) {
      enableProdMode();
      }
      let app: void | NgModuleRef<AppModule>;
      async function render() {
      app = await platformBrowserDynamic()
      .bootstrapModule(AppModule)
      .catch((err) => console.error(err));
      }
      if (!(window as any).__POWERED_BY_QIANKUN__) {
      render();
      }
      export async function bootstrap(props: Object) {
      console.log(props);
      }
      export async function mount(props: Object) {
      render();
      }
      -
      export async function unmount(props: Object) {
      console.log(props);
      // @ts-ignore
      app.destroy();
      }
    4. 修改 webpack 打包配置

      先安装 @angular-builders/custom-webpack 插件,注意:angular 9 项目只能安装 9.x 版本,angular 10 项目可以安装最新版

      npm i @angular-builders/custom-webpack@9.2.0 -D

      在根目录增加 custom-webpack.config.js ,内容为:

      const appName = require('./package.json').name;
      module.exports = {
      devServer: {
      headers: {
      'Access-Control-Allow-Origin': '*',
      },
      },
      output: {
      library: `${appName}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${appName}`,
      },
      };

      修改 angular.json,将 [packageName] > architect > build > builder[packageName] > architect > serve > builder 的值改为我们安装的插件,将我们的打包配置文件加入到 [packageName] > architect > build > options

      - "builder": "@angular-devkit/build-angular:browser",
      + "builder": "@angular-builders/custom-webpack:browser",
      "options": {
      + "customWebpackConfig": {
      + "path": "./custom-webpack.config.js"
      + }
      }
      - "builder": "@angular-devkit/build-angular:dev-server",
      + "builder": "@angular-builders/custom-webpack:dev-server",
    5. 解决 zone.js 的问题

      父应用引入 zone.js,需要在 import qiankun 之前引入。

      将微应用的 src/polyfills.ts 里面的引入 zone.js 代码删掉。

      - import 'zone.js/dist/zone';

      在微应用的 src/index.html 里面的 <head> 标签加上下面内容,微应用独立访问时使用。

      <!-- 也可以使用其他的CDN/本地的包 -->
      <script src="https://unpkg.com/zone.js" ignore></script>
    6. 修正 ng build 打包报错问题,修改 tsconfig.json 文件,参考issues/431

      - "target": "es2015",
      + "target": "es5",
      + "typeRoots": [
      + "node_modules/@types"
      + ],
    7. 为了防止主应用或其他微应用也为 angular 时,<app-root></app-root> 会冲突的问题,建议给<app-root> 加上一个唯一的 id,比如说当前应用名称。

      src/index.html :

      - <app-root></app-root>
      + <app-root id="angular9"></app-root>

      src/app/app.component.ts :

      - selector: 'app-root',
      + selector: '#angular9 app-root',

    当然,也可以选择使用 single-spa-angular 插件,参考 single-spa-angular 的官网angular demo

    补充)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 webpack 打包配置的步骤如下:

    除了安装 angular-builders/custom-webpack 插件的 7.x 版本外,还需要安装 angular-builders/dev-server

    npm i @angular-builders/custom-webpack@7 -D
    npm i @angular-builders/dev-server -D

    在根目录增加 custom-webpack.config.js ,内容同上。

    修改 angular.json[packageName] > architect > build > builder 的修改和 angular9 一样, [packageName] > architect > serve > builder 的修改和 angular9 不同。

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
    "options": {
    + "customWebpackConfig": {
    + "path": "./custom-webpack.config.js"
    + }
    }
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/dev-server:generic",

    非 webpack 构建的微应用

    一些非 webpack 构建的项目,例如 jQuery 项目、jsp 项目,都可以按照这个处理。

    接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如 https://qiankun.umijs.org/logo.png),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。

    接入非常简单,只需要额外声明一个 script,用于 export 相对应的 lifecycles。例如:

    1. 声明 entry 入口

      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Purehtml Example</title>
      </head>
      <body>
      <div>
      Purehtml Example
      </div>
      </body>
      + <script src="//yourhost/entry.js" entry></script>
      </html>
    2. 在 entry js 里声明 lifecycles

      const render = ($) => {
      $('#purehtml-container').html('Hello, render with jQuery');
      return Promise.resolve();
      };
      -
      ((global) => {
      global['purehtml'] = {
      bootstrap: () => {
      console.log('purehtml bootstrap');
      return Promise.resolve();
      },
      mount: () => {
      console.log('purehtml mount');
      return render($);
      },
      unmount: () => {
      console.log('purehtml unmount');
      return Promise.resolve();
      },
      };
      })(window);

    你也可以直接参照 examples 中 purehtml 部分的代码

    同时,你也需要开启相关资源的 CORS,具体请参照此处

    umi-qiankun 项目

    umi-qiankun 的教程请移步 umi 官网umi-qiankun 的官方 demo

    +
    export async function unmount(props: Object) {
    console.log(props);
    // @ts-ignore
    app.destroy();
    }
  • 修改 webpack 打包配置

    先安装 @angular-builders/custom-webpack 插件,注意:angular 9 项目只能安装 9.x 版本,angular 10 项目可以安装最新版

    npm i @angular-builders/custom-webpack@9.2.0 -D

    在根目录增加 custom-webpack.config.js ,内容为:

    const appName = require('./package.json').name;
    module.exports = {
    devServer: {
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    output: {
    library: `${appName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${appName}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    },
    };

    修改 angular.json,将 [packageName] > architect > build > builder[packageName] > architect > serve > builder 的值改为我们安装的插件,将我们的打包配置文件加入到 [packageName] > architect > build > options

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
    "options": {
    + "customWebpackConfig": {
    + "path": "./custom-webpack.config.js"
    + }
    }
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/custom-webpack:dev-server",
  • 解决 zone.js 的问题

    父应用引入 zone.js,需要在 import qiankun 之前引入。

    将微应用的 src/polyfills.ts 里面的引入 zone.js 代码删掉。

    - import 'zone.js/dist/zone';

    在微应用的 src/index.html 里面的 <head> 标签加上下面内容,微应用独立访问时使用。

    <!-- 也可以使用其他的CDN/本地的包 -->
    <script src="https://unpkg.com/zone.js" ignore></script>
  • 修正 ng build 打包报错问题,修改 tsconfig.json 文件,参考issues/431

    - "target": "es2015",
    + "target": "es5",
    + "typeRoots": [
    + "node_modules/@types"
    + ],
  • 为了防止主应用或其他微应用也为 angular 时,<app-root></app-root> 会冲突的问题,建议给<app-root> 加上一个唯一的 id,比如说当前应用名称。

    src/index.html :

    - <app-root></app-root>
    + <app-root id="angular9"></app-root>

    src/app/app.component.ts :

    - selector: 'app-root',
    + selector: '#angular9 app-root',
  • 当然,也可以选择使用 single-spa-angular 插件,参考 single-spa-angular 的官网angular demo

    补充)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 webpack 打包配置的步骤如下:

    除了安装 angular-builders/custom-webpack 插件的 7.x 版本外,还需要安装 angular-builders/dev-server

    npm i @angular-builders/custom-webpack@7 -D
    npm i @angular-builders/dev-server -D

    在根目录增加 custom-webpack.config.js ,内容同上。

    修改 angular.json[packageName] > architect > build > builder 的修改和 angular9 一样, [packageName] > architect > serve > builder 的修改和 angular9 不同。

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
    "options": {
    + "customWebpackConfig": {
    + "path": "./custom-webpack.config.js"
    + }
    }
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/dev-server:generic",

    非 webpack 构建的微应用

    一些非 webpack 构建的项目,例如 jQuery 项目、jsp 项目,都可以按照这个处理。

    接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如 https://qiankun.umijs.org/logo.png),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。

    接入非常简单,只需要额外声明一个 script,用于 export 相对应的 lifecycles。例如:

    1. 声明 entry 入口

      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Purehtml Example</title>
      </head>
      <body>
      <div>
      Purehtml Example
      </div>
      </body>
      + <script src="//yourhost/entry.js" entry></script>
      </html>
    2. 在 entry js 里声明 lifecycles

      const render = ($) => {
      $('#purehtml-container').html('Hello, render with jQuery');
      return Promise.resolve();
      };
      +
      ((global) => {
      global['purehtml'] = {
      bootstrap: () => {
      console.log('purehtml bootstrap');
      return Promise.resolve();
      },
      mount: () => {
      console.log('purehtml mount');
      return render($);
      },
      unmount: () => {
      console.log('purehtml unmount');
      return Promise.resolve();
      },
      };
      })(window);

    你也可以直接参照 examples 中 purehtml 部分的代码

    同时,你也需要开启相关资源的 CORS,具体请参照此处

    umi-qiankun 项目

    umi-qiankun 的教程请移步 umi 官网umi-qiankun 的官方 demo

    - + diff --git a/zh/index.html b/zh/index.html index c66dc8096..4ad4c45f5 100644 --- a/zh/index.html +++ b/zh/index.html @@ -99,6 +99,6 @@ } })(); - + diff --git a/~demos/:uuid/index.html b/~demos/:uuid/index.html index a3bde7ea8..fa29d3b94 100644 --- a/~demos/:uuid/index.html +++ b/~demos/:uuid/index.html @@ -93,6 +93,6 @@ } })(); - +