🎨 upgrade prettier and format docs (#1377)
This commit is contained in:
parent
90906feaea
commit
8e521521b9
|
|
@ -2,7 +2,7 @@
|
|||
title: qiankun
|
||||
hero:
|
||||
title: qiankun
|
||||
desc: Probably the most complete micro-frontends solution you ever met🧐
|
||||
desc: Probably the most complete micro-frontends solution you ever met🧐
|
||||
actions:
|
||||
- text: Get Started →
|
||||
link: /guide
|
||||
|
|
@ -33,7 +33,7 @@ loadMicroApp({
|
|||
entry: '//localhost:7100',
|
||||
container: '#container',
|
||||
props: {
|
||||
slogan: 'Hello Qiankun'
|
||||
slogan: 'Hello Qiankun',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
title: qiankun
|
||||
hero:
|
||||
title: qiankun
|
||||
desc: 可能是你见过最完善的微前端解决方案🧐
|
||||
desc: 可能是你见过最完善的微前端解决方案🧐
|
||||
actions:
|
||||
- text: 快速开始 →
|
||||
- text: 快速开始 →
|
||||
link: /zh/guide
|
||||
features:
|
||||
- title: 简单
|
||||
|
|
@ -33,7 +33,7 @@ loadMicroApp({
|
|||
entry: '//localhost:7100',
|
||||
container: '#container',
|
||||
props: {
|
||||
slogan: 'Hello Qiankun'
|
||||
slogan: 'Hello Qiankun',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -30,41 +30,41 @@ By linking the micro-application to some url rules, the function of automaticall
|
|||
- If configured as `object`, the value of `html` is the html content string of the micro application, not the access address of the micro application. The `publicPath` of the micro application will be set to `/`.
|
||||
- container - `string | HTMLElement` - required,A selector or Element instance of the container node of a micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`.
|
||||
|
||||
- activeRule - - `string | (location: Location) => boolean | Array<string | (location: Location) => boolean> ` - required,activation rules for micro-apps.
|
||||
- activeRule - - `string | (location: Location) => boolean | Array<string | (location: Location) => boolean> ` - required,activation rules for micro-apps.
|
||||
|
||||
* Support direct configuration of string or string array, such as `activeRule: '/app1'` or `activeRule: ['/app1', '/app2']`, when configured as a string, it will directly follow the path part in the url Do a prefix match. A successful match indicates that the current application will be activated.
|
||||
* Support to configure an active function or a group of active functions. The function will pass in the current location as a parameter. When the function returns true, it indicates that the current micro application will be activated. Such as `location => location.pathname.startsWith ('/app1')`.
|
||||
- Support direct configuration of string or string array, such as `activeRule: '/app1'` or `activeRule: ['/app1', '/app2']`, when configured as a string, it will directly follow the path part in the url Do a prefix match. A successful match indicates that the current application will be activated.
|
||||
- Support to configure an active function or a group of active functions. The function will pass in the current location as a parameter. When the function returns true, it indicates that the current micro application will be activated. Such as `location => location.pathname.startsWith ('/app1')`.
|
||||
|
||||
Example rules:
|
||||
|
||||
`'/app1'`
|
||||
|
||||
* ✅ https://app.com/app1
|
||||
- ✅ https://app.com/app1
|
||||
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
- ✅ https://app.com/app1/anything/everything
|
||||
|
||||
* 🚫 https://app.com/app2
|
||||
- 🚫 https://app.com/app2
|
||||
|
||||
`'/users/:userId/profile'`
|
||||
|
||||
* ✅ https://app.com/users/123/profile
|
||||
* ✅ https://app.com/users/123/profile/sub-profile/
|
||||
* 🚫 https://app.com/users//profile/sub-profile/
|
||||
* 🚫 https://app.com/users/profile/sub-profile/
|
||||
- ✅ https://app.com/users/123/profile
|
||||
- ✅ https://app.com/users/123/profile/sub-profile/
|
||||
- 🚫 https://app.com/users//profile/sub-profile/
|
||||
- 🚫 https://app.com/users/profile/sub-profile/
|
||||
|
||||
`'/pathname/#/hash'`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/another-hash
|
||||
- ✅ https://app.com/pathname/#/hash
|
||||
- ✅ https://app.com/pathname/#/hash/route/nested
|
||||
- 🚫 https://app.com/pathname#/hash/route/nested
|
||||
- 🚫 https://app.com/pathname#/another-hash
|
||||
|
||||
`['/pathname/#/hash', '/app1']`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
* 🚫 https://app.com/pathname/app1
|
||||
* 🚫 https://app.com/app2
|
||||
- ✅ https://app.com/pathname/#/hash/route/nested
|
||||
- ✅ https://app.com/app1/anything/everything
|
||||
- 🚫 https://app.com/pathname/app1
|
||||
- 🚫 https://app.com/app2
|
||||
|
||||
This function is called when the browser url changes, and `activeRule` returns `true` to indicate that the subapplication needs to be activated.
|
||||
|
||||
|
|
@ -102,14 +102,12 @@ By linking the micro-application to some url rules, the function of automaticall
|
|||
activeRule: '/react',
|
||||
props: {
|
||||
name: 'kuitos',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
beforeLoad: app => console.log('before load', app.name),
|
||||
beforeMount: [
|
||||
app => console.log('before mount', app.name),
|
||||
],
|
||||
beforeLoad: (app) => console.log('before load', app.name),
|
||||
beforeMount: [(app) => console.log('before mount', app.name)],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
|
@ -151,8 +149,7 @@ By linking the micro-application to some url rules, the function of automaticall
|
|||
}
|
||||
```
|
||||
|
||||
notice:
|
||||
@keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten)
|
||||
notice: @keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten)
|
||||
|
||||
- singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. default is `true`.
|
||||
|
||||
|
|
@ -161,7 +158,7 @@ By linking the micro-application to some url rules, the function of automaticall
|
|||
- getPublicPath - `(entry: Entry) => string` - optional,The parameter is the entry value of the micro application.
|
||||
|
||||
- getTemplate - `(tpl: string) => string` - optional
|
||||
|
||||
|
||||
- excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking
|
||||
|
||||
- Usage
|
||||
|
|
@ -224,16 +221,18 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
|
||||
### `loadMicroApp(app, configuration?)`
|
||||
|
||||
* Parameters
|
||||
* app - `LoadableApp` - Required, basic information of micro application
|
||||
* name - `string` - Required, the name of the micro application must be unique among the micro applications.
|
||||
* entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required, The entry of the micro application(The detailed description is the same as above).
|
||||
* container - `string | HTMLElement` - Required, selector or Element instance of the container node of the micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`.
|
||||
* props - `object` - Optional, the data that needs to be passed to the micro-application during initialization.
|
||||
- Parameters
|
||||
|
||||
* configuration - `Configuration` - Optional, configuration information of the micro application
|
||||
- app - `LoadableApp` - Required, basic information of micro application
|
||||
|
||||
* sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - optional, whether to open the js sandbox, default is `true`.
|
||||
- name - `string` - Required, the name of the micro application must be unique among the micro applications.
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required, The entry of the micro application(The detailed description is the same as above).
|
||||
- container - `string | HTMLElement` - Required, selector or Element instance of the container node of the micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`.
|
||||
- props - `object` - Optional, the data that needs to be passed to the micro-application during initialization.
|
||||
|
||||
- configuration - `Configuration` - Optional, configuration information of the micro application
|
||||
|
||||
- sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - optional, whether to open the js sandbox, default is `true`.
|
||||
|
||||
When configured as `{strictStyleIsolation: true}`, qiankun will convert the container dom of each application to a [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), to ensure that the style of the application will not leak to the global.
|
||||
|
||||
|
|
@ -244,48 +243,36 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
.app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
div[data-qiankun-react16] .app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
notice:
|
||||
@keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten)
|
||||
|
||||
* singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. Default is `false`.
|
||||
|
||||
* fetch - `Function` - Optional, custom fetch method.
|
||||
|
||||
* getPublicPath - `(url: string) => string` - Optional,The parameter is the entry value of the micro application.
|
||||
|
||||
* getTemplate - `(tpl: string) => string` - Optional
|
||||
|
||||
* excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking
|
||||
|
||||
* 返回值 - `MicroApp` - Micro application examples
|
||||
* mount(): Promise<null>;
|
||||
* unmount(): Promise<null>;
|
||||
* update(customProps: object): Promise<any>;
|
||||
* getStatus():
|
||||
| "NOT_LOADED"
|
||||
| "LOADING_SOURCE_CODE"
|
||||
| "NOT_BOOTSTRAPPED"
|
||||
| "BOOTSTRAPPING"
|
||||
| "NOT_MOUNTED"
|
||||
| "MOUNTING"
|
||||
| "MOUNTED"
|
||||
| "UPDATING"
|
||||
| "UNMOUNTING"
|
||||
| "UNLOADING"
|
||||
| "SKIP_BECAUSE_BROKEN"
|
||||
| "LOAD_ERROR";
|
||||
* loadPromise: Promise<null>;
|
||||
* bootstrapPromise: Promise<null>;
|
||||
* mountPromise: Promise<null>;
|
||||
* unmountPromise: Promise<null>;
|
||||
|
||||
* Usage
|
||||
notice: @keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten)
|
||||
|
||||
- singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. Default is `false`.
|
||||
|
||||
- fetch - `Function` - Optional, custom fetch method.
|
||||
|
||||
- getPublicPath - `(url: string) => string` - Optional,The parameter is the entry value of the micro application.
|
||||
|
||||
- getTemplate - `(tpl: string) => string` - Optional
|
||||
|
||||
- excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking
|
||||
|
||||
- 返回值 - `MicroApp` - Micro application examples
|
||||
|
||||
- mount(): Promise<null>;
|
||||
- unmount(): Promise<null>;
|
||||
- update(customProps: object): Promise<any>;
|
||||
- getStatus(): | "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" | "SKIP_BECAUSE_BROKEN" | "LOAD_ERROR";
|
||||
- loadPromise: Promise<null>;
|
||||
- bootstrapPromise: Promise<null>;
|
||||
- mountPromise: Promise<null>;
|
||||
- unmountPromise: Promise<null>;
|
||||
|
||||
- Usage
|
||||
|
||||
Load a micro application manually.
|
||||
|
||||
|
|
@ -302,21 +289,23 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
}
|
||||
```
|
||||
|
||||
* Sample
|
||||
- Sample
|
||||
|
||||
```jsx
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
import React from 'react';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
containerRef = React.createRef();
|
||||
microApp = null;
|
||||
|
||||
componentDidMount() {
|
||||
this.microApp = loadMicroApp(
|
||||
{ name: 'app1', entry: '//localhost:1234', container: this.containerRef.current, props: { brand: 'qiankun' } },
|
||||
);
|
||||
this.microApp = loadMicroApp({
|
||||
name: 'app1',
|
||||
entry: '//localhost:1234',
|
||||
container: this.containerRef.current,
|
||||
props: { brand: 'qiankun' },
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
@ -336,10 +325,12 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
### `prefetchApps(apps, importEntryOpts?)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- apps - `AppMetadata[]` - Required - list of preloaded apps
|
||||
- importEntryOpts - Optional - Load configuration
|
||||
|
||||
- Type
|
||||
|
||||
- `AppMetadata`
|
||||
- name - `string` - Required - Application name
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required,The entry address of the microapp
|
||||
|
|
@ -353,7 +344,10 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
```ts
|
||||
import { prefetchApps } from 'qiankun';
|
||||
|
||||
prefetchApps([ { name: 'app1', entry: '//locahost:7001' }, { name: 'app2', entry: '//locahost:7002' } ])
|
||||
prefetchApps([
|
||||
{ name: 'app1', entry: '//locahost:7001' },
|
||||
{ name: 'app2', entry: '//locahost:7002' },
|
||||
]);
|
||||
```
|
||||
|
||||
## [addErrorHandler/removeErrorHandler](https://single-spa.js.org/docs/api#adderrorhandler)
|
||||
|
|
@ -373,7 +367,7 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
```ts
|
||||
import { addGlobalUncaughtErrorHandler } from 'qiankun';
|
||||
|
||||
addGlobalUncaughtErrorHandler(event => console.log(event));
|
||||
addGlobalUncaughtErrorHandler((event) => console.log(event));
|
||||
```
|
||||
|
||||
## `removeGlobalUncaughtErrorHandler(handler)`
|
||||
|
|
@ -417,6 +411,7 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
- Sample
|
||||
|
||||
Master:
|
||||
|
||||
```ts
|
||||
import { initGlobalState, MicroAppStateActions } from 'qiankun';
|
||||
|
||||
|
|
@ -432,10 +427,10 @@ A criterion for judging whether the business is closely related: <strong>Look at
|
|||
```
|
||||
|
||||
Slave:
|
||||
|
||||
```ts
|
||||
// get actions from mount
|
||||
export function mount(props) {
|
||||
|
||||
props.onGlobalStateChange((state, prev) => {
|
||||
// state: new state; prev old state
|
||||
console.log(state, prev);
|
||||
|
|
|
|||
|
|
@ -32,39 +32,39 @@ toc: menu
|
|||
|
||||
- activeRule - `string | (location: Location) => boolean | Array<string | (location: Location) => boolean> ` - 必选,微应用的激活规则。
|
||||
|
||||
* 支持直接配置字符串或字符串数组,如 `activeRule: '/app1'` 或 `activeRule: ['/app1', '/app2']`,当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
|
||||
* 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 `location => location.pathname.startsWith('/app1')`。
|
||||
- 支持直接配置字符串或字符串数组,如 `activeRule: '/app1'` 或 `activeRule: ['/app1', '/app2']`,当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
|
||||
- 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 `location => location.pathname.startsWith('/app1')`。
|
||||
|
||||
规则示例:
|
||||
|
||||
`'/app1'`
|
||||
|
||||
* ✅ https://app.com/app1
|
||||
- ✅ https://app.com/app1
|
||||
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
- ✅ https://app.com/app1/anything/everything
|
||||
|
||||
* 🚫 https://app.com/app2
|
||||
- 🚫 https://app.com/app2
|
||||
|
||||
`'/users/:userId/profile'`
|
||||
|
||||
* ✅ https://app.com/users/123/profile
|
||||
* ✅ https://app.com/users/123/profile/sub-profile/
|
||||
* 🚫 https://app.com/users//profile/sub-profile/
|
||||
* 🚫 https://app.com/users/profile/sub-profile/
|
||||
- ✅ https://app.com/users/123/profile
|
||||
- ✅ https://app.com/users/123/profile/sub-profile/
|
||||
- 🚫 https://app.com/users//profile/sub-profile/
|
||||
- 🚫 https://app.com/users/profile/sub-profile/
|
||||
|
||||
`'/pathname/#/hash'`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/another-hash
|
||||
- ✅ https://app.com/pathname/#/hash
|
||||
- ✅ https://app.com/pathname/#/hash/route/nested
|
||||
- 🚫 https://app.com/pathname#/hash/route/nested
|
||||
- 🚫 https://app.com/pathname#/another-hash
|
||||
|
||||
`['/pathname/#/hash', '/app1']`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
* 🚫 https://app.com/pathname/app1
|
||||
* 🚫 https://app.com/app2
|
||||
- ✅ https://app.com/pathname/#/hash/route/nested
|
||||
- ✅ https://app.com/app1/anything/everything
|
||||
- 🚫 https://app.com/pathname/app1
|
||||
- 🚫 https://app.com/app2
|
||||
|
||||
浏览器 url 发生变化会调用 activeRule 里的规则,`activeRule` 任意一个返回 `true` 时表明该微应用需要被激活。
|
||||
|
||||
|
|
@ -102,14 +102,12 @@ toc: menu
|
|||
activeRule: '/react',
|
||||
props: {
|
||||
name: 'kuitos',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
beforeLoad: app => console.log('before load', app.name),
|
||||
beforeMount: [
|
||||
app => console.log('before mount', app.name),
|
||||
],
|
||||
beforeLoad: (app) => console.log('before load', app.name),
|
||||
beforeMount: [(app) => console.log('before mount', app.name)],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
|
@ -155,8 +153,7 @@ toc: menu
|
|||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
@keyframes, @font-face, @import, @page 将不被支持 (i.e. 不会被改写)
|
||||
注意: @keyframes, @font-face, @import, @page 将不被支持 (i.e. 不会被改写)
|
||||
|
||||
- singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `true`。
|
||||
|
||||
|
|
@ -228,16 +225,18 @@ toc: menu
|
|||
|
||||
### `loadMicroApp(app, configuration?)`
|
||||
|
||||
* 参数
|
||||
* app - `LoadableApp` - 必选,微应用的基础信息
|
||||
* name - `string` - 必选,微应用的名称,微应用之间必须确保唯一。
|
||||
* entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的入口(详细说明同上)。
|
||||
* container - `string | HTMLElement` - 必选,微应用的容器节点的选择器或者 Element 实例。如`container: '#root'` 或 `container: document.querySelector('#root')`。
|
||||
* props - `object` - 可选,初始化时需要传递给微应用的数据。
|
||||
- 参数
|
||||
|
||||
* configuration - `Configuration` - 可选,微应用的配置信息
|
||||
- app - `LoadableApp` - 必选,微应用的基础信息
|
||||
|
||||
* sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - 可选,是否开启沙箱,默认为 `true`。
|
||||
- name - `string` - 必选,微应用的名称,微应用之间必须确保唯一。
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的入口(详细说明同上)。
|
||||
- container - `string | HTMLElement` - 必选,微应用的容器节点的选择器或者 Element 实例。如`container: '#root'` 或 `container: document.querySelector('#root')`。
|
||||
- props - `object` - 可选,初始化时需要传递给微应用的数据。
|
||||
|
||||
- configuration - `Configuration` - 可选,微应用的配置信息
|
||||
|
||||
- sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - 可选,是否开启沙箱,默认为 `true`。
|
||||
|
||||
默认情况下沙箱可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离。当配置为 `{ strictStyleIsolation: true }` 时表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 [shadow dom](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) 节点,从而确保微应用的样式不会对全局造成影响。
|
||||
|
||||
|
|
@ -258,42 +257,30 @@ toc: menu
|
|||
}
|
||||
```
|
||||
|
||||
注意事项:
|
||||
目前 @keyframes, @font-face, @import, @page 等规则不会支持 (i.e. 不会被改写)
|
||||
注意事项: 目前 @keyframes, @font-face, @import, @page 等规则不会支持 (i.e. 不会被改写)
|
||||
|
||||
* singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `false`。
|
||||
- singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `false`。
|
||||
|
||||
* fetch - `Function` - 可选,自定义的 fetch 方法。
|
||||
- fetch - `Function` - 可选,自定义的 fetch 方法。
|
||||
|
||||
* getPublicPath - `(entry: Entry) => string` - 可选,参数是微应用的 entry 值。
|
||||
- getPublicPath - `(entry: Entry) => string` - 可选,参数是微应用的 entry 值。
|
||||
|
||||
* getTemplate - `(tpl: string) => string` - 可选
|
||||
|
||||
* excludeAssetFilter - `(assetUrl: string) => boolean` - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被qiankun 劫持处理
|
||||
- getTemplate - `(tpl: string) => string` - 可选
|
||||
|
||||
* 返回值 - `MicroApp` - 微应用实例
|
||||
* mount(): Promise<null>;
|
||||
* unmount(): Promise<null>;
|
||||
* update(customProps: object): Promise<any>;
|
||||
* getStatus():
|
||||
| "NOT_LOADED"
|
||||
| "LOADING_SOURCE_CODE"
|
||||
| "NOT_BOOTSTRAPPED"
|
||||
| "BOOTSTRAPPING"
|
||||
| "NOT_MOUNTED"
|
||||
| "MOUNTING"
|
||||
| "MOUNTED"
|
||||
| "UPDATING"
|
||||
| "UNMOUNTING"
|
||||
| "UNLOADING"
|
||||
| "SKIP_BECAUSE_BROKEN"
|
||||
| "LOAD_ERROR";
|
||||
* loadPromise: Promise<null>;
|
||||
* bootstrapPromise: Promise<null>;
|
||||
* mountPromise: Promise<null>;
|
||||
* unmountPromise: Promise<null>;
|
||||
- excludeAssetFilter - `(assetUrl: string) => boolean` - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被 qiankun 劫持处理
|
||||
|
||||
* 用法
|
||||
- 返回值 - `MicroApp` - 微应用实例
|
||||
|
||||
- mount(): Promise<null>;
|
||||
- unmount(): Promise<null>;
|
||||
- update(customProps: object): Promise<any>;
|
||||
- getStatus(): | "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" | "SKIP_BECAUSE_BROKEN" | "LOAD_ERROR";
|
||||
- loadPromise: Promise<null>;
|
||||
- bootstrapPromise: Promise<null>;
|
||||
- mountPromise: Promise<null>;
|
||||
- unmountPromise: Promise<null>;
|
||||
|
||||
- 用法
|
||||
|
||||
手动加载一个微应用。
|
||||
|
||||
|
|
@ -310,21 +297,23 @@ toc: menu
|
|||
}
|
||||
```
|
||||
|
||||
* 示例
|
||||
- 示例
|
||||
|
||||
```jsx
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
import React from 'react';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
containerRef = React.createRef();
|
||||
microApp = null;
|
||||
|
||||
componentDidMount() {
|
||||
this.microApp = loadMicroApp(
|
||||
{ name: 'app1', entry: '//localhost:1234', container: this.containerRef.current, props: { brand: 'qiankun' } },
|
||||
);
|
||||
this.microApp = loadMicroApp({
|
||||
name: 'app1',
|
||||
entry: '//localhost:1234',
|
||||
container: this.containerRef.current,
|
||||
props: { brand: 'qiankun' },
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
@ -344,10 +333,12 @@ toc: menu
|
|||
### `prefetchApps(apps, importEntryOpts?)`
|
||||
|
||||
- 参数
|
||||
|
||||
- apps - `AppMetadata[]` - 必选 - 预加载的应用列表
|
||||
- importEntryOpts - 可选 - 加载配置
|
||||
|
||||
- 类型
|
||||
|
||||
- `AppMetadata`
|
||||
- name - `string` - 必选 - 应用名
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的 entry 地址
|
||||
|
|
@ -361,7 +352,10 @@ toc: menu
|
|||
```ts
|
||||
import { prefetchApps } from 'qiankun';
|
||||
|
||||
prefetchApps([ { name: 'app1', entry: '//locahost:7001' }, { name: 'app2', entry: '//locahost:7002' } ])
|
||||
prefetchApps([
|
||||
{ name: 'app1', entry: '//locahost:7001' },
|
||||
{ name: 'app2', entry: '//locahost:7002' },
|
||||
]);
|
||||
```
|
||||
|
||||
## [addErrorHandler/removeErrorHandler](https://single-spa.js.org/docs/api#adderrorhandler)
|
||||
|
|
@ -381,7 +375,7 @@ toc: menu
|
|||
```ts
|
||||
import { addGlobalUncaughtErrorHandler } from 'qiankun';
|
||||
|
||||
addGlobalUncaughtErrorHandler(event => console.log(event));
|
||||
addGlobalUncaughtErrorHandler((event) => console.log(event));
|
||||
```
|
||||
|
||||
## `removeGlobalUncaughtErrorHandler(handler)`
|
||||
|
|
@ -425,6 +419,7 @@ toc: menu
|
|||
- 示例
|
||||
|
||||
主应用:
|
||||
|
||||
```ts
|
||||
import { initGlobalState, MicroAppStateActions } from 'qiankun';
|
||||
|
||||
|
|
@ -440,10 +435,10 @@ toc: menu
|
|||
```
|
||||
|
||||
微应用:
|
||||
|
||||
```ts
|
||||
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
|
||||
export function mount(props) {
|
||||
|
||||
props.onGlobalStateChange((state, prev) => {
|
||||
// state: 变更后的状态; prev 变更前的状态
|
||||
console.log(state, prev);
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ When registering micro apps, `activeRule` needs to be written like this:
|
|||
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/app',
|
||||
{
|
||||
name: 'app',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/app',
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
|
@ -31,33 +31,37 @@ registerMicroApps([
|
|||
|
||||
2. When the micro app is in the `hash` mode, the performance of the three routes is inconsistent
|
||||
|
||||
| routing | main app jump `/app/#/about` | special configuration |
|
||||
| ---------------| -------------------------------| --------------------------|
|
||||
| vue-router | Response `about` routing | none |
|
||||
| react-router | not responding `about` routing | none |
|
||||
| angular-router | Response `about` routing | need to set `--base-href` |
|
||||
| routing | main app jump `/app/#/about` | special configuration |
|
||||
| -------------- | ------------------------------ | ------------------------- |
|
||||
| vue-router | Response `about` routing | none |
|
||||
| react-router | not responding `about` routing | none |
|
||||
| angular-router | Response `about` routing | need to set `--base-href` |
|
||||
|
||||
`Angular` app set `--base-href` in `package.json`:
|
||||
```diff
|
||||
- "start": "ng serve",
|
||||
+ "start": "ng serve --base-href /angular9",
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9",
|
||||
```
|
||||
`Angular` app set `--base-href` in `package.json`:
|
||||
|
||||
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:
|
||||
```diff
|
||||
- "start": "ng serve",
|
||||
+ "start": "ng serve --base-href /angular9",
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9",
|
||||
```
|
||||
|
||||
- Solution 1: Modify `public-path.js` to:
|
||||
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:
|
||||
|
||||
```js
|
||||
__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:
|
||||
- Solution 1: Modify `public-path.js` to:
|
||||
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9 --deploy-url /angular9/",
|
||||
```
|
||||
```js
|
||||
__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:
|
||||
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9 --deploy-url /angular9/",
|
||||
```
|
||||
|
||||
### activeRule uses hash to distinguish micro apps
|
||||
|
||||
|
|
@ -66,13 +70,13 @@ When the micro apps are all in the `hash` mode, `hash` can be used to distinguis
|
|||
When registering micro apps, `activeRule` needs to be written like this:
|
||||
|
||||
```js
|
||||
const getActiveRule = hash => location => location.hash.startsWith(hash);
|
||||
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app-hash',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: getActiveRule('#/app-hash'),
|
||||
{
|
||||
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.
|
||||
},
|
||||
|
|
@ -91,9 +95,9 @@ const routes = [
|
|||
component: Home,
|
||||
children: [
|
||||
// All other routes are written here
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### When there are multiple micro apps at the same time
|
||||
|
|
@ -148,7 +152,7 @@ Suppose we have a main app and 6 micro apps ( respectively `vue-hash`, `vue-hist
|
|||
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.
|
||||
|
||||
| app | routing base | publicPath | real access path |
|
||||
| --------------- | ------------------------| ------------------------| ---------------------------------------------|
|
||||
| --------------- | ----------------------- | ----------------------- | -------------------------------------------- |
|
||||
| vue-hash | none | /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 | none | /child/react-hash/ | http://localhost:8080/child/react-hash/ |
|
||||
|
|
@ -158,61 +162,72 @@ At this time, you need to set the `publicPath` and the route `base` of the `hist
|
|||
|
||||
- `vue-history` micro app
|
||||
|
||||
Routing's base configuration:
|
||||
```js
|
||||
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/',
|
||||
```
|
||||
Webpack's publicPath configuration(`vue.config.js`):
|
||||
```js
|
||||
module.exports = {
|
||||
publicPath: '/child/vue-history/'
|
||||
}
|
||||
```
|
||||
Routing's base configuration:
|
||||
|
||||
```js
|
||||
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/',
|
||||
```
|
||||
|
||||
Webpack's publicPath configuration(`vue.config.js`):
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
publicPath: '/child/vue-history/',
|
||||
};
|
||||
```
|
||||
|
||||
- `react-history` micro app
|
||||
|
||||
Routing's base configuration:
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/child/react-history/'}>
|
||||
```
|
||||
Webpack's publicPath configuration:
|
||||
```js
|
||||
module.exports = {
|
||||
output: {
|
||||
publicPath: '/child/react-history/',
|
||||
}
|
||||
}
|
||||
```
|
||||
Routing's base configuration:
|
||||
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/child/react-history/'}>
|
||||
```
|
||||
|
||||
Webpack's publicPath configuration:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
output: {
|
||||
publicPath: '/child/react-history/',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
- `angular-history` micro app
|
||||
|
||||
Routing's base configuration:
|
||||
```js
|
||||
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`:
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --deploy-url /child/angular-history/",
|
||||
```
|
||||
Routing's base configuration:
|
||||
|
||||
```js
|
||||
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`:
|
||||
|
||||
```diff
|
||||
- "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):
|
||||
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app-vue-hash',
|
||||
name: 'app-vue-hash',
|
||||
entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/
|
||||
container: '#container',
|
||||
activeRule: '/app-vue-hash',
|
||||
container: '#container',
|
||||
activeRule: '/app-vue-hash',
|
||||
},
|
||||
{
|
||||
{
|
||||
name: 'app-vue-history',
|
||||
entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/
|
||||
container: '#container',
|
||||
container: '#container',
|
||||
activeRule: '/app-vue-history',
|
||||
},
|
||||
// angular and react same as above
|
||||
|
|
@ -270,7 +285,7 @@ the `Nginx` proxy configuration of the main app is:
|
|||
```
|
||||
/app1/ {
|
||||
proxy_pass http://www.b.com/app1/;
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header Host $host:$server_port;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -279,10 +294,10 @@ When the main app registers micro apps, `entry` can be a relative path, and `act
|
|||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
name: 'app1',
|
||||
entry: '/app1/', // http://localhost:8080/app1/
|
||||
container: '#container',
|
||||
activeRule: '/child-app1',
|
||||
container: '#container',
|
||||
activeRule: '/child-app1',
|
||||
},
|
||||
],
|
||||
```
|
||||
|
|
@ -293,8 +308,8 @@ For micro apps bundled by `webpack`, the `publicPath` bundled by the micro app's
|
|||
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.
|
||||
|
|
@ -321,4 +336,4 @@ The basic modification of `registerMicroApps` function is as follows:
|
|||
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`.
|
||||
2. Added `getPublicPath` and `getTemplate` to replace `RegisterMicroAppsOpts`.
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ toc: menu
|
|||
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/app',
|
||||
{
|
||||
name: 'app',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/app',
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
|
@ -31,33 +31,37 @@ registerMicroApps([
|
|||
|
||||
2. 当微应用是 `hash` 模式时,三种路由的表现不一致
|
||||
|
||||
| 路由 | 主应用跳转/app/#/about | 特殊配置 |
|
||||
| ---------------| ----------------------| --------------------|
|
||||
| vue-router | 响应 about 路由 | 无 |
|
||||
| react-router | 不响应 about 路由 | 无 |
|
||||
| angular-router | 响应 about 路由 | 需要设置 --base-href |
|
||||
| 路由 | 主应用跳转/app/#/about | 特殊配置 |
|
||||
| -------------- | ---------------------- | -------------------- |
|
||||
| vue-router | 响应 about 路由 | 无 |
|
||||
| react-router | 不响应 about 路由 | 无 |
|
||||
| angular-router | 响应 about 路由 | 需要设置 --base-href |
|
||||
|
||||
`angular` 应用在 `package.json` 里面设置 `--base-href`:
|
||||
```diff
|
||||
- "start": "ng serve",
|
||||
+ "start": "ng serve --base-href /angular9",
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9",
|
||||
```
|
||||
`angular` 应用在 `package.json` 里面设置 `--base-href`:
|
||||
|
||||
打包部署后,`angular` 微应用可以被主应用访问。但是独立访问时,懒加载的路由会报错,路径不正确。这里有两个解决办法:
|
||||
```diff
|
||||
- "start": "ng serve",
|
||||
+ "start": "ng serve --base-href /angular9",
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9",
|
||||
```
|
||||
|
||||
- 方法1:修改 `public-path.js` 为:
|
||||
打包部署后,`angular` 微应用可以被主应用访问。但是独立访问时,懒加载的路由会报错,路径不正确。这里有两个解决办法:
|
||||
|
||||
```js
|
||||
__webpack_public_path__ = window.__POWERED_BY_QIANKUN__ ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ : `http://${ip}:${port}/`; // 填写你的实际部署地址
|
||||
```
|
||||
- 方法2:修改打包命令,并且将微应用部署在 `angular9` 目录:
|
||||
- 方法 1:修改 `public-path.js` 为:
|
||||
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9 --deploy-url /angular9/",
|
||||
```
|
||||
```js
|
||||
__webpack_public_path__ = window.__POWERED_BY_QIANKUN__
|
||||
? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
|
||||
: `http://${ip}:${port}/`; // 填写你的实际部署地址
|
||||
```
|
||||
|
||||
- 方法 2:修改打包命令,并且将微应用部署在 `angular9` 目录:
|
||||
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --base-href /angular9 --deploy-url /angular9/",
|
||||
```
|
||||
|
||||
### `activeRule` 使用 `location.hash` 区分微应用
|
||||
|
||||
|
|
@ -66,13 +70,13 @@ registerMicroApps([
|
|||
注册微应用时 `activeRule` 需要这样写:
|
||||
|
||||
```js
|
||||
const getActiveRule = hash => location => location.hash.startsWith(hash);
|
||||
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app-hash',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: getActiveRule('#/app-hash'),
|
||||
{
|
||||
name: 'app-hash',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: getActiveRule('#/app-hash'),
|
||||
// 这里也可以直接写 activeRule: '#/app-hash',但是如果主应用是 history 模式或者主应用部署在非根目录,这样写不会生效。
|
||||
},
|
||||
]);
|
||||
|
|
@ -90,9 +94,9 @@ const routes = [
|
|||
component: Home,
|
||||
children: [
|
||||
// 其他的路由都写到这里
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### 同时存在多个微应用时
|
||||
|
|
@ -105,7 +109,7 @@ const routes = [
|
|||
|
||||
**建议**:主应用和微应用都是独立开发和部署,即它们都属于不同的仓库和服务。
|
||||
|
||||
### 场景1:主应用和微应用部署到同一个服务器(同一个IP和端口)
|
||||
### 场景 1:主应用和微应用部署到同一个服务器(同一个 IP 和端口)
|
||||
|
||||
如果服务器数量有限,或不能跨域等原因需要把主应用和微应用部署到一起。
|
||||
|
||||
|
|
@ -113,7 +117,7 @@ const routes = [
|
|||
|
||||
微应用想部署在非根目录,在微应用打包之前需要做两件事:
|
||||
|
||||
1. 必须配置 `webpack` 构建时的 `publicPath` 为**目录名称**,更多信息请看 [webpack 官方说明](https://www.webpackjs.com/configuration/output/#output-publicpath) 和 [vue-cli3 的官方说明](https://cli.vuejs.org/zh/config/#publicpath)
|
||||
1. 必须配置 `webpack` 构建时的 `publicPath` 为**目录名称**,更多信息请看 [webpack 官方说明](https://www.webpackjs.com/configuration/output/#output-publicpath) 和 [vue-cli3 的官方说明](https://cli.vuejs.org/zh/config/#publicpath)
|
||||
|
||||
2. `history` 路由的微应用需要设置 `base` ,值为**目录名称**,用于独立访问时使用。
|
||||
|
||||
|
|
@ -125,9 +129,10 @@ const routes = [
|
|||
|
||||
具体的部署有以下两种方式,选择其一即可。
|
||||
|
||||
#### 方案1:微应用都放在在一个特殊名称(**不会和微应用重名**)的文件夹下(**建议使用**)
|
||||
#### 方案 1:微应用都放在在一个特殊名称(**不会和微应用重名**)的文件夹下(**建议使用**)
|
||||
|
||||
假设我们有一个主应用和 6 个微应用(分别为 `vue-hash`、`vue-history`、`react-hash`、`react-history`、`angular-hash`、`angular-history` ),打包后如下放置:
|
||||
|
||||
```
|
||||
└── html/ # 根文件夹
|
||||
|
|
||||
|
|
@ -145,8 +150,8 @@ const routes = [
|
|||
|
||||
此时需要设置微应用构建时的 `publicPath` 和 `history` 模式的路由 `base`,然后才能打包放到对应的目录里。
|
||||
|
||||
| 项目 | 路由 base | publicPath | 真实访问路径 |
|
||||
| --------------- | ------------------------| ------------------------| ---------------------------------------------|
|
||||
| 项目 | 路由 base | publicPath | 真实访问路径 |
|
||||
| --------------- | ----------------------- | ----------------------- | -------------------------------------------- |
|
||||
| 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/ |
|
||||
|
|
@ -156,61 +161,72 @@ const routes = [
|
|||
|
||||
- vue-history 微应用
|
||||
|
||||
路由设置:
|
||||
```js
|
||||
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/',
|
||||
```
|
||||
webpack 打包 publicPath 配置(`vue.config.js`):
|
||||
```js
|
||||
module.exports = {
|
||||
publicPath: '/child/vue-history/'
|
||||
}
|
||||
```
|
||||
路由设置:
|
||||
|
||||
```js
|
||||
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/',
|
||||
```
|
||||
|
||||
webpack 打包 publicPath 配置(`vue.config.js`):
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
publicPath: '/child/vue-history/',
|
||||
};
|
||||
```
|
||||
|
||||
- react-history 微应用
|
||||
|
||||
路由设置:
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/child/react-history/'}>
|
||||
```
|
||||
webpack 打包 publicPath 配置:
|
||||
```js
|
||||
module.exports = {
|
||||
output: {
|
||||
publicPath: '/child/react-history/',
|
||||
}
|
||||
}
|
||||
```
|
||||
路由设置:
|
||||
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/child/react-history/'}>
|
||||
```
|
||||
|
||||
webpack 打包 publicPath 配置:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
output: {
|
||||
publicPath: '/child/react-history/',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
- angular-history 微应用
|
||||
|
||||
路由设置:
|
||||
```js
|
||||
providers: [{
|
||||
provide: APP_BASE_HREF,
|
||||
useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/'
|
||||
}]
|
||||
```
|
||||
webpack 打包的 `publicPath` 通过 `deploy-url` 来修改,修改 `package.json`:
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --deploy-url /child/angular-history/",
|
||||
```
|
||||
路由设置:
|
||||
|
||||
```js
|
||||
providers: [
|
||||
{
|
||||
provide: APP_BASE_HREF,
|
||||
useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
webpack 打包的 `publicPath` 通过 `deploy-url` 来修改,修改 `package.json`:
|
||||
|
||||
```diff
|
||||
- "build": "ng build",
|
||||
+ "build": "ng build --deploy-url /child/angular-history/",
|
||||
```
|
||||
|
||||
那么此时的注册函数是这样的(需要保证 `activeRule` 和 `entry` 不同):
|
||||
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app-vue-hash',
|
||||
name: 'app-vue-hash',
|
||||
entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/
|
||||
container: '#container',
|
||||
activeRule: '/app-vue-hash',
|
||||
container: '#container',
|
||||
activeRule: '/app-vue-hash',
|
||||
},
|
||||
{
|
||||
{
|
||||
name: 'app-vue-history',
|
||||
entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/
|
||||
container: '#container',
|
||||
container: '#container',
|
||||
activeRule: '/app-vue-history',
|
||||
},
|
||||
// angular 和 react 同上
|
||||
|
|
@ -239,7 +255,7 @@ server {
|
|||
}
|
||||
```
|
||||
|
||||
#### 方案2:微应用直接放在二级目录,但是设置特殊的 `activeRule`
|
||||
#### 方案 2:微应用直接放在二级目录,但是设置特殊的 `activeRule`
|
||||
|
||||
```
|
||||
└── html/ # 根文件夹
|
||||
|
|
@ -257,7 +273,7 @@ server {
|
|||
|
||||
基本操作和上面是一样的,只要保证 `activeRule` 和微应用的存放路径名不一样即可。
|
||||
|
||||
### 场景2:主应用和微应用部署在不同的服务器,使用 Nginx 代理访问
|
||||
### 场景 2:主应用和微应用部署在不同的服务器,使用 Nginx 代理访问
|
||||
|
||||
一般这么做是因为**不允许主应用跨域访问微应用**,做法就是将主应用服务器上一个特殊路径的请求全部转发到微应用的服务器上,即通过代理实现“微应用部署在主应用服务器上”的效果。
|
||||
|
||||
|
|
@ -268,7 +284,7 @@ server {
|
|||
```
|
||||
/app1/ {
|
||||
proxy_pass http://www.b.com/app1/;
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header Host $host:$server_port;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -277,10 +293,10 @@ server {
|
|||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
name: 'app1',
|
||||
entry: '/app1/', // http://localhost:8080/app1/
|
||||
container: '#container',
|
||||
activeRule: '/child-app1',
|
||||
container: '#container',
|
||||
activeRule: '/child-app1',
|
||||
},
|
||||
],
|
||||
```
|
||||
|
|
@ -291,8 +307,8 @@ registerMicroApps([
|
|||
module.exports = {
|
||||
output: {
|
||||
publicPath: `/app1/`,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
微应用打包的 `publicPath` 加上 `/app1/` 之后,必须部署在 `/app1` 目录,否则无法独立访问。
|
||||
|
|
@ -319,4 +335,4 @@ if ($http_custom_referer != "main") {
|
|||
`start` 函数基本修改如下:
|
||||
|
||||
1. `jsSandbox` 配置去掉,改为 `sandbox` ,可选值也修改了。
|
||||
2. 新增了 `getPublicPath` 和 `getTemplate` ,用于替代`RegisterMicroAppsOpts`。
|
||||
2. 新增了 `getPublicPath` 和 `getTemplate` ,用于替代`RegisterMicroAppsOpts`。
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ To solve the exception, try the following steps:
|
|||
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:
|
||||
|
||||
```html {2}
|
||||
<script src="/antd.js"></script>
|
||||
<script src="/appEntry.js" entry></script>
|
||||
|
|
@ -29,8 +30,7 @@ To solve the exception, try the following steps:
|
|||
|
||||
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](https://github.com/umijs/qiankun/issues/1092)
|
||||
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:
|
||||
7. If you're using webpack5, please see [here](https://github.com/umijs/qiankun/issues/1092) 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:
|
||||
|
||||
|
|
@ -64,48 +64,50 @@ module.exports = {
|
|||
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:
|
||||
```js
|
||||
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:
|
||||
```js
|
||||
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'));
|
||||
}
|
||||
```
|
||||
|
||||
`vue` micro app:
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```js
|
||||
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 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:
|
||||
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:
|
||||
|
||||
```html
|
||||
<script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>
|
||||
```
|
||||
```html
|
||||
<script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>
|
||||
```
|
||||
|
||||
In other cases, please do not use `document.write`.
|
||||
In other cases, please do not use `document.write`.
|
||||
|
||||
## `Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!`
|
||||
|
||||
|
|
@ -126,38 +128,38 @@ It must be ensured that the routing page of the main app is also loaded when the
|
|||
`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**.
|
||||
```js
|
||||
const routes = [
|
||||
{
|
||||
path: '/portal/*',
|
||||
name: 'portal',
|
||||
component: () => import('../views/Portal.vue'),
|
||||
}
|
||||
]
|
||||
```
|
||||
```js
|
||||
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.
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/portal/app1',
|
||||
},
|
||||
]);
|
||||
```
|
||||
```js
|
||||
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**.
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
export default {
|
||||
mounted() {
|
||||
if (!window.qiankunStarted) {
|
||||
window.qiankunStarted = true;
|
||||
start();
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
```js
|
||||
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.
|
||||
|
||||
|
|
@ -165,40 +167,39 @@ It must be ensured that the routing page of the main app is also loaded when the
|
|||
|
||||
1. The main app registers a wildcard sub route for this route, and the content is empty.
|
||||
|
||||
```ts
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'portal',
|
||||
component: PortalComponent,
|
||||
children: [
|
||||
{ path: '**', component: EmptyComponent },
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
```ts
|
||||
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.
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/portal/app1',
|
||||
},
|
||||
]);
|
||||
```
|
||||
```js
|
||||
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**.
|
||||
```ts
|
||||
import { start } from 'qiankun';
|
||||
export class PortalComponent implements AfterViewInit {
|
||||
ngAfterViewInit(): void {
|
||||
if (!window.qiankunStarted) {
|
||||
window.qiankunStarted = true;
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```ts
|
||||
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`
|
||||
|
||||
|
|
@ -208,7 +209,7 @@ There are three lines code in the `vue-router` as followed, and it will access `
|
|||
|
||||
```javascript
|
||||
if (inBrowser && window.Vue) {
|
||||
window.Vue.use(VueRouter)
|
||||
window.Vue.use(VueRouter);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -259,203 +260,195 @@ There are mainly the following solutions:
|
|||
|
||||
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**)
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
```js
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`vue-cli3` project:
|
||||
`vue-cli3` project:
|
||||
|
||||
```js
|
||||
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()
|
||||
},
|
||||
}
|
||||
```
|
||||
```js
|
||||
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();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
3. Use the `file-loader` of `webpack` to inject the full path when packaging it (suitable for projects with large font files and images)
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```js
|
||||
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()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Combine the two schemes, convert small files to `base64`, and inject path prefixes for large files
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```js
|
||||
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: {
|
||||
```js
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
```
|
||||
publicPath,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'fonts/[name].[hash:8].[ext]',
|
||||
publicPath,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`vue-cli3` project:
|
||||
|
||||
```js
|
||||
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();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
4. Combine the two schemes, convert small files to `base64`, and inject path prefixes for large files
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```js
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
5. 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](https://cli.vuejs.org/zh/config/#css-extract):
|
||||
Configuration reference [vue-cli3 official website](https://cli.vuejs.org/zh/config/#css-extract):
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
css: {
|
||||
extract: false
|
||||
},
|
||||
}
|
||||
```
|
||||
```js
|
||||
module.exports = {
|
||||
css: {
|
||||
extract: false,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Must a sub app asset support cors?
|
||||
|
||||
|
|
@ -489,7 +482,7 @@ Example for antd:
|
|||
|
||||
```jsx
|
||||
import { ConfigProvider } from 'antd';
|
||||
|
||||
|
||||
export const MyApp = () => (
|
||||
<ConfigProvider prefixCls="yourPrefix">
|
||||
<App />
|
||||
|
|
@ -585,10 +578,11 @@ In singular mode, you can use the `excludeAssetFilter` parameter to release this
|
|||
If you use JSONP in not-singular mode, simply using `excludeAssetFilter` does not achieve good results, because each application is isolated by the sandbox; you can provide a unified JSONP tool in the main application, and the subapplication just calls the tool.
|
||||
|
||||
## 404 after refresh of child application?
|
||||
It is usually because you are routing in Browser mode, which requires the server to open it.
|
||||
Specific configuration mode reference:
|
||||
* [HTML5 History Mode](https://router.vuejs.org/guide/essentials/history-mode.html)
|
||||
* [browserRouter](https://reactrouter.com/web/api/BrowserRouter)
|
||||
|
||||
It is usually because you are routing in Browser mode, which requires the server to open it. Specific configuration mode reference:
|
||||
|
||||
- [HTML5 History Mode](https://router.vuejs.org/guide/essentials/history-mode.html)
|
||||
- [browserRouter](https://reactrouter.com/web/api/BrowserRouter)
|
||||
|
||||
## How to configure the 404 page in the main application?
|
||||
|
||||
|
|
@ -597,16 +591,17 @@ First of all, you cannot use the wildcard `*`. You can register the 404 page as
|
|||
Take `vue-router` as an example, the pseudo code is as follows:
|
||||
|
||||
```js
|
||||
const childrenPath = ['/app1','/app2'];
|
||||
const childrenPath = ['/app1', '/app2'];
|
||||
router.beforeEach((to, from, next) => {
|
||||
if(to.name) {// There is a name attribute, indicating that it is the route of the main project
|
||||
next()
|
||||
if (to.name) {
|
||||
// There is a name attribute, indicating that it is the route of the main project
|
||||
next();
|
||||
}
|
||||
if(childrenPath.some(item => to.path.includes(item))){
|
||||
next()
|
||||
if (childrenPath.some((item) => to.path.includes(item))) {
|
||||
next();
|
||||
}
|
||||
next({ name: '404' })
|
||||
})
|
||||
next({ name: '404' });
|
||||
});
|
||||
```
|
||||
|
||||
## How to jump between micro apps?
|
||||
|
|
@ -615,11 +610,10 @@ router.beforeEach((to, from, next) => {
|
|||
|
||||
-The main application judges the micro application based on the `path`
|
||||
|
||||
It is not possible to directly use the routing instance of the micro-application to jump between micro-applications in the `history` mode or to jump to the main application page. The reason is that the routing instance jumps of the micro-application are all based on the `base` of the route. There are two ways to jump:
|
||||
|
||||
1. `history.pushState()`: [mdn usage introduction](https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState)
|
||||
2. Pass the routing instance of the main application to the micro application through `props`, and the micro application will jump to this routing instance.
|
||||
It is not possible to directly use the routing instance of the micro-application to jump between micro-applications in the `history` mode or to jump to the main application page. The reason is that the routing instance jumps of the micro-application are all based on the `base` of the route. There are two ways to jump:
|
||||
|
||||
1. `history.pushState()`: [mdn usage introduction](https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState)
|
||||
2. Pass the routing instance of the main application to the micro application through `props`, and the micro application will jump to this routing instance.
|
||||
|
||||
## After the microapp file is updated, the old version of the file is still accessed
|
||||
|
||||
|
|
@ -635,15 +629,15 @@ location = /index.html {
|
|||
|
||||
## micro app styles was lost when using config entry
|
||||
|
||||
Some scenarios we had to use config entry to load micro app (** not recommended **):
|
||||
Some scenarios we had to use config entry to load micro app (** not recommended **):
|
||||
|
||||
```js
|
||||
loadMicroApp({
|
||||
name: 'configEntry',
|
||||
entry: {
|
||||
scripts: ['//t.com/t.js'],
|
||||
styles: ['//t.com/t.css']
|
||||
}
|
||||
styles: ['//t.com/t.css'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -651,9 +645,10 @@ Since there is no HTML attached to entry JS for microapp, the mount hook simply
|
|||
|
||||
```js
|
||||
export async function mount(props) {
|
||||
ReactDOM.render(<App/>, props.container);
|
||||
ReactDOM.render(<App />, props.container);
|
||||
}
|
||||
```
|
||||
|
||||
As `props.container` is not an empty container and will contain information such as the style sheet that the microapp registers through the styles configuration, when we render directly for the container that the react application is applying with 'props.container', all the original DOM structures in the container will be overwritten, causing the style sheet to be lost.
|
||||
|
||||
We need to build an empty render container for micro applications that use Config Entry to mount react applications:
|
||||
|
|
@ -682,12 +677,12 @@ export async function mount(props) {
|
|||
|
||||
As the requests to pull micro-app entry are all cross-domain, when your micro-app relies on cookies (such as authentication), you need to customize the fetch method to enable the cors mode:
|
||||
|
||||
* If you load the microapps through [registerMicroApps](/api#registermicroappsapps-lifecycles), you need to configure a custom fetch in the start method, such as:
|
||||
- If you load the microapps through [registerMicroApps](/api#registermicroappsapps-lifecycles), you need to configure a custom fetch in the start method, such as:
|
||||
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
|
||||
start({
|
||||
|
||||
start({
|
||||
fetch(url, ...args) {
|
||||
// Enable cors mode for the specified microapp
|
||||
if (url === 'http://app.alipay.com/entry.html') {
|
||||
|
|
@ -697,17 +692,17 @@ As the requests to pull micro-app entry are all cross-domain, when your micro-ap
|
|||
credentials: 'include',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return window.fetch(url, ...args);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
* If you load the microapp via [loadMicroApp](/api#loadmicroappapp-configuration), you need to configure a custom fetch when invoking, such as:
|
||||
- If you load the microapp via [loadMicroApp](/api#loadmicroappapp-configuration), you need to configure a custom fetch when invoking, such as:
|
||||
|
||||
```js
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
|
||||
|
||||
loadMicroApp(app, {
|
||||
fetch(url, ...args) {
|
||||
// Enable cors mode for the specified microapp
|
||||
|
|
@ -718,8 +713,8 @@ As the requests to pull micro-app entry are all cross-domain, when your micro-ap
|
|||
credentials: 'include',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return window.fetch(url, ...args);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ qiankun 抛出这个错误是因为无法从微应用的 entry js 中识别出
|
|||
|
||||
6. 如果开发环境可以,生产环境不行,检查微应用的 `index.html` 和 `entry js` 是否正常返回,比如说返回了 `404.html`。
|
||||
|
||||
7. 如果你正在使用 webpack5,请看[这个issues](https://github.com/umijs/qiankun/issues/1092)
|
||||
7. 如果你正在使用 webpack5,请看[这个 issues](https://github.com/umijs/qiankun/issues/1092)
|
||||
|
||||
如果在上述步骤完成后仍有问题,通常说明是浏览器兼容性问题导致的。可以尝试 **将有问题的微应用的 webpack `output.library` 配置成跟主应用中注册的 `name` 字段一致**,如:
|
||||
|
||||
|
|
@ -67,47 +67,49 @@ qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在
|
|||
|
||||
1. 微应用的根 `id` 与其他 DOM 冲突。解决办法是:修改根 `id` 的查找范围。
|
||||
|
||||
`vue` 微应用:
|
||||
```js
|
||||
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);
|
||||
}
|
||||
```
|
||||
`vue` 微应用:
|
||||
|
||||
`react` 微应用:
|
||||
```js
|
||||
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'));
|
||||
}
|
||||
```
|
||||
```js
|
||||
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` 微应用:
|
||||
|
||||
```js
|
||||
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 导致的,先看看升级能否解决,比如说高德地图升级到 2.x 版本即可。
|
||||
|
||||
如果升级无法解决,建议将地图放到主应用加载,微应用也引入这个地图 js(独立运行时使用),但是给 `<script>` 标签加上 `ignore` 属性:
|
||||
如果升级无法解决,建议将地图放到主应用加载,微应用也引入这个地图 js(独立运行时使用),但是给 `<script>` 标签加上 `ignore` 属性:
|
||||
|
||||
```html
|
||||
<script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>
|
||||
```
|
||||
```html
|
||||
<script src="https://map.qq.com/api/gljs?v=1.exp" ignore></script>
|
||||
```
|
||||
|
||||
如果是其他的情况,请不要使用 `document.write` 。
|
||||
如果是其他的情况,请不要使用 `document.write` 。
|
||||
|
||||
## `Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!`
|
||||
|
||||
|
|
@ -128,38 +130,38 @@ qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在
|
|||
`vue` + `vue-router` 技术栈的主应用:
|
||||
|
||||
1. 主应用注册这个路由时给 `path` 加一个 `*`,**注意:如果这个路由有其他子路由,需要另外注册一个路由,仍然使用这个组件即可**。
|
||||
```js
|
||||
const routes = [
|
||||
{
|
||||
path: '/portal/*',
|
||||
name: 'portal',
|
||||
component: () => import('../views/Portal.vue'),
|
||||
}
|
||||
]
|
||||
```
|
||||
```js
|
||||
const routes = [
|
||||
{
|
||||
path: '/portal/*',
|
||||
name: 'portal',
|
||||
component: () => import('../views/Portal.vue'),
|
||||
},
|
||||
];
|
||||
```
|
||||
2. 微应用的 `activeRule` 需要包含主应用的这个路由 `path`。
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/portal/app1',
|
||||
},
|
||||
]);
|
||||
```
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/portal/app1',
|
||||
},
|
||||
]);
|
||||
```
|
||||
3. 在 `Portal.vue` 这个组件的 `mounted` 周期调用 `start` 函数,**注意不要重复调用**。
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
export default {
|
||||
mounted() {
|
||||
if (!window.qiankunStarted) {
|
||||
window.qiankunStarted = true;
|
||||
start();
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
export default {
|
||||
mounted() {
|
||||
if (!window.qiankunStarted) {
|
||||
window.qiankunStarted = true;
|
||||
start();
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`react` + `react-router` 技术栈的主应用:只需要让微应用的 `activeRule` 包含主应用的这个路由即可。
|
||||
|
||||
|
|
@ -167,40 +169,40 @@ qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在
|
|||
|
||||
1. 主应用给这个路由注册一个通配符的子路由,内容为空。
|
||||
|
||||
```ts
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'portal',
|
||||
component: PortalComponent,
|
||||
children: [
|
||||
{ path: '**', component: EmptyComponent },
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
```ts
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'portal',
|
||||
component: PortalComponent,
|
||||
children: [{ path: '**', component: EmptyComponent }],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
2. 微应用的 `activeRule` 需要包含主应用的这个路由 `path`。
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/portal/app1',
|
||||
},
|
||||
]);
|
||||
```
|
||||
```js
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'app1',
|
||||
entry: 'http://localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/portal/app1',
|
||||
},
|
||||
]);
|
||||
```
|
||||
3. 在这个路由组件的 `ngAfterViewInit` 周期调用 `start` 函数,**注意不要重复调用**。
|
||||
```ts
|
||||
import { start } from 'qiankun';
|
||||
export class PortalComponent implements AfterViewInit {
|
||||
ngAfterViewInit(): void {
|
||||
if (!window.qiankunStarted) {
|
||||
window.qiankunStarted = true;
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```ts
|
||||
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` 属性,如果没有就去父应用里查找。
|
||||
|
|
@ -209,7 +211,7 @@ qiankun 中的代码使用 Proxy 去代理父页面的 window,来实现的沙
|
|||
|
||||
```javascript
|
||||
if (inBrowser && window.Vue) {
|
||||
window.Vue.use(VueRouter)
|
||||
window.Vue.use(VueRouter);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -262,203 +264,195 @@ runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、
|
|||
|
||||
2. 借助 `webpack` 的 `url-loader` 将字体文件和图片打包成 `base64`(适用于字体文件和图片体积小的项目)(**推荐**)
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
```js
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`vue-cli3` 项目写法:
|
||||
`vue-cli3` 项目写法:
|
||||
|
||||
```js
|
||||
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()
|
||||
},
|
||||
}
|
||||
```
|
||||
```js
|
||||
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();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
3. 借助 `webpack` 的 `file-loader` ,在打包时给其注入完整路径(适用于字体文件和图片体积比较大的项目)
|
||||
|
||||
```js
|
||||
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` 项目写法:
|
||||
|
||||
```js
|
||||
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()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. 将两种方案结合起来,小文件转 `base64` ,大文件注入路径前缀
|
||||
|
||||
```js
|
||||
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` 项目写法:
|
||||
|
||||
```js
|
||||
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: {
|
||||
```js
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
```
|
||||
publicPath,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'fonts/[name].[hash:8].[ext]',
|
||||
publicPath,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`vue-cli3` 项目写法:
|
||||
|
||||
```js
|
||||
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();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
4. 将两种方案结合起来,小文件转 `base64` ,大文件注入路径前缀
|
||||
|
||||
```js
|
||||
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` 项目写法:
|
||||
|
||||
```js
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
5. `vue-cli3` 项目可以将 `css` 打包到 `js`里面,不单独生成文件(不推荐,仅适用于 `css` 较少的项目)
|
||||
|
||||
配置参考 [vue-cli3 官网](https://cli.vuejs.org/zh/config/#css-extract):
|
||||
配置参考 [vue-cli3 官网](https://cli.vuejs.org/zh/config/#css-extract):
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
css: {
|
||||
extract: false
|
||||
},
|
||||
}
|
||||
```
|
||||
```js
|
||||
module.exports = {
|
||||
css: {
|
||||
extract: false,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 微应用静态资源一定要支持跨域吗?
|
||||
|
||||
|
|
@ -490,7 +484,7 @@ import { start } from 'qiankun';
|
|||
start({
|
||||
getTemplate(tpl) {
|
||||
return tpl.replace('<script src="/to-be-replaced.js"><script>', '');
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -505,12 +499,14 @@ start({
|
|||
fetch(url, ...args) {
|
||||
if (url === 'http://to-be-replaced.js') {
|
||||
return {
|
||||
async text() { return '' }
|
||||
async text() {
|
||||
return '';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return window.fetch(url, ...args);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -557,7 +553,7 @@ qiankun 将会自动隔离微应用之间的样式(开启沙箱的情况下)
|
|||
|
||||
```jsx
|
||||
import { ConfigProvider } from 'antd';
|
||||
|
||||
|
||||
export const MyApp = () => (
|
||||
<ConfigProvider prefixCls="yourPrefix">
|
||||
<App />
|
||||
|
|
@ -645,7 +641,7 @@ import 'core-js/web/url';
|
|||
|
||||
## 报错 `Here is no "fetch" on the window env, you need to polyfill it`
|
||||
|
||||
qiankun 依赖的 import-html-entry 通过 `window.fetch` 来获取微应用的资源,部分[不支持 fetch 的浏览器](https://caniuse.com/#search=fetch)需要在入口处打上相应的 [polyfill](https://github.com/github/fetch)
|
||||
qiankun 依赖的 import-html-entry 通过 `window.fetch` 来获取微应用的资源, 部分[不支持 fetch 的浏览器](https://caniuse.com/#search=fetch)需要在入口处打上相应的 [polyfill](https://github.com/github/fetch)
|
||||
|
||||
## 微应用 JSONP 跨域错误怎么处理?
|
||||
|
||||
|
|
@ -656,28 +652,30 @@ qiankun 会将微应用的动态 script 加载(例如 JSONP)转化为 fetch
|
|||
若在多实例模式下使用 JSONP,单纯使用 `excludeAssetFilter` 并不能取得好的效果,因为各应用被沙箱所隔离;你可以在主应用提供统一的 JSONP 工具,微应用调用主应用提供的该工具来曲线救国。
|
||||
|
||||
## 微应用路径下刷新后 404?
|
||||
通常是因为你使用的是 browser 模式的路由,这种路由模式的开启需要服务端配合才行。
|
||||
具体配置方式参考:
|
||||
* [HTML5 History 模式](https://router.vuejs.org/zh/guide/essentials/history-mode.html)
|
||||
* [browserHistory](https://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html#browserHistory)
|
||||
|
||||
## 主应用如何配置404页面?
|
||||
通常是因为你使用的是 browser 模式的路由,这种路由模式的开启需要服务端配合才行。具体配置方式参考:
|
||||
|
||||
- [HTML5 History 模式](https://router.vuejs.org/zh/guide/essentials/history-mode.html)
|
||||
- [browserHistory](https://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html#browserHistory)
|
||||
|
||||
## 主应用如何配置 404 页面?
|
||||
|
||||
首先不应该写通配符 `*` ,可以将 404 页面注册为一个普通路由页面,比如说 `/404` ,然后在主应用的路由钩子函数里面判断一下,如果既不是主应用路由,也不是微应用,就跳转到 404 页面。
|
||||
|
||||
以`vue-router`为例,伪代码如下:
|
||||
|
||||
```js
|
||||
const childrenPath = ['/app1','/app2'];
|
||||
const childrenPath = ['/app1', '/app2'];
|
||||
router.beforeEach((to, from, next) => {
|
||||
if(to.name) { // 有 name 属性,说明是主应用的路由
|
||||
next()
|
||||
if (to.name) {
|
||||
// 有 name 属性,说明是主应用的路由
|
||||
next();
|
||||
}
|
||||
if(childrenPath.some(item => to.path.includes(item))){
|
||||
next()
|
||||
if (childrenPath.some((item) => to.path.includes(item))) {
|
||||
next();
|
||||
}
|
||||
next({ name: '404' })
|
||||
})
|
||||
next({ name: '404' });
|
||||
});
|
||||
```
|
||||
|
||||
## 微应用之间如何跳转?
|
||||
|
|
@ -688,10 +686,9 @@ router.beforeEach((to, from, next) => {
|
|||
|
||||
`history` 模式的微应用之间的跳转,或者微应用跳主应用页面,直接使用微应用的路由实例是不行的,原因是微应用的路由实例跳转都基于路由的 `base`。有两种办法可以跳转:
|
||||
|
||||
1. `history.pushState()`:[mdn用法介绍](https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState)
|
||||
1. `history.pushState()`:[mdn 用法介绍](https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState)
|
||||
2. 将主应用的路由实例通过 `props` 传给微应用,微应用这个路由实例跳转。
|
||||
|
||||
|
||||
## 微应用文件更新之后,访问的还是旧版文件
|
||||
|
||||
服务器需要给微应用的 `index.html` 配置一个响应头:`Cache-Control no-cache`,意思就是每次请求都检查是否更新。
|
||||
|
|
@ -713,8 +710,8 @@ loadMicroApp({
|
|||
name: 'configEntry',
|
||||
entry: {
|
||||
scripts: ['//t.com/t.js'],
|
||||
styles: ['//t.com/t.css']
|
||||
}
|
||||
styles: ['//t.com/t.css'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -722,7 +719,7 @@ loadMicroApp({
|
|||
|
||||
```js
|
||||
export async function mount(props) {
|
||||
ReactDOM.render(<App/>, props.container);
|
||||
ReactDOM.render(<App />, props.container);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -754,12 +751,12 @@ export async function mount(props) {
|
|||
|
||||
因为拉取微应用 entry 的请求都是跨域的,所以当你的微应用是依赖 cookie (如登陆鉴权)的情况下,你需要通过自定义 fetch 的方式,开启 fetch 的 cors 模式:
|
||||
|
||||
* 如果你是通过 [registerMicroApps](/zh/api#registermicroappsapps-lifecycles) 加载微应用的,你需要在 start 方法里配置自定义 fetch,如:
|
||||
- 如果你是通过 [registerMicroApps](/zh/api#registermicroappsapps-lifecycles) 加载微应用的,你需要在 start 方法里配置自定义 fetch,如:
|
||||
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
|
||||
start({
|
||||
|
||||
start({
|
||||
fetch(url, ...args) {
|
||||
// 给指定的微应用 entry 开启跨域请求
|
||||
if (url === 'http://app.alipay.com/entry.html') {
|
||||
|
|
@ -769,17 +766,17 @@ export async function mount(props) {
|
|||
credentials: 'include',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return window.fetch(url, ...args);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
* 如果你是通过 [loadMicroApp](/zh/api#loadmicroappapp-configuration) 加载微应用的,你需要在调用时配置自定义 fetch,如:
|
||||
- 如果你是通过 [loadMicroApp](/zh/api#loadmicroappapp-configuration) 加载微应用的,你需要在调用时配置自定义 fetch,如:
|
||||
|
||||
```js
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
|
||||
|
||||
loadMicroApp(app, {
|
||||
fetch(url, ...args) {
|
||||
// 给指定的微应用 entry 开启跨域请求
|
||||
|
|
@ -790,13 +787,13 @@ export async function mount(props) {
|
|||
credentials: 'include',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return window.fetch(url, ...args);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
* 如果你是通过 [umi plugin](https://umijs.org/zh-CN/plugins/plugin-qiankun) 来使用 qiankun 的,那么你只需要给对应的微应用开启 credentials 配置即可:
|
||||
- 如果你是通过 [umi plugin](https://umijs.org/zh-CN/plugins/plugin-qiankun) 来使用 qiankun 的,那么你只需要给对应的微应用开启 credentials 配置即可:
|
||||
|
||||
```diff
|
||||
export default {
|
||||
|
|
@ -813,7 +810,3 @@ export async function mount(props) {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ toc: menu
|
|||
# Getting Started
|
||||
|
||||
## Master Application
|
||||
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
|
|
@ -37,7 +38,9 @@ start();
|
|||
After the sub-application information is registered, the matching logic of the qiankun will be automatically triggered once the browser url changes, and all the render methods corresponding to the subapplications whose activeRule methods returns `true` will be called, at the same time the subapplications' exposed lifecycle hooks will be called in turn.
|
||||
|
||||
## Sub Application
|
||||
|
||||
Sub applications do not need to install any additional dependencies to integrate to qiankun master application.
|
||||
|
||||
### 1. Exports Lifecycles From Sub App Entry
|
||||
|
||||
The child application needs to export `bootstrap`,`mount`, `unmount` three lifecycle hooks in its own entry js (usually the entry js of webpack you configure) for the main application to call at the appropriate time.
|
||||
|
|
@ -66,7 +69,9 @@ export async function mount(props) {
|
|||
* 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'));
|
||||
ReactDOM.unmountComponentAtNode(
|
||||
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ toc: menu
|
|||
# 快速上手
|
||||
|
||||
## 主应用
|
||||
|
||||
### 1. 安装 qiankun
|
||||
|
||||
```bash
|
||||
|
|
@ -41,16 +42,15 @@ start();
|
|||
```ts
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
|
||||
loadMicroApp(
|
||||
{
|
||||
name: 'app',
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
}
|
||||
);
|
||||
loadMicroApp({
|
||||
name: 'app',
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
});
|
||||
```
|
||||
|
||||
## 微应用
|
||||
|
||||
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
|
||||
|
||||
### 1. 导出相应的生命周期钩子
|
||||
|
|
@ -77,7 +77,9 @@ export async function mount(props) {
|
|||
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
|
||||
*/
|
||||
export async function unmount(props) {
|
||||
ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
|
||||
ReactDOM.unmountComponentAtNode(
|
||||
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -90,7 +92,6 @@ export async function update(props) {
|
|||
|
||||
qiankun 基于 single-spa,所以你可以在[这里](https://single-spa.js.org/docs/building-applications.html#registered-application-lifecycle)找到更多关于微应用生命周期相关的文档说明。
|
||||
|
||||
|
||||
无 webpack 等构建工具的应用接入方式请见[这里](/zh/guide/tutorial#%E9%9D%9E-webpack-%E6%9E%84%E5%BB%BA%E7%9A%84%E5%BE%AE%E5%BA%94%E7%94%A8)
|
||||
|
||||
### 2. 配置微应用的打包工具
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ Micro apps are divided into projects with `webpack` and without `webpack`. The t
|
|||
|
||||
1. Added `public-path.js` file, used to modify the runtime `publicPath`.[What is publicPath at runtime?](https://webpack.js.org/guides/public-path/#on-the-fly).
|
||||
|
||||
<Alert>
|
||||
Note: `publicPath` at runtime and `publicPath` at build time are different, and the two cannot be equivalently substituted.
|
||||
</Alert>
|
||||
<Alert>
|
||||
Note: `publicPath` at runtime and `publicPath` at build time are different, and the two cannot be equivalently substituted.
|
||||
</Alert>
|
||||
|
||||
2. It is recommended to use the route of the `history` mode for the micro app. The route `base` needs to be set, and the value is the same as its `activeRule`.
|
||||
3. Import `public-path.js` at the top of the entry file, modify and export three `lifecycles` functions.
|
||||
|
|
@ -69,176 +69,181 @@ Take the `react 16` project generated by `create react app` as an example, with
|
|||
|
||||
1. Add `public-path.js` in the `src` directory:
|
||||
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
|
||||
2. Set the `base` of `history` mode routing:
|
||||
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
|
||||
```
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
|
||||
```
|
||||
|
||||
3. The entry file `index.js` is modified. In order to avoid the root id `#root` from conflicting with other DOMs, the search scope needs to be limited.
|
||||
|
||||
```js
|
||||
import './public-path';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
```js
|
||||
import './public-path';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
function render(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
|
||||
}
|
||||
function render(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
|
||||
}
|
||||
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render({});
|
||||
}
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render({});
|
||||
}
|
||||
|
||||
export async function bootstrap() {
|
||||
console.log('[react16] react app bootstraped');
|
||||
}
|
||||
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 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'));
|
||||
}
|
||||
```
|
||||
export async function unmount(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
|
||||
}
|
||||
```
|
||||
|
||||
4. Modify `webpack` configuration
|
||||
|
||||
Install the plugin `@rescripts/cli`, of course, you can also choose other plugins, such as `react-app-rewired`.
|
||||
```dash
|
||||
npm i -D @rescripts/cli
|
||||
```
|
||||
Install the plugin `@rescripts/cli`, of course, you can also choose other plugins, such as `react-app-rewired`.
|
||||
|
||||
Add `.rescriptsrc.js` to the root directory:
|
||||
```js
|
||||
const { name } = require('./package');
|
||||
```dash
|
||||
npm i -D @rescripts/cli
|
||||
```
|
||||
|
||||
module.exports = {
|
||||
webpack: config => {
|
||||
config.output.library = `${name}-[name]`;
|
||||
config.output.libraryTarget = 'umd';
|
||||
config.output.jsonpFunction = `webpackJsonp_${name}`;
|
||||
config.output.globalObject = 'window';
|
||||
Add `.rescriptsrc.js` to the root directory:
|
||||
|
||||
return config;
|
||||
},
|
||||
```js
|
||||
const { name } = require('./package');
|
||||
|
||||
devServer: _ => {
|
||||
const config = _;
|
||||
module.exports = {
|
||||
webpack: (config) => {
|
||||
config.output.library = `${name}-[name]`;
|
||||
config.output.libraryTarget = 'umd';
|
||||
config.output.jsonpFunction = `webpackJsonp_${name}`;
|
||||
config.output.globalObject = 'window';
|
||||
|
||||
config.headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
};
|
||||
config.historyApiFallback = true;
|
||||
config.hot = false;
|
||||
config.watchContentBase = false;
|
||||
config.liveReload = false;
|
||||
return config;
|
||||
},
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
devServer: (_) => {
|
||||
const config = _;
|
||||
|
||||
config.headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
};
|
||||
config.historyApiFallback = true;
|
||||
config.hot = false;
|
||||
config.watchContentBase = false;
|
||||
config.liveReload = false;
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Modify `package.json`:
|
||||
|
||||
```diff
|
||||
- "start": "react-scripts start",
|
||||
+ "start": "rescripts start",
|
||||
- "build": "react-scripts build",
|
||||
+ "build": "rescripts build",
|
||||
- "test": "react-scripts test",
|
||||
+ "test": "rescripts test",
|
||||
- "eject": "react-scripts eject"
|
||||
```
|
||||
|
||||
Modify `package.json`:
|
||||
```diff
|
||||
- "start": "react-scripts start",
|
||||
+ "start": "rescripts start",
|
||||
- "build": "react-scripts build",
|
||||
+ "build": "rescripts build",
|
||||
- "test": "react-scripts test",
|
||||
+ "test": "rescripts test",
|
||||
- "eject": "react-scripts eject"
|
||||
```
|
||||
### Vue micro app
|
||||
|
||||
Take the `vue 2.x` project generated by `vue-cli 3+` as an example, and add it after the `vue 3` version becomes stable.
|
||||
|
||||
1. Add `public-path.js` in the `src` directory:
|
||||
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
|
||||
3. The entry file `main.js` is modified. In order to avoid the root id `#app` from conflicting with other DOMs, the search scope needs to be limited.
|
||||
2. The entry file `main.js` is modified. In order to avoid the root id `#app` from conflicting with other DOMs, the search scope needs to be limited.
|
||||
|
||||
```js
|
||||
import './public-path';
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import App from './App.vue';
|
||||
import routes from './router';
|
||||
import store from './store';
|
||||
```js
|
||||
import './public-path';
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import App from './App.vue';
|
||||
import routes from './router';
|
||||
import store from './store';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
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,
|
||||
});
|
||||
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');
|
||||
}
|
||||
instance = new Vue({
|
||||
router,
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
}).$mount(container ? container.querySelector('#app') : '#app');
|
||||
}
|
||||
|
||||
// when run independently
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render();
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
```
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
3. Modify `webpack` configuration(`vue.config.js`):
|
||||
3. Modify `webpack` configuration(`vue.config.js`):
|
||||
|
||||
```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}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
```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
|
||||
|
||||
|
|
@ -246,147 +251,149 @@ Take the `angular 9` project generated by `Angular-cli 9` as an example, other v
|
|||
|
||||
1. Add the file `public-path.js` in the `src` directory with the content:
|
||||
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
```js
|
||||
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:
|
||||
|
||||
```diff
|
||||
+ 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' : '/' }]
|
||||
})
|
||||
```
|
||||
```diff
|
||||
+ 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:
|
||||
|
||||
```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';
|
||||
```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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
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 bootstrap(props: Object) {
|
||||
console.log(props);
|
||||
}
|
||||
|
||||
export async function mount (props: Object) {
|
||||
render();
|
||||
}
|
||||
export async function mount(props: Object) {
|
||||
render();
|
||||
}
|
||||
|
||||
export async function unmount (props: Object) {
|
||||
console.log(props);
|
||||
// @ts-ignore
|
||||
app.destroy();
|
||||
}
|
||||
```
|
||||
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**.
|
||||
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**.
|
||||
|
||||
```bash
|
||||
npm i @angular-builders/custom-webpack@9.2.0 -D
|
||||
```
|
||||
```bash
|
||||
npm i @angular-builders/custom-webpack@9.2.0 -D
|
||||
```
|
||||
|
||||
Add `custom-webpack.config.js` to the root directory with the content:
|
||||
Add `custom-webpack.config.js` to the root directory with the content:
|
||||
|
||||
```js
|
||||
const appName = require('./package.json').name;
|
||||
module.exports = {
|
||||
devServer: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
library: `${appName}-[name]`,
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${appName}`,
|
||||
},
|
||||
};
|
||||
```
|
||||
```js
|
||||
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`.
|
||||
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`.
|
||||
|
||||
```diff
|
||||
- "builder": "@angular-devkit/build-angular:browser",
|
||||
+ "builder": "@angular-builders/custom-webpack:browser",
|
||||
"options": {
|
||||
+ "customWebpackConfig": {
|
||||
+ "path": "./custom-webpack.config.js"
|
||||
+ }
|
||||
}
|
||||
```
|
||||
```diff
|
||||
- "builder": "@angular-devkit/build-angular:browser",
|
||||
+ "builder": "@angular-builders/custom-webpack:browser",
|
||||
"options": {
|
||||
+ "customWebpackConfig": {
|
||||
+ "path": "./custom-webpack.config.js"
|
||||
+ }
|
||||
}
|
||||
```
|
||||
|
||||
```diff
|
||||
- "builder": "@angular-devkit/build-angular:dev-server",
|
||||
+ "builder": "@angular-builders/custom-webpack:dev-server",
|
||||
```
|
||||
```diff
|
||||
- "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` in **main app**, it needs to be imported before `import qiankun`.
|
||||
|
||||
```diff
|
||||
- import 'zone.js/dist/zone';
|
||||
```
|
||||
Delete the code of import `zone.js` in the `src/polyfills.ts` of the micro app.
|
||||
|
||||
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.
|
||||
```diff
|
||||
- import 'zone.js/dist/zone';
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Other CDN/local packages can also be used -->
|
||||
<script src="https://unpkg.com/zone.js" ignore></script>
|
||||
```
|
||||
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.
|
||||
|
||||
```html
|
||||
<!-- 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, reference[issues/431](https://github.com/umijs/qiankun/issues/431).
|
||||
|
||||
```diff
|
||||
- "target": "es2015",
|
||||
+ "target": "es5",
|
||||
+ "typeRoots": [
|
||||
+ "node_modules/@types"
|
||||
+ ],
|
||||
```
|
||||
```diff
|
||||
- "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 :
|
||||
```diff
|
||||
- <app-root></app-root>
|
||||
+ <app-root id="angular9"></app-root>
|
||||
```
|
||||
src/index.html :
|
||||
|
||||
src/app/app.component.ts :
|
||||
```diff
|
||||
- selector: 'app-root',
|
||||
+ selector: '#angular9 app-root',
|
||||
```
|
||||
```diff
|
||||
- <app-root></app-root>
|
||||
+ <app-root id="angular9"></app-root>
|
||||
```
|
||||
|
||||
src/app/app.component.ts :
|
||||
|
||||
```diff
|
||||
- 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 website](https://single-spa.js.org/docs/ecosystem-angular) 和 [angular demo](https://github.com/umijs/qiankun/tree/master/examples/angular9)
|
||||
|
||||
|
|
@ -418,6 +425,7 @@ Modify `angular.json`, `[packageName] > architect > build > builder` is the same
|
|||
- "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.
|
||||
|
|
@ -430,49 +438,49 @@ example:
|
|||
|
||||
1. declare entry script
|
||||
|
||||
```diff
|
||||
<!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>
|
||||
```diff
|
||||
<!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>
|
||||
```
|
||||
+ <script src="//yourhost/entry.js" entry></script>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. export lifecycles in the entry
|
||||
|
||||
```javascript
|
||||
const render = ($) => {
|
||||
$('#purehtml-container').html("Hello, render with jQuery");
|
||||
return Promise.resolve();
|
||||
}
|
||||
```javascript
|
||||
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);
|
||||
```
|
||||
((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](https://github.com/umijs/qiankun/tree/master/examples/purehtml)
|
||||
|
||||
|
|
|
|||
|
|
@ -47,13 +47,13 @@ start();
|
|||
|
||||
## 微应用
|
||||
|
||||
微应用分为有 `webpack` 构建和无 `webpack` 构建项目,有 `webpack` 的微应用(主要是指Vue、React、Angular)需要做的事情有:
|
||||
微应用分为有 `webpack` 构建和无 `webpack` 构建项目,有 `webpack` 的微应用(主要是指 Vue、React、Angular)需要做的事情有:
|
||||
|
||||
1. 新增 `public-path.js` 文件,用于修改运行时的 `publicPath`。[什么是运行时的 publicPath ?](https://webpack.docschina.org/guides/public-path/#on-the-fly)。
|
||||
|
||||
<Alert>
|
||||
注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。
|
||||
</Alert>
|
||||
<Alert>
|
||||
注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。
|
||||
</Alert>
|
||||
|
||||
2. 微应用建议使用 `history` 模式的路由,需要设置路由 `base`,值和它的 `activeRule` 是一样的。
|
||||
3. 在入口文件最顶部引入 `public-path.js`,修改并导出三个生命周期函数。
|
||||
|
|
@ -69,176 +69,181 @@ start();
|
|||
|
||||
1. 在 `src` 目录新增 `public-path.js`:
|
||||
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
|
||||
2. 设置 `history` 模式路由的 `base`:
|
||||
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
|
||||
```
|
||||
```html
|
||||
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
|
||||
```
|
||||
|
||||
3. 入口文件 `index.js` 修改,为了避免根 id `#root` 与其他的 DOM 冲突,需要限制查找范围。
|
||||
|
||||
```js
|
||||
import './public-path';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
```js
|
||||
import './public-path';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
function render(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
|
||||
}
|
||||
function render(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
|
||||
}
|
||||
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render({});
|
||||
}
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render({});
|
||||
}
|
||||
|
||||
export async function bootstrap() {
|
||||
console.log('[react16] react app bootstraped');
|
||||
}
|
||||
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 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'));
|
||||
}
|
||||
```
|
||||
export async function unmount(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
|
||||
}
|
||||
```
|
||||
|
||||
4. 修改 `webpack` 配置
|
||||
|
||||
安装插件 `@rescripts/cli`,当然也可以选择其他的插件,例如 `react-app-rewired`。
|
||||
```dash
|
||||
npm i -D @rescripts/cli
|
||||
```
|
||||
安装插件 `@rescripts/cli`,当然也可以选择其他的插件,例如 `react-app-rewired`。
|
||||
|
||||
根目录新增 `.rescriptsrc.js`:
|
||||
```js
|
||||
const { name } = require('./package');
|
||||
```dash
|
||||
npm i -D @rescripts/cli
|
||||
```
|
||||
|
||||
module.exports = {
|
||||
webpack: config => {
|
||||
config.output.library = `${name}-[name]`;
|
||||
config.output.libraryTarget = 'umd';
|
||||
config.output.jsonpFunction = `webpackJsonp_${name}`;
|
||||
config.output.globalObject = 'window';
|
||||
根目录新增 `.rescriptsrc.js`:
|
||||
|
||||
return config;
|
||||
},
|
||||
```js
|
||||
const { name } = require('./package');
|
||||
|
||||
devServer: _ => {
|
||||
const config = _;
|
||||
module.exports = {
|
||||
webpack: (config) => {
|
||||
config.output.library = `${name}-[name]`;
|
||||
config.output.libraryTarget = 'umd';
|
||||
config.output.jsonpFunction = `webpackJsonp_${name}`;
|
||||
config.output.globalObject = 'window';
|
||||
|
||||
config.headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
};
|
||||
config.historyApiFallback = true;
|
||||
config.hot = false;
|
||||
config.watchContentBase = false;
|
||||
config.liveReload = false;
|
||||
return config;
|
||||
},
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
devServer: (_) => {
|
||||
const config = _;
|
||||
|
||||
config.headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
};
|
||||
config.historyApiFallback = true;
|
||||
config.hot = false;
|
||||
config.watchContentBase = false;
|
||||
config.liveReload = false;
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
修改 `package.json`:
|
||||
|
||||
```diff
|
||||
- "start": "react-scripts start",
|
||||
+ "start": "rescripts start",
|
||||
- "build": "react-scripts build",
|
||||
+ "build": "rescripts build",
|
||||
- "test": "react-scripts test",
|
||||
+ "test": "rescripts test",
|
||||
- "eject": "react-scripts eject"
|
||||
```
|
||||
|
||||
修改 `package.json`:
|
||||
```diff
|
||||
- "start": "react-scripts start",
|
||||
+ "start": "rescripts start",
|
||||
- "build": "react-scripts build",
|
||||
+ "build": "rescripts build",
|
||||
- "test": "react-scripts test",
|
||||
+ "test": "rescripts test",
|
||||
- "eject": "react-scripts eject"
|
||||
```
|
||||
### Vue 微应用
|
||||
|
||||
以 `vue-cli 3+` 生成的 `vue 2.x` 项目为例,`vue 3` 版本等稳定后再补充。
|
||||
|
||||
1. 在 `src` 目录新增 `public-path.js`:
|
||||
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
|
||||
3. 入口文件 `main.js` 修改,为了避免根 id `#app` 与其他的 DOM 冲突,需要限制查找范围。
|
||||
2. 入口文件 `main.js` 修改,为了避免根 id `#app` 与其他的 DOM 冲突,需要限制查找范围。
|
||||
|
||||
```js
|
||||
import './public-path';
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import App from './App.vue';
|
||||
import routes from './router';
|
||||
import store from './store';
|
||||
```js
|
||||
import './public-path';
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import App from './App.vue';
|
||||
import routes from './router';
|
||||
import store from './store';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
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,
|
||||
});
|
||||
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');
|
||||
}
|
||||
instance = new Vue({
|
||||
router,
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
}).$mount(container ? container.querySelector('#app') : '#app');
|
||||
}
|
||||
|
||||
// 独立运行时
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render();
|
||||
}
|
||||
// 独立运行时
|
||||
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;
|
||||
}
|
||||
```
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
3. 打包配置修改(`vue.config.js`):
|
||||
|
||||
```js
|
||||
const { name } = require('./package');
|
||||
module.exports = {
|
||||
devServer: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
},
|
||||
configureWebpack: {
|
||||
output: {
|
||||
library: `${name}-[name]`,
|
||||
libraryTarget: 'umd',// 把微应用打包成 umd 库格式
|
||||
jsonpFunction: `webpackJsonp_${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
```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 微应用
|
||||
|
||||
|
|
@ -246,151 +251,153 @@ start();
|
|||
|
||||
1. 在 `src` 目录新增 `public-path.js` 文件,内容为:
|
||||
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
```js
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
```
|
||||
|
||||
2. 设置 `history` 模式路由的 `base`,`src/app/app-routing.module.ts` 文件:
|
||||
|
||||
```diff
|
||||
+ 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' : '/' }]
|
||||
})
|
||||
```
|
||||
```diff
|
||||
+ 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` 文件。
|
||||
|
||||
```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';
|
||||
```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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
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 bootstrap(props: Object) {
|
||||
console.log(props);
|
||||
}
|
||||
|
||||
export async function mount (props: Object) {
|
||||
render();
|
||||
}
|
||||
export async function mount(props: Object) {
|
||||
render();
|
||||
}
|
||||
|
||||
export async function unmount (props: Object) {
|
||||
console.log(props);
|
||||
// @ts-ignore
|
||||
app.destroy();
|
||||
}
|
||||
```
|
||||
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` 项目可以安装最新版**。
|
||||
先安装 `@angular-builders/custom-webpack` 插件,**注意:`angular 9` 项目只能安装 `9.x` 版本,`angular 10` 项目可以安装最新版**。
|
||||
|
||||
```bash
|
||||
npm i @angular-builders/custom-webpack@9.2.0 -D
|
||||
```
|
||||
```bash
|
||||
npm i @angular-builders/custom-webpack@9.2.0 -D
|
||||
```
|
||||
|
||||
在根目录增加 `custom-webpack.config.js` ,内容为:
|
||||
在根目录增加 `custom-webpack.config.js` ,内容为:
|
||||
|
||||
```js
|
||||
const appName = require('./package.json').name;
|
||||
module.exports = {
|
||||
devServer: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
library: `${appName}-[name]`,
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${appName}`,
|
||||
},
|
||||
};
|
||||
```
|
||||
```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`。
|
||||
修改 `angular.json`,将 `[packageName] > architect > build > builder` 和 `[packageName] > architect > serve > builder` 的值改为我们安装的插件,将我们的打包配置文件加入到 `[packageName] > architect > build > options`。
|
||||
|
||||
```diff
|
||||
- "builder": "@angular-devkit/build-angular:browser",
|
||||
+ "builder": "@angular-builders/custom-webpack:browser",
|
||||
"options": {
|
||||
+ "customWebpackConfig": {
|
||||
+ "path": "./custom-webpack.config.js"
|
||||
+ }
|
||||
}
|
||||
```
|
||||
```diff
|
||||
- "builder": "@angular-devkit/build-angular:browser",
|
||||
+ "builder": "@angular-builders/custom-webpack:browser",
|
||||
"options": {
|
||||
+ "customWebpackConfig": {
|
||||
+ "path": "./custom-webpack.config.js"
|
||||
+ }
|
||||
}
|
||||
```
|
||||
|
||||
```diff
|
||||
- "builder": "@angular-devkit/build-angular:dev-server",
|
||||
+ "builder": "@angular-builders/custom-webpack:dev-server",
|
||||
```
|
||||
```diff
|
||||
- "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` 代码删掉。
|
||||
在**父应用**引入 `zone.js`,需要在 `import qiankun` 之前引入。
|
||||
|
||||
```diff
|
||||
- import 'zone.js/dist/zone';
|
||||
```
|
||||
将微应用的 `src/polyfills.ts` 里面的引入 `zone.js` 代码删掉。
|
||||
|
||||
在微应用的 `src/index.html` 里面的 `<head>` 标签加上下面内容,微应用独立访问时使用。
|
||||
```diff
|
||||
- import 'zone.js/dist/zone';
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- 也可以使用其他的CDN/本地的包 -->
|
||||
<script src="https://unpkg.com/zone.js" ignore></script>
|
||||
```
|
||||
在微应用的 `src/index.html` 里面的 `<head>` 标签加上下面内容,微应用独立访问时使用。
|
||||
|
||||
```html
|
||||
<!-- 也可以使用其他的CDN/本地的包 -->
|
||||
<script src="https://unpkg.com/zone.js" ignore></script>
|
||||
```
|
||||
|
||||
6. 修正 `ng build` 打包报错问题,修改 `tsconfig.json` 文件,参考[issues/431](https://github.com/umijs/qiankun/issues/431)
|
||||
|
||||
```diff
|
||||
- "target": "es2015",
|
||||
+ "target": "es5",
|
||||
+ "typeRoots": [
|
||||
+ "node_modules/@types"
|
||||
+ ],
|
||||
```
|
||||
```diff
|
||||
- "target": "es2015",
|
||||
+ "target": "es5",
|
||||
+ "typeRoots": [
|
||||
+ "node_modules/@types"
|
||||
+ ],
|
||||
```
|
||||
|
||||
7. 为了防止主应用或其他微应用也为 `angular` 时,`<app-root></app-root>` 会冲突的问题,建议给`<app-root>` 加上一个唯一的 id,比如说当前应用名称。
|
||||
|
||||
src/index.html :
|
||||
```diff
|
||||
- <app-root></app-root>
|
||||
+ <app-root id="angular9"></app-root>
|
||||
```
|
||||
src/index.html :
|
||||
|
||||
src/app/app.component.ts :
|
||||
```diff
|
||||
- selector: 'app-root',
|
||||
+ selector: '#angular9 app-root',
|
||||
```
|
||||
```diff
|
||||
- <app-root></app-root>
|
||||
+ <app-root id="angular9"></app-root>
|
||||
```
|
||||
|
||||
src/app/app.component.ts :
|
||||
|
||||
```diff
|
||||
- selector: 'app-root',
|
||||
+ selector: '#angular9 app-root',
|
||||
```
|
||||
|
||||
当然,也可以选择使用 `single-spa-angular` 插件,参考[ single-spa-angular 的官网](https://single-spa.js.org/docs/ecosystem-angular) 和 [angular demo](https://github.com/umijs/qiankun/tree/master/examples/angular9)
|
||||
|
||||
(**补充**)angular7 项目除了第4步以外,其他的步骤和 angular9 一模一样。angular7 修改 `webpack` 打包配置的步骤如下:
|
||||
(**补充**)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 `webpack` 打包配置的步骤如下:
|
||||
|
||||
除了安装 `angular-builders/custom-webpack` 插件的 7.x 版本外,还需要安装 `angular-builders/dev-server`。
|
||||
|
||||
|
|
@ -429,49 +436,49 @@ npm i @angular-builders/dev-server -D
|
|||
|
||||
1. 声明 entry 入口
|
||||
|
||||
```diff
|
||||
<!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>
|
||||
```diff
|
||||
<!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>
|
||||
```
|
||||
+ <script src="//yourhost/entry.js" entry></script>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. 在 entry js 里声明 lifecycles
|
||||
|
||||
```js
|
||||
const render = ($) => {
|
||||
$('#purehtml-container').html("Hello, render with jQuery");
|
||||
return Promise.resolve();
|
||||
}
|
||||
```js
|
||||
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);
|
||||
```
|
||||
((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 部分的[代码](https://github.com/umijs/qiankun/tree/master/examples/purehtml)
|
||||
|
||||
|
|
@ -480,4 +487,3 @@ npm i @angular-builders/dev-server -D
|
|||
### umi-qiankun 项目
|
||||
|
||||
`umi-qiankun` 的教程请移步 [umi 官网](https://umijs.org/zh-CN/plugins/plugin-qiankun) 和 [umi-qiankun 的官方 demo](https://github.com/umijs/umi-plugin-qiankun/tree/master/examples)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
"husky": "^2.3.0",
|
||||
"jest": "^25.2.2",
|
||||
"levenary": "^1.1.1",
|
||||
"lint-staged": "^9.4.2",
|
||||
"lint-staged": "^10.5.4",
|
||||
"np": "^5.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.1.2",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user