@umijs/plugins 4.0.0-beta.13 → 4.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/dva.js +1 -1
- package/dist/layout.js +17 -8
- package/dist/locale.d.ts +1 -0
- package/dist/locale.js +199 -1
- package/dist/qiankun/constants.d.ts +5 -0
- package/dist/qiankun/constants.js +8 -0
- package/dist/qiankun/master.d.ts +6 -0
- package/dist/qiankun/master.js +116 -0
- package/dist/qiankun/slave.d.ts +3 -0
- package/dist/qiankun/slave.js +142 -0
- package/dist/qiankun.js +15 -1
- package/dist/request.js +293 -1
- package/dist/utils/localeUtils.d.ts +33 -0
- package/dist/utils/localeUtils.js +135 -0
- package/dist/utils/modelUtils.d.ts +1 -0
- package/dist/utils/modelUtils.js +17 -3
- package/libs/locale/SelectLang.tpl +478 -0
- package/libs/locale/locale.tpl +82 -0
- package/libs/locale/localeExports.tpl +271 -0
- package/libs/locale/runtime.tpl +33 -0
- package/libs/qiankun/master/AntdErrorBoundary.tsx +34 -0
- package/libs/qiankun/master/AntdLoader.tsx +15 -0
- package/libs/qiankun/master/ErrorBoundary.tsx +7 -0
- package/libs/qiankun/master/MicroApp.tsx +262 -0
- package/libs/qiankun/master/MicroAppWithMemoHistory.tsx +43 -0
- package/libs/qiankun/master/common.ts +133 -0
- package/libs/qiankun/master/constants.ts +7 -0
- package/libs/qiankun/master/getMicroAppRouteComponent.tsx.tpl +45 -0
- package/libs/qiankun/master/masterRuntimePlugin.tsx +133 -0
- package/libs/qiankun/master/types.ts +44 -0
- package/libs/qiankun/slave/connectMaster.tsx +15 -0
- package/libs/qiankun/slave/lifecycles.ts +149 -0
- package/libs/qiankun/slave/qiankunModel.ts +19 -0
- package/libs/qiankun/slave/slaveRuntimePlugin.ts +21 -0
- package/package.json +12 -4
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createIntl,
|
|
3
|
+
IntlShape,
|
|
4
|
+
MessageDescriptor,
|
|
5
|
+
} from '{{{ reactIntlPkgPath }}}';
|
|
6
|
+
import { ApplyPluginsType } from 'umi';
|
|
7
|
+
import { getPluginManager } from '../core/plugin';
|
|
8
|
+
import EventEmitter from '{{{EventEmitterPkg}}}';
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
import warning from '{{{ warningPkgPath }}}';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
createIntl,
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
FormattedDate,
|
|
17
|
+
FormattedDateParts,
|
|
18
|
+
FormattedDisplayName,
|
|
19
|
+
FormattedHTMLMessage,
|
|
20
|
+
FormattedList,
|
|
21
|
+
FormattedMessage,
|
|
22
|
+
FormattedNumber,
|
|
23
|
+
FormattedNumberParts,
|
|
24
|
+
FormattedPlural,
|
|
25
|
+
FormattedRelativeTime,
|
|
26
|
+
FormattedTime,
|
|
27
|
+
FormattedTimeParts,
|
|
28
|
+
IntlContext,
|
|
29
|
+
IntlProvider,
|
|
30
|
+
RawIntlProvider,
|
|
31
|
+
createIntlCache,
|
|
32
|
+
defineMessages,
|
|
33
|
+
injectIntl,
|
|
34
|
+
useIntl,
|
|
35
|
+
} from '{{{ reactIntlPkgPath }}}';
|
|
36
|
+
|
|
37
|
+
let g_intl: IntlShape;
|
|
38
|
+
|
|
39
|
+
const useLocalStorage = {{{UseLocalStorage}}};
|
|
40
|
+
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
export const event = new EventEmitter();
|
|
43
|
+
|
|
44
|
+
export const LANG_CHANGE_EVENT = Symbol('LANG_CHANGE');
|
|
45
|
+
|
|
46
|
+
{{#LocaleList}}
|
|
47
|
+
{{#antdLocale}}
|
|
48
|
+
import {{lang}}{{country}}{{index}} from '{{{locale}}}';
|
|
49
|
+
{{/antdLocale}}
|
|
50
|
+
{{#paths}}
|
|
51
|
+
import lang_{{lang}}{{country}}{{index}} from "{{{path}}}";
|
|
52
|
+
{{/paths}}
|
|
53
|
+
{{/LocaleList}}
|
|
54
|
+
|
|
55
|
+
export const localeInfo: {[key: string]: any} = {
|
|
56
|
+
{{#LocaleList}}
|
|
57
|
+
'{{name}}': {
|
|
58
|
+
messages: {
|
|
59
|
+
{{#paths}}...lang_{{lang}}{{country}}{{index}},{{/paths}}
|
|
60
|
+
},
|
|
61
|
+
locale: '{{locale}}',
|
|
62
|
+
{{#Antd}}antd: {
|
|
63
|
+
{{#antdLocale}}
|
|
64
|
+
...{{lang}}{{country}}{{index}},
|
|
65
|
+
{{/antdLocale}}
|
|
66
|
+
},{{/Antd}}
|
|
67
|
+
momentLocale: '{{momentLocale}}',
|
|
68
|
+
},
|
|
69
|
+
{{/LocaleList}}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 增加一个新的国际化语言
|
|
74
|
+
* @param name 语言的 key
|
|
75
|
+
* @param messages 对应的枚举对象
|
|
76
|
+
* @param extraLocales momentLocale, antd 国际化
|
|
77
|
+
*/
|
|
78
|
+
export const addLocale = (
|
|
79
|
+
name: string,
|
|
80
|
+
messages: Object,
|
|
81
|
+
extraLocales: {
|
|
82
|
+
momentLocale:string;
|
|
83
|
+
antd:string
|
|
84
|
+
},
|
|
85
|
+
) => {
|
|
86
|
+
if (!name) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// 可以合并
|
|
90
|
+
const mergeMessages = localeInfo[name]?.messages
|
|
91
|
+
? Object.assign({}, localeInfo[name].messages, messages)
|
|
92
|
+
: messages;
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
const { momentLocale, antd } = extraLocales || {};
|
|
96
|
+
const locale = name.split('{{BaseSeparator}}')?.join('-')
|
|
97
|
+
localeInfo[name] = {
|
|
98
|
+
messages: mergeMessages,
|
|
99
|
+
locale,
|
|
100
|
+
momentLocale: momentLocale,
|
|
101
|
+
{{#Antd}}antd,{{/Antd}}
|
|
102
|
+
};
|
|
103
|
+
// 如果这是的 name 和当前的locale 相同需要重新设置一下,不然更新不了
|
|
104
|
+
if (locale === getLocale()) {
|
|
105
|
+
event.emit(LANG_CHANGE_EVENT, locale);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 获取当前的 intl 对象,可以在 node 中使用
|
|
111
|
+
* @param locale 需要切换的语言类型
|
|
112
|
+
* @param changeIntl 是否不使用 g_intl
|
|
113
|
+
* @returns IntlShape
|
|
114
|
+
*/
|
|
115
|
+
export const getIntl = (locale?: string, changeIntl?: boolean) => {
|
|
116
|
+
// 如果全局的 g_intl 存在,且不是 setIntl 调用
|
|
117
|
+
if (g_intl && !changeIntl && !locale) {
|
|
118
|
+
return g_intl;
|
|
119
|
+
}
|
|
120
|
+
// 如果存在于 localeInfo 中
|
|
121
|
+
if (locale&&localeInfo[locale]) {
|
|
122
|
+
return createIntl(localeInfo[locale]);
|
|
123
|
+
}
|
|
124
|
+
{{#ExistLocaleDir}}
|
|
125
|
+
// 不存在需要一个报错提醒
|
|
126
|
+
warning(
|
|
127
|
+
!locale||!!localeInfo[locale],
|
|
128
|
+
`The current popular language does not exist, please check the {{{LocaleDir}}} folder!`,
|
|
129
|
+
);
|
|
130
|
+
{{/ExistLocaleDir}}
|
|
131
|
+
// 使用 zh-CN
|
|
132
|
+
if (localeInfo[{{{ DefaultLocale }}}]) return createIntl(localeInfo[{{{ DefaultLocale }}}]);
|
|
133
|
+
|
|
134
|
+
// 如果还没有,返回一个空的
|
|
135
|
+
return createIntl({
|
|
136
|
+
locale: {{{ DefaultLocale }}},
|
|
137
|
+
messages: {},
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 切换全局的 intl 的设置
|
|
143
|
+
* @param locale 语言的key
|
|
144
|
+
*/
|
|
145
|
+
export const setIntl = (locale: string) => {
|
|
146
|
+
g_intl = getIntl(locale, true);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取当前选择的语言
|
|
151
|
+
* @returns string
|
|
152
|
+
*/
|
|
153
|
+
export const getLocale = () => {
|
|
154
|
+
const runtimeLocale = getPluginManager().applyPlugins({
|
|
155
|
+
key: 'locale',
|
|
156
|
+
type: ApplyPluginsType.modify,
|
|
157
|
+
initialValue: {},
|
|
158
|
+
});
|
|
159
|
+
// runtime getLocale for user define
|
|
160
|
+
if (typeof runtimeLocale?.getLocale === 'function') {
|
|
161
|
+
return runtimeLocale.getLocale();
|
|
162
|
+
}
|
|
163
|
+
// please clear localStorage if you change the baseSeparator config
|
|
164
|
+
// because changing will break the app
|
|
165
|
+
const lang =
|
|
166
|
+
typeof localStorage !== 'undefined' && useLocalStorage
|
|
167
|
+
? window.localStorage.getItem('umi_locale')
|
|
168
|
+
: '';
|
|
169
|
+
// support baseNavigator, default true
|
|
170
|
+
let browserLang;
|
|
171
|
+
{{#BaseNavigator}}
|
|
172
|
+
const isNavigatorLanguageValid =
|
|
173
|
+
typeof navigator !== 'undefined' && typeof navigator.language === 'string';
|
|
174
|
+
browserLang = isNavigatorLanguageValid
|
|
175
|
+
? navigator.language.split('-').join('{{BaseSeparator}}')
|
|
176
|
+
: '';
|
|
177
|
+
{{/BaseNavigator}}
|
|
178
|
+
return lang || browserLang || {{{DefaultLocale}}};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 获取当前选择的方向
|
|
184
|
+
* @returns string
|
|
185
|
+
*/
|
|
186
|
+
export const getDirection = () => {
|
|
187
|
+
const lang = getLocale();
|
|
188
|
+
// array with all prefixs for rtl langueges ex: ar-EG , he-IL
|
|
189
|
+
const rtlLangs = ['he', 'ar', 'fa', 'ku']
|
|
190
|
+
const direction = rtlLangs.filter(lng => lang.startsWith(lng)).length ? 'rtl' : 'ltr';
|
|
191
|
+
return direction;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 切换语言
|
|
196
|
+
* @param lang 语言的 key
|
|
197
|
+
* @param realReload 是否刷新页面,默认刷新
|
|
198
|
+
* @returns string
|
|
199
|
+
*/
|
|
200
|
+
export const setLocale = (lang: string, realReload: boolean = true) => {
|
|
201
|
+
//const { pluginManager } = useAppContext();
|
|
202
|
+
//const runtimeLocale = pluginManagerapplyPlugins({
|
|
203
|
+
// key: 'locale',
|
|
204
|
+
// type: ApplyPluginsType.modify,
|
|
205
|
+
// initialValue: {},
|
|
206
|
+
//});
|
|
207
|
+
|
|
208
|
+
const updater = () => {
|
|
209
|
+
if (getLocale() !== lang) {
|
|
210
|
+
if (typeof window.localStorage !== 'undefined' && useLocalStorage) {
|
|
211
|
+
window.localStorage.setItem('umi_locale', lang || '');
|
|
212
|
+
}
|
|
213
|
+
setIntl(lang);
|
|
214
|
+
if (realReload) {
|
|
215
|
+
window.location.reload();
|
|
216
|
+
} else {
|
|
217
|
+
event.emit(LANG_CHANGE_EVENT, lang);
|
|
218
|
+
// chrome 不支持这个事件。所以人肉触发一下
|
|
219
|
+
if (window.dispatchEvent) {
|
|
220
|
+
const event = new Event('languagechange');
|
|
221
|
+
window.dispatchEvent(event);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
//if (typeof runtimeLocale?.setLocale === 'function') {
|
|
228
|
+
// runtimeLocale.setLocale({
|
|
229
|
+
// lang,
|
|
230
|
+
// realReload,
|
|
231
|
+
// updater: updater,
|
|
232
|
+
// });
|
|
233
|
+
// return;
|
|
234
|
+
//}
|
|
235
|
+
|
|
236
|
+
updater();
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
let firstWaring = true;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* intl.formatMessage 的语法糖
|
|
243
|
+
* @deprecated 使用此 api 会造成切换语言的时候无法自动刷新,请使用 useIntl 或 injectIntl
|
|
244
|
+
* @param descriptor { id : string, defaultMessage : string }
|
|
245
|
+
* @param values { [key:string] : string }
|
|
246
|
+
* @returns string
|
|
247
|
+
*/
|
|
248
|
+
export const formatMessage: IntlShape['formatMessage'] = (
|
|
249
|
+
descriptor: MessageDescriptor,
|
|
250
|
+
values: any,
|
|
251
|
+
) => {
|
|
252
|
+
if (firstWaring) {
|
|
253
|
+
warning(
|
|
254
|
+
false,
|
|
255
|
+
`Using this API will cause automatic refresh when switching languages, please use useIntl or injectIntl.
|
|
256
|
+
|
|
257
|
+
使用此 api 会造成切换语言的时候无法自动刷新,请使用 useIntl 或 injectIntl。
|
|
258
|
+
|
|
259
|
+
http://j.mp/37Fkd5Q
|
|
260
|
+
`,
|
|
261
|
+
);
|
|
262
|
+
firstWaring = false;
|
|
263
|
+
}
|
|
264
|
+
return g_intl.formatMessage(descriptor, values);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 获取语言列表
|
|
269
|
+
* @returns string[]
|
|
270
|
+
*/
|
|
271
|
+
export const getAllLocales = () => Object.keys(localeInfo);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { _LocaleContainer } from './locale';
|
|
4
|
+
{{#Title}}
|
|
5
|
+
import { getIntl, getLocale } from './localeExports';
|
|
6
|
+
{{/Title}}
|
|
7
|
+
export function i18nProvider(container: Element) {
|
|
8
|
+
return React.createElement(_LocaleContainer, null, container);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
{{#Title}}
|
|
12
|
+
export function patchRoutes({ routes }) {
|
|
13
|
+
// loop all route for patch title field
|
|
14
|
+
const intl = getIntl(getLocale());
|
|
15
|
+
const traverseRoute = (routes) => {
|
|
16
|
+
Object.keys(routes).forEach((key) => {
|
|
17
|
+
const route = routes[key];
|
|
18
|
+
if (route.title) {
|
|
19
|
+
const newTitle = intl.messages[route.title] ? intl.formatMessage({ id: route.title }, {}) : route.title;
|
|
20
|
+
route.name = intl.messages[route.title] ? intl.formatMessage({ id: route.title }, {}) : route.name;
|
|
21
|
+
route.title = newTitle;
|
|
22
|
+
}
|
|
23
|
+
if (route.routes) {
|
|
24
|
+
traverseRoute(route.routes);
|
|
25
|
+
}
|
|
26
|
+
if (route.routes) {
|
|
27
|
+
traverseRoute(route.routes);
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
traverseRoute(routes);
|
|
32
|
+
}
|
|
33
|
+
{{/Title}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
import { Button, Result } from 'antd';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { getLocale } from 'umi';
|
|
6
|
+
|
|
7
|
+
const defaultLocale = 'en-US';
|
|
8
|
+
|
|
9
|
+
export const ErrorBoundary = ({ error }: { error: any }) => {
|
|
10
|
+
console.error(error);
|
|
11
|
+
|
|
12
|
+
const currentLocale = getLocale ? getLocale() : defaultLocale;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Result
|
|
16
|
+
status="500"
|
|
17
|
+
title={
|
|
18
|
+
currentLocale === defaultLocale
|
|
19
|
+
? 'Whoops, something went wrong'
|
|
20
|
+
: '出错了'
|
|
21
|
+
}
|
|
22
|
+
subTitle={
|
|
23
|
+
currentLocale === defaultLocale
|
|
24
|
+
? 'This page failed to load, please try again later'
|
|
25
|
+
: '页面加载失败,请稍后重试'
|
|
26
|
+
}
|
|
27
|
+
extra={
|
|
28
|
+
<Button type="primary" onClick={() => window.location.reload()}>
|
|
29
|
+
{currentLocale === defaultLocale ? 'Reload' : '再试一次'}
|
|
30
|
+
</Button>
|
|
31
|
+
}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
import { Spin } from 'antd';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
export default function AntdLoader(props: { loading: boolean }) {
|
|
7
|
+
const { loading } = props;
|
|
8
|
+
const style = {
|
|
9
|
+
position: 'absolute',
|
|
10
|
+
top: '50%',
|
|
11
|
+
left: '50%',
|
|
12
|
+
transform: 'translate(-50%, -50%)',
|
|
13
|
+
};
|
|
14
|
+
return <Spin spinning={loading} size="large" style={style} />;
|
|
15
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
__USE_MODEL__;
|
|
4
|
+
import concat from 'lodash/concat';
|
|
5
|
+
import mergeWith from 'lodash/mergeWith';
|
|
6
|
+
import noop from 'lodash/noop';
|
|
7
|
+
import {
|
|
8
|
+
FrameworkConfiguration,
|
|
9
|
+
loadMicroApp,
|
|
10
|
+
MicroApp as MicroAppType,
|
|
11
|
+
prefetchApps,
|
|
12
|
+
} from 'qiankun';
|
|
13
|
+
import React, {
|
|
14
|
+
forwardRef,
|
|
15
|
+
Ref,
|
|
16
|
+
useEffect,
|
|
17
|
+
useImperativeHandle,
|
|
18
|
+
useRef,
|
|
19
|
+
useState,
|
|
20
|
+
} from 'react';
|
|
21
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
22
|
+
import { getMasterOptions } from './masterOptions';
|
|
23
|
+
import MicroAppLoader from './MicroAppLoader';
|
|
24
|
+
import { MasterOptions } from './types';
|
|
25
|
+
|
|
26
|
+
const qiankunStateForSlaveModelNamespace = '@@qiankunStateForSlave';
|
|
27
|
+
|
|
28
|
+
type HashHistory = {
|
|
29
|
+
type?: 'hash';
|
|
30
|
+
} & any;
|
|
31
|
+
|
|
32
|
+
type BrowserHistory = {
|
|
33
|
+
type?: 'browser';
|
|
34
|
+
} & any;
|
|
35
|
+
|
|
36
|
+
type MemoryHistory = {
|
|
37
|
+
type?: 'memory';
|
|
38
|
+
} & any;
|
|
39
|
+
|
|
40
|
+
export type Props = {
|
|
41
|
+
name: string;
|
|
42
|
+
settings?: FrameworkConfiguration;
|
|
43
|
+
base?: string;
|
|
44
|
+
history?:
|
|
45
|
+
| 'hash'
|
|
46
|
+
| 'browser'
|
|
47
|
+
| 'memory'
|
|
48
|
+
| HashHistory
|
|
49
|
+
| BrowserHistory
|
|
50
|
+
| MemoryHistory;
|
|
51
|
+
getMatchedBase?: () => string;
|
|
52
|
+
loader?: (loading: boolean) => React.ReactNode;
|
|
53
|
+
errorBoundary?: (error: any) => React.ReactNode;
|
|
54
|
+
onHistoryInit?: (history: any) => void;
|
|
55
|
+
autoSetLoading?: boolean;
|
|
56
|
+
autoCaptureError?: boolean;
|
|
57
|
+
// 仅开启 loader 时需要
|
|
58
|
+
wrapperClassName?: string;
|
|
59
|
+
className?: string;
|
|
60
|
+
} & Record<string, any>;
|
|
61
|
+
|
|
62
|
+
function unmountMicroApp(microApp?: MicroAppType) {
|
|
63
|
+
if (microApp) {
|
|
64
|
+
microApp.mountPromise.then(() => microApp.unmount());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let noneMounted = true;
|
|
69
|
+
|
|
70
|
+
export const MicroApp = forwardRef(
|
|
71
|
+
(componentProps: Props, componentRef: Ref<MicroAppType>) => {
|
|
72
|
+
const {
|
|
73
|
+
masterHistoryType,
|
|
74
|
+
apps = [],
|
|
75
|
+
lifeCycles: globalLifeCycles,
|
|
76
|
+
prefetch = true,
|
|
77
|
+
...globalSettings
|
|
78
|
+
} = getMasterOptions() as MasterOptions;
|
|
79
|
+
|
|
80
|
+
const {
|
|
81
|
+
name,
|
|
82
|
+
settings: settingsFromProps = {},
|
|
83
|
+
loader,
|
|
84
|
+
errorBoundary,
|
|
85
|
+
lifeCycles,
|
|
86
|
+
wrapperClassName,
|
|
87
|
+
className,
|
|
88
|
+
...propsFromParams
|
|
89
|
+
} = componentProps;
|
|
90
|
+
|
|
91
|
+
const [loading, setLoading] = useState(true);
|
|
92
|
+
const [error, setError] = useState<any>(null);
|
|
93
|
+
// 未配置自定义 errorBoundary 且开启了 autoCaptureError 场景下,使用插件默认的 errorBoundary,否则使用自定义 errorBoundary
|
|
94
|
+
const microAppErrorBoundary =
|
|
95
|
+
errorBoundary ||
|
|
96
|
+
(propsFromParams.autoCaptureError
|
|
97
|
+
? (e) => <ErrorBoundary error={e} />
|
|
98
|
+
: null);
|
|
99
|
+
|
|
100
|
+
// 配置了 errorBoundary 才改 error 状态,否则直接往上抛异常
|
|
101
|
+
const setComponentError = (error: any) => {
|
|
102
|
+
if (microAppErrorBoundary) {
|
|
103
|
+
setError(error);
|
|
104
|
+
// error log 出来,不要吞
|
|
105
|
+
if (error) {
|
|
106
|
+
console.error(error);
|
|
107
|
+
}
|
|
108
|
+
} else if (error) {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const containerRef = useRef<HTMLDivElement>();
|
|
114
|
+
const microAppRef = useRef<MicroAppType>();
|
|
115
|
+
const updatingPromise = useRef<Promise<any>>();
|
|
116
|
+
const updatingTimestamp = useRef(Date.now());
|
|
117
|
+
|
|
118
|
+
useImperativeHandle(componentRef, () => microAppRef.current);
|
|
119
|
+
|
|
120
|
+
const appConfig = apps.find((app: any) => app.name === name);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!appConfig) {
|
|
123
|
+
setComponentError(
|
|
124
|
+
new Error(
|
|
125
|
+
`[@umijs/plugin-qiankun]: Can not find the configuration of ${name} app!`,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return noop;
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
// 约定使用 src/app.ts/useQiankunStateForSlave 中的数据作为主应用透传给微应用的 props,优先级高于 propsFromConfig
|
|
133
|
+
const stateForSlave = (useModel || noop)(
|
|
134
|
+
qiankunStateForSlaveModelNamespace,
|
|
135
|
+
);
|
|
136
|
+
const { entry, props: propsFromConfig = {} } = appConfig || {};
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
setComponentError(null);
|
|
140
|
+
setLoading(true);
|
|
141
|
+
const configuration = {
|
|
142
|
+
globalContext: window,
|
|
143
|
+
...globalSettings,
|
|
144
|
+
...settingsFromProps,
|
|
145
|
+
};
|
|
146
|
+
microAppRef.current = loadMicroApp(
|
|
147
|
+
{
|
|
148
|
+
name,
|
|
149
|
+
entry,
|
|
150
|
+
container: containerRef.current!,
|
|
151
|
+
props: {
|
|
152
|
+
...propsFromConfig,
|
|
153
|
+
...stateForSlave,
|
|
154
|
+
...propsFromParams,
|
|
155
|
+
setLoading,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
configuration,
|
|
159
|
+
mergeWith({}, globalLifeCycles, lifeCycles, (v1, v2) =>
|
|
160
|
+
concat(v1 ?? [], v2 ?? []),
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// 当配置了 prefetch true 时,在第一个应用 mount 完成之后,再去预加载其他应用
|
|
165
|
+
if (prefetch && prefetch !== 'all' && noneMounted) {
|
|
166
|
+
microAppRef.current?.mountPromise.then(() => {
|
|
167
|
+
if (noneMounted) {
|
|
168
|
+
if (Array.isArray(prefetch)) {
|
|
169
|
+
const specialPrefetchApps = apps.filter(
|
|
170
|
+
(app) => app.name !== name && prefetch.indexOf(app.name) !== -1,
|
|
171
|
+
);
|
|
172
|
+
prefetchApps(specialPrefetchApps, configuration);
|
|
173
|
+
} else {
|
|
174
|
+
const otherNotMountedApps = apps.filter(
|
|
175
|
+
(app) => app.name !== name,
|
|
176
|
+
);
|
|
177
|
+
prefetchApps(otherNotMountedApps, configuration);
|
|
178
|
+
}
|
|
179
|
+
noneMounted = false;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
(['loadPromise', 'bootstrapPromise', 'mountPromise'] as const).forEach(
|
|
185
|
+
(key) => {
|
|
186
|
+
const promise = microAppRef.current?.[key];
|
|
187
|
+
promise.catch((e) => {
|
|
188
|
+
setComponentError(e);
|
|
189
|
+
setLoading(false);
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
return () => unmountMicroApp(microAppRef.current);
|
|
195
|
+
}, [name]);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
const microApp = microAppRef.current;
|
|
199
|
+
if (microApp) {
|
|
200
|
+
if (!updatingPromise.current) {
|
|
201
|
+
// 初始化 updatingPromise 为 microApp.mountPromise,从而确保后续更新是在应用 mount 完成之后
|
|
202
|
+
updatingPromise.current = microApp.mountPromise;
|
|
203
|
+
} else {
|
|
204
|
+
// 确保 microApp.update 调用是跟组件状态变更顺序一致的,且后一个微应用更新必须等待前一个更新完成
|
|
205
|
+
updatingPromise.current = updatingPromise.current.then(() => {
|
|
206
|
+
const canUpdate = (microApp?: MicroAppType) =>
|
|
207
|
+
microApp?.update && microApp.getStatus() === 'MOUNTED';
|
|
208
|
+
if (canUpdate(microApp)) {
|
|
209
|
+
const props = {
|
|
210
|
+
...propsFromConfig,
|
|
211
|
+
...stateForSlave,
|
|
212
|
+
...propsFromParams,
|
|
213
|
+
setLoading,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (process.env.NODE_ENV === 'development') {
|
|
217
|
+
if (Date.now() - updatingTimestamp.current < 200) {
|
|
218
|
+
console.warn(
|
|
219
|
+
`[@umijs/plugin-qiankun] It seems like microApp ${name} is updating too many times in a short time(200ms), you may need to do some optimization to avoid the unnecessary re-rendering.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.info(
|
|
224
|
+
`[@umijs/plugin-qiankun] MicroApp ${name} is updating with props: `,
|
|
225
|
+
props,
|
|
226
|
+
);
|
|
227
|
+
updatingTimestamp.current = Date.now();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 返回 microApp.update 形成链式调用
|
|
231
|
+
// @ts-ignore
|
|
232
|
+
return microApp.update(props);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return void 0;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return noop;
|
|
241
|
+
}, Object.values({ ...stateForSlave, ...propsFromParams }));
|
|
242
|
+
|
|
243
|
+
// 未配置自定义 loader 且开启了 autoSetLoading 场景下,使用插件默认的 loader,否则使用自定义 loader
|
|
244
|
+
const microAppLoader =
|
|
245
|
+
loader ||
|
|
246
|
+
(propsFromParams.autoSetLoading
|
|
247
|
+
? (loading) => <MicroAppLoader loading={loading} />
|
|
248
|
+
: null);
|
|
249
|
+
|
|
250
|
+
return Boolean(microAppLoader) || Boolean(microAppErrorBoundary) ? (
|
|
251
|
+
<div style={{ position: 'relative' }} className={wrapperClassName}>
|
|
252
|
+
{Boolean(microAppLoader) && microAppLoader(loading)}
|
|
253
|
+
{Boolean(microAppErrorBoundary) &&
|
|
254
|
+
Boolean(error) &&
|
|
255
|
+
microAppErrorBoundary(error)}
|
|
256
|
+
<div ref={containerRef} className={className} />
|
|
257
|
+
</div>
|
|
258
|
+
) : (
|
|
259
|
+
<div ref={containerRef} className={className} />
|
|
260
|
+
);
|
|
261
|
+
},
|
|
262
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
5
|
+
import { MicroApp, Props as MicroAppProps } from './MicroApp';
|
|
6
|
+
|
|
7
|
+
export interface Props extends MicroAppProps {
|
|
8
|
+
history?: never;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function MicroAppWithMemoHistory(componentProps: Props) {
|
|
12
|
+
const { url, ...rest } = componentProps;
|
|
13
|
+
const history = useRef();
|
|
14
|
+
// url 的变更不会透传给下游,组件内自己会处理掉,所以这里直接用 ref 来存
|
|
15
|
+
const historyOpts = useRef({
|
|
16
|
+
type: 'memory',
|
|
17
|
+
initialEntries: [url],
|
|
18
|
+
initialIndex: 1,
|
|
19
|
+
});
|
|
20
|
+
const historyInitHandler = useCallback((h) => (history.current = h), []);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
// push history for slave app when url property changed
|
|
24
|
+
// the initial url will be ignored because the history has not been initialized
|
|
25
|
+
if (history.current && url) {
|
|
26
|
+
history.current.push(url);
|
|
27
|
+
}
|
|
28
|
+
}, [url]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
// reset the history when name changed
|
|
32
|
+
historyOpts.current.initialEntries = [url];
|
|
33
|
+
historyOpts.current.initialIndex = 1;
|
|
34
|
+
}, [componentProps.name]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<MicroApp
|
|
38
|
+
{...rest}
|
|
39
|
+
history={historyOpts.current}
|
|
40
|
+
onHistoryInit={historyInitHandler}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|