@micro-zoe/micro-app 1.0.0-rc.4 → 1.0.0-rc.5

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/lib/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = '1.0.0-rc.4';
1
+ const version = '1.0.0-rc.5';
2
2
  // do not use isUndefined
3
3
  const isBrowser = typeof window !== 'undefined';
4
4
  // do not use isUndefined
@@ -116,6 +116,14 @@ function isMicroAppBody(target) {
116
116
  function isProxyDocument(target) {
117
117
  return toTypeString(target) === '[object ProxyDocument]';
118
118
  }
119
+ function isTargetExtension(path, suffix) {
120
+ try {
121
+ return createURL(path).pathname.split('.').pop() === suffix;
122
+ }
123
+ catch (_a) {
124
+ return false;
125
+ }
126
+ }
119
127
  function includes(target, searchElement, fromIndex) {
120
128
  if (target == null) {
121
129
  throw new TypeError('includes target is null or undefined');
@@ -200,12 +208,12 @@ function formatAppURL(url, appName = null) {
200
208
  return '';
201
209
  try {
202
210
  const { origin, pathname, search } = createURL(addProtocol(url), (window.rawWindow || window).location.href);
203
- // If it ends with .html/.node/.php/.net/.etc, don’t need to add /
204
- if (/\.(\w+)$/.test(pathname)) {
205
- return `${origin}${pathname}${search}`;
206
- }
207
- const fullPath = `${origin}${pathname}/`.replace(/\/\/$/, '/');
208
- return /^https?:\/\//.test(fullPath) ? `${fullPath}${search}` : '';
211
+ /**
212
+ * keep the original url unchanged, such as .html .node .php .net .etc, search, except hash
213
+ * BUG FIX: Never using '/' to complete url, refer to https://github.com/micro-zoe/micro-app/issues/1147
214
+ */
215
+ const fullPath = `${origin}${pathname}${search}`;
216
+ return /^https?:\/\//.test(fullPath) ? fullPath : '';
209
217
  }
210
218
  catch (e) {
211
219
  logError(e, appName);
@@ -228,7 +236,9 @@ function formatAppName(name) {
228
236
  return name.replace(/(^\d+)|([^\w\d-_])/gi, '');
229
237
  }
230
238
  /**
231
- * Get valid address, such as https://xxx/xx/xx.html to https://xxx/xx/
239
+ * Get valid address, such as
240
+ * 1. https://domain/xx/xx.html to https://domain/xx/
241
+ * 2. https://domain/xx to https://domain/xx/
232
242
  * @param url app.url
233
243
  */
234
244
  function getEffectivePath(url) {
@@ -561,6 +571,17 @@ function getBaseHTMLElement() {
561
571
  var _a;
562
572
  return (((_a = window.rawWindow) === null || _a === void 0 ? void 0 : _a.HTMLElement) || window.HTMLElement);
563
573
  }
574
+ /**
575
+ * Format event name
576
+ * In with sandbox, child event and lifeCycles bind to microAppElement, there are two events with same name - mounted unmount, it should be handled specifically to prevent conflicts
577
+ * Issue: https://github.com/micro-zoe/micro-app/issues/1161
578
+ * @param type event name
579
+ * @param appName app name
580
+ */
581
+ const formatEventList = ['mounted', 'unmount'];
582
+ function formatEventType(type, appName) {
583
+ return formatEventList.includes(type) ? `${type}-${appName}` : type;
584
+ }
564
585
 
565
586
  function formatEventInfo(event, element) {
566
587
  Object.defineProperties(event, {
@@ -604,19 +625,19 @@ function dispatchLifecyclesEvent(element, appName, lifecycleName, error) {
604
625
  formatEventInfo(event, element);
605
626
  // global hooks
606
627
  if (isFunction((_a = microApp.options.lifeCycles) === null || _a === void 0 ? void 0 : _a[lifecycleName])) {
607
- microApp.options.lifeCycles[lifecycleName](event);
628
+ microApp.options.lifeCycles[lifecycleName](event, appName);
608
629
  }
609
630
  element.dispatchEvent(event);
610
631
  }
611
632
  /**
612
633
  * Dispatch custom event to micro app
613
634
  * @param app app
614
- * @param eventName event name ['unmount', 'appstate-change']
635
+ * @param eventName event name ['mounted', 'unmount', 'appstate-change', 'statechange']
615
636
  * @param detail event detail
616
637
  */
617
638
  function dispatchCustomEventToMicroApp(app, eventName, detail = {}) {
618
639
  var _a;
619
- const event = new CustomEvent(eventName, {
640
+ const event = new CustomEvent(formatEventType(eventName, app.name), {
620
641
  detail,
621
642
  });
622
643
  (_a = app.sandBox) === null || _a === void 0 ? void 0 : _a.microAppWindow.dispatchEvent(event);
@@ -660,7 +681,8 @@ class HTMLLoader {
660
681
  run(app, successCb) {
661
682
  const appName = app.name;
662
683
  const htmlUrl = app.ssrUrl || app.url;
663
- const htmlPromise = htmlUrl.includes('.js')
684
+ const isJsResource = isTargetExtension(htmlUrl, 'js');
685
+ const htmlPromise = isJsResource
664
686
  ? Promise.resolve(`<micro-app-head><script src='${htmlUrl}'></script></micro-app-head><micro-app-body></micro-app-body>`)
665
687
  : fetchSource(htmlUrl, appName, { cache: 'no-cache' });
666
688
  htmlPromise.then((htmlStr) => {
@@ -824,7 +846,7 @@ class CSSParser {
824
846
  if (!this.scopecssDisableNextLine &&
825
847
  (!this.scopecssDisable || this.scopecssDisableSelectors.length)) {
826
848
  cssValue = cssValue.replace(/url\(["']?([^)"']+)["']?\)/gm, (all, $1) => {
827
- if (/^((data|blob):|#)/.test($1) || /^(https?:)?\/\//.test($1)) {
849
+ if (/^((data|blob):|#|%23)/.test($1) || /^(https?:)?\/\//.test($1)) {
828
850
  return all;
829
851
  }
830
852
  // ./a/b.png ../a/b.png a/b.png
@@ -1250,7 +1272,7 @@ function extractLinkFromHtml(link, parent, app, isDynamic = false) {
1250
1272
  return { address: href, linkInfo };
1251
1273
  }
1252
1274
  }
1253
- else if (rel && ['prefetch', 'preload', 'prerender'].includes(rel)) {
1275
+ else if (rel && ['prefetch', 'preload', 'prerender', 'modulepreload'].includes(rel)) {
1254
1276
  // preload prefetch prerender ....
1255
1277
  if (isDynamic) {
1256
1278
  replaceComment = document.createComment(`link element with rel=${rel}${href ? ' & href=' + href : ''} removed by micro-app`);
@@ -1470,17 +1492,32 @@ var MicroAppConfig;
1470
1492
  MicroAppConfig["SSR"] = "ssr";
1471
1493
  MicroAppConfig["FIBER"] = "fiber";
1472
1494
  })(MicroAppConfig || (MicroAppConfig = {}));
1495
+ /**
1496
+ * global key must be static key, they can not rewrite
1497
+ * e.g.
1498
+ * window.Promise = newValue
1499
+ * new Promise ==> still get old value, not newValue, because they are cached by top function
1500
+ * NOTE:
1501
+ * 1. Do not add fetch, XMLHttpRequest, EventSource
1502
+ */
1503
+ const GLOBAL_CACHED_KEY = 'window,self,globalThis,document,Document,Array,Object,String,Boolean,Math,Number,Symbol,Date,Function,Proxy,WeakMap,WeakSet,Set,Map,Reflect,Element,Node,RegExp,Error,TypeError,JSON,isNaN,parseFloat,parseInt,performance,console,decodeURI,encodeURI,decodeURIComponent,encodeURIComponent,navigator,undefined,location,history';
1473
1504
  // prefetch level
1474
1505
  const PREFETCH_LEVEL = [1, 2, 3];
1475
- // memory router constants
1476
- // default mode, child router info will sync to browser url
1477
- const DEFAULT_ROUTER_MODE = 'search';
1478
1506
  /**
1479
- * render base on browser url, and location.origin location.href point to base app
1480
- * equal to disable-memory-router
1507
+ * memory router modes
1481
1508
  * NOTE:
1482
1509
  * 1. The only difference between native and native-scope is location.origin, in native-scope mode location.origin point to child app
1510
+ * 2. native mode equal to disable-memory-router
1483
1511
  */
1512
+ // 临时注释,1.0版本放开,默认模式切换为state
1513
+ // // default mode, sync child app router info to history.state
1514
+ // export const DEFAULT_ROUTER_MODE = 'state'
1515
+ // // sync child app router info to browser url as search
1516
+ // export const ROUTER_MODE_SEARCH = 'search'
1517
+ // 临时放开,1.0版本去除
1518
+ const ROUTER_MODE_STATE = 'state';
1519
+ const DEFAULT_ROUTER_MODE = 'search';
1520
+ // render base on browser url, and location.origin location.href point to base app
1484
1521
  const ROUTER_MODE_NATIVE = 'native';
1485
1522
  // render base on browser url, but location.origin location.href point to child app
1486
1523
  const ROUTER_MODE_NATIVE_SCOPE = 'native-scope';
@@ -1488,12 +1525,13 @@ const ROUTER_MODE_NATIVE_SCOPE = 'native-scope';
1488
1525
  const ROUTER_MODE_PURE = 'pure';
1489
1526
  const ROUTER_MODE_LIST = [
1490
1527
  DEFAULT_ROUTER_MODE,
1528
+ ROUTER_MODE_STATE,
1491
1529
  ROUTER_MODE_NATIVE,
1492
1530
  ROUTER_MODE_NATIVE_SCOPE,
1493
1531
  ROUTER_MODE_PURE,
1494
1532
  ];
1495
1533
  // event bound to child app window
1496
- const SCOPE_WINDOW_EVENT = [
1534
+ const BASE_SCOPE_WINDOW_EVENT = [
1497
1535
  'popstate',
1498
1536
  'hashchange',
1499
1537
  'load',
@@ -1504,9 +1542,15 @@ const SCOPE_WINDOW_EVENT = [
1504
1542
  'statechange',
1505
1543
  'mounted',
1506
1544
  ];
1545
+ // bind event of with sandbox
1546
+ const SCOPE_WINDOW_EVENT_OF_WITH = BASE_SCOPE_WINDOW_EVENT;
1547
+ // bind event of iframe sandbox
1548
+ const SCOPE_WINDOW_EVENT_OF_IFRAME = BASE_SCOPE_WINDOW_EVENT.concat([
1549
+ 'unhandledrejection',
1550
+ ]);
1507
1551
  // on event bound to child app window
1508
1552
  // TODO: with和iframe处理方式不同,需修改
1509
- const SCOPE_WINDOW_ON_EVENT = [
1553
+ const BASE_SCOPE_WINDOW_ON_EVENT = [
1510
1554
  'onpopstate',
1511
1555
  'onhashchange',
1512
1556
  'onload',
@@ -1514,6 +1558,12 @@ const SCOPE_WINDOW_ON_EVENT = [
1514
1558
  'onunload',
1515
1559
  'onerror'
1516
1560
  ];
1561
+ // bind on event of with sandbox
1562
+ const SCOPE_WINDOW_ON_EVENT_OF_WITH = BASE_SCOPE_WINDOW_ON_EVENT;
1563
+ // bind on event of iframe sandbox
1564
+ const SCOPE_WINDOW_ON_EVENT_OF_IFRAME = BASE_SCOPE_WINDOW_ON_EVENT.concat([
1565
+ 'onunhandledrejection',
1566
+ ]);
1517
1567
  // event bound to child app document
1518
1568
  const SCOPE_DOCUMENT_EVENT = [
1519
1569
  'DOMContentLoaded',
@@ -1530,15 +1580,13 @@ const GLOBAL_KEY_TO_WINDOW = [
1530
1580
  'globalThis',
1531
1581
  ];
1532
1582
  const RAW_GLOBAL_TARGET = ['rawWindow', 'rawDocument'];
1533
- /**
1534
- * global key must be static key, they can not rewrite
1535
- * e.g.
1536
- * window.Promise = newValue
1537
- * new Promise ==> still get old value, not newValue, because they are cached by top function
1538
- * NOTE:
1539
- * 1. Do not add fetch, XMLHttpRequest, EventSource
1540
- */
1541
- const GLOBAL_CACHED_KEY = 'window,self,globalThis,document,Document,Array,Object,String,Boolean,Math,Number,Symbol,Date,Function,Proxy,WeakMap,WeakSet,Set,Map,Reflect,Element,Node,RegExp,Error,TypeError,JSON,isNaN,parseFloat,parseInt,performance,console,decodeURI,encodeURI,decodeURIComponent,encodeURIComponent,navigator,undefined,location,history';
1583
+ const HIJACK_LOCATION_KEYS = [
1584
+ 'host',
1585
+ 'hostname',
1586
+ 'port',
1587
+ 'protocol',
1588
+ 'origin',
1589
+ ];
1542
1590
 
1543
1591
  const scriptTypes = ['text/javascript', 'text/ecmascript', 'application/javascript', 'application/ecmascript', 'module', 'systemjs-module', 'systemjs-importmap'];
1544
1592
  // whether use type='module' script
@@ -2989,7 +3037,7 @@ function patchWindowProperty(microAppWindow) {
2989
3037
  const rawWindow = globalEnv.rawWindow;
2990
3038
  Object.getOwnPropertyNames(rawWindow)
2991
3039
  .filter((key) => {
2992
- return /^on/.test(key) && !SCOPE_WINDOW_ON_EVENT.includes(key);
3040
+ return /^on/.test(key) && !SCOPE_WINDOW_ON_EVENT_OF_WITH.includes(key);
2993
3041
  })
2994
3042
  .forEach((eventName) => {
2995
3043
  const { enumerable, writable, set } = Object.getOwnPropertyDescriptor(rawWindow, eventName) || {
@@ -3138,7 +3186,7 @@ function patchWindowEffect(microAppWindow, appName) {
3138
3186
  */
3139
3187
  function getEventTarget(type) {
3140
3188
  var _a;
3141
- if (SCOPE_WINDOW_EVENT.includes(type) && ((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.container)) {
3189
+ if (SCOPE_WINDOW_EVENT_OF_WITH.includes(type) && ((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.container)) {
3142
3190
  return getRootContainer(appInstanceMap.get(appName).container);
3143
3191
  }
3144
3192
  return rawWindow;
@@ -3151,6 +3199,7 @@ function patchWindowEffect(microAppWindow, appName) {
3151
3199
  * window.addEventListener.call(非window, type, listener, options)
3152
3200
  */
3153
3201
  microAppWindow.addEventListener = function (type, listener, options) {
3202
+ type = formatEventType(type, appName);
3154
3203
  const listenerList = eventListenerMap.get(type);
3155
3204
  if (listenerList) {
3156
3205
  listenerList.add(listener);
@@ -3162,6 +3211,7 @@ function patchWindowEffect(microAppWindow, appName) {
3162
3211
  rawAddEventListener.call(getEventTarget(type), type, listener, options);
3163
3212
  };
3164
3213
  microAppWindow.removeEventListener = function (type, listener, options) {
3214
+ type = formatEventType(type, appName);
3165
3215
  const listenerList = eventListenerMap.get(type);
3166
3216
  if ((listenerList === null || listenerList === void 0 ? void 0 : listenerList.size) && listenerList.has(listener)) {
3167
3217
  listenerList.delete(listener);
@@ -3254,42 +3304,39 @@ function patchWindowEffect(microAppWindow, appName) {
3254
3304
  }
3255
3305
 
3256
3306
  // set micro app state to origin state
3257
- function setMicroState(appName, microState) {
3258
- if (isRouterModeSearch(appName)) {
3259
- const rawState = globalEnv.rawWindow.history.state;
3260
- const additionalState = {
3261
- microAppState: assign({}, rawState === null || rawState === void 0 ? void 0 : rawState.microAppState, {
3262
- [appName]: microState
3263
- })
3264
- };
3265
- // create new state object
3266
- return assign({}, rawState, additionalState);
3267
- }
3268
- return microState;
3307
+ function setMicroState(appName, microState, targetLocation) {
3308
+ // TODO: 验证native模式下修改state nextjs路由是否正常
3309
+ const rawState = globalEnv.rawWindow.history.state;
3310
+ const additionalState = {
3311
+ __MICRO_APP_STATE__: assign({}, rawState === null || rawState === void 0 ? void 0 : rawState.__MICRO_APP_STATE__, {
3312
+ [appName]: {
3313
+ fullPath: targetLocation.pathname + targetLocation.search + targetLocation.hash,
3314
+ state: microState,
3315
+ mode: getRouterMode(appName),
3316
+ }
3317
+ })
3318
+ };
3319
+ // create new state object
3320
+ return assign({}, rawState, additionalState);
3269
3321
  }
3270
3322
  // delete micro app state form origin state
3271
3323
  function removeMicroState(appName, rawState) {
3272
- if (isRouterModeSearch(appName)) {
3273
- if (isPlainObject(rawState === null || rawState === void 0 ? void 0 : rawState.microAppState)) {
3274
- if (!isUndefined(rawState.microAppState[appName])) {
3275
- delete rawState.microAppState[appName];
3276
- }
3277
- if (!Object.keys(rawState.microAppState).length) {
3278
- delete rawState.microAppState;
3279
- }
3324
+ if (isPlainObject(rawState === null || rawState === void 0 ? void 0 : rawState.__MICRO_APP_STATE__)) {
3325
+ if (!isUndefined(rawState.__MICRO_APP_STATE__[appName])) {
3326
+ delete rawState.__MICRO_APP_STATE__[appName];
3327
+ }
3328
+ if (!Object.keys(rawState.__MICRO_APP_STATE__).length) {
3329
+ delete rawState.__MICRO_APP_STATE__;
3280
3330
  }
3281
- return assign({}, rawState);
3282
3331
  }
3283
- return rawState;
3332
+ return assign({}, rawState);
3284
3333
  }
3285
3334
  // get micro app state form origin state
3286
3335
  function getMicroState(appName) {
3287
- var _a;
3336
+ var _a, _b;
3288
3337
  const rawState = globalEnv.rawWindow.history.state;
3289
- if (isRouterModeSearch(appName)) {
3290
- return ((_a = rawState === null || rawState === void 0 ? void 0 : rawState.microAppState) === null || _a === void 0 ? void 0 : _a[appName]) || null;
3291
- }
3292
- return rawState;
3338
+ // rawState?.__MICRO_APP_STATE__?.[appName]?.state || (isRouterModeCustom(appName) ? rawState : null)
3339
+ return ((_b = (_a = rawState === null || rawState === void 0 ? void 0 : rawState.__MICRO_APP_STATE__) === null || _a === void 0 ? void 0 : _a[appName]) === null || _b === void 0 ? void 0 : _b.state) || (isRouterModeCustom(appName) ? rawState : null);
3293
3340
  }
3294
3341
  const ENC_AD_RE = /&/g; // %M1
3295
3342
  const ENC_EQ_RE = /=/g; // %M2
@@ -3325,17 +3372,30 @@ function formatQueryAppName(appName) {
3325
3372
  * @param appName app.name
3326
3373
  */
3327
3374
  function getMicroPathFromURL(appName) {
3328
- var _a, _b;
3329
- // TODO: pure模式从state中获取地址
3330
- if (isRouterModePure(appName))
3331
- return null;
3375
+ var _a, _b, _c, _d;
3332
3376
  const rawLocation = globalEnv.rawWindow.location;
3377
+ const rawState = globalEnv.rawWindow.history.state;
3333
3378
  if (isRouterModeSearch(appName)) {
3334
3379
  const queryObject = getQueryObjectFromURL(rawLocation.search, rawLocation.hash);
3335
3380
  const microPath = ((_a = queryObject.hashQuery) === null || _a === void 0 ? void 0 : _a[formatQueryAppName(appName)]) || ((_b = queryObject.searchQuery) === null || _b === void 0 ? void 0 : _b[formatQueryAppName(appName)]);
3336
3381
  return isString(microPath) ? decodeMicroPath(microPath) : null;
3337
3382
  }
3338
- return rawLocation.pathname + rawLocation.search + rawLocation.hash;
3383
+ /**
3384
+ * Get fullPath from __MICRO_APP_STATE__
3385
+ * NOTE:
3386
+ * 1. state mode: all base on __MICRO_APP_STATE__
3387
+ * 2. pure mode: navigate by location.xxx may contain one-time information in __MICRO_APP_STATE__
3388
+ * 3. native/scope mode: vue-router@4 will exec replaceState base on state before pushState, like:
3389
+ * history.replaceState(
3390
+ * assign({}, history.state, {...}),
3391
+ * title,
3392
+ * history.state.current, <---
3393
+ * )
3394
+ * when base app jump to another page from child page, it will replace child path with base app path
3395
+ * e.g: base-home --> child-home --> child-about(will replace with child-home before jump to base-home) --> base-home, when go back, it will back to child-home not child-about
3396
+ * So we take the fullPath as the standard
3397
+ */
3398
+ return ((_d = (_c = rawState === null || rawState === void 0 ? void 0 : rawState.__MICRO_APP_STATE__) === null || _c === void 0 ? void 0 : _c[appName]) === null || _d === void 0 ? void 0 : _d.fullPath) || (isRouterModeCustom(appName) ? rawLocation.pathname + rawLocation.search + rawLocation.hash : null);
3339
3399
  }
3340
3400
  /**
3341
3401
  * Attach child app fullPath to browser url
@@ -3343,10 +3403,11 @@ function getMicroPathFromURL(appName) {
3343
3403
  * @param targetLocation location of child app or rawLocation of window
3344
3404
  */
3345
3405
  function setMicroPathToURL(appName, targetLocation) {
3346
- const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash;
3406
+ const rawLocation = globalEnv.rawWindow.location;
3407
+ let targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash;
3347
3408
  let isAttach2Hash = false;
3348
3409
  if (isRouterModeSearch(appName)) {
3349
- let { pathname, search, hash } = globalEnv.rawWindow.location;
3410
+ let { pathname, search, hash } = rawLocation;
3350
3411
  const queryObject = getQueryObjectFromURL(search, hash);
3351
3412
  const encodedMicroPath = encodeMicroPath(targetFullPath);
3352
3413
  /**
@@ -3356,6 +3417,7 @@ function setMicroPathToURL(appName, targetLocation) {
3356
3417
  // If hash exists and search does not exist, it is considered as a hash route
3357
3418
  if (hash && !search) {
3358
3419
  isAttach2Hash = true;
3420
+ // TODO: 这里和下面的if判断可以简化一下
3359
3421
  if (queryObject.hashQuery) {
3360
3422
  queryObject.hashQuery[formatQueryAppName(appName)] = encodedMicroPath;
3361
3423
  }
@@ -3383,6 +3445,9 @@ function setMicroPathToURL(appName, targetLocation) {
3383
3445
  isAttach2Hash,
3384
3446
  };
3385
3447
  }
3448
+ if (isRouterModeState(appName) || isRouterModePure(appName)) {
3449
+ targetFullPath = rawLocation.pathname + rawLocation.search + rawLocation.hash;
3450
+ }
3386
3451
  return {
3387
3452
  fullPath: targetFullPath,
3388
3453
  isAttach2Hash,
@@ -3391,11 +3456,10 @@ function setMicroPathToURL(appName, targetLocation) {
3391
3456
  /**
3392
3457
  * Delete child app fullPath from browser url
3393
3458
  * @param appName app.name
3394
- * @param targetLocation target Location, default is rawLocation
3395
3459
  */
3396
- function removeMicroPathFromURL(appName, targetLocation) {
3460
+ function removeMicroPathFromURL(appName) {
3397
3461
  var _a, _b, _c, _d;
3398
- let { pathname, search, hash } = targetLocation || globalEnv.rawWindow.location;
3462
+ let { pathname, search, hash } = globalEnv.rawWindow.location;
3399
3463
  let isAttach2Hash = false;
3400
3464
  if (isRouterModeSearch(appName)) {
3401
3465
  const queryObject = getQueryObjectFromURL(search, hash);
@@ -3457,25 +3521,33 @@ function isEffectiveApp(appName) {
3457
3521
  */
3458
3522
  return !!(app && !app.isPrefetch);
3459
3523
  }
3524
+ /**
3525
+ * get router mode of app
3526
+ * NOTE: app maybe undefined
3527
+ */
3528
+ function getRouterMode(appName) {
3529
+ var _a;
3530
+ return (_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.routerMode;
3531
+ }
3460
3532
  // router mode is search
3461
3533
  function isRouterModeSearch(appName) {
3462
- const app = appInstanceMap.get(appName);
3463
- return !!(app && app.sandBox && app.routerMode === DEFAULT_ROUTER_MODE);
3534
+ return getRouterMode(appName) === DEFAULT_ROUTER_MODE;
3535
+ }
3536
+ // router mode is state
3537
+ function isRouterModeState(appName) {
3538
+ return getRouterMode(appName) === ROUTER_MODE_STATE;
3464
3539
  }
3465
3540
  // router mode is history
3466
3541
  function isRouterModeNative(appName) {
3467
- const app = appInstanceMap.get(appName);
3468
- return !!(app && app.sandBox && app.routerMode === ROUTER_MODE_NATIVE);
3542
+ return getRouterMode(appName) === ROUTER_MODE_NATIVE;
3469
3543
  }
3470
3544
  // router mode is disable
3471
3545
  function isRouterModeNativeScope(appName) {
3472
- const app = appInstanceMap.get(appName);
3473
- return !!(app && app.sandBox && app.routerMode === ROUTER_MODE_NATIVE_SCOPE);
3546
+ return getRouterMode(appName) === ROUTER_MODE_NATIVE_SCOPE;
3474
3547
  }
3475
3548
  // router mode is pure
3476
3549
  function isRouterModePure(appName) {
3477
- const app = appInstanceMap.get(appName);
3478
- return !!(app && app.sandBox && app.routerMode === ROUTER_MODE_PURE);
3550
+ return getRouterMode(appName) === ROUTER_MODE_PURE;
3479
3551
  }
3480
3552
  /**
3481
3553
  * router mode is history or disable
@@ -3492,7 +3564,7 @@ function isRouterModeCustom(appName) {
3492
3564
  * @param inlineDisableMemoryRouter disable-memory-router set by micro-app element or prerender
3493
3565
  * @returns router mode
3494
3566
  */
3495
- function getRouterMode(mode, inlineDisableMemoryRouter) {
3567
+ function initRouterMode(mode, inlineDisableMemoryRouter) {
3496
3568
  /**
3497
3569
  * compatible with disable-memory-router in older versions
3498
3570
  * if disable-memory-router is true, router-mode will be disable
@@ -3527,7 +3599,21 @@ function addHistoryListener(appName) {
3527
3599
  excludePreRender: true,
3528
3600
  }).includes(appName) &&
3529
3601
  !e.onlyForBrowser) {
3602
+ /**
3603
+ * TODO: vue-router@4 navigate async when receive popstateEvent, but child may respond to popstateEvent immediately(vue2, react), so when go back throw browser child will not unmount sync, and will respond to popstateEvent before base app, this will cause some problems
3604
+ * __MICRO_APP_BASE_ROUTE__不可控,用户设置的值是随机的且不一定使用,用它作为判断依据太过危险
3605
+ */
3606
+ // const microAppWindow = appInstanceMap.get(appName)!.sandBox!.microAppWindow
3607
+ // const rawLocation = globalEnv.rawWindow.location
3608
+ // if (
3609
+ // !isRouterModeCustom(appName) ||
3610
+ // !microAppWindow.__MICRO_APP_BASE_ROUTE__ ||
3611
+ // // 主history、子hash,主、子都是hash如何处理
3612
+ // microAppWindow.__MICRO_APP_BASE_ROUTE__.includes('#') ||
3613
+ // `${rawLocation.pathname}/`.startsWith(('/' + microAppWindow.__MICRO_APP_BASE_ROUTE__).replace(/^\/+/, '/'))
3614
+ // ) {
3530
3615
  updateMicroLocationWithEvent(appName, getMicroPathFromURL(appName));
3616
+ // }
3531
3617
  }
3532
3618
  };
3533
3619
  rawWindow.addEventListener('popstate', popStateHandler);
@@ -3575,14 +3661,18 @@ function dispatchPopStateEventToMicroApp(appName, proxyWindow, microAppWindow) {
3575
3661
  * TODO: test
3576
3662
  * angular14 takes e.type as type judgment
3577
3663
  * when e.type is popstate-appName popstate event will be invalid
3664
+ * Object.defineProperty(newPopStateEvent, 'type', {
3665
+ * value: 'popstate',
3666
+ * writable: true,
3667
+ * configurable: true,
3668
+ * enumerable: true,
3669
+ * })
3670
+ */
3671
+ /**
3672
+ * create PopStateEvent named popstate-appName with sub app state
3673
+ * TODO: feeling like there's something wrong, check carefully
3674
+ * In native mode, getMicroState(appName) return rawWindow.history.state when use microApp.router.push/replace or other scenes when state.__MICRO_APP_STATE__[appName] is null
3578
3675
  */
3579
- // Object.defineProperty(newPopStateEvent, 'type', {
3580
- // value: 'popstate',
3581
- // writable: true,
3582
- // configurable: true,
3583
- // enumerable: true,
3584
- // })
3585
- // create PopStateEvent named popstate-appName with sub app state
3586
3676
  const newPopStateEvent = new PopStateEvent('popstate', { state: getMicroState(appName) });
3587
3677
  microAppWindow.dispatchEvent(newPopStateEvent);
3588
3678
  if (!isIframeSandbox(appName)) {
@@ -3661,7 +3751,7 @@ function createMicroHistory(appName, microLocation) {
3661
3751
  const targetLocation = createURL(rests[2], microLocation.href);
3662
3752
  const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash;
3663
3753
  if (!isRouterModePure(appName)) {
3664
- navigateWithNativeEvent(appName, methodName, setMicroPathToURL(appName, targetLocation), true, setMicroState(appName, rests[0]), rests[1]);
3754
+ navigateWithNativeEvent(appName, methodName, setMicroPathToURL(appName, targetLocation), true, setMicroState(appName, rests[0], targetLocation), rests[1]);
3665
3755
  }
3666
3756
  if (targetFullPath !== microLocation.fullPath) {
3667
3757
  updateMicroLocation(appName, targetFullPath, microLocation);
@@ -3675,8 +3765,12 @@ function createMicroHistory(appName, microLocation) {
3675
3765
  }
3676
3766
  const pushState = getMicroHistoryMethod('pushState');
3677
3767
  const replaceState = getMicroHistoryMethod('replaceState');
3678
- if (isIframeSandbox(appName))
3679
- return { pushState, replaceState };
3768
+ if (isIframeSandbox(appName)) {
3769
+ return {
3770
+ pushState,
3771
+ replaceState,
3772
+ };
3773
+ }
3680
3774
  return new Proxy(rawHistory, {
3681
3775
  get(target, key) {
3682
3776
  if (key === 'state') {
@@ -3739,11 +3833,7 @@ function navigateWithNativeEvent(appName, methodName, result, onlyForBrowser, st
3739
3833
  const oldHref = result.isAttach2Hash && oldFullPath !== result.fullPath ? rawLocation.href : null;
3740
3834
  // navigate with native history method
3741
3835
  nativeHistoryNavigate(appName, methodName, result.fullPath, state, title);
3742
- /**
3743
- * TODO:
3744
- * 1. 如果所有模式统一发送popstate事件,则isRouterModeSearch(appName)要去掉
3745
- * 2. 如果发送事件,则会导致vue router-view :key='router.path'绑定,无限卸载应用,死循环
3746
- */
3836
+ // just search mode will dispatch native event
3747
3837
  if (oldFullPath !== result.fullPath && isRouterModeSearch(appName)) {
3748
3838
  dispatchNativeEvent(appName, onlyForBrowser, oldHref);
3749
3839
  }
@@ -3760,22 +3850,22 @@ function attachRouteToBrowserURL(appName, result, state) {
3760
3850
  navigateWithNativeEvent(appName, 'replaceState', result, true, state);
3761
3851
  }
3762
3852
  /**
3763
- * When path is same, keep the microAppState in history.state
3764
- * Fix bug of missing microAppState when base app is next.js or angular
3853
+ * When path is same, keep the __MICRO_APP_STATE__ in history.state
3854
+ * Fix bug of missing __MICRO_APP_STATE__ when base app is next.js or angular
3765
3855
  * @param method history.pushState/replaceState
3766
3856
  */
3767
3857
  function reWriteHistoryMethod(method) {
3768
3858
  const rawWindow = globalEnv.rawWindow;
3769
3859
  return function (...rests) {
3770
3860
  var _a;
3771
- if (((_a = rawWindow.history.state) === null || _a === void 0 ? void 0 : _a.microAppState) &&
3772
- (!isPlainObject(rests[0]) || !rests[0].microAppState) &&
3861
+ if (((_a = rawWindow.history.state) === null || _a === void 0 ? void 0 : _a.__MICRO_APP_STATE__) &&
3862
+ (!isPlainObject(rests[0]) || !rests[0].__MICRO_APP_STATE__) &&
3773
3863
  (isString(rests[2]) || isURL(rests[2]))) {
3774
3864
  const currentHref = rawWindow.location.href;
3775
3865
  const targetLocation = createURL(rests[2], currentHref);
3776
3866
  if (targetLocation.href === currentHref) {
3777
3867
  rests[0] = assign({}, rests[0], {
3778
- microAppState: rawWindow.history.state.microAppState,
3868
+ __MICRO_APP_STATE__: rawWindow.history.state.__MICRO_APP_STATE__,
3779
3869
  });
3780
3870
  }
3781
3871
  }
@@ -3791,9 +3881,9 @@ function reWriteHistoryMethod(method) {
3791
3881
  excludeHiddenApp: true,
3792
3882
  excludePreRender: true,
3793
3883
  }).forEach(appName => {
3794
- if (isRouterModeSearch(appName) && !getMicroPathFromURL(appName)) {
3884
+ if ((isRouterModeSearch(appName) || isRouterModeState(appName)) && !getMicroPathFromURL(appName)) {
3795
3885
  const app = appInstanceMap.get(appName);
3796
- attachRouteToBrowserURL(appName, setMicroPathToURL(appName, app.sandBox.proxyWindow.location), setMicroState(appName, getMicroState(appName)));
3886
+ attachRouteToBrowserURL(appName, setMicroPathToURL(appName, app.sandBox.proxyWindow.location), setMicroState(appName, getMicroState(appName), app.sandBox.proxyWindow.location));
3797
3887
  }
3798
3888
  });
3799
3889
  // fix bug for nest app
@@ -3802,7 +3892,7 @@ function reWriteHistoryMethod(method) {
3802
3892
  }
3803
3893
  /**
3804
3894
  * rewrite history.pushState/replaceState
3805
- * used to fix the problem that the microAppState maybe missing when mainApp navigate to same path
3895
+ * used to fix the problem that the __MICRO_APP_STATE__ maybe missing when mainApp navigate to same path
3806
3896
  * e.g: when nextjs, angular receive popstate event, they will use history.replaceState to update browser url with a new state object
3807
3897
  */
3808
3898
  function patchHistory() {
@@ -3825,7 +3915,7 @@ function createRouterApi() {
3825
3915
  * @param state to.state
3826
3916
  */
3827
3917
  function navigateWithRawHistory(appName, methodName, targetLocation, state) {
3828
- navigateWithNativeEvent(appName, methodName, setMicroPathToURL(appName, targetLocation), false, setMicroState(appName, state !== null && state !== void 0 ? state : null));
3918
+ navigateWithNativeEvent(appName, methodName, setMicroPathToURL(appName, targetLocation), false, setMicroState(appName, state !== null && state !== void 0 ? state : null, targetLocation));
3829
3919
  // clear element scope after navigate
3830
3920
  removeDomScope();
3831
3921
  }
@@ -3843,23 +3933,13 @@ function createRouterApi() {
3843
3933
  const currentFullPath = microLocation.pathname + microLocation.search + microLocation.hash;
3844
3934
  const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash;
3845
3935
  if (currentFullPath !== targetFullPath || getMicroPathFromURL(appName) !== targetFullPath) {
3936
+ // pure mode will not call history.pushState/replaceState
3846
3937
  if (!isRouterModePure(appName)) {
3847
3938
  const methodName = (replace && to.replace !== false) || to.replace === true ? 'replaceState' : 'pushState';
3848
3939
  navigateWithRawHistory(appName, methodName, targetLocation, to.state);
3849
3940
  }
3850
- /**
3851
- * TODO:
3852
- * 1. 关闭虚拟路由的跳转地址不同:baseRoute + 子应用地址,文档中要说明
3853
- * 2. 关闭虚拟路由时跳转方式不同:1、基座跳转但不发送popstate事件 2、控制子应用更新location,内部发送popstate事件。
3854
- * 核心思路:减小对基座的影响(子应用跳转不向基座发送popstate事件,其他操作一致),但这是必要的吗,只是多了一个触发popstate的操作
3855
- * 路由优化方案有两种:
3856
- * 1、减少对基座的影响,主要是解决vue循环刷新的问题
3857
- * 2、全局发送popstate事件,解决主、子都是vue3的冲突问题
3858
- * 两者选一个吧,如果选2,则下面这两行代码可以去掉
3859
- * NOTE1: history和search模式采用2,这样可以解决vue3的问题,custom采用1,避免vue循环刷新的问题,这样在用户出现问题时各有解决方案。但反过来说,每种方案又分别导致另外的问题,不统一,导致复杂度增高
3860
- * NOTE2: 关闭虚拟路由,同时发送popstate事件还是无法解决vue3的问题(毕竟history.state理论上还是会冲突),那么就没必要发送popstate事件了。
3861
- */
3862
- if (isRouterModeCustom(appName) || isRouterModePure(appName)) {
3941
+ // only search mode will dispatch PopStateEvent to browser
3942
+ if (!isRouterModeSearch(appName)) {
3863
3943
  updateMicroLocationWithEvent(appName, targetFullPath);
3864
3944
  }
3865
3945
  }
@@ -3884,27 +3964,18 @@ function createRouterApi() {
3884
3964
  * 2. disable memory-router
3885
3965
  */
3886
3966
  /**
3887
- * TODO: 子应用开始渲染但是还没渲染完成
3888
- * 1、调用跳转改如何处理
3967
+ * TODO:
3968
+ * 1、子应用开始渲染但是还没渲染完成,调用跳转改如何处理
3889
3969
  * 2、iframe的沙箱还没初始化时执行跳转报错,如何处理。。。
3890
- * 3、hidden app 是否支持跳转
3970
+ * 3、hidden app、预渲染 app 是否支持跳转 --- 支持(这里还涉及子应用内部跳转的支持)
3891
3971
  */
3892
3972
  if (getActiveApps({ excludeHiddenApp: true, excludePreRender: true }).includes(appName)) {
3893
3973
  const app = appInstanceMap.get(appName);
3894
3974
  resolve(app.sandBox.sandboxReady.then(() => handleNavigate(appName, app, to, replace)));
3895
3975
  }
3896
3976
  else {
3897
- reject(logError('navigation failed, app does not exist or is inactive'));
3977
+ reject(logError('导航失败,请确保子应用渲染后再调用此方法'));
3898
3978
  }
3899
- // /**
3900
- // * app not exit or unmounted, update browser URL with replaceState
3901
- // * use base app location.origin as baseURL
3902
- // * 应用不存在或已卸载,依然使用replaceState来更新浏览器地址 -- 不合理
3903
- // */
3904
- // /**
3905
- // * TODO: 应用还没渲染或已经卸载最好不要支持跳转了,我知道这是因为解决一些特殊场景,但这么做是非常反直觉的
3906
- // * 并且在新版本中有多种路由模式,如果应用不存在,我们根本无法知道是哪种模式,那么这里的操作就无意义了。
3907
- // */
3908
3979
  // const rawLocation = globalEnv.rawWindow.location
3909
3980
  // const targetLocation = createURL(to.path, rawLocation.origin)
3910
3981
  // const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash
@@ -3977,9 +4048,9 @@ function createRouterApi() {
3977
4048
  * 3. router mode is custom
3978
4049
  */
3979
4050
  function commonHandlerForAttachToURL(appName) {
3980
- if (isRouterModeSearch(appName)) {
4051
+ if (isRouterModeSearch(appName) || isRouterModeState(appName)) {
3981
4052
  const app = appInstanceMap.get(appName);
3982
- attachRouteToBrowserURL(appName, setMicroPathToURL(appName, app.sandBox.proxyWindow.location), setMicroState(appName, getMicroState(appName)));
4053
+ attachRouteToBrowserURL(appName, setMicroPathToURL(appName, app.sandBox.proxyWindow.location), setMicroState(appName, getMicroState(appName), app.sandBox.proxyWindow.location));
3983
4054
  }
3984
4055
  }
3985
4056
  /**
@@ -4078,94 +4149,6 @@ function createRouterApi() {
4078
4149
  }
4079
4150
  const { router, executeNavigationGuard, clearRouterWhenUnmount, } = createRouterApi();
4080
4151
 
4081
- const escape2RawWindowKeys = [
4082
- 'getComputedStyle',
4083
- 'visualViewport',
4084
- 'matchMedia',
4085
- // 'DOMParser',
4086
- 'ResizeObserver',
4087
- 'IntersectionObserver',
4088
- ];
4089
- const escape2RawWindowRegExpKeys = [
4090
- /animationFrame$/i,
4091
- /mutationObserver$/i,
4092
- /height$|width$/i,
4093
- /offset$/i,
4094
- // /event$/i,
4095
- /selection$/i,
4096
- /^range/i,
4097
- /^screen/i,
4098
- /^scroll/i,
4099
- /X$|Y$/,
4100
- ];
4101
- const uniqueDocumentElement = [
4102
- 'body',
4103
- 'head',
4104
- 'html',
4105
- 'title',
4106
- ];
4107
- const hijackMicroLocationKeys = [
4108
- 'host',
4109
- 'hostname',
4110
- 'port',
4111
- 'protocol',
4112
- 'origin',
4113
- ];
4114
- // 有shadowRoot则代理到shadowRoot否则代理到原生document上 (属性)
4115
- const proxy2RawDocOrShadowKeys = [
4116
- 'childElementCount',
4117
- 'children',
4118
- 'firstElementChild',
4119
- 'firstChild',
4120
- 'lastElementChild',
4121
- 'activeElement',
4122
- 'fullscreenElement',
4123
- 'pictureInPictureElement',
4124
- 'pointerLockElement',
4125
- 'styleSheets',
4126
- ];
4127
- // 有shadowRoot则代理到shadowRoot否则代理到原生document上 (方法)
4128
- const proxy2RawDocOrShadowMethods = [
4129
- 'append',
4130
- 'contains',
4131
- 'replaceChildren',
4132
- 'createRange',
4133
- 'getSelection',
4134
- 'elementFromPoint',
4135
- 'elementsFromPoint',
4136
- 'getAnimations',
4137
- ];
4138
- // 直接代理到原生document上 (属性)
4139
- const proxy2RawDocumentKeys = [
4140
- 'characterSet',
4141
- 'compatMode',
4142
- 'contentType',
4143
- 'designMode',
4144
- 'dir',
4145
- 'doctype',
4146
- 'embeds',
4147
- 'fullscreenEnabled',
4148
- 'hidden',
4149
- 'implementation',
4150
- 'lastModified',
4151
- 'pictureInPictureEnabled',
4152
- 'plugins',
4153
- 'readyState',
4154
- 'referrer',
4155
- 'visibilityState',
4156
- 'fonts',
4157
- ];
4158
- // 直接代理到原生document上 (方法)
4159
- const proxy2RawDocumentMethods = [
4160
- 'execCommand',
4161
- 'createRange',
4162
- 'exitFullscreen',
4163
- 'exitPictureInPicture',
4164
- 'getElementsByTagNameNS',
4165
- 'hasFocus',
4166
- 'prepend',
4167
- ];
4168
-
4169
4152
  // origin is readonly, so we ignore when updateMicroLocation
4170
4153
  const locationKeys = ['href', 'pathname', 'search', 'hash', 'host', 'hostname', 'port', 'protocol', 'search'];
4171
4154
  // origin, fullPath is necessary for guardLocation
@@ -4209,39 +4192,47 @@ function createMicroLocation(appName, url, microAppWindow, childStaticLocation,
4209
4192
  if (targetLocation.origin === proxyLocation.origin) {
4210
4193
  const setMicroPathResult = setMicroPathToURL(appName, targetLocation);
4211
4194
  // if disable memory-router, navigate directly through rawLocation
4212
- if (isRouterModeSearch(appName)) {
4195
+ if (!isRouterModeCustom(appName)) {
4196
+ methodName = isRouterModePure(appName) ? 'replaceState' : methodName;
4213
4197
  /**
4214
4198
  * change hash with location.href will not trigger the browser reload
4215
4199
  * so we use pushState & reload to imitate href behavior
4216
4200
  * NOTE:
4217
- * 1. if child app only change hash, it should not trigger browser reload
4218
- * 2. if address is same and has hash, it should not add route stack
4201
+ * 1. if child app only change hash, it will not reload browser
4202
+ * 2. if address is same and has hash, it will not add route stack
4219
4203
  */
4220
4204
  if (targetLocation.pathname === proxyLocation.pathname &&
4221
4205
  targetLocation.search === proxyLocation.search) {
4222
4206
  let oldHref = null;
4223
- if (targetLocation.hash !== proxyLocation.hash) {
4224
- if (setMicroPathResult.isAttach2Hash)
4207
+ // NOTE: if pathname & search is same, it should record router info to history.state in pure mode
4208
+ if (targetLocation.hash !== proxyLocation.hash || isRouterModePure(appName)) {
4209
+ // search mode only
4210
+ if (setMicroPathResult.isAttach2Hash) {
4225
4211
  oldHref = rawLocation.href;
4226
- nativeHistoryNavigate(appName, methodName, setMicroPathResult.fullPath);
4212
+ }
4213
+ // if router mode is pure and targetLocation.hash exist, it will not call nativeHistoryNavigate
4214
+ if (!isRouterModePure(appName) || !targetLocation.hash) {
4215
+ nativeHistoryNavigate(appName, methodName, setMicroPathResult.fullPath, !isRouterModeSearch(appName) ? setMicroState(appName, null, targetLocation) : null);
4216
+ }
4227
4217
  }
4228
4218
  if (targetLocation.hash) {
4229
- dispatchNativeEvent(appName, false, oldHref);
4219
+ if (isRouterModeSearch(appName)) {
4220
+ dispatchNativeEvent(appName, false, oldHref);
4221
+ }
4222
+ else {
4223
+ updateMicroLocationWithEvent(appName, targetLocation.pathname + targetLocation.search + targetLocation.hash);
4224
+ }
4230
4225
  }
4231
4226
  else {
4232
4227
  reload();
4233
4228
  }
4234
4229
  return void 0;
4235
- /**
4236
- * when baseApp is hash router, address change of child can not reload browser
4237
- * so we imitate behavior of browser (reload) manually
4238
- */
4239
- }
4240
- else if (setMicroPathResult.isAttach2Hash) {
4241
- nativeHistoryNavigate(appName, methodName, setMicroPathResult.fullPath);
4242
- reload();
4243
- return void 0;
4244
4230
  }
4231
+ // when pathname or search change, simulate behavior of browser (reload) manually
4232
+ // TODO: state模式下pushState会带上上一个页面的state,会不会有问题,尤其是vue3,应不应该将主应用的state设置为null
4233
+ nativeHistoryNavigate(appName, methodName, setMicroPathResult.fullPath, !isRouterModeSearch(appName) ? setMicroState(appName, null, targetLocation) : null);
4234
+ reload();
4235
+ return void 0;
4245
4236
  }
4246
4237
  return setMicroPathResult.fullPath;
4247
4238
  }
@@ -4266,7 +4257,9 @@ function createMicroLocation(appName, url, microAppWindow, childStaticLocation,
4266
4257
  * pathname: /path ==> /path#hash, /path ==> /path?query
4267
4258
  * search: ?query ==> ?query#hash
4268
4259
  */
4269
- nativeHistoryNavigate(appName, targetLocation[key] === proxyLocation[key] ? 'replaceState' : 'pushState', setMicroPathToURL(appName, targetLocation).fullPath);
4260
+ nativeHistoryNavigate(appName, (targetLocation[key] === proxyLocation[key] || isRouterModePure(appName))
4261
+ ? 'replaceState'
4262
+ : 'pushState', setMicroPathToURL(appName, targetLocation).fullPath, !isRouterModeSearch(appName) ? setMicroState(appName, null, targetLocation) : null);
4270
4263
  reload();
4271
4264
  }
4272
4265
  }
@@ -4305,17 +4298,27 @@ function createMicroLocation(appName, url, microAppWindow, childStaticLocation,
4305
4298
  return target;
4306
4299
  if (key === 'fullPath')
4307
4300
  return target.fullPath;
4308
- if (isRouterModeNative(appName)) {
4309
- return bindFunctionToRawTarget(Reflect.get(rawLocation, key), rawLocation, 'LOCATION');
4310
- }
4311
- // src of iframe is base app address, it needs to be replaced separately
4312
- if (isIframe) {
4313
- // host hostname port protocol
4314
- if (hijackMicroLocationKeys.includes(key)) {
4301
+ /**
4302
+ * Special keys: host, hostname, port, protocol, origin, href
4303
+ * NOTE:
4304
+ * 1. In native mode this keys point to browser, in other mode this keys point to child app origin
4305
+ * 2. In iframe sandbox, iframe.src is base app address, so origin points to the browser by default, we need to replace it with child app origin
4306
+ * 3. In other modes, origin points to child app
4307
+ */
4308
+ if (HIJACK_LOCATION_KEYS.includes(key)) {
4309
+ if (isRouterModeNative(appName)) {
4310
+ return rawLocation[key];
4311
+ }
4312
+ if (isIframe) {
4315
4313
  return childStaticLocation[key];
4316
4314
  }
4317
- if (key === 'href') {
4318
- // do not use target, because target may be deleted
4315
+ }
4316
+ if (key === 'href') {
4317
+ if (isRouterModeNative(appName)) {
4318
+ return target[key].replace(target.origin, rawLocation.origin);
4319
+ }
4320
+ if (isIframe) {
4321
+ // target may be deleted
4319
4322
  return target[key].replace(browserHost, childHost);
4320
4323
  }
4321
4324
  }
@@ -4365,7 +4368,12 @@ function createMicroLocation(appName, url, microAppWindow, childStaticLocation,
4365
4368
  const targetLocation = createURL(targetPath, url);
4366
4369
  // The same hash will not trigger popStateEvent
4367
4370
  if (targetLocation.hash !== proxyLocation.hash) {
4368
- navigateWithNativeEvent(appName, 'pushState', setMicroPathToURL(appName, targetLocation), false);
4371
+ if (!isRouterModePure(appName)) {
4372
+ navigateWithNativeEvent(appName, 'pushState', setMicroPathToURL(appName, targetLocation), false, setMicroState(appName, null, targetLocation));
4373
+ }
4374
+ if (!isRouterModeSearch(appName)) {
4375
+ updateMicroLocationWithEvent(appName, targetLocation.pathname + targetLocation.search + targetLocation.hash);
4376
+ }
4369
4377
  }
4370
4378
  }
4371
4379
  }
@@ -4432,11 +4440,6 @@ function updateMicroLocation(appName, path, microLocation, type) {
4432
4440
  }
4433
4441
  }
4434
4442
 
4435
- /**
4436
- * TODO: 关于关闭虚拟路由系统的custom、history模式
4437
- * 1. 是否需要发送popstate事件,为了减小对基座的影响,现在不发送
4438
- * 2. 关闭后导致的vue3路由冲突问题需要在文档中明确指出(2处:在关闭虚拟路由系统的配置那里着重说明,在vue常见问题中说明)
4439
- */
4440
4443
  /**
4441
4444
  * The router system has two operations: read and write
4442
4445
  * Read through location and write through history & location
@@ -4461,6 +4464,9 @@ function initRouteStateWithURL(appName, microLocation, defaultPage) {
4461
4464
  const microPath = getMicroPathFromURL(appName);
4462
4465
  if (microPath) {
4463
4466
  updateMicroLocation(appName, microPath, microLocation, 'auto');
4467
+ if (isRouterModePure(appName)) {
4468
+ removePathFromBrowser(appName);
4469
+ }
4464
4470
  }
4465
4471
  else {
4466
4472
  updateBrowserURLWithLocation(appName, microLocation, defaultPage);
@@ -4478,7 +4484,7 @@ function updateBrowserURLWithLocation(appName, microLocation, defaultPage) {
4478
4484
  updateMicroLocation(appName, defaultPage, microLocation, 'prevent');
4479
4485
  if (!isRouterModePure(appName)) {
4480
4486
  // attach microApp route info to browser URL
4481
- attachRouteToBrowserURL(appName, setMicroPathToURL(appName, microLocation), setMicroState(appName, null));
4487
+ attachRouteToBrowserURL(appName, setMicroPathToURL(appName, microLocation), setMicroState(appName, null, microLocation));
4482
4488
  }
4483
4489
  // trigger guards after change browser URL
4484
4490
  autoTriggerNavigationGuard(appName, microLocation);
@@ -4491,14 +4497,13 @@ function updateBrowserURLWithLocation(appName, microLocation, defaultPage) {
4491
4497
  * @param keepRouteState keep-router-state is only used to control whether to clear the location of microApp, default is false
4492
4498
  */
4493
4499
  function clearRouteStateFromURL(appName, url, microLocation, keepRouteState) {
4494
- if (!isRouterModeCustom(appName)) {
4495
- if (!keepRouteState) {
4496
- const { pathname, search, hash } = createURL(url);
4497
- updateMicroLocation(appName, pathname + search + hash, microLocation, 'prevent');
4498
- }
4499
- if (!isRouterModePure(appName)) {
4500
- removePathFromBrowser(appName);
4501
- }
4500
+ // TODO: keep-router-state 功能太弱,是否可以增加优先级,或者去掉
4501
+ if (!keepRouteState && !isRouterModeCustom(appName)) {
4502
+ const { pathname, search, hash } = createURL(url);
4503
+ updateMicroLocation(appName, pathname + search + hash, microLocation, 'prevent');
4504
+ }
4505
+ if (!isRouterModePure(appName)) {
4506
+ removePathFromBrowser(appName);
4502
4507
  }
4503
4508
  clearRouterWhenUnmount(appName);
4504
4509
  }
@@ -5155,13 +5160,29 @@ class WithSandBox extends BaseSandbox {
5155
5160
  WithSandBox.activeCount = 0; // number of active sandbox
5156
5161
 
5157
5162
  function patchRouter(appName, url, microAppWindow, browserHost) {
5163
+ const rawHistory = globalEnv.rawWindow.history;
5158
5164
  const childStaticLocation = createURL(url);
5159
5165
  const childHost = childStaticLocation.protocol + '//' + childStaticLocation.host;
5160
5166
  const childFullPath = childStaticLocation.pathname + childStaticLocation.search + childStaticLocation.hash;
5161
5167
  // rewrite microAppWindow.history
5162
5168
  const microHistory = microAppWindow.history;
5169
+ // save history.replaceState, it will be used in updateMicroLocation
5163
5170
  microAppWindow.rawReplaceState = microHistory.replaceState;
5171
+ // rewrite microAppWindow.history
5164
5172
  assign(microHistory, createMicroHistory(appName, microAppWindow.location));
5173
+ // scrollRestoration proxy to rawHistory
5174
+ rawDefineProperties(microHistory, {
5175
+ scrollRestoration: {
5176
+ configurable: true,
5177
+ enumerable: true,
5178
+ get() {
5179
+ return rawHistory.scrollRestoration;
5180
+ },
5181
+ set(value) {
5182
+ rawHistory.scrollRestoration = value;
5183
+ }
5184
+ }
5185
+ });
5165
5186
  /**
5166
5187
  * Init microLocation before exec sandbox.start
5167
5188
  * NOTE:
@@ -5173,6 +5194,87 @@ function patchRouter(appName, url, microAppWindow, browserHost) {
5173
5194
  return createMicroLocation(appName, url, microAppWindow, childStaticLocation, browserHost, childHost);
5174
5195
  }
5175
5196
 
5197
+ const escape2RawWindowKeys = [
5198
+ 'getComputedStyle',
5199
+ 'visualViewport',
5200
+ 'matchMedia',
5201
+ // 'DOMParser',
5202
+ 'ResizeObserver',
5203
+ 'IntersectionObserver',
5204
+ ];
5205
+ const escape2RawWindowRegExpKeys = [
5206
+ /animationFrame$/i,
5207
+ /mutationObserver$/i,
5208
+ /height$|width$/i,
5209
+ /offset$/i,
5210
+ // /event$/i,
5211
+ /selection$/i,
5212
+ /^range/i,
5213
+ /^screen/i,
5214
+ /^scroll/i,
5215
+ /X$|Y$/,
5216
+ ];
5217
+ const uniqueDocumentElement = [
5218
+ 'body',
5219
+ 'head',
5220
+ 'html',
5221
+ 'title',
5222
+ ];
5223
+ // 有shadowRoot则代理到shadowRoot否则代理到原生document上 (属性)
5224
+ const proxy2RawDocOrShadowKeys = [
5225
+ 'childElementCount',
5226
+ 'children',
5227
+ 'firstElementChild',
5228
+ 'firstChild',
5229
+ 'lastElementChild',
5230
+ 'activeElement',
5231
+ 'fullscreenElement',
5232
+ 'pictureInPictureElement',
5233
+ 'pointerLockElement',
5234
+ 'styleSheets',
5235
+ ];
5236
+ // 有shadowRoot则代理到shadowRoot否则代理到原生document上 (方法)
5237
+ const proxy2RawDocOrShadowMethods = [
5238
+ 'append',
5239
+ 'contains',
5240
+ 'replaceChildren',
5241
+ 'createRange',
5242
+ 'getSelection',
5243
+ 'elementFromPoint',
5244
+ 'elementsFromPoint',
5245
+ 'getAnimations',
5246
+ ];
5247
+ // 直接代理到原生document上 (属性)
5248
+ const proxy2RawDocumentKeys = [
5249
+ 'characterSet',
5250
+ 'compatMode',
5251
+ 'contentType',
5252
+ 'designMode',
5253
+ 'dir',
5254
+ 'doctype',
5255
+ 'embeds',
5256
+ 'fullscreenEnabled',
5257
+ 'hidden',
5258
+ 'implementation',
5259
+ 'lastModified',
5260
+ 'pictureInPictureEnabled',
5261
+ 'plugins',
5262
+ 'readyState',
5263
+ 'referrer',
5264
+ 'visibilityState',
5265
+ 'fonts',
5266
+ ];
5267
+ // 直接代理到原生document上 (方法)
5268
+ const proxy2RawDocumentMethods = [
5269
+ 'execCommand',
5270
+ 'createRange',
5271
+ 'exitFullscreen',
5272
+ 'exitPictureInPicture',
5273
+ 'getElementsByTagNameNS',
5274
+ 'hasFocus',
5275
+ 'prepend',
5276
+ ];
5277
+
5176
5278
  /**
5177
5279
  * patch window of child app
5178
5280
  * @param appName app name
@@ -5220,7 +5322,7 @@ function patchWindowProperty$1(appName, microAppWindow) {
5220
5322
  }
5221
5323
  return false;
5222
5324
  });
5223
- return /^on/.test(key) && !SCOPE_WINDOW_ON_EVENT.includes(key);
5325
+ return /^on/.test(key) && !SCOPE_WINDOW_ON_EVENT_OF_IFRAME.includes(key);
5224
5326
  })
5225
5327
  .forEach((eventName) => {
5226
5328
  const { enumerable, writable, set } = Object.getOwnPropertyDescriptor(microAppWindow, eventName) || {
@@ -5309,7 +5411,7 @@ function patchWindowEffect$1(microAppWindow) {
5309
5411
  const eventListenerMap = new Map();
5310
5412
  const sstEventListenerMap = new Map();
5311
5413
  function getEventTarget(type) {
5312
- return SCOPE_WINDOW_EVENT.includes(type) ? microAppWindow : rawWindow;
5414
+ return SCOPE_WINDOW_EVENT_OF_IFRAME.includes(type) ? microAppWindow : rawWindow;
5313
5415
  }
5314
5416
  // TODO: listener 是否需要绑定microAppWindow,否则函数中的this指向原生window
5315
5417
  microAppWindow.addEventListener = function (type, listener, options) {
@@ -5847,7 +5949,7 @@ function patchIframeNode(appName, microAppWindow, sandbox) {
5847
5949
  configurable: true,
5848
5950
  enumerable: true,
5849
5951
  get() {
5850
- return this.__PURE_ELEMENT__
5952
+ return this.__PURE_ELEMENT__ || this === microDocument
5851
5953
  ? rawOwnerDocumentDesc.get.call(this)
5852
5954
  : microDocument;
5853
5955
  },
@@ -6433,6 +6535,7 @@ class CreateApp {
6433
6535
  // mount before prerender exec mount (loading source), set isPrerender to false
6434
6536
  this.isPrerender = false;
6435
6537
  // dispatch state event to micro app
6538
+ // TODO: statechange 还是 state-change,保持一致
6436
6539
  dispatchCustomEventToMicroApp(this, 'statechange', {
6437
6540
  appState: appStates.LOADING
6438
6541
  });
@@ -6456,6 +6559,14 @@ class CreateApp {
6456
6559
  if (this.isPrerender &&
6457
6560
  isDivElement(this.container) &&
6458
6561
  this.container.hasAttribute('prerender')) {
6562
+ /**
6563
+ * current this.container is <div prerender='true'></div>
6564
+ * set this.container to <micro-app></micro-app>
6565
+ * NOTE:
6566
+ * 1. must exec before this.sandBox.rebuildEffectSnapshot
6567
+ * 2. must exec before this.preRenderEvents?.forEach((cb) => cb())
6568
+ */
6569
+ this.container = this.cloneContainer(container, this.container, false);
6459
6570
  /**
6460
6571
  * rebuild effect event of window, document, data center
6461
6572
  * explain:
@@ -6464,14 +6575,6 @@ class CreateApp {
6464
6575
  * 3. rebuild after js exec end, normal recovery effect event
6465
6576
  */
6466
6577
  (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.rebuildEffectSnapshot();
6467
- // current this.container is <div prerender='true'></div>
6468
- this.cloneContainer(container, this.container, false);
6469
- /**
6470
- * set this.container to <micro-app></micro-app>
6471
- * NOTE:
6472
- * must exec before this.preRenderEvents?.forEach((cb) => cb())
6473
- */
6474
- this.container = container;
6475
6578
  (_b = this.preRenderEvents) === null || _b === void 0 ? void 0 : _b.forEach((cb) => cb());
6476
6579
  // reset isPrerender config
6477
6580
  this.isPrerender = false;
@@ -6768,6 +6871,14 @@ class CreateApp {
6768
6871
  // show app when connectedCallback called with keep-alive
6769
6872
  showKeepAliveApp(container) {
6770
6873
  var _a, _b;
6874
+ /**
6875
+ * NOTE:
6876
+ * 1. this.container must set to container(micro-app element) before exec rebuildEffectSnapshot
6877
+ * ISSUE: https://github.com/micro-zoe/micro-app/issues/1115
6878
+ * 2. rebuildEffectSnapshot must exec before dispatch beforeshow event
6879
+ */
6880
+ const oldContainer = this.container;
6881
+ this.container = container;
6771
6882
  (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.rebuildEffectSnapshot();
6772
6883
  // dispatch beforeShow event to micro-app
6773
6884
  dispatchCustomEventToMicroApp(this, 'appstate-change', {
@@ -6776,7 +6887,7 @@ class CreateApp {
6776
6887
  // dispatch beforeShow event to base app
6777
6888
  dispatchLifecyclesEvent(container, this.name, lifeCycles.BEFORESHOW);
6778
6889
  this.setKeepAliveState(keepAliveStates.KEEP_ALIVE_SHOW);
6779
- this.container = this.cloneContainer(container, this.container, false);
6890
+ this.cloneContainer(this.container, oldContainer, false);
6780
6891
  /**
6781
6892
  * TODO:
6782
6893
  * 问题:当路由模式为custom时,keep-alive应用在重新展示,是否需要根据子应用location信息更新浏览器地址?
@@ -6826,7 +6937,7 @@ class CreateApp {
6826
6937
  */
6827
6938
  cloneContainer(target, origin, deep) {
6828
6939
  // 在基座接受到afterhidden方法后立即执行unmount,彻底destroy应用时,因为unmount时同步执行,所以this.container为null后才执行cloneContainer
6829
- if (origin) {
6940
+ if (origin && target) {
6830
6941
  target.innerHTML = '';
6831
6942
  Array.from(deep ? this.parseHtmlString(origin.innerHTML).childNodes : origin.childNodes).forEach((node) => {
6832
6943
  target.appendChild(node);
@@ -7846,12 +7957,12 @@ function defineElement(tagName) {
7846
7957
  /**
7847
7958
  * url is different & old app is unmounted or prefetch, create new app to replace old one
7848
7959
  */
7849
- logWarn(`the ${oldApp.isPrefetch ? 'prefetch' : 'unmounted'} app with url: ${oldAppUrl} replaced by a new app with url: ${targetUrl}`, this.appName);
7960
+ logWarn(`the ${oldApp.isPrefetch ? 'prefetch' : 'unmounted'} app with url ${oldAppUrl} replaced by a new app with url ${targetUrl}`, this.appName);
7850
7961
  }
7851
7962
  this.handleCreateApp();
7852
7963
  }
7853
7964
  else {
7854
- logError(`app name conflict, an app named: ${this.appName} with url: ${oldAppUrl} is running`);
7965
+ logError(`app name conflict, an app named ${this.appName} with url ${oldAppUrl} is running`);
7855
7966
  }
7856
7967
  }
7857
7968
  else {
@@ -8095,6 +8206,7 @@ function defineElement(tagName) {
8095
8206
  }
8096
8207
  else {
8097
8208
  // get path from browser URL
8209
+ // TODO: 新版本路由系统要重新兼容ssr
8098
8210
  let targetPath = getNoHashMicroPathFromURL(this.appName, baseUrl);
8099
8211
  const defaultPagePath = this.getDefaultPage();
8100
8212
  if (!targetPath && defaultPagePath) {
@@ -8122,8 +8234,9 @@ function defineElement(tagName) {
8122
8234
  * @returns router-mode
8123
8235
  */
8124
8236
  getMemoryRouterMode() {
8125
- return getRouterMode(this.getAttribute('router-mode'),
8237
+ return initRouterMode(this.getAttribute('router-mode'),
8126
8238
  // is micro-app element set disable-memory-router, like <micro-app disable-memory-router></micro-app>
8239
+ // or <micro-app disable-memory-router='false'></micro-app>
8127
8240
  this.compatibleProperties('disable-memory-router') && this.compatibleDisableProperties('disable-memory-router'));
8128
8241
  }
8129
8242
  /**
@@ -8272,7 +8385,7 @@ function preFetchAction(options) {
8272
8385
  * 问题:
8273
8386
  * 1、如何确保子应用进行跳转时不影响到浏览器地址??pure??
8274
8387
  */
8275
- routerMode: getRouterMode(options['router-mode']),
8388
+ routerMode: initRouterMode(options['router-mode']),
8276
8389
  baseroute: options.baseroute,
8277
8390
  disablePatchRequest: options['disable-patch-request'],
8278
8391
  });
@@ -8309,7 +8422,7 @@ function getGlobalAssets(assets) {
8309
8422
  // TODO: requestIdleCallback for every file
8310
8423
  function fetchGlobalResources(resources, suffix, sourceHandler) {
8311
8424
  if (isArray(resources)) {
8312
- const effectiveResource = resources.filter((path) => isString(path) && path.includes(`.${suffix}`) && !sourceHandler.hasInfo(path));
8425
+ const effectiveResource = resources.filter((path) => isString(path) && isTargetExtension(path, suffix) && !sourceHandler.hasInfo(path));
8313
8426
  const fetchResourcePromise = effectiveResource.map((path) => fetchSource(path));
8314
8427
  // fetch resource with stream
8315
8428
  promiseStream(fetchResourcePromise, (res) => {