Skip to content

Commit

Permalink
📝 add faq: Target container not existed (umijs#1117)
Browse files Browse the repository at this point in the history
  • Loading branch information
gongshun authored Dec 3, 2020
1 parent 31b070c commit 0143f7d
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 5 deletions.
100 changes: 100 additions & 0 deletions docs/faq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ To solve the exception, try the following steps:
<script src="https://www.google.com/analytics.js"></script>
```

5. 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.

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:
Expand Down Expand Up @@ -54,6 +56,104 @@ module.exports = {
};
```

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

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

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

`vue` micro app:
```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 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>
```

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

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

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

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

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

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

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

`vue` + `vue-router` main app:

1. When the main app registers this route, add a `*` to `path`, **Note: If this route has other sub-routes, you need to register another route, just use this component**.
```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',
},
]);
```
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();
}
},
}
```

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

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

If you pass `{ sandbox: true }` to `start()` function, `qiankun` will use `Proxy` to isolate global `window` object for sub applications. When you access `window.Vue` in sub application's code,it will check whether the `Vue` property in the proxyed `window` object. If the property does not exist, it will look it up in the global `window` object and return it.
Expand Down
110 changes: 105 additions & 5 deletions docs/faq/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ qiankun 抛出这个错误是因为无法从微应用的 entry js 中识别出
<script src="https://www.google.com/analytics.js"></script>
```

5. 如果开发环境可以,生产环境不行,检查微应用的 `index.html``entry js` 是否正常返回,比如说返回了 `404.html`

如果在上述步骤完成后仍有问题,通常说明是浏览器兼容性问题导致的。可以尝试 **将有问题的微应用的 webpack `output.library` 配置成跟主应用中注册的 `name` 字段一致**,如:

假如主应用配置是这样的:
Expand Down Expand Up @@ -55,11 +57,109 @@ module.exports = {
};
```

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

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);
}
```

`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(独立运行时使用),但是给 `<script>` 标签加上 `ignore` 属性:

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

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

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

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

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

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

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

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

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

1. 主应用注册这个路由时给 `path` 加一个 `*`**注意:如果这个路由有其他子路由,需要另外注册一个路由,任然使用这个组件即可**
```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',
},
]);
```
3.`Portal.vue` 这个组件的 `mounted` 周期调用 `start` 函数,**注意不要重复调用**
```js
import { start } from 'qiankun';
export default {
mounted() {
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start();
}
},
}
```

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

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

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

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

```javascript
if (inBrowser && window.Vue) {
Expand All @@ -82,7 +182,7 @@ qiankun 主应用根据 `activeRule` 配置激活对应微应用。

### b. 主应用是 history 模式

当主应用是 history 模式且微应用也是 history 模式时,表现完美。如果微应用需要添加 base 路径,设置子项目的 [base](https://router.vuejs.org/zh/api/#base) 属性即可。
当主应用是 history 模式且微应用也是 history 模式时,表现完美。如果微应用需要添加 base 路径,设置微应用的 [base](https://router.vuejs.org/zh/api/#base) 属性即可。

当主应用是 history 模式,微应用是 hash 模式,表现完美。

Expand Down Expand Up @@ -579,7 +679,7 @@ qiankun 会将微应用的动态 script 加载(例如 JSONP)转化为 fetch
在单实例模式下,你可以使用 `excludeAssetFilter` 参数来放行这部分资源请求,但是注意,被该选项放行的资源会逃逸出沙箱,由此带来的副作用需要你自行处理。
若在多实例模式下使用 JSONP,单纯使用 `excludeAssetFilter` 并不能取得好的效果,因为各应用被沙箱所隔离;你可以在主应用提供统一的 JSONP 工具,子应用调用主应用提供的该工具来曲线救国
若在多实例模式下使用 JSONP,单纯使用 `excludeAssetFilter` 并不能取得好的效果,因为各应用被沙箱所隔离;你可以在主应用提供统一的 JSONP 工具,微应用调用主应用提供的该工具来曲线救国
## 微应用路径下刷新后 404?
通常是因为你使用的是 browser 模式的路由,这种路由模式的开启需要服务端配合才行。
Expand All @@ -589,14 +689,14 @@ qiankun 会将微应用的动态 script 加载(例如 JSONP)转化为 fetch
## 主应用如何配置404页面?
首先不应该写通配符 `*` ,可以将 404 页面注册为一个普通路由页面,比如说 `/404`然后在主项目的路由钩子函数里面判断一下,如果既不是主应用路由,也不是微应用,就跳转到 404 页面。
首先不应该写通配符 `*` ,可以将 404 页面注册为一个普通路由页面,比如说 `/404`然后在主应用的路由钩子函数里面判断一下,如果既不是主应用路由,也不是微应用,就跳转到 404 页面。
`vue-router`为例,伪代码如下:
```js
const childrenPath = ['/app1','/app2'];
router.beforeEach((to, from, next) => {
if(to.name) { // 有 name 属性,说明是主项目的路由
if(to.name) { // 有 name 属性,说明是主应用的路由
next()
}
if(childrenPath.some(item => to.path.includes(item))){
Expand Down

0 comments on commit 0143f7d

Please sign in to comment.