@micro-zoe/micro-app 0.8.0 → 0.8.1

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 = '0.8.0';
1
+ const version = '0.8.1';
2
2
  // do not use isUndefined
3
3
  const isBrowser = typeof window !== 'undefined';
4
4
  // do not use isUndefined
@@ -153,10 +153,10 @@ function CompletionPath(path, baseURI) {
153
153
  /**
154
154
  * Get the folder where the link resource is located,
155
155
  * which is used to complete the relative address in the css
156
- * @param linkpath full link address
156
+ * @param linkPath full link address
157
157
  */
158
- function getLinkFileDir(linkpath) {
159
- const pathArr = linkpath.split('/');
158
+ function getLinkFileDir(linkPath) {
159
+ const pathArr = linkPath.split('/');
160
160
  pathArr.pop();
161
161
  return addProtocol(pathArr.join('/') + '/');
162
162
  }
@@ -233,13 +233,10 @@ let currentMicroAppName = null;
233
233
  function setCurrentAppName(appName) {
234
234
  currentMicroAppName = appName;
235
235
  }
236
- let isWaitingForReset = false;
237
236
  function throttleDeferForSetAppName(appName) {
238
- if (!isWaitingForReset || currentMicroAppName !== appName) {
239
- isWaitingForReset = true;
237
+ if (currentMicroAppName !== appName) {
240
238
  setCurrentAppName(appName);
241
239
  defer(() => {
242
- isWaitingForReset = false;
243
240
  setCurrentAppName(null);
244
241
  });
245
242
  }
@@ -363,12 +360,12 @@ function fetchSource(url, appName = null, options = {}) {
363
360
  const rootSelectorREG = /(^|\s+)(html|:root)(?=[\s>~[.#:]+|$)/;
364
361
  const bodySelectorREG = /(^|\s+)((html[\s>~]+body)|body)(?=[\s>~[.#:]+|$)/;
365
362
  const cssUrlREG = /url\(["']?([^)"']+)["']?\)/gm;
366
- function parseError(msg, linkpath) {
367
- msg = linkpath ? `${linkpath}:${msg}` : msg;
363
+ function parseError(msg, linkPath) {
364
+ msg = linkPath ? `${linkPath}:${msg}` : msg;
368
365
  const err = new Error(msg);
369
366
  err.reason = msg;
370
- if (linkpath) {
371
- err.filename = linkpath;
367
+ if (linkPath) {
368
+ err.filename = linkPath;
372
369
  }
373
370
  throw err;
374
371
  }
@@ -376,14 +373,14 @@ function parseError(msg, linkpath) {
376
373
  * Reference resources https://github.com/reworkcss/css
377
374
  * CSSParser mainly deals with 3 scenes: styleRule, @, and comment
378
375
  * And scopecss deals with 2 scenes: selector & url
379
- * It can also disable scopecss with inline comments
376
+ * And can also disable scopecss with inline comments
380
377
  */
381
378
  class CSSParser {
382
379
  constructor() {
383
380
  this.cssText = ''; // css content
384
381
  this.prefix = ''; // prefix as micro-app[name=xxx]
385
382
  this.baseURI = ''; // domain name
386
- this.linkpath = ''; // link resource address, if it is the style converted from link, it will have linkpath
383
+ this.linkPath = ''; // link resource address, if it is the style converted from link, it will have linkPath
387
384
  this.result = ''; // parsed cssText
388
385
  this.scopecssDisable = false; // use block comments /* scopecss-disable */ to disable scopecss in your file, and use /* scopecss-enable */ to enable scopecss
389
386
  this.scopecssDisableSelectors = []; // disable or enable scopecss for specific selectors
@@ -401,16 +398,16 @@ class CSSParser {
401
398
  // https://developer.mozilla.org/en-US/docs/Web/API/CSSNamespaceRule
402
399
  this.namespaceRule = this.createMatcherForNoneBraceAtRule('namespace');
403
400
  }
404
- exec(cssText, prefix, baseURI, linkpath) {
401
+ exec(cssText, prefix, baseURI, linkPath) {
405
402
  this.cssText = cssText;
406
403
  this.prefix = prefix;
407
404
  this.baseURI = baseURI;
408
- this.linkpath = linkpath || '';
405
+ this.linkPath = linkPath || '';
409
406
  this.matchRules();
410
407
  return this.result;
411
408
  }
412
409
  reset() {
413
- this.cssText = this.prefix = this.baseURI = this.linkpath = this.result = '';
410
+ this.cssText = this.prefix = this.baseURI = this.linkPath = this.result = '';
414
411
  this.scopecssDisable = this.scopecssDisableNextLine = false;
415
412
  this.scopecssDisableSelectors = [];
416
413
  }
@@ -430,7 +427,7 @@ class CSSParser {
430
427
  // reset scopecssDisableNextLine
431
428
  this.scopecssDisableNextLine = false;
432
429
  if (!selectorList)
433
- return parseError('selector missing', this.linkpath);
430
+ return parseError('selector missing', this.linkPath);
434
431
  this.result += selectorList.join(', ');
435
432
  this.matchComments();
436
433
  this.styleDeclarations();
@@ -440,23 +437,23 @@ class CSSParser {
440
437
  // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
441
438
  styleDeclarations() {
442
439
  if (!this.matchOpenBrace())
443
- return parseError("Declaration missing '{'", this.linkpath);
440
+ return parseError("Declaration missing '{'", this.linkPath);
444
441
  this.matchComments();
445
442
  while (this.styleDeclaration()) {
446
443
  this.matchComments();
447
444
  }
448
445
  if (!this.matchCloseBrace())
449
- return parseError("Declaration missing '}'", this.linkpath);
446
+ return parseError("Declaration missing '}'", this.linkPath);
450
447
  return true;
451
448
  }
452
449
  // match one styleDeclaration at a time
453
450
  styleDeclaration() {
454
451
  // css property
455
- if (!this.commonMatch(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/))
452
+ if (!this.commonMatch(/^(\*?[-#+\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/))
456
453
  return false;
457
454
  // match :
458
455
  if (!this.commonMatch(/^:\s*/))
459
- return parseError("property missing ':'", this.linkpath);
456
+ return parseError("property missing ':'", this.linkPath);
460
457
  // match css value
461
458
  const r = this.commonMatch(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/, true);
462
459
  let cssValue = r ? r[0] : '';
@@ -467,8 +464,8 @@ class CSSParser {
467
464
  return all;
468
465
  }
469
466
  // ./a/b.png ../a/b.png a/b.png
470
- if (/^((\.\.?\/)|[^/])/.test($1) && this.linkpath) {
471
- this.baseURI = getLinkFileDir(this.linkpath);
467
+ if (/^((\.\.?\/)|[^/])/.test($1) && this.linkPath) {
468
+ this.baseURI = getLinkFileDir(this.linkPath);
472
469
  }
473
470
  return `url("${CompletionPath($1, this.baseURI)}")`;
474
471
  });
@@ -520,7 +517,7 @@ class CSSParser {
520
517
  this.scopecssDisableNextLine = false;
521
518
  return this.keyframesRule() ||
522
519
  this.mediaRule() ||
523
- this.custommediaRule() ||
520
+ this.customMediaRule() ||
524
521
  this.supportsRule() ||
525
522
  this.importRule() ||
526
523
  this.charsetRule() ||
@@ -528,23 +525,23 @@ class CSSParser {
528
525
  this.documentRule() ||
529
526
  this.pageRule() ||
530
527
  this.hostRule() ||
531
- this.fontfaceRule();
528
+ this.fontFaceRule();
532
529
  }
533
530
  // https://developer.mozilla.org/en-US/docs/Web/API/CSSKeyframesRule
534
531
  keyframesRule() {
535
532
  if (!this.commonMatch(/^@([-\w]+)?keyframes\s*/))
536
533
  return false;
537
534
  if (!this.commonMatch(/^([-\w]+)\s*/))
538
- return parseError('@keyframes missing name', this.linkpath);
535
+ return parseError('@keyframes missing name', this.linkPath);
539
536
  this.matchComments();
540
537
  if (!this.matchOpenBrace())
541
- return parseError("@keyframes missing '{'", this.linkpath);
538
+ return parseError("@keyframes missing '{'", this.linkPath);
542
539
  this.matchComments();
543
540
  while (this.keyframeRule()) {
544
541
  this.matchComments();
545
542
  }
546
543
  if (!this.matchCloseBrace())
547
- return parseError("@keyframes missing '}'", this.linkpath);
544
+ return parseError("@keyframes missing '}'", this.linkPath);
548
545
  this.matchLeadingSpaces();
549
546
  return true;
550
547
  }
@@ -562,7 +559,7 @@ class CSSParser {
562
559
  return true;
563
560
  }
564
561
  // https://github.com/postcss/postcss-custom-media
565
- custommediaRule() {
562
+ customMediaRule() {
566
563
  if (!this.commonMatch(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/))
567
564
  return false;
568
565
  this.matchLeadingSpaces();
@@ -578,7 +575,7 @@ class CSSParser {
578
575
  return this.commonHandlerForAtRuleWithSelfRule('page');
579
576
  }
580
577
  // https://developer.mozilla.org/en-US/docs/Web/API/CSSFontFaceRule
581
- fontfaceRule() {
578
+ fontFaceRule() {
582
579
  if (!this.commonMatch(/^@font-face\s*/))
583
580
  return false;
584
581
  return this.commonHandlerForAtRuleWithSelfRule('font-face');
@@ -589,11 +586,11 @@ class CSSParser {
589
586
  if (!this.commonMatch(reg))
590
587
  return false;
591
588
  if (!this.matchOpenBrace())
592
- return parseError(`@${name} missing '{'`, this.linkpath);
589
+ return parseError(`@${name} missing '{'`, this.linkPath);
593
590
  this.matchComments();
594
591
  this.matchRules();
595
592
  if (!this.matchCloseBrace())
596
- return parseError(`@${name} missing '}'`, this.linkpath);
593
+ return parseError(`@${name} missing '}'`, this.linkPath);
597
594
  this.matchLeadingSpaces();
598
595
  return true;
599
596
  };
@@ -611,13 +608,13 @@ class CSSParser {
611
608
  // common handler for @font-face, @page
612
609
  commonHandlerForAtRuleWithSelfRule(name) {
613
610
  if (!this.matchOpenBrace())
614
- return parseError(`@${name} missing '{'`, this.linkpath);
611
+ return parseError(`@${name} missing '{'`, this.linkPath);
615
612
  this.matchComments();
616
613
  while (this.styleDeclaration()) {
617
614
  this.matchComments();
618
615
  }
619
616
  if (!this.matchCloseBrace())
620
- return parseError(`@${name} missing '}'`, this.linkpath);
617
+ return parseError(`@${name} missing '}'`, this.linkPath);
621
618
  this.matchLeadingSpaces();
622
619
  return true;
623
620
  }
@@ -637,7 +634,7 @@ class CSSParser {
637
634
  ++i;
638
635
  i += 2;
639
636
  if (this.cssText.charAt(i - 1) === '') {
640
- return parseError('End of comment missing', this.linkpath);
637
+ return parseError('End of comment missing', this.linkPath);
641
638
  }
642
639
  // get comment content
643
640
  let commentText = this.cssText.slice(2, i - 2);
@@ -691,12 +688,12 @@ class CSSParser {
691
688
  /**
692
689
  * common method of bind CSS
693
690
  */
694
- function commonAction(styleElement, appName, prefix, baseURI, linkpath) {
691
+ function commonAction(styleElement, appName, prefix, baseURI, linkPath) {
695
692
  if (!styleElement.__MICRO_APP_HAS_SCOPED__) {
696
693
  styleElement.__MICRO_APP_HAS_SCOPED__ = true;
697
694
  let result = null;
698
695
  try {
699
- result = parser.exec(styleElement.textContent, prefix, baseURI, linkpath);
696
+ result = parser.exec(styleElement.textContent, prefix, baseURI, linkPath);
700
697
  parser.reset();
701
698
  }
702
699
  catch (e) {
@@ -883,7 +880,7 @@ function fetchLinkSuccess(url, info, data, microAppHead, app) {
883
880
  * @param originLink origin link element
884
881
  * @param replaceStyle style element which replaced origin link
885
882
  */
886
- function foramtDynamicLink(url, info, app, originLink, replaceStyle) {
883
+ function formatDynamicLink(url, info, app, originLink, replaceStyle) {
887
884
  if (app.source.links.has(url)) {
888
885
  replaceStyle.textContent = app.source.links.get(url).code;
889
886
  scopedCSS(replaceStyle, app);
@@ -912,2162 +909,2144 @@ function foramtDynamicLink(url, info, app, originLink, replaceStyle) {
912
909
  });
913
910
  }
914
911
 
915
- const globalEnv = {};
916
- /**
917
- * Note loop nesting
918
- * Only prototype or unique values can be put here
919
- */
920
- function initGlobalEnv() {
921
- if (isBrowser) {
922
- /**
923
- * save patch raw methods
924
- * pay attention to this binding
925
- */
926
- const rawSetAttribute = Element.prototype.setAttribute;
927
- const rawAppendChild = Element.prototype.appendChild;
928
- const rawInsertBefore = Element.prototype.insertBefore;
929
- const rawReplaceChild = Element.prototype.replaceChild;
930
- const rawRemoveChild = Element.prototype.removeChild;
931
- const rawAppend = Element.prototype.append;
932
- const rawPrepend = Element.prototype.prepend;
933
- const rawCloneNode = Element.prototype.cloneNode;
934
- const rawCreateElement = Document.prototype.createElement;
935
- const rawCreateElementNS = Document.prototype.createElementNS;
936
- const rawCreateDocumentFragment = Document.prototype.createDocumentFragment;
937
- const rawQuerySelector = Document.prototype.querySelector;
938
- const rawQuerySelectorAll = Document.prototype.querySelectorAll;
939
- const rawGetElementById = Document.prototype.getElementById;
940
- const rawGetElementsByClassName = Document.prototype.getElementsByClassName;
941
- const rawGetElementsByTagName = Document.prototype.getElementsByTagName;
942
- const rawGetElementsByName = Document.prototype.getElementsByName;
943
- const ImageProxy = new Proxy(Image, {
944
- construct(Target, args) {
945
- const elementImage = new Target(...args);
946
- elementImage.__MICRO_APP_NAME__ = getCurrentAppName();
947
- return elementImage;
948
- },
949
- });
950
- const rawWindow = Function('return window')();
951
- const rawDocument = Function('return document')();
952
- const supportModuleScript = isSupportModuleScript();
953
- /**
954
- * save effect raw methods
955
- * pay attention to this binding, especially setInterval, setTimeout, clearInterval, clearTimeout
956
- */
957
- const rawWindowAddEventListener = rawWindow.addEventListener;
958
- const rawWindowRemoveEventListener = rawWindow.removeEventListener;
959
- const rawSetInterval = rawWindow.setInterval;
960
- const rawSetTimeout = rawWindow.setTimeout;
961
- const rawClearInterval = rawWindow.clearInterval;
962
- const rawClearTimeout = rawWindow.clearTimeout;
963
- const rawDocumentAddEventListener = rawDocument.addEventListener;
964
- const rawDocumentRemoveEventListener = rawDocument.removeEventListener;
965
- // mark current application as base application
966
- window.__MICRO_APP_BASE_APPLICATION__ = true;
967
- Object.assign(globalEnv, {
968
- // source/patch
969
- rawSetAttribute,
970
- rawAppendChild,
971
- rawInsertBefore,
972
- rawReplaceChild,
973
- rawRemoveChild,
974
- rawAppend,
975
- rawPrepend,
976
- rawCloneNode,
977
- rawCreateElement,
978
- rawCreateElementNS,
979
- rawCreateDocumentFragment,
980
- rawQuerySelector,
981
- rawQuerySelectorAll,
982
- rawGetElementById,
983
- rawGetElementsByClassName,
984
- rawGetElementsByTagName,
985
- rawGetElementsByName,
986
- ImageProxy,
987
- // common global vars
988
- rawWindow,
989
- rawDocument,
990
- supportModuleScript,
991
- // sandbox/effect
992
- rawWindowAddEventListener,
993
- rawWindowRemoveEventListener,
994
- rawSetInterval,
995
- rawSetTimeout,
996
- rawClearInterval,
997
- rawClearTimeout,
998
- rawDocumentAddEventListener,
999
- rawDocumentRemoveEventListener,
1000
- });
1001
- }
1002
- }
1003
-
1004
- // Global scripts, reuse across apps
1005
- const globalScripts = new Map();
912
+ // Record element and map element
913
+ const dynamicElementInMicroAppMap = new WeakMap();
1006
914
  /**
1007
- * Extract script elements
1008
- * @param script script element
1009
- * @param parent parent element of script
915
+ * Process the new node and format the style, link and script element
916
+ * @param parent parent node
917
+ * @param child new node
1010
918
  * @param app app
1011
- * @param isDynamic dynamic insert
1012
919
  */
1013
- function extractScriptElement(script, parent, app, isDynamic = false) {
1014
- let replaceComment = null;
1015
- let src = script.getAttribute('src');
1016
- if (script.hasAttribute('exclude')) {
1017
- replaceComment = document.createComment('script element with exclude attribute removed by micro-app');
1018
- }
1019
- else if ((script.type && !['text/javascript', 'text/ecmascript', 'application/javascript', 'application/ecmascript', 'module'].includes(script.type)) ||
1020
- script.hasAttribute('ignore')) {
1021
- return null;
1022
- }
1023
- else if ((globalEnv.supportModuleScript && script.noModule) ||
1024
- (!globalEnv.supportModuleScript && script.type === 'module')) {
1025
- replaceComment = document.createComment(`${script.noModule ? 'noModule' : 'module'} script ignored by micro-app`);
1026
- }
1027
- else if (src) { // remote script
1028
- src = CompletionPath(src, app.url);
1029
- const info = {
1030
- code: '',
1031
- isExternal: true,
1032
- isDynamic: isDynamic,
1033
- async: script.hasAttribute('async'),
1034
- defer: script.defer || script.type === 'module',
1035
- module: script.type === 'module',
1036
- isGlobal: script.hasAttribute('global'),
1037
- };
1038
- if (!isDynamic) {
1039
- app.source.scripts.set(src, info);
1040
- replaceComment = document.createComment(`script with src='${src}' extract by micro-app`);
920
+ function handleNewNode(parent, child, app) {
921
+ if (child instanceof HTMLStyleElement) {
922
+ if (child.hasAttribute('exclude')) {
923
+ const replaceComment = document.createComment('style element with exclude attribute ignored by micro-app');
924
+ dynamicElementInMicroAppMap.set(child, replaceComment);
925
+ return replaceComment;
1041
926
  }
1042
- else {
1043
- return { url: src, info };
927
+ else if (app.scopecss && !child.hasAttribute('ignore')) {
928
+ return scopedCSS(child, app);
1044
929
  }
930
+ return child;
1045
931
  }
1046
- else if (script.textContent) { // inline script
1047
- const nonceStr = createNonceSrc();
1048
- const info = {
1049
- code: script.textContent,
1050
- isExternal: false,
1051
- isDynamic: isDynamic,
1052
- async: false,
1053
- defer: script.type === 'module',
1054
- module: script.type === 'module',
1055
- };
1056
- if (!isDynamic) {
1057
- app.source.scripts.set(nonceStr, info);
1058
- replaceComment = document.createComment('inline script extract by micro-app');
932
+ else if (child instanceof HTMLLinkElement) {
933
+ if (child.hasAttribute('exclude')) {
934
+ const linkReplaceComment = document.createComment('link element with exclude attribute ignored by micro-app');
935
+ dynamicElementInMicroAppMap.set(child, linkReplaceComment);
936
+ return linkReplaceComment;
1059
937
  }
1060
- else {
1061
- return { url: nonceStr, info };
938
+ else if (child.hasAttribute('ignore')) {
939
+ return child;
1062
940
  }
941
+ const { url, info, replaceComment } = extractLinkFromHtml(child, parent, app, true);
942
+ if (url && info) {
943
+ const replaceStyle = pureCreateElement('style');
944
+ replaceStyle.__MICRO_APP_LINK_PATH__ = url;
945
+ formatDynamicLink(url, info, app, child, replaceStyle);
946
+ dynamicElementInMicroAppMap.set(child, replaceStyle);
947
+ return replaceStyle;
948
+ }
949
+ else if (replaceComment) {
950
+ dynamicElementInMicroAppMap.set(child, replaceComment);
951
+ return replaceComment;
952
+ }
953
+ return child;
1063
954
  }
1064
- else if (!isDynamic) {
1065
- /**
1066
- * script with empty src or empty script.textContent remove in static html
1067
- * & not removed if it created by dynamic
1068
- */
1069
- replaceComment = document.createComment('script element removed by micro-app');
1070
- }
1071
- if (isDynamic) {
1072
- return { replaceComment };
1073
- }
1074
- else {
1075
- return parent.replaceChild(replaceComment, script);
1076
- }
1077
- }
1078
- /**
1079
- * Get remote resources of script
1080
- * @param wrapElement htmlDom
1081
- * @param app app
1082
- */
1083
- function fetchScriptsFromHtml(wrapElement, app) {
1084
- const scriptEntries = Array.from(app.source.scripts.entries());
1085
- const fetchScriptPromise = [];
1086
- const fetchScriptPromiseInfo = [];
1087
- for (const [url, info] of scriptEntries) {
1088
- if (info.isExternal) {
1089
- const globalScriptText = globalScripts.get(url);
1090
- if (globalScriptText) {
1091
- info.code = globalScriptText;
955
+ else if (child instanceof HTMLScriptElement) {
956
+ const { replaceComment, url, info } = extractScriptElement(child, parent, app, true) || {};
957
+ if (url && info) {
958
+ if (!info.isExternal) { // inline script
959
+ const replaceElement = runScript(url, app, info, true);
960
+ dynamicElementInMicroAppMap.set(child, replaceElement);
961
+ return replaceElement;
1092
962
  }
1093
- else if (!info.defer && !info.async) {
1094
- fetchScriptPromise.push(fetchSource(url, app.name));
1095
- fetchScriptPromiseInfo.push([url, info]);
963
+ else { // remote script
964
+ const replaceElement = runDynamicRemoteScript(url, info, app, child);
965
+ dynamicElementInMicroAppMap.set(child, replaceElement);
966
+ return replaceElement;
1096
967
  }
1097
968
  }
969
+ else if (replaceComment) {
970
+ dynamicElementInMicroAppMap.set(child, replaceComment);
971
+ return replaceComment;
972
+ }
973
+ return child;
1098
974
  }
1099
- if (fetchScriptPromise.length) {
1100
- promiseStream(fetchScriptPromise, (res) => {
1101
- fetchScriptSuccess(fetchScriptPromiseInfo[res.index][0], fetchScriptPromiseInfo[res.index][1], res.data);
1102
- }, (err) => {
1103
- logError(err, app.name);
1104
- }, () => {
1105
- app.onLoad(wrapElement);
1106
- });
1107
- }
1108
- else {
1109
- app.onLoad(wrapElement);
1110
- }
1111
- }
1112
- /**
1113
- * fetch js succeeded, record the code value
1114
- * @param url script address
1115
- * @param info resource script info
1116
- * @param data code
1117
- */
1118
- function fetchScriptSuccess(url, info, data) {
1119
- if (info.isGlobal && !globalScripts.has(url)) {
1120
- globalScripts.set(url, data);
1121
- }
1122
- info.code = data;
975
+ return child;
1123
976
  }
1124
977
  /**
1125
- * Execute js in the mount lifecycle
1126
- * @param scriptList script list
978
+ * Handle the elements inserted into head and body, and execute normally in other cases
1127
979
  * @param app app
1128
- * @param initedHook callback for umd mode
980
+ * @param method raw method
981
+ * @param parent parent node
982
+ * @param targetChild target node
983
+ * @param passiveChild second param of insertBefore and replaceChild
1129
984
  */
1130
- function execScripts(scriptList, app, initedHook) {
1131
- const scriptListEntries = Array.from(scriptList.entries());
1132
- const deferScriptPromise = [];
1133
- const deferScriptInfo = [];
1134
- for (const [url, info] of scriptListEntries) {
1135
- if (!info.isDynamic) {
1136
- // Notice the second render
1137
- if (info.defer || info.async) {
1138
- if (info.isExternal && !info.code) {
1139
- deferScriptPromise.push(fetchSource(url, app.name));
1140
- }
1141
- else {
1142
- deferScriptPromise.push(info.code);
1143
- }
1144
- deferScriptInfo.push([url, info]);
1145
- info.module && (initedHook.moduleCount = initedHook.moduleCount ? ++initedHook.moduleCount : 1);
1146
- }
1147
- else {
1148
- runScript(url, app, info, false);
1149
- initedHook(false);
985
+ function invokePrototypeMethod(app, rawMethod, parent, targetChild, passiveChild) {
986
+ /**
987
+ * If passiveChild is not the child node, insertBefore replaceChild will have a problem, at this time, it will be degraded to appendChild
988
+ * E.g: document.head.insertBefore(targetChild, document.head.childNodes[0])
989
+ */
990
+ if (parent === document.head) {
991
+ const microAppHead = app.container.querySelector('micro-app-head');
992
+ /**
993
+ * 1. If passiveChild exists, it must be insertBefore or replaceChild
994
+ * 2. When removeChild, targetChild may not be in microAppHead or head
995
+ */
996
+ if (passiveChild && !microAppHead.contains(passiveChild)) {
997
+ return globalEnv.rawAppendChild.call(microAppHead, targetChild);
998
+ }
999
+ else if (rawMethod === globalEnv.rawRemoveChild && !microAppHead.contains(targetChild)) {
1000
+ if (parent.contains(targetChild)) {
1001
+ return rawMethod.call(parent, targetChild);
1150
1002
  }
1003
+ return targetChild;
1004
+ }
1005
+ else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
1006
+ return rawMethod.call(microAppHead, targetChild);
1151
1007
  }
1008
+ return rawMethod.call(microAppHead, targetChild, passiveChild);
1152
1009
  }
1153
- if (deferScriptPromise.length) {
1154
- promiseStream(deferScriptPromise, (res) => {
1155
- const info = deferScriptInfo[res.index][1];
1156
- info.code = info.code || res.data;
1157
- }, (err) => {
1158
- initedHook.errorCount = initedHook.errorCount ? ++initedHook.errorCount : 1;
1159
- logError(err, app.name);
1160
- }, () => {
1161
- deferScriptInfo.forEach(([url, info]) => {
1162
- if (info.code) {
1163
- runScript(url, app, info, false, initedHook);
1164
- !info.module && initedHook(false);
1165
- }
1166
- });
1167
- initedHook(isUndefined(initedHook.moduleCount) ||
1168
- initedHook.errorCount === deferScriptPromise.length);
1169
- });
1010
+ else if (parent === document.body) {
1011
+ const microAppBody = app.container.querySelector('micro-app-body');
1012
+ if (passiveChild && !microAppBody.contains(passiveChild)) {
1013
+ return globalEnv.rawAppendChild.call(microAppBody, targetChild);
1014
+ }
1015
+ else if (rawMethod === globalEnv.rawRemoveChild && !microAppBody.contains(targetChild)) {
1016
+ if (parent.contains(targetChild)) {
1017
+ return rawMethod.call(parent, targetChild);
1018
+ }
1019
+ return targetChild;
1020
+ }
1021
+ else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
1022
+ return rawMethod.call(microAppBody, targetChild);
1023
+ }
1024
+ return rawMethod.call(microAppBody, targetChild, passiveChild);
1170
1025
  }
1171
- else {
1172
- initedHook(true);
1026
+ else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
1027
+ return rawMethod.call(parent, targetChild);
1173
1028
  }
1029
+ return rawMethod.call(parent, targetChild, passiveChild);
1030
+ }
1031
+ // Get the map element
1032
+ function getMappingNode(node) {
1033
+ var _a;
1034
+ return (_a = dynamicElementInMicroAppMap.get(node)) !== null && _a !== void 0 ? _a : node;
1174
1035
  }
1175
1036
  /**
1176
- * run code
1177
- * @param url script address
1178
- * @param app app
1179
- * @param info script info
1180
- * @param isDynamic dynamically created script
1181
- * @param callback callback of module script
1037
+ * method of handle new node
1038
+ * @param parent parent node
1039
+ * @param newChild new node
1040
+ * @param passiveChild passive node
1041
+ * @param rawMethod method
1182
1042
  */
1183
- function runScript(url, app, info, isDynamic, callback) {
1184
- var _a;
1185
- try {
1186
- const code = bindScope(url, app, info.code, info.module);
1187
- if (app.inline || info.module) {
1188
- const scriptElement = pureCreateElement('script');
1189
- runCode2InlineScript(url, code, info.module, scriptElement, callback);
1190
- if (isDynamic)
1191
- return scriptElement;
1192
- // TEST IGNORE
1193
- (_a = app.container) === null || _a === void 0 ? void 0 : _a.querySelector('micro-app-body').appendChild(scriptElement);
1043
+ function commonElementHandler(parent, newChild, passiveChild, rawMethod) {
1044
+ if (newChild === null || newChild === void 0 ? void 0 : newChild.__MICRO_APP_NAME__) {
1045
+ const app = appInstanceMap.get(newChild.__MICRO_APP_NAME__);
1046
+ if (app === null || app === void 0 ? void 0 : app.container) {
1047
+ return invokePrototypeMethod(app, rawMethod, parent, handleNewNode(parent, newChild, app), passiveChild && getMappingNode(passiveChild));
1194
1048
  }
1195
- else {
1196
- runCode2Function(code, info);
1197
- if (isDynamic)
1198
- return document.createComment('dynamic script extract by micro-app');
1049
+ else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
1050
+ return rawMethod.call(parent, newChild);
1199
1051
  }
1052
+ return rawMethod.call(parent, newChild, passiveChild);
1200
1053
  }
1201
- catch (e) {
1202
- console.error(`[micro-app from runScript] app ${app.name}: `, e);
1054
+ else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
1055
+ const appName = getCurrentAppName();
1056
+ if (!(newChild instanceof Node) && appName) {
1057
+ const app = appInstanceMap.get(appName);
1058
+ if (app === null || app === void 0 ? void 0 : app.container) {
1059
+ if (parent === document.head) {
1060
+ return rawMethod.call(app.container.querySelector('micro-app-head'), newChild);
1061
+ }
1062
+ else if (parent === document.body) {
1063
+ return rawMethod.call(app.container.querySelector('micro-app-body'), newChild);
1064
+ }
1065
+ }
1066
+ }
1067
+ return rawMethod.call(parent, newChild);
1203
1068
  }
1069
+ return rawMethod.call(parent, newChild, passiveChild);
1204
1070
  }
1205
1071
  /**
1206
- * Get dynamically created remote script
1207
- * @param url script address
1208
- * @param info info
1209
- * @param app app
1210
- * @param originScript origin script element
1072
+ * Rewrite element prototype method
1211
1073
  */
1212
- function runDynamicRemoteScript(url, info, app, originScript) {
1213
- const dispatchScriptOnLoadEvent = () => dispatchOnLoadEvent(originScript);
1214
- // url is unique
1215
- if (app.source.scripts.has(url)) {
1216
- const existInfo = app.source.scripts.get(url);
1217
- !existInfo.module && defer(dispatchScriptOnLoadEvent);
1218
- return runScript(url, app, existInfo, true, dispatchScriptOnLoadEvent);
1219
- }
1220
- if (globalScripts.has(url)) {
1221
- const code = globalScripts.get(url);
1222
- info.code = code;
1223
- app.source.scripts.set(url, info);
1224
- !info.module && defer(dispatchScriptOnLoadEvent);
1225
- return runScript(url, app, info, true, dispatchScriptOnLoadEvent);
1226
- }
1227
- let replaceElement;
1228
- if (app.inline || info.module) {
1229
- replaceElement = pureCreateElement('script');
1230
- }
1231
- else {
1232
- replaceElement = document.createComment(`dynamic script with src='${url}' extract by micro-app`);
1233
- }
1234
- fetchSource(url, app.name).then((code) => {
1235
- info.code = code;
1236
- app.source.scripts.set(url, info);
1237
- info.isGlobal && globalScripts.set(url, code);
1238
- try {
1239
- code = bindScope(url, app, code, info.module);
1240
- if (app.inline || info.module) {
1241
- runCode2InlineScript(url, code, info.module, replaceElement, dispatchScriptOnLoadEvent);
1242
- }
1243
- else {
1244
- runCode2Function(code, info);
1245
- }
1074
+ function patchElementPrototypeMethods() {
1075
+ patchDocument();
1076
+ // prototype methods of add element👇
1077
+ Element.prototype.appendChild = function appendChild(newChild) {
1078
+ return commonElementHandler(this, newChild, null, globalEnv.rawAppendChild);
1079
+ };
1080
+ Element.prototype.insertBefore = function insertBefore(newChild, refChild) {
1081
+ return commonElementHandler(this, newChild, refChild, globalEnv.rawInsertBefore);
1082
+ };
1083
+ Element.prototype.replaceChild = function replaceChild(newChild, oldChild) {
1084
+ return commonElementHandler(this, newChild, oldChild, globalEnv.rawReplaceChild);
1085
+ };
1086
+ Element.prototype.append = function append(...nodes) {
1087
+ let i = 0;
1088
+ const length = nodes.length;
1089
+ while (i < length) {
1090
+ commonElementHandler(this, nodes[i], null, globalEnv.rawAppend);
1091
+ i++;
1246
1092
  }
1247
- catch (e) {
1248
- console.error(`[micro-app from runDynamicScript] app ${app.name}: `, e, url);
1093
+ };
1094
+ Element.prototype.prepend = function prepend(...nodes) {
1095
+ let i = nodes.length;
1096
+ while (i > 0) {
1097
+ commonElementHandler(this, nodes[i - 1], null, globalEnv.rawPrepend);
1098
+ i--;
1249
1099
  }
1250
- !info.module && dispatchOnLoadEvent(originScript);
1251
- }).catch((err) => {
1252
- logError(err, app.name);
1253
- dispatchOnErrorEvent(originScript);
1254
- });
1255
- return replaceElement;
1256
- }
1257
- /**
1258
- * common handle for inline script
1259
- * @param url script address
1260
- * @param code bound code
1261
- * @param module type='module' of script
1262
- * @param scriptElement target script element
1263
- * @param callback callback of module script
1264
- */
1265
- function runCode2InlineScript(url, code, module, scriptElement, callback) {
1266
- if (module) {
1267
- // module script is async, transform it to a blob for subsequent operations
1268
- const blob = new Blob([code], { type: 'text/javascript' });
1269
- scriptElement.src = URL.createObjectURL(blob);
1270
- scriptElement.setAttribute('type', 'module');
1271
- if (callback) {
1272
- callback.moduleCount && callback.moduleCount--;
1273
- scriptElement.onload = callback.bind(scriptElement, callback.moduleCount === 0);
1100
+ };
1101
+ // prototype methods of delete element👇
1102
+ Element.prototype.removeChild = function removeChild(oldChild) {
1103
+ if (oldChild === null || oldChild === void 0 ? void 0 : oldChild.__MICRO_APP_NAME__) {
1104
+ const app = appInstanceMap.get(oldChild.__MICRO_APP_NAME__);
1105
+ if (app === null || app === void 0 ? void 0 : app.container) {
1106
+ return invokePrototypeMethod(app, globalEnv.rawRemoveChild, this, getMappingNode(oldChild));
1107
+ }
1108
+ return globalEnv.rawRemoveChild.call(this, oldChild);
1274
1109
  }
1275
- }
1276
- else {
1277
- scriptElement.textContent = code;
1278
- }
1279
- if (!url.startsWith('inline-')) {
1280
- scriptElement.setAttribute('data-origin-src', url);
1281
- }
1282
- }
1283
- // init & run code2Function
1284
- function runCode2Function(code, info) {
1285
- if (!info.code2Function) {
1286
- info.code2Function = new Function(code);
1287
- }
1288
- info.code2Function.call(window);
1110
+ return globalEnv.rawRemoveChild.call(this, oldChild);
1111
+ };
1112
+ // patch cloneNode
1113
+ Element.prototype.cloneNode = function cloneNode(deep) {
1114
+ const clonedNode = globalEnv.rawCloneNode.call(this, deep);
1115
+ this.__MICRO_APP_NAME__ && (clonedNode.__MICRO_APP_NAME__ = this.__MICRO_APP_NAME__);
1116
+ return clonedNode;
1117
+ };
1289
1118
  }
1290
1119
  /**
1291
- * bind js scope
1292
- * @param url script address
1293
- * @param app app
1294
- * @param code code
1295
- * @param module type='module' of script
1120
+ * Mark the newly created element in the micro application
1121
+ * @param element new element
1296
1122
  */
1297
- function bindScope(url, app, code, module) {
1298
- if (isPlainObject(microApp.plugins)) {
1299
- code = usePlugins(url, code, app.name, microApp.plugins);
1300
- }
1301
- if (app.sandBox && !module) {
1302
- globalEnv.rawWindow.__MICRO_APP_PROXY_WINDOW__ = app.sandBox.proxyWindow;
1303
- return `;(function(proxyWindow){with(proxyWindow.__MICRO_APP_WINDOW__){(function(${globalKeyToBeCached}){;${code}\n}).call(proxyWindow,${globalKeyToBeCached})}})(window.__MICRO_APP_PROXY_WINDOW__);`;
1304
- }
1305
- return code;
1123
+ function markElement(element) {
1124
+ const appName = getCurrentAppName();
1125
+ appName && (element.__MICRO_APP_NAME__ = appName);
1126
+ return element;
1306
1127
  }
1307
- /**
1308
- * Call the plugin to process the file
1309
- * @param url script address
1310
- * @param code code
1311
- * @param appName app name
1312
- * @param plugins plugin list
1313
- */
1314
- function usePlugins(url, code, appName, plugins) {
1315
- var _a;
1316
- if (isArray(plugins.global)) {
1317
- for (const plugin of plugins.global) {
1318
- if (isPlainObject(plugin) && isFunction(plugin.loader)) {
1319
- code = plugin.loader(code, url, plugin.options);
1320
- }
1128
+ // methods of document
1129
+ function patchDocument() {
1130
+ const rawDocument = globalEnv.rawDocument;
1131
+ // create element 👇
1132
+ Document.prototype.createElement = function createElement(tagName, options) {
1133
+ const element = globalEnv.rawCreateElement.call(this, tagName, options);
1134
+ return markElement(element);
1135
+ };
1136
+ Document.prototype.createElementNS = function createElementNS(namespaceURI, name, options) {
1137
+ const element = globalEnv.rawCreateElementNS.call(this, namespaceURI, name, options);
1138
+ return markElement(element);
1139
+ };
1140
+ Document.prototype.createDocumentFragment = function createDocumentFragment() {
1141
+ const element = globalEnv.rawCreateDocumentFragment.call(this);
1142
+ return markElement(element);
1143
+ };
1144
+ // query element👇
1145
+ function querySelector(selectors) {
1146
+ var _a, _b, _c;
1147
+ const appName = getCurrentAppName();
1148
+ if (!appName ||
1149
+ !selectors ||
1150
+ isUniqueElement(selectors) ||
1151
+ // see https://github.com/micro-zoe/micro-app/issues/56
1152
+ rawDocument !== this) {
1153
+ return globalEnv.rawQuerySelector.call(this, selectors);
1321
1154
  }
1155
+ return (_c = (_b = (_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.container) === null || _b === void 0 ? void 0 : _b.querySelector(selectors)) !== null && _c !== void 0 ? _c : null;
1322
1156
  }
1323
- if (isArray((_a = plugins.modules) === null || _a === void 0 ? void 0 : _a[appName])) {
1324
- for (const plugin of plugins.modules[appName]) {
1325
- if (isPlainObject(plugin) && isFunction(plugin.loader)) {
1326
- code = plugin.loader(code, url, plugin.options);
1327
- }
1157
+ function querySelectorAll(selectors) {
1158
+ var _a, _b, _c;
1159
+ const appName = getCurrentAppName();
1160
+ if (!appName ||
1161
+ !selectors ||
1162
+ isUniqueElement(selectors) ||
1163
+ rawDocument !== this) {
1164
+ return globalEnv.rawQuerySelectorAll.call(this, selectors);
1328
1165
  }
1166
+ return (_c = (_b = (_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.container) === null || _b === void 0 ? void 0 : _b.querySelectorAll(selectors)) !== null && _c !== void 0 ? _c : [];
1329
1167
  }
1330
- return code;
1331
- }
1332
-
1333
- /**
1334
- * transform html string to dom
1335
- * @param str string dom
1336
- */
1337
- function getWrapElement(str) {
1338
- const wrapDiv = pureCreateElement('div');
1339
- wrapDiv.innerHTML = str;
1340
- return wrapDiv;
1168
+ Document.prototype.querySelector = querySelector;
1169
+ Document.prototype.querySelectorAll = querySelectorAll;
1170
+ Document.prototype.getElementById = function getElementById(key) {
1171
+ if (!getCurrentAppName() || isInvalidQuerySelectorKey(key)) {
1172
+ return globalEnv.rawGetElementById.call(this, key);
1173
+ }
1174
+ try {
1175
+ return querySelector.call(this, `#${key}`);
1176
+ }
1177
+ catch (_a) {
1178
+ return globalEnv.rawGetElementById.call(this, key);
1179
+ }
1180
+ };
1181
+ Document.prototype.getElementsByClassName = function getElementsByClassName(key) {
1182
+ if (!getCurrentAppName() || isInvalidQuerySelectorKey(key)) {
1183
+ return globalEnv.rawGetElementsByClassName.call(this, key);
1184
+ }
1185
+ try {
1186
+ return querySelectorAll.call(this, `.${key}`);
1187
+ }
1188
+ catch (_a) {
1189
+ return globalEnv.rawGetElementsByClassName.call(this, key);
1190
+ }
1191
+ };
1192
+ Document.prototype.getElementsByTagName = function getElementsByTagName(key) {
1193
+ var _a;
1194
+ const appName = getCurrentAppName();
1195
+ if (!appName ||
1196
+ isUniqueElement(key) ||
1197
+ isInvalidQuerySelectorKey(key) ||
1198
+ (!((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.inline) && /^script$/i.test(key))) {
1199
+ return globalEnv.rawGetElementsByTagName.call(this, key);
1200
+ }
1201
+ try {
1202
+ return querySelectorAll.call(this, key);
1203
+ }
1204
+ catch (_b) {
1205
+ return globalEnv.rawGetElementsByTagName.call(this, key);
1206
+ }
1207
+ };
1208
+ Document.prototype.getElementsByName = function getElementsByName(key) {
1209
+ if (!getCurrentAppName() || isInvalidQuerySelectorKey(key)) {
1210
+ return globalEnv.rawGetElementsByName.call(this, key);
1211
+ }
1212
+ try {
1213
+ return querySelectorAll.call(this, `[name=${key}]`);
1214
+ }
1215
+ catch (_a) {
1216
+ return globalEnv.rawGetElementsByName.call(this, key);
1217
+ }
1218
+ };
1341
1219
  }
1342
1220
  /**
1343
- * Recursively process each child element
1344
- * @param parent parent element
1345
- * @param app app
1346
- * @param microAppHead micro-app-head element
1221
+ * patchSetAttribute is different from other patch
1222
+ * it not dependent on sandbox
1223
+ * it should exec when micro-app first created & release when all app unmounted
1347
1224
  */
1348
- function flatChildren(parent, app, microAppHead) {
1349
- const children = Array.from(parent.children);
1350
- children.length && children.forEach((child) => {
1351
- flatChildren(child, app);
1352
- });
1353
- for (const dom of children) {
1354
- if (dom instanceof HTMLLinkElement) {
1355
- if (dom.hasAttribute('exclude')) {
1356
- parent.replaceChild(document.createComment('link element with exclude attribute ignored by micro-app'), dom);
1357
- }
1358
- else if (!dom.hasAttribute('ignore')) {
1359
- extractLinkFromHtml(dom, parent, app);
1360
- }
1361
- else if (dom.hasAttribute('href')) {
1362
- dom.setAttribute('href', CompletionPath(dom.getAttribute('href'), app.url));
1363
- }
1364
- }
1365
- else if (dom instanceof HTMLStyleElement) {
1366
- if (dom.hasAttribute('exclude')) {
1367
- parent.replaceChild(document.createComment('style element with exclude attribute ignored by micro-app'), dom);
1225
+ let hasRewriteSetAttribute = false;
1226
+ function patchSetAttribute() {
1227
+ if (hasRewriteSetAttribute)
1228
+ return;
1229
+ hasRewriteSetAttribute = true;
1230
+ Element.prototype.setAttribute = function setAttribute(key, value) {
1231
+ if (/^micro-app(-\S+)?/i.test(this.tagName) && key === 'data') {
1232
+ if (isPlainObject(value)) {
1233
+ const cloneValue = {};
1234
+ Object.getOwnPropertyNames(value).forEach((propertyKey) => {
1235
+ if (!(isString(propertyKey) && propertyKey.indexOf('__') === 0)) {
1236
+ // @ts-ignore
1237
+ cloneValue[propertyKey] = value[propertyKey];
1238
+ }
1239
+ });
1240
+ this.data = cloneValue;
1368
1241
  }
1369
- else if (app.scopecss && !dom.hasAttribute('ignore')) {
1370
- scopedCSS(dom, app);
1242
+ else if (value !== '[object Object]') {
1243
+ logWarn('property data must be an object', this.getAttribute('name'));
1371
1244
  }
1372
1245
  }
1373
- else if (dom instanceof HTMLScriptElement) {
1374
- extractScriptElement(dom, parent, app);
1375
- }
1376
- else if (dom instanceof HTMLMetaElement || dom instanceof HTMLTitleElement) {
1377
- parent.removeChild(dom);
1246
+ else if ((((key === 'src' || key === 'srcset') && /^(img|script)$/i.test(this.tagName)) ||
1247
+ (key === 'href' && /^link$/i.test(this.tagName))) &&
1248
+ this.__MICRO_APP_NAME__ &&
1249
+ appInstanceMap.has(this.__MICRO_APP_NAME__)) {
1250
+ const app = appInstanceMap.get(this.__MICRO_APP_NAME__);
1251
+ globalEnv.rawSetAttribute.call(this, key, CompletionPath(value, app.url));
1378
1252
  }
1379
- else if (dom instanceof HTMLImageElement && dom.hasAttribute('src')) {
1380
- dom.setAttribute('src', CompletionPath(dom.getAttribute('src'), app.url));
1253
+ else {
1254
+ globalEnv.rawSetAttribute.call(this, key, value);
1381
1255
  }
1382
- }
1256
+ };
1383
1257
  }
1384
- /**
1385
- * Extract link and script, bind style scope
1386
- * @param htmlStr html string
1387
- * @param app app
1388
- */
1389
- function extractSourceDom(htmlStr, app) {
1390
- const wrapElement = getWrapElement(htmlStr);
1391
- const microAppHead = wrapElement.querySelector('micro-app-head');
1392
- const microAppBody = wrapElement.querySelector('micro-app-body');
1393
- if (!microAppHead || !microAppBody) {
1394
- const msg = `element ${microAppHead ? 'body' : 'head'} is missing`;
1395
- app.onerror(new Error(msg));
1396
- return logError(msg, app.name);
1397
- }
1398
- flatChildren(wrapElement, app);
1399
- if (app.source.links.size) {
1400
- fetchLinksFromHtml(wrapElement, app, microAppHead);
1401
- }
1402
- else {
1403
- app.onLoad(wrapElement);
1258
+ function releasePatchSetAttribute() {
1259
+ hasRewriteSetAttribute = false;
1260
+ Element.prototype.setAttribute = globalEnv.rawSetAttribute;
1261
+ }
1262
+ function releasePatchDocument() {
1263
+ Document.prototype.createElement = globalEnv.rawCreateElement;
1264
+ Document.prototype.createElementNS = globalEnv.rawCreateElementNS;
1265
+ Document.prototype.createDocumentFragment = globalEnv.rawCreateDocumentFragment;
1266
+ Document.prototype.querySelector = globalEnv.rawQuerySelector;
1267
+ Document.prototype.querySelectorAll = globalEnv.rawQuerySelectorAll;
1268
+ Document.prototype.getElementById = globalEnv.rawGetElementById;
1269
+ Document.prototype.getElementsByClassName = globalEnv.rawGetElementsByClassName;
1270
+ Document.prototype.getElementsByTagName = globalEnv.rawGetElementsByTagName;
1271
+ Document.prototype.getElementsByName = globalEnv.rawGetElementsByName;
1272
+ }
1273
+ // release patch
1274
+ function releasePatches() {
1275
+ setCurrentAppName(null);
1276
+ releasePatchDocument();
1277
+ Element.prototype.appendChild = globalEnv.rawAppendChild;
1278
+ Element.prototype.insertBefore = globalEnv.rawInsertBefore;
1279
+ Element.prototype.replaceChild = globalEnv.rawReplaceChild;
1280
+ Element.prototype.removeChild = globalEnv.rawRemoveChild;
1281
+ Element.prototype.append = globalEnv.rawAppend;
1282
+ Element.prototype.prepend = globalEnv.rawPrepend;
1283
+ Element.prototype.cloneNode = globalEnv.rawCloneNode;
1284
+ }
1285
+ // Set the style of micro-app-head and micro-app-body
1286
+ let hasRejectMicroAppStyle = false;
1287
+ function rejectMicroAppStyle() {
1288
+ if (!hasRejectMicroAppStyle) {
1289
+ hasRejectMicroAppStyle = true;
1290
+ const style = pureCreateElement('style');
1291
+ globalEnv.rawSetAttribute.call(style, 'type', 'text/css');
1292
+ style.textContent = `\n${microApp.tagName}, micro-app-body { display: block; } \nmicro-app-head { display: none; }`;
1293
+ globalEnv.rawDocument.head.appendChild(style);
1404
1294
  }
1405
- if (app.source.scripts.size) {
1406
- fetchScriptsFromHtml(wrapElement, app);
1295
+ }
1296
+
1297
+ function unmountNestedApp() {
1298
+ releaseUnmountOfNestedApp();
1299
+ appInstanceMap.forEach(app => {
1300
+ // @ts-ignore
1301
+ app.container && getRootContainer(app.container).disconnectedCallback();
1302
+ });
1303
+ !window.__MICRO_APP_UMD_MODE__ && appInstanceMap.clear();
1304
+ }
1305
+ // if micro-app run in micro application, delete all next generation application when unmount event received
1306
+ function listenUmountOfNestedApp() {
1307
+ if (window.__MICRO_APP_ENVIRONMENT__) {
1308
+ window.addEventListener('unmount', unmountNestedApp, false);
1407
1309
  }
1408
- else {
1409
- app.onLoad(wrapElement);
1310
+ }
1311
+ // release listener
1312
+ function releaseUnmountOfNestedApp() {
1313
+ if (window.__MICRO_APP_ENVIRONMENT__) {
1314
+ window.removeEventListener('unmount', unmountNestedApp, false);
1410
1315
  }
1411
1316
  }
1317
+
1318
+ const globalEnv = {};
1412
1319
  /**
1413
- * Get and format html
1414
- * @param app app
1320
+ * Note loop nesting
1321
+ * Only prototype or unique values can be put here
1415
1322
  */
1416
- function extractHtml(app) {
1417
- fetchSource(app.ssrUrl || app.url, app.name, { cache: 'no-cache' }).then((htmlStr) => {
1418
- if (!htmlStr) {
1419
- const msg = 'html is empty, please check in detail';
1420
- app.onerror(new Error(msg));
1421
- return logError(msg, app.name);
1422
- }
1423
- htmlStr = htmlStr
1424
- .replace(/<head[^>]*>[\s\S]*?<\/head>/i, (match) => {
1425
- return match
1426
- .replace(/<head/i, '<micro-app-head')
1427
- .replace(/<\/head>/i, '</micro-app-head>');
1428
- })
1429
- .replace(/<body[^>]*>[\s\S]*?<\/body>/i, (match) => {
1430
- return match
1431
- .replace(/<body/i, '<micro-app-body')
1432
- .replace(/<\/body>/i, '</micro-app-body>');
1323
+ function initGlobalEnv() {
1324
+ if (isBrowser) {
1325
+ /**
1326
+ * save patch raw methods
1327
+ * pay attention to this binding
1328
+ */
1329
+ const rawSetAttribute = Element.prototype.setAttribute;
1330
+ const rawAppendChild = Element.prototype.appendChild;
1331
+ const rawInsertBefore = Element.prototype.insertBefore;
1332
+ const rawReplaceChild = Element.prototype.replaceChild;
1333
+ const rawRemoveChild = Element.prototype.removeChild;
1334
+ const rawAppend = Element.prototype.append;
1335
+ const rawPrepend = Element.prototype.prepend;
1336
+ const rawCloneNode = Element.prototype.cloneNode;
1337
+ const rawCreateElement = Document.prototype.createElement;
1338
+ const rawCreateElementNS = Document.prototype.createElementNS;
1339
+ const rawCreateDocumentFragment = Document.prototype.createDocumentFragment;
1340
+ const rawQuerySelector = Document.prototype.querySelector;
1341
+ const rawQuerySelectorAll = Document.prototype.querySelectorAll;
1342
+ const rawGetElementById = Document.prototype.getElementById;
1343
+ const rawGetElementsByClassName = Document.prototype.getElementsByClassName;
1344
+ const rawGetElementsByTagName = Document.prototype.getElementsByTagName;
1345
+ const rawGetElementsByName = Document.prototype.getElementsByName;
1346
+ const ImageProxy = new Proxy(Image, {
1347
+ construct(Target, args) {
1348
+ const elementImage = new Target(...args);
1349
+ elementImage.__MICRO_APP_NAME__ = getCurrentAppName();
1350
+ return elementImage;
1351
+ },
1433
1352
  });
1434
- extractSourceDom(htmlStr, app);
1435
- }).catch((e) => {
1436
- logError(`Failed to fetch data from ${app.url}, micro-app stop rendering`, app.name, e);
1437
- app.onLoadError(e);
1438
- });
1353
+ const rawWindow = Function('return window')();
1354
+ const rawDocument = Function('return document')();
1355
+ const supportModuleScript = isSupportModuleScript();
1356
+ /**
1357
+ * save effect raw methods
1358
+ * pay attention to this binding, especially setInterval, setTimeout, clearInterval, clearTimeout
1359
+ */
1360
+ const rawWindowAddEventListener = rawWindow.addEventListener;
1361
+ const rawWindowRemoveEventListener = rawWindow.removeEventListener;
1362
+ const rawSetInterval = rawWindow.setInterval;
1363
+ const rawSetTimeout = rawWindow.setTimeout;
1364
+ const rawClearInterval = rawWindow.clearInterval;
1365
+ const rawClearTimeout = rawWindow.clearTimeout;
1366
+ const rawDocumentAddEventListener = rawDocument.addEventListener;
1367
+ const rawDocumentRemoveEventListener = rawDocument.removeEventListener;
1368
+ // mark current application as base application
1369
+ window.__MICRO_APP_BASE_APPLICATION__ = true;
1370
+ Object.assign(globalEnv, {
1371
+ // source/patch
1372
+ rawSetAttribute,
1373
+ rawAppendChild,
1374
+ rawInsertBefore,
1375
+ rawReplaceChild,
1376
+ rawRemoveChild,
1377
+ rawAppend,
1378
+ rawPrepend,
1379
+ rawCloneNode,
1380
+ rawCreateElement,
1381
+ rawCreateElementNS,
1382
+ rawCreateDocumentFragment,
1383
+ rawQuerySelector,
1384
+ rawQuerySelectorAll,
1385
+ rawGetElementById,
1386
+ rawGetElementsByClassName,
1387
+ rawGetElementsByTagName,
1388
+ rawGetElementsByName,
1389
+ ImageProxy,
1390
+ // common global vars
1391
+ rawWindow,
1392
+ rawDocument,
1393
+ supportModuleScript,
1394
+ // sandbox/effect
1395
+ rawWindowAddEventListener,
1396
+ rawWindowRemoveEventListener,
1397
+ rawSetInterval,
1398
+ rawSetTimeout,
1399
+ rawClearInterval,
1400
+ rawClearTimeout,
1401
+ rawDocumentAddEventListener,
1402
+ rawDocumentRemoveEventListener,
1403
+ });
1404
+ // global effect
1405
+ rejectMicroAppStyle();
1406
+ releaseUnmountOfNestedApp();
1407
+ listenUmountOfNestedApp();
1408
+ }
1439
1409
  }
1440
1410
 
1441
- class EventCenter {
1442
- constructor() {
1443
- this.eventList = new Map();
1411
+ // Global scripts, reuse across apps
1412
+ const globalScripts = new Map();
1413
+ /**
1414
+ * Extract script elements
1415
+ * @param script script element
1416
+ * @param parent parent element of script
1417
+ * @param app app
1418
+ * @param isDynamic dynamic insert
1419
+ */
1420
+ function extractScriptElement(script, parent, app, isDynamic = false) {
1421
+ let replaceComment = null;
1422
+ let src = script.getAttribute('src');
1423
+ if (script.hasAttribute('exclude')) {
1424
+ replaceComment = document.createComment('script element with exclude attribute removed by micro-app');
1444
1425
  }
1445
- // whether the name is legal
1446
- isLegalName(name) {
1447
- if (!name) {
1448
- logError('event-center: Invalid name');
1449
- return false;
1450
- }
1451
- return true;
1426
+ else if ((script.type && !['text/javascript', 'text/ecmascript', 'application/javascript', 'application/ecmascript', 'module'].includes(script.type)) ||
1427
+ script.hasAttribute('ignore')) {
1428
+ return null;
1452
1429
  }
1453
- /**
1454
- * add listener
1455
- * @param name event name
1456
- * @param f listener
1457
- * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
1458
- */
1459
- on(name, f, autoTrigger = false) {
1460
- if (this.isLegalName(name)) {
1461
- if (!isFunction(f)) {
1462
- return logError('event-center: Invalid callback function');
1463
- }
1464
- let eventInfo = this.eventList.get(name);
1465
- if (!eventInfo) {
1466
- eventInfo = {
1467
- data: {},
1468
- callbacks: new Set(),
1469
- };
1470
- this.eventList.set(name, eventInfo);
1471
- }
1472
- else if (autoTrigger && Object.getOwnPropertyNames(eventInfo.data).length) {
1473
- // auto trigger when data not null
1474
- f(eventInfo.data);
1475
- }
1476
- eventInfo.callbacks.add(f);
1477
- }
1430
+ else if ((globalEnv.supportModuleScript && script.noModule) ||
1431
+ (!globalEnv.supportModuleScript && script.type === 'module')) {
1432
+ replaceComment = document.createComment(`${script.noModule ? 'noModule' : 'module'} script ignored by micro-app`);
1478
1433
  }
1479
- // remove listener, but the data is not cleared
1480
- off(name, f) {
1481
- if (this.isLegalName(name)) {
1482
- const eventInfo = this.eventList.get(name);
1483
- if (eventInfo) {
1484
- if (isFunction(f)) {
1485
- eventInfo.callbacks.delete(f);
1486
- }
1487
- else {
1488
- eventInfo.callbacks.clear();
1489
- }
1490
- }
1434
+ else if (src) { // remote script
1435
+ src = CompletionPath(src, app.url);
1436
+ const info = {
1437
+ code: '',
1438
+ isExternal: true,
1439
+ isDynamic: isDynamic,
1440
+ async: script.hasAttribute('async'),
1441
+ defer: script.defer || script.type === 'module',
1442
+ module: script.type === 'module',
1443
+ isGlobal: script.hasAttribute('global'),
1444
+ };
1445
+ if (!isDynamic) {
1446
+ app.source.scripts.set(src, info);
1447
+ replaceComment = document.createComment(`script with src='${src}' extract by micro-app`);
1448
+ }
1449
+ else {
1450
+ return { url: src, info };
1491
1451
  }
1492
1452
  }
1493
- // dispatch data
1494
- dispatch(name, data) {
1495
- if (this.isLegalName(name)) {
1496
- if (!isPlainObject(data)) {
1497
- return logError('event-center: data must be object');
1498
- }
1499
- let eventInfo = this.eventList.get(name);
1500
- if (eventInfo) {
1501
- // Update when the data is not equal
1502
- if (eventInfo.data !== data) {
1503
- eventInfo.data = data;
1504
- for (const f of eventInfo.callbacks) {
1505
- f(data);
1506
- }
1507
- }
1508
- }
1509
- else {
1510
- eventInfo = {
1511
- data: data,
1512
- callbacks: new Set(),
1513
- };
1514
- this.eventList.set(name, eventInfo);
1515
- }
1453
+ else if (script.textContent) { // inline script
1454
+ const nonceStr = createNonceSrc();
1455
+ const info = {
1456
+ code: script.textContent,
1457
+ isExternal: false,
1458
+ isDynamic: isDynamic,
1459
+ async: false,
1460
+ defer: script.type === 'module',
1461
+ module: script.type === 'module',
1462
+ };
1463
+ if (!isDynamic) {
1464
+ app.source.scripts.set(nonceStr, info);
1465
+ replaceComment = document.createComment('inline script extract by micro-app');
1466
+ }
1467
+ else {
1468
+ return { url: nonceStr, info };
1516
1469
  }
1517
1470
  }
1518
- // get data
1519
- getData(name) {
1520
- var _a;
1521
- const eventInfo = this.eventList.get(name);
1522
- return (_a = eventInfo === null || eventInfo === void 0 ? void 0 : eventInfo.data) !== null && _a !== void 0 ? _a : null;
1471
+ else if (!isDynamic) {
1472
+ /**
1473
+ * script with empty src or empty script.textContent remove in static html
1474
+ * & not removed if it created by dynamic
1475
+ */
1476
+ replaceComment = document.createComment('script element removed by micro-app');
1477
+ }
1478
+ if (isDynamic) {
1479
+ return { replaceComment };
1480
+ }
1481
+ else {
1482
+ return parent.replaceChild(replaceComment, script);
1523
1483
  }
1524
1484
  }
1525
-
1526
- const eventCenter = new EventCenter();
1527
1485
  /**
1528
- * Format event name
1529
- * @param appName app.name
1530
- * @param fromBaseApp is from base app
1486
+ * Get remote resources of script
1487
+ * @param wrapElement htmlDom
1488
+ * @param app app
1531
1489
  */
1532
- function formatEventName(appName, fromBaseApp) {
1533
- if (!isString(appName) || !appName)
1534
- return '';
1535
- return fromBaseApp ? `__from_base_app_${appName}__` : `__from_micro_app_${appName}__`;
1536
- }
1537
- // Global data
1538
- class EventCenterForGlobal {
1539
- /**
1540
- * add listener of global data
1541
- * @param cb listener
1542
- * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
1543
- */
1544
- addGlobalDataListener(cb, autoTrigger) {
1545
- const appName = this.appName;
1546
- // if appName exists, this is in sub app
1547
- if (appName) {
1548
- cb.__APP_NAME__ = appName;
1549
- cb.__AUTO_TRIGGER__ = autoTrigger;
1490
+ function fetchScriptsFromHtml(wrapElement, app) {
1491
+ const scriptEntries = Array.from(app.source.scripts.entries());
1492
+ const fetchScriptPromise = [];
1493
+ const fetchScriptPromiseInfo = [];
1494
+ for (const [url, info] of scriptEntries) {
1495
+ if (info.isExternal) {
1496
+ const globalScriptText = globalScripts.get(url);
1497
+ if (globalScriptText) {
1498
+ info.code = globalScriptText;
1499
+ }
1500
+ else if (!info.defer && !info.async) {
1501
+ fetchScriptPromise.push(fetchSource(url, app.name));
1502
+ fetchScriptPromiseInfo.push([url, info]);
1503
+ }
1550
1504
  }
1551
- eventCenter.on('global', cb, autoTrigger);
1552
1505
  }
1553
- /**
1554
- * remove listener of global data
1555
- * @param cb listener
1556
- */
1557
- removeGlobalDataListener(cb) {
1558
- isFunction(cb) && eventCenter.off('global', cb);
1506
+ if (fetchScriptPromise.length) {
1507
+ promiseStream(fetchScriptPromise, (res) => {
1508
+ fetchScriptSuccess(fetchScriptPromiseInfo[res.index][0], fetchScriptPromiseInfo[res.index][1], res.data);
1509
+ }, (err) => {
1510
+ logError(err, app.name);
1511
+ }, () => {
1512
+ app.onLoad(wrapElement);
1513
+ });
1559
1514
  }
1560
- /**
1561
- * dispatch global data
1562
- * @param data data
1563
- */
1564
- setGlobalData(data) {
1565
- // clear dom scope before dispatch global data, apply to micro app
1566
- removeDomScope();
1567
- eventCenter.dispatch('global', data);
1515
+ else {
1516
+ app.onLoad(wrapElement);
1568
1517
  }
1569
- /**
1570
- * get global data
1571
- */
1572
- getGlobalData() {
1573
- return eventCenter.getData('global');
1518
+ }
1519
+ /**
1520
+ * fetch js succeeded, record the code value
1521
+ * @param url script address
1522
+ * @param info resource script info
1523
+ * @param data code
1524
+ */
1525
+ function fetchScriptSuccess(url, info, data) {
1526
+ if (info.isGlobal && !globalScripts.has(url)) {
1527
+ globalScripts.set(url, data);
1574
1528
  }
1575
- /**
1576
- * clear all listener of global data
1577
- * if appName exists, only the specified functions is cleared
1578
- * if appName not exists, only clear the base app functions
1579
- */
1580
- clearGlobalDataListener() {
1581
- const appName = this.appName;
1582
- const eventInfo = eventCenter.eventList.get('global');
1583
- if (eventInfo) {
1584
- for (const cb of eventInfo.callbacks) {
1585
- if ((appName && appName === cb.__APP_NAME__) ||
1586
- !(appName || cb.__APP_NAME__)) {
1587
- eventInfo.callbacks.delete(cb);
1529
+ info.code = data;
1530
+ }
1531
+ /**
1532
+ * Execute js in the mount lifecycle
1533
+ * @param scriptList script list
1534
+ * @param app app
1535
+ * @param initHook callback for umd mode
1536
+ */
1537
+ function execScripts(scriptList, app, initHook) {
1538
+ const scriptListEntries = Array.from(scriptList.entries());
1539
+ const deferScriptPromise = [];
1540
+ const deferScriptInfo = [];
1541
+ for (const [url, info] of scriptListEntries) {
1542
+ if (!info.isDynamic) {
1543
+ // Notice the second render
1544
+ if (info.defer || info.async) {
1545
+ if (info.isExternal && !info.code) {
1546
+ deferScriptPromise.push(fetchSource(url, app.name));
1547
+ }
1548
+ else {
1549
+ deferScriptPromise.push(info.code);
1588
1550
  }
1551
+ deferScriptInfo.push([url, info]);
1552
+ info.module && (initHook.moduleCount = initHook.moduleCount ? ++initHook.moduleCount : 1);
1553
+ }
1554
+ else {
1555
+ runScript(url, app, info, false);
1556
+ initHook(false);
1589
1557
  }
1590
1558
  }
1591
1559
  }
1592
- }
1593
- // Event center for base app
1594
- class EventCenterForBaseApp extends EventCenterForGlobal {
1595
- /**
1596
- * add listener
1597
- * @param appName app.name
1598
- * @param cb listener
1599
- * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
1600
- */
1601
- addDataListener(appName, cb, autoTrigger) {
1602
- eventCenter.on(formatEventName(formatAppName(appName), false), cb, autoTrigger);
1603
- }
1604
- /**
1605
- * remove listener
1606
- * @param appName app.name
1607
- * @param cb listener
1608
- */
1609
- removeDataListener(appName, cb) {
1610
- isFunction(cb) && eventCenter.off(formatEventName(formatAppName(appName), false), cb);
1611
- }
1612
- /**
1613
- * get data from micro app or base app
1614
- * @param appName app.name
1615
- * @param fromBaseApp whether get data from base app, default is false
1616
- */
1617
- getData(appName, fromBaseApp = false) {
1618
- return eventCenter.getData(formatEventName(formatAppName(appName), fromBaseApp));
1619
- }
1620
- /**
1621
- * Dispatch data to the specified micro app
1622
- * @param appName app.name
1623
- * @param data data
1624
- */
1625
- setData(appName, data) {
1626
- eventCenter.dispatch(formatEventName(formatAppName(appName), true), data);
1627
- }
1628
- /**
1629
- * clear all listener for specified micro app
1630
- * @param appName app.name
1631
- */
1632
- clearDataListener(appName) {
1633
- eventCenter.off(formatEventName(formatAppName(appName), false));
1634
- }
1635
- }
1636
- // Event center for sub app
1637
- class EventCenterForMicroApp extends EventCenterForGlobal {
1638
- constructor(appName) {
1639
- super();
1640
- this.appName = formatAppName(appName);
1641
- !this.appName && logError(`Invalid appName ${appName}`);
1642
- }
1643
- /**
1644
- * add listener, monitor the data sent by the base app
1645
- * @param cb listener
1646
- * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
1647
- */
1648
- addDataListener(cb, autoTrigger) {
1649
- cb.__AUTO_TRIGGER__ = autoTrigger;
1650
- eventCenter.on(formatEventName(this.appName, true), cb, autoTrigger);
1651
- }
1652
- /**
1653
- * remove listener
1654
- * @param cb listener
1655
- */
1656
- removeDataListener(cb) {
1657
- isFunction(cb) && eventCenter.off(formatEventName(this.appName, true), cb);
1658
- }
1659
- /**
1660
- * get data from base app
1661
- */
1662
- getData() {
1663
- return eventCenter.getData(formatEventName(this.appName, true));
1664
- }
1665
- /**
1666
- * dispatch data to base app
1667
- * @param data data
1668
- */
1669
- dispatch(data) {
1670
- removeDomScope();
1671
- eventCenter.dispatch(formatEventName(this.appName, false), data);
1672
- const app = appInstanceMap.get(this.appName);
1673
- if ((app === null || app === void 0 ? void 0 : app.container) && isPlainObject(data)) {
1674
- const event = new CustomEvent('datachange', {
1675
- detail: {
1676
- data,
1560
+ if (deferScriptPromise.length) {
1561
+ promiseStream(deferScriptPromise, (res) => {
1562
+ const info = deferScriptInfo[res.index][1];
1563
+ info.code = info.code || res.data;
1564
+ }, (err) => {
1565
+ initHook.errorCount = initHook.errorCount ? ++initHook.errorCount : 1;
1566
+ logError(err, app.name);
1567
+ }, () => {
1568
+ deferScriptInfo.forEach(([url, info]) => {
1569
+ if (info.code) {
1570
+ runScript(url, app, info, false, initHook);
1571
+ !info.module && initHook(false);
1677
1572
  }
1678
1573
  });
1679
- getRootContainer(app.container).dispatchEvent(event);
1680
- }
1574
+ initHook(isUndefined(initHook.moduleCount) ||
1575
+ initHook.errorCount === deferScriptPromise.length);
1576
+ });
1681
1577
  }
1682
- /**
1683
- * clear all listeners
1684
- */
1685
- clearDataListener() {
1686
- eventCenter.off(formatEventName(this.appName, true));
1578
+ else {
1579
+ initHook(true);
1687
1580
  }
1688
1581
  }
1689
1582
  /**
1690
- * Record UMD function before exec umdHookMount
1691
- * @param microAppEventCneter
1583
+ * run code
1584
+ * @param url script address
1585
+ * @param app app
1586
+ * @param info script info
1587
+ * @param isDynamic dynamically created script
1588
+ * @param callback callback of module script
1692
1589
  */
1693
- function recordDataCenterSnapshot(microAppEventCneter) {
1694
- const appName = microAppEventCneter.appName;
1695
- microAppEventCneter.umdDataListeners = { global: new Set(), normal: new Set() };
1696
- const globalEventInfo = eventCenter.eventList.get('global');
1697
- if (globalEventInfo) {
1698
- for (const cb of globalEventInfo.callbacks) {
1699
- if (appName === cb.__APP_NAME__) {
1700
- microAppEventCneter.umdDataListeners.global.add(cb);
1701
- }
1590
+ function runScript(url, app, info, isDynamic, callback) {
1591
+ var _a;
1592
+ try {
1593
+ const code = bindScope(url, app, info.code, info.module);
1594
+ if (app.inline || info.module) {
1595
+ const scriptElement = pureCreateElement('script');
1596
+ runCode2InlineScript(url, code, info.module, scriptElement, callback);
1597
+ if (isDynamic)
1598
+ return scriptElement;
1599
+ // TEST IGNORE
1600
+ (_a = app.container) === null || _a === void 0 ? void 0 : _a.querySelector('micro-app-body').appendChild(scriptElement);
1601
+ }
1602
+ else {
1603
+ runCode2Function(code, info);
1604
+ if (isDynamic)
1605
+ return document.createComment('dynamic script extract by micro-app');
1702
1606
  }
1703
1607
  }
1704
- const subAppEventInfo = eventCenter.eventList.get(formatEventName(appName, true));
1705
- if (subAppEventInfo) {
1706
- microAppEventCneter.umdDataListeners.normal = new Set(subAppEventInfo.callbacks);
1608
+ catch (e) {
1609
+ console.error(`[micro-app from runScript] app ${app.name}: `, e);
1707
1610
  }
1708
1611
  }
1709
1612
  /**
1710
- * Rebind the UMD function of the record before remount
1711
- * @param microAppEventCneter instance of EventCenterForMicroApp
1613
+ * Get dynamically created remote script
1614
+ * @param url script address
1615
+ * @param info info
1616
+ * @param app app
1617
+ * @param originScript origin script element
1712
1618
  */
1713
- function rebuildDataCenterSnapshot(microAppEventCneter) {
1714
- for (const cb of microAppEventCneter.umdDataListeners.global) {
1715
- microAppEventCneter.addGlobalDataListener(cb, cb.__AUTO_TRIGGER__);
1619
+ function runDynamicRemoteScript(url, info, app, originScript) {
1620
+ const dispatchScriptOnLoadEvent = () => dispatchOnLoadEvent(originScript);
1621
+ // url is unique
1622
+ if (app.source.scripts.has(url)) {
1623
+ const existInfo = app.source.scripts.get(url);
1624
+ !existInfo.module && defer(dispatchScriptOnLoadEvent);
1625
+ return runScript(url, app, existInfo, true, dispatchScriptOnLoadEvent);
1716
1626
  }
1717
- for (const cb of microAppEventCneter.umdDataListeners.normal) {
1718
- microAppEventCneter.addDataListener(cb, cb.__AUTO_TRIGGER__);
1627
+ if (globalScripts.has(url)) {
1628
+ const code = globalScripts.get(url);
1629
+ info.code = code;
1630
+ app.source.scripts.set(url, info);
1631
+ !info.module && defer(dispatchScriptOnLoadEvent);
1632
+ return runScript(url, app, info, true, dispatchScriptOnLoadEvent);
1719
1633
  }
1720
- }
1721
-
1722
- /* eslint-disable no-return-assign */
1723
- function isBoundedFunction(value) {
1724
- if (isBoolean(value.__MICRO_APP_ISBOUND_FUNCTION))
1725
- return value.__MICRO_APP_ISBOUND_FUNCTION;
1726
- return value.__MICRO_APP_ISBOUND_FUNCTION = isBoundFunction(value);
1727
- }
1728
- function isConstructor(value) {
1729
- var _a;
1730
- if (isBoolean(value.__MICRO_APP_ISCONSTRUCTOR))
1731
- return value.__MICRO_APP_ISCONSTRUCTOR;
1732
- const valueStr = value.toString();
1733
- const result = (((_a = value.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === value &&
1734
- Object.getOwnPropertyNames(value.prototype).length > 1) ||
1735
- /^function\s+[A-Z]/.test(valueStr) ||
1736
- /^class\s+/.test(valueStr);
1737
- return value.__MICRO_APP_ISCONSTRUCTOR = result;
1738
- }
1739
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
1740
- function bindFunctionToRawWindow(rawWindow, value) {
1741
- if (value.__MICRO_APP_BOUND_WINDOW_FUNCTION)
1742
- return value.__MICRO_APP_BOUND_WINDOW_FUNCTION;
1743
- if (!isConstructor(value) && !isBoundedFunction(value)) {
1744
- const bindRawWindowValue = value.bind(rawWindow);
1745
- for (const key in value) {
1746
- bindRawWindowValue[key] = value[key];
1747
- }
1748
- if (value.hasOwnProperty('prototype')) {
1749
- rawDefineProperty(bindRawWindowValue, 'prototype', {
1750
- value: value.prototype,
1751
- configurable: true,
1752
- enumerable: false,
1753
- writable: true,
1754
- });
1755
- }
1756
- return value.__MICRO_APP_BOUND_WINDOW_FUNCTION = bindRawWindowValue;
1757
- }
1758
- return value;
1759
- }
1760
-
1761
- // document.onclick binding list, the binding function of each application is unique
1762
- const documentClickListMap = new Map();
1763
- let hasRewriteDocumentOnClick = false;
1764
- /**
1765
- * Rewrite document.onclick and execute it only once
1766
- */
1767
- function overwriteDocumentOnClick() {
1768
- hasRewriteDocumentOnClick = true;
1769
- if (Object.getOwnPropertyDescriptor(document, 'onclick')) {
1770
- return logWarn('Cannot redefine document property onclick');
1634
+ let replaceElement;
1635
+ if (app.inline || info.module) {
1636
+ replaceElement = pureCreateElement('script');
1771
1637
  }
1772
- const rawOnClick = document.onclick;
1773
- document.onclick = null;
1774
- let hasDocumentClickInited = false;
1775
- function onClickHandler(e) {
1776
- documentClickListMap.forEach((f) => {
1777
- isFunction(f) && f.call(document, e);
1778
- });
1638
+ else {
1639
+ replaceElement = document.createComment(`dynamic script with src='${url}' extract by micro-app`);
1779
1640
  }
1780
- rawDefineProperty(document, 'onclick', {
1781
- configurable: true,
1782
- enumerable: true,
1783
- get() {
1784
- const appName = getCurrentAppName();
1785
- return appName ? documentClickListMap.get(appName) : documentClickListMap.get('base');
1786
- },
1787
- set(f) {
1788
- const appName = getCurrentAppName();
1789
- if (appName) {
1790
- documentClickListMap.set(appName, f);
1641
+ fetchSource(url, app.name).then((code) => {
1642
+ info.code = code;
1643
+ app.source.scripts.set(url, info);
1644
+ info.isGlobal && globalScripts.set(url, code);
1645
+ try {
1646
+ code = bindScope(url, app, code, info.module);
1647
+ if (app.inline || info.module) {
1648
+ runCode2InlineScript(url, code, info.module, replaceElement, dispatchScriptOnLoadEvent);
1791
1649
  }
1792
1650
  else {
1793
- documentClickListMap.set('base', f);
1794
- }
1795
- if (!hasDocumentClickInited && isFunction(f)) {
1796
- hasDocumentClickInited = true;
1797
- globalEnv.rawDocumentAddEventListener.call(globalEnv.rawDocument, 'click', onClickHandler, false);
1651
+ runCode2Function(code, info);
1798
1652
  }
1799
1653
  }
1654
+ catch (e) {
1655
+ console.error(`[micro-app from runDynamicScript] app ${app.name}: `, e, url);
1656
+ }
1657
+ !info.module && dispatchOnLoadEvent(originScript);
1658
+ }).catch((err) => {
1659
+ logError(err, app.name);
1660
+ dispatchOnErrorEvent(originScript);
1800
1661
  });
1801
- rawOnClick && (document.onclick = rawOnClick);
1662
+ return replaceElement;
1802
1663
  }
1803
1664
  /**
1804
- * The document event is globally, we need to clear these event bindings when micro application unmounted
1665
+ * common handle for inline script
1666
+ * @param url script address
1667
+ * @param code bound code
1668
+ * @param module type='module' of script
1669
+ * @param scriptElement target script element
1670
+ * @param callback callback of module script
1805
1671
  */
1806
- const documentEventListenerMap = new Map();
1807
- function effectDocumentEvent() {
1808
- const { rawDocument, rawDocumentAddEventListener, rawDocumentRemoveEventListener, } = globalEnv;
1809
- !hasRewriteDocumentOnClick && overwriteDocumentOnClick();
1810
- document.addEventListener = function (type, listener, options) {
1811
- var _a;
1812
- const appName = getCurrentAppName();
1813
- /**
1814
- * ignore bound function of document event in umd mode, used to solve problem of react global events
1815
- */
1816
- if (appName && !(((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.umdMode) && isBoundFunction(listener))) {
1817
- const appListenersMap = documentEventListenerMap.get(appName);
1818
- if (appListenersMap) {
1819
- const appListenerList = appListenersMap.get(type);
1820
- if (appListenerList) {
1821
- appListenerList.add(listener);
1822
- }
1823
- else {
1824
- appListenersMap.set(type, new Set([listener]));
1825
- }
1826
- }
1827
- else {
1828
- documentEventListenerMap.set(appName, new Map([[type, new Set([listener])]]));
1829
- }
1830
- listener && (listener.__MICRO_APP_MARK_OPTIONS__ = options);
1831
- }
1832
- rawDocumentAddEventListener.call(rawDocument, type, listener, options);
1833
- };
1834
- document.removeEventListener = function (type, listener, options) {
1835
- var _a;
1836
- const appName = getCurrentAppName();
1837
- if (appName && !(((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.umdMode) && isBoundFunction(listener))) {
1838
- const appListenersMap = documentEventListenerMap.get(appName);
1839
- if (appListenersMap) {
1840
- const appListenerList = appListenersMap.get(type);
1841
- if ((appListenerList === null || appListenerList === void 0 ? void 0 : appListenerList.size) && appListenerList.has(listener)) {
1842
- appListenerList.delete(listener);
1843
- }
1844
- }
1672
+ function runCode2InlineScript(url, code, module, scriptElement, callback) {
1673
+ if (module) {
1674
+ // module script is async, transform it to a blob for subsequent operations
1675
+ const blob = new Blob([code], { type: 'text/javascript' });
1676
+ scriptElement.src = URL.createObjectURL(blob);
1677
+ scriptElement.setAttribute('type', 'module');
1678
+ if (callback) {
1679
+ callback.moduleCount && callback.moduleCount--;
1680
+ scriptElement.onload = callback.bind(scriptElement, callback.moduleCount === 0);
1845
1681
  }
1846
- rawDocumentRemoveEventListener.call(rawDocument, type, listener, options);
1847
- };
1682
+ }
1683
+ else {
1684
+ scriptElement.textContent = code;
1685
+ }
1686
+ if (!url.startsWith('inline-')) {
1687
+ scriptElement.setAttribute('data-origin-src', url);
1688
+ }
1848
1689
  }
1849
- // Clear the document event agent
1850
- function releaseEffectDocumentEvent() {
1851
- document.addEventListener = globalEnv.rawDocumentAddEventListener;
1852
- document.removeEventListener = globalEnv.rawDocumentRemoveEventListener;
1690
+ // init & run code2Function
1691
+ function runCode2Function(code, info) {
1692
+ if (!info.code2Function) {
1693
+ info.code2Function = new Function(code);
1694
+ }
1695
+ info.code2Function.call(window);
1853
1696
  }
1854
- // this events should be sent to the specified app
1855
- const formatEventList = ['unmount', 'appstate-change'];
1856
1697
  /**
1857
- * Format event name
1858
- * @param type event name
1859
- * @param microAppWindow micro window
1698
+ * bind js scope
1699
+ * @param url script address
1700
+ * @param app app
1701
+ * @param code code
1702
+ * @param module type='module' of script
1860
1703
  */
1861
- function formatEventType(type, microAppWindow) {
1862
- if (formatEventList.includes(type)) {
1863
- return `${type}-${microAppWindow.__MICRO_APP_NAME__}`;
1704
+ function bindScope(url, app, code, module) {
1705
+ if (isPlainObject(microApp.plugins)) {
1706
+ code = usePlugins(url, code, app.name, microApp.plugins);
1864
1707
  }
1865
- return type;
1708
+ if (app.sandBox && !module) {
1709
+ globalEnv.rawWindow.__MICRO_APP_PROXY_WINDOW__ = app.sandBox.proxyWindow;
1710
+ return `;(function(proxyWindow){with(proxyWindow.__MICRO_APP_WINDOW__){(function(${globalKeyToBeCached}){;${code}\n}).call(proxyWindow,${globalKeyToBeCached})}})(window.__MICRO_APP_PROXY_WINDOW__);`;
1711
+ }
1712
+ return code;
1866
1713
  }
1867
1714
  /**
1868
- * Rewrite side-effect events
1869
- * @param microAppWindow micro window
1715
+ * Call the plugin to process the file
1716
+ * @param url script address
1717
+ * @param code code
1718
+ * @param appName app name
1719
+ * @param plugins plugin list
1870
1720
  */
1871
- function effect(microAppWindow) {
1872
- const appName = microAppWindow.__MICRO_APP_NAME__;
1873
- const eventListenerMap = new Map();
1874
- const intervalIdMap = new Map();
1875
- const timeoutIdMap = new Map();
1876
- const { rawWindow, rawDocument, rawWindowAddEventListener, rawWindowRemoveEventListener, rawSetInterval, rawSetTimeout, rawClearInterval, rawClearTimeout, rawDocumentRemoveEventListener, } = globalEnv;
1877
- // listener may be null, e.g test-passive
1878
- microAppWindow.addEventListener = function (type, listener, options) {
1879
- type = formatEventType(type, microAppWindow);
1880
- const listenerList = eventListenerMap.get(type);
1881
- if (listenerList) {
1882
- listenerList.add(listener);
1721
+ function usePlugins(url, code, appName, plugins) {
1722
+ var _a;
1723
+ const newCode = processCode(plugins.global, code, url);
1724
+ return processCode((_a = plugins.modules) === null || _a === void 0 ? void 0 : _a[appName], newCode, url);
1725
+ }
1726
+ function processCode(configs, code, url) {
1727
+ if (!isArray(configs)) {
1728
+ return code;
1729
+ }
1730
+ return configs.reduce((preCode, config) => {
1731
+ if (isPlainObject(config) && isFunction(config.loader)) {
1732
+ return config.loader(preCode, url, config.options);
1883
1733
  }
1884
- else {
1885
- eventListenerMap.set(type, new Set([listener]));
1734
+ return preCode;
1735
+ }, code);
1736
+ }
1737
+
1738
+ /**
1739
+ * transform html string to dom
1740
+ * @param str string dom
1741
+ */
1742
+ function getWrapElement(str) {
1743
+ const wrapDiv = pureCreateElement('div');
1744
+ wrapDiv.innerHTML = str;
1745
+ return wrapDiv;
1746
+ }
1747
+ /**
1748
+ * Recursively process each child element
1749
+ * @param parent parent element
1750
+ * @param app app
1751
+ * @param microAppHead micro-app-head element
1752
+ */
1753
+ function flatChildren(parent, app, microAppHead) {
1754
+ const children = Array.from(parent.children);
1755
+ children.length && children.forEach((child) => {
1756
+ flatChildren(child, app);
1757
+ });
1758
+ for (const dom of children) {
1759
+ if (dom instanceof HTMLLinkElement) {
1760
+ if (dom.hasAttribute('exclude')) {
1761
+ parent.replaceChild(document.createComment('link element with exclude attribute ignored by micro-app'), dom);
1762
+ }
1763
+ else if (!dom.hasAttribute('ignore')) {
1764
+ extractLinkFromHtml(dom, parent, app);
1765
+ }
1766
+ else if (dom.hasAttribute('href')) {
1767
+ dom.setAttribute('href', CompletionPath(dom.getAttribute('href'), app.url));
1768
+ }
1886
1769
  }
1887
- listener && (listener.__MICRO_APP_MARK_OPTIONS__ = options);
1888
- rawWindowAddEventListener.call(rawWindow, type, listener, options);
1889
- };
1890
- microAppWindow.removeEventListener = function (type, listener, options) {
1891
- type = formatEventType(type, microAppWindow);
1892
- const listenerList = eventListenerMap.get(type);
1893
- if ((listenerList === null || listenerList === void 0 ? void 0 : listenerList.size) && listenerList.has(listener)) {
1894
- listenerList.delete(listener);
1770
+ else if (dom instanceof HTMLStyleElement) {
1771
+ if (dom.hasAttribute('exclude')) {
1772
+ parent.replaceChild(document.createComment('style element with exclude attribute ignored by micro-app'), dom);
1773
+ }
1774
+ else if (app.scopecss && !dom.hasAttribute('ignore')) {
1775
+ scopedCSS(dom, app);
1776
+ }
1895
1777
  }
1896
- rawWindowRemoveEventListener.call(rawWindow, type, listener, options);
1897
- };
1898
- microAppWindow.setInterval = function (handler, timeout, ...args) {
1899
- const intervalId = rawSetInterval.call(rawWindow, handler, timeout, ...args);
1900
- intervalIdMap.set(intervalId, { handler, timeout, args });
1901
- return intervalId;
1902
- };
1903
- microAppWindow.setTimeout = function (handler, timeout, ...args) {
1904
- const timeoutId = rawSetTimeout.call(rawWindow, handler, timeout, ...args);
1905
- timeoutIdMap.set(timeoutId, { handler, timeout, args });
1906
- return timeoutId;
1907
- };
1908
- microAppWindow.clearInterval = function (intervalId) {
1909
- intervalIdMap.delete(intervalId);
1910
- rawClearInterval.call(rawWindow, intervalId);
1911
- };
1912
- microAppWindow.clearTimeout = function (timeoutId) {
1913
- timeoutIdMap.delete(timeoutId);
1914
- rawClearTimeout.call(rawWindow, timeoutId);
1915
- };
1916
- const umdWindowListenerMap = new Map();
1917
- const umdDocumentListenerMap = new Map();
1918
- let umdIntervalIdMap = new Map();
1919
- let umdTimeoutIdMap = new Map();
1920
- let umdOnClickHandler;
1921
- // record event and timer before exec umdMountHook
1922
- const recordUmdEffect = () => {
1923
- // record window event
1924
- eventListenerMap.forEach((listenerList, type) => {
1925
- if (listenerList.size) {
1926
- umdWindowListenerMap.set(type, new Set(listenerList));
1927
- }
1928
- });
1929
- // record timers
1930
- if (intervalIdMap.size) {
1931
- umdIntervalIdMap = new Map(intervalIdMap);
1778
+ else if (dom instanceof HTMLScriptElement) {
1779
+ extractScriptElement(dom, parent, app);
1932
1780
  }
1933
- if (timeoutIdMap.size) {
1934
- umdTimeoutIdMap = new Map(timeoutIdMap);
1781
+ else if (dom instanceof HTMLMetaElement || dom instanceof HTMLTitleElement) {
1782
+ parent.removeChild(dom);
1935
1783
  }
1936
- // record onclick handler
1937
- umdOnClickHandler = documentClickListMap.get(appName);
1938
- // record document event
1939
- const documentAppListenersMap = documentEventListenerMap.get(appName);
1940
- if (documentAppListenersMap) {
1941
- documentAppListenersMap.forEach((listenerList, type) => {
1942
- if (listenerList.size) {
1943
- umdDocumentListenerMap.set(type, new Set(listenerList));
1944
- }
1945
- });
1784
+ else if (dom instanceof HTMLImageElement && dom.hasAttribute('src')) {
1785
+ dom.setAttribute('src', CompletionPath(dom.getAttribute('src'), app.url));
1946
1786
  }
1947
- };
1948
- // rebuild event and timer before remount umd app
1949
- const rebuildUmdEffect = () => {
1950
- // rebuild window event
1951
- umdWindowListenerMap.forEach((listenerList, type) => {
1952
- for (const listener of listenerList) {
1953
- microAppWindow.addEventListener(type, listener, listener === null || listener === void 0 ? void 0 : listener.__MICRO_APP_MARK_OPTIONS__);
1954
- }
1955
- });
1956
- // rebuild timer
1957
- umdIntervalIdMap.forEach((info) => {
1958
- microAppWindow.setInterval(info.handler, info.timeout, ...info.args);
1959
- });
1960
- umdTimeoutIdMap.forEach((info) => {
1961
- microAppWindow.setTimeout(info.handler, info.timeout, ...info.args);
1962
- });
1963
- // rebuild onclick event
1964
- umdOnClickHandler && documentClickListMap.set(appName, umdOnClickHandler);
1965
- // rebuild document event
1966
- setCurrentAppName(appName);
1967
- umdDocumentListenerMap.forEach((listenerList, type) => {
1968
- for (const listener of listenerList) {
1969
- document.addEventListener(type, listener, listener === null || listener === void 0 ? void 0 : listener.__MICRO_APP_MARK_OPTIONS__);
1970
- }
1971
- });
1972
- setCurrentAppName(null);
1973
- };
1974
- // release all event listener & interval & timeout when unmount app
1975
- const releaseEffect = () => {
1976
- // Clear window binding events
1977
- if (eventListenerMap.size) {
1978
- eventListenerMap.forEach((listenerList, type) => {
1979
- for (const listener of listenerList) {
1980
- rawWindowRemoveEventListener.call(rawWindow, type, listener);
1981
- }
1982
- });
1983
- eventListenerMap.clear();
1787
+ }
1788
+ }
1789
+ /**
1790
+ * Extract link and script, bind style scope
1791
+ * @param htmlStr html string
1792
+ * @param app app
1793
+ */
1794
+ function extractSourceDom(htmlStr, app) {
1795
+ const wrapElement = getWrapElement(htmlStr);
1796
+ const microAppHead = wrapElement.querySelector('micro-app-head');
1797
+ const microAppBody = wrapElement.querySelector('micro-app-body');
1798
+ if (!microAppHead || !microAppBody) {
1799
+ const msg = `element ${microAppHead ? 'body' : 'head'} is missing`;
1800
+ app.onerror(new Error(msg));
1801
+ return logError(msg, app.name);
1802
+ }
1803
+ flatChildren(wrapElement, app);
1804
+ if (app.source.links.size) {
1805
+ fetchLinksFromHtml(wrapElement, app, microAppHead);
1806
+ }
1807
+ else {
1808
+ app.onLoad(wrapElement);
1809
+ }
1810
+ if (app.source.scripts.size) {
1811
+ fetchScriptsFromHtml(wrapElement, app);
1812
+ }
1813
+ else {
1814
+ app.onLoad(wrapElement);
1815
+ }
1816
+ }
1817
+ /**
1818
+ * Get and format html
1819
+ * @param app app
1820
+ */
1821
+ function extractHtml(app) {
1822
+ fetchSource(app.ssrUrl || app.url, app.name, { cache: 'no-cache' }).then((htmlStr) => {
1823
+ if (!htmlStr) {
1824
+ const msg = 'html is empty, please check in detail';
1825
+ app.onerror(new Error(msg));
1826
+ return logError(msg, app.name);
1984
1827
  }
1985
- // Clear timers
1986
- if (intervalIdMap.size) {
1987
- intervalIdMap.forEach((_, intervalId) => {
1988
- rawClearInterval.call(rawWindow, intervalId);
1989
- });
1990
- intervalIdMap.clear();
1828
+ htmlStr = htmlStr
1829
+ .replace(/<head[^>]*>[\s\S]*?<\/head>/i, (match) => {
1830
+ return match
1831
+ .replace(/<head/i, '<micro-app-head')
1832
+ .replace(/<\/head>/i, '</micro-app-head>');
1833
+ })
1834
+ .replace(/<body[^>]*>[\s\S]*?<\/body>/i, (match) => {
1835
+ return match
1836
+ .replace(/<body/i, '<micro-app-body')
1837
+ .replace(/<\/body>/i, '</micro-app-body>');
1838
+ });
1839
+ extractSourceDom(htmlStr, app);
1840
+ }).catch((e) => {
1841
+ logError(`Failed to fetch data from ${app.url}, micro-app stop rendering`, app.name, e);
1842
+ app.onLoadError(e);
1843
+ });
1844
+ }
1845
+
1846
+ class EventCenter {
1847
+ constructor() {
1848
+ this.eventList = new Map();
1849
+ }
1850
+ // whether the name is legal
1851
+ isLegalName(name) {
1852
+ if (!name) {
1853
+ logError('event-center: Invalid name');
1854
+ return false;
1991
1855
  }
1992
- if (timeoutIdMap.size) {
1993
- timeoutIdMap.forEach((_, timeoutId) => {
1994
- rawClearTimeout.call(rawWindow, timeoutId);
1995
- });
1996
- timeoutIdMap.clear();
1856
+ return true;
1857
+ }
1858
+ /**
1859
+ * add listener
1860
+ * @param name event name
1861
+ * @param f listener
1862
+ * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
1863
+ */
1864
+ on(name, f, autoTrigger = false) {
1865
+ if (this.isLegalName(name)) {
1866
+ if (!isFunction(f)) {
1867
+ return logError('event-center: Invalid callback function');
1868
+ }
1869
+ let eventInfo = this.eventList.get(name);
1870
+ if (!eventInfo) {
1871
+ eventInfo = {
1872
+ data: {},
1873
+ callbacks: new Set(),
1874
+ };
1875
+ this.eventList.set(name, eventInfo);
1876
+ }
1877
+ else if (autoTrigger && Object.getOwnPropertyNames(eventInfo.data).length) {
1878
+ // auto trigger when data not null
1879
+ f(eventInfo.data);
1880
+ }
1881
+ eventInfo.callbacks.add(f);
1997
1882
  }
1998
- // Clear the function bound by micro application through document.onclick
1999
- documentClickListMap.delete(appName);
2000
- // Clear document binding event
2001
- const documentAppListenersMap = documentEventListenerMap.get(appName);
2002
- if (documentAppListenersMap) {
2003
- documentAppListenersMap.forEach((listenerList, type) => {
2004
- for (const listener of listenerList) {
2005
- rawDocumentRemoveEventListener.call(rawDocument, type, listener);
1883
+ }
1884
+ // remove listener, but the data is not cleared
1885
+ off(name, f) {
1886
+ if (this.isLegalName(name)) {
1887
+ const eventInfo = this.eventList.get(name);
1888
+ if (eventInfo) {
1889
+ if (isFunction(f)) {
1890
+ eventInfo.callbacks.delete(f);
2006
1891
  }
2007
- });
2008
- documentAppListenersMap.clear();
1892
+ else {
1893
+ eventInfo.callbacks.clear();
1894
+ }
1895
+ }
2009
1896
  }
2010
- };
2011
- return {
2012
- recordUmdEffect,
2013
- rebuildUmdEffect,
2014
- releaseEffect,
2015
- };
2016
- }
2017
- // window.addEventListener('mousedown', (e: Event) => {
2018
- // const targetNode = e.target
2019
- // const activeApps = getActiveApps(true)
2020
- // let isScopeOfMicroApp = false
2021
- // for (const appName of activeApps) {
2022
- // const app = appInstanceMap.get(appName)!
2023
- // if (targetNode instanceof Node && app.container!.contains(targetNode)) {
2024
- // isScopeOfMicroApp = true
2025
- // // console.log(111111, appName)
2026
- // setCurrentAppName(appName)
2027
- // break
2028
- // }
2029
- // }
2030
- // if (!isScopeOfMicroApp) {
2031
- // setCurrentAppName(null)
2032
- // }
2033
- // }, false)
2034
- // let isWaitingForMacroReset = false
2035
- // window.addEventListener('mouseup', () => {
2036
- // if (!isWaitingForMacroReset && getCurrentAppName()) {
2037
- // isWaitingForMacroReset = true
2038
- // setTimeout(() => {
2039
- // setCurrentAppName(null)
2040
- // isWaitingForMacroReset = false
2041
- // })
2042
- // }
2043
- // }, false)
2044
-
2045
- // Variables that can escape to rawWindow
2046
- const staticEscapeProperties = [
2047
- 'System',
2048
- '__cjsWrapper',
2049
- ];
2050
- // Variables that can only assigned to rawWindow
2051
- const escapeSetterKeyList = [
2052
- 'location',
2053
- ];
2054
- const globalPropertyList = ['window', 'self', 'globalThis'];
2055
- class SandBox {
2056
- constructor(appName, url) {
2057
- // Scoped global Properties(Properties that can only get and set in microAppWindow, will not escape to rawWindow)
2058
- this.scopeProperties = ['webpackJsonp'];
2059
- // Properties that can be escape to rawWindow
2060
- this.escapeProperties = [];
2061
- // Properties newly added to microAppWindow
2062
- this.injectedKeys = new Set();
2063
- // Properties escape to rawWindow, cleared when unmount
2064
- this.escapeKeys = new Set();
2065
- // sandbox state
2066
- this.active = false;
2067
- this.microAppWindow = {}; // Proxy target
2068
- // get scopeProperties and escapeProperties from plugins
2069
- this.getScopeProperties(appName);
2070
- // create proxyWindow with Proxy(microAppWindow)
2071
- this.proxyWindow = this.createProxyWindow();
2072
- // inject global properties
2073
- this.initMicroAppWindow(this.microAppWindow, appName, url);
2074
- // Rewrite global event listener & timeout
2075
- Object.assign(this, effect(this.microAppWindow));
2076
1897
  }
2077
- start(baseroute) {
2078
- if (!this.active) {
2079
- this.active = true;
2080
- this.microAppWindow.__MICRO_APP_BASE_ROUTE__ = this.microAppWindow.__MICRO_APP_BASE_URL__ = baseroute;
2081
- // BUG FIX: bable-polyfill@6.x
2082
- globalEnv.rawWindow._babelPolyfill && (globalEnv.rawWindow._babelPolyfill = false);
2083
- if (++SandBox.activeCount === 1) {
2084
- effectDocumentEvent();
1898
+ // dispatch data
1899
+ dispatch(name, data) {
1900
+ if (this.isLegalName(name)) {
1901
+ if (!isPlainObject(data)) {
1902
+ return logError('event-center: data must be object');
1903
+ }
1904
+ let eventInfo = this.eventList.get(name);
1905
+ if (eventInfo) {
1906
+ // Update when the data is not equal
1907
+ if (eventInfo.data !== data) {
1908
+ eventInfo.data = data;
1909
+ for (const f of eventInfo.callbacks) {
1910
+ f(data);
1911
+ }
1912
+ }
1913
+ }
1914
+ else {
1915
+ eventInfo = {
1916
+ data: data,
1917
+ callbacks: new Set(),
1918
+ };
1919
+ this.eventList.set(name, eventInfo);
2085
1920
  }
2086
1921
  }
2087
1922
  }
2088
- stop() {
2089
- if (this.active) {
2090
- this.active = false;
2091
- this.releaseEffect();
2092
- this.microAppWindow.microApp.clearDataListener();
2093
- this.microAppWindow.microApp.clearGlobalDataListener();
2094
- this.injectedKeys.forEach((key) => {
2095
- Reflect.deleteProperty(this.microAppWindow, key);
2096
- });
2097
- this.injectedKeys.clear();
2098
- this.escapeKeys.forEach((key) => {
2099
- Reflect.deleteProperty(globalEnv.rawWindow, key);
2100
- });
2101
- this.escapeKeys.clear();
2102
- if (--SandBox.activeCount === 0) {
2103
- releaseEffectDocumentEvent();
2104
- }
1923
+ // get data
1924
+ getData(name) {
1925
+ var _a;
1926
+ const eventInfo = this.eventList.get(name);
1927
+ return (_a = eventInfo === null || eventInfo === void 0 ? void 0 : eventInfo.data) !== null && _a !== void 0 ? _a : null;
1928
+ }
1929
+ }
1930
+
1931
+ const eventCenter = new EventCenter();
1932
+ /**
1933
+ * Format event name
1934
+ * @param appName app.name
1935
+ * @param fromBaseApp is from base app
1936
+ */
1937
+ function formatEventName(appName, fromBaseApp) {
1938
+ if (!isString(appName) || !appName)
1939
+ return '';
1940
+ return fromBaseApp ? `__from_base_app_${appName}__` : `__from_micro_app_${appName}__`;
1941
+ }
1942
+ // Global data
1943
+ class EventCenterForGlobal {
1944
+ /**
1945
+ * add listener of global data
1946
+ * @param cb listener
1947
+ * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
1948
+ */
1949
+ addGlobalDataListener(cb, autoTrigger) {
1950
+ const appName = this.appName;
1951
+ // if appName exists, this is in sub app
1952
+ if (appName) {
1953
+ cb.__APP_NAME__ = appName;
1954
+ cb.__AUTO_TRIGGER__ = autoTrigger;
2105
1955
  }
1956
+ eventCenter.on('global', cb, autoTrigger);
2106
1957
  }
2107
- // record umd snapshot before the first execution of umdHookMount
2108
- recordUmdSnapshot() {
2109
- this.microAppWindow.__MICRO_APP_UMD_MODE__ = true;
2110
- this.recordUmdEffect();
2111
- recordDataCenterSnapshot(this.microAppWindow.microApp);
2112
- this.recordUmdinjectedValues = new Map();
2113
- this.injectedKeys.forEach((key) => {
2114
- this.recordUmdinjectedValues.set(key, Reflect.get(this.microAppWindow, key));
2115
- });
1958
+ /**
1959
+ * remove listener of global data
1960
+ * @param cb listener
1961
+ */
1962
+ removeGlobalDataListener(cb) {
1963
+ isFunction(cb) && eventCenter.off('global', cb);
2116
1964
  }
2117
- // rebuild umd snapshot before remount umd app
2118
- rebuildUmdSnapshot() {
2119
- this.recordUmdinjectedValues.forEach((value, key) => {
2120
- Reflect.set(this.proxyWindow, key, value);
2121
- });
2122
- this.rebuildUmdEffect();
2123
- rebuildDataCenterSnapshot(this.microAppWindow.microApp);
1965
+ /**
1966
+ * dispatch global data
1967
+ * @param data data
1968
+ */
1969
+ setGlobalData(data) {
1970
+ // clear dom scope before dispatch global data, apply to micro app
1971
+ removeDomScope();
1972
+ eventCenter.dispatch('global', data);
2124
1973
  }
2125
1974
  /**
2126
- * get scopeProperties and escapeProperties from plugins
2127
- * @param appName app name
1975
+ * get global data
2128
1976
  */
2129
- getScopeProperties(appName) {
2130
- var _a;
2131
- if (!isPlainObject(microApp.plugins))
2132
- return;
2133
- if (isArray(microApp.plugins.global)) {
2134
- for (const plugin of microApp.plugins.global) {
2135
- if (isPlainObject(plugin)) {
2136
- if (isArray(plugin.scopeProperties)) {
2137
- this.scopeProperties = this.scopeProperties.concat(plugin.scopeProperties);
2138
- }
2139
- if (isArray(plugin.escapeProperties)) {
2140
- this.escapeProperties = this.escapeProperties.concat(plugin.escapeProperties);
2141
- }
2142
- }
2143
- }
2144
- }
2145
- if (isArray((_a = microApp.plugins.modules) === null || _a === void 0 ? void 0 : _a[appName])) {
2146
- for (const plugin of microApp.plugins.modules[appName]) {
2147
- if (isPlainObject(plugin)) {
2148
- if (isArray(plugin.scopeProperties)) {
2149
- this.scopeProperties = this.scopeProperties.concat(plugin.scopeProperties);
2150
- }
2151
- if (isArray(plugin.escapeProperties)) {
2152
- this.escapeProperties = this.escapeProperties.concat(plugin.escapeProperties);
2153
- }
1977
+ getGlobalData() {
1978
+ return eventCenter.getData('global');
1979
+ }
1980
+ /**
1981
+ * clear all listener of global data
1982
+ * if appName exists, only the specified functions is cleared
1983
+ * if appName not exists, only clear the base app functions
1984
+ */
1985
+ clearGlobalDataListener() {
1986
+ const appName = this.appName;
1987
+ const eventInfo = eventCenter.eventList.get('global');
1988
+ if (eventInfo) {
1989
+ for (const cb of eventInfo.callbacks) {
1990
+ if ((appName && appName === cb.__APP_NAME__) ||
1991
+ !(appName || cb.__APP_NAME__)) {
1992
+ eventInfo.callbacks.delete(cb);
2154
1993
  }
2155
1994
  }
2156
1995
  }
2157
1996
  }
2158
- // create proxyWindow with Proxy(microAppWindow)
2159
- createProxyWindow() {
2160
- const rawWindow = globalEnv.rawWindow;
2161
- const descriptorTargetMap = new Map();
2162
- // window.xxx will trigger proxy
2163
- return new Proxy(this.microAppWindow, {
2164
- get: (target, key) => {
2165
- if (Reflect.has(target, key) ||
2166
- (isString(key) && /^__MICRO_APP_/.test(key)) ||
2167
- this.scopeProperties.includes(key))
2168
- return Reflect.get(target, key);
2169
- const rawValue = Reflect.get(rawWindow, key);
2170
- return isFunction(rawValue) ? bindFunctionToRawWindow(rawWindow, rawValue) : rawValue;
2171
- },
2172
- set: (target, key, value) => {
2173
- if (this.active) {
2174
- if (escapeSetterKeyList.includes(key)) {
2175
- Reflect.set(rawWindow, key, value);
2176
- }
2177
- else if (
2178
- // target.hasOwnProperty has been rewritten
2179
- !rawHasOwnProperty.call(target, key) &&
2180
- rawHasOwnProperty.call(rawWindow, key) &&
2181
- !this.scopeProperties.includes(key)) {
2182
- const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key);
2183
- const { configurable, enumerable, writable, set } = descriptor;
2184
- // set value because it can be set
2185
- rawDefineProperty(target, key, {
2186
- value,
2187
- configurable,
2188
- enumerable,
2189
- writable: writable !== null && writable !== void 0 ? writable : !!set,
2190
- });
2191
- this.injectedKeys.add(key);
2192
- }
2193
- else {
2194
- Reflect.set(target, key, value);
2195
- this.injectedKeys.add(key);
2196
- }
2197
- if ((this.escapeProperties.includes(key) ||
2198
- (staticEscapeProperties.includes(key) && !Reflect.has(rawWindow, key))) &&
2199
- !this.scopeProperties.includes(key)) {
2200
- Reflect.set(rawWindow, key, value);
2201
- this.escapeKeys.add(key);
2202
- }
2203
- }
2204
- return true;
2205
- },
2206
- has: (target, key) => {
2207
- if (this.scopeProperties.includes(key))
2208
- return key in target;
2209
- return key in target || key in rawWindow;
2210
- },
2211
- // Object.getOwnPropertyDescriptor(window, key)
2212
- getOwnPropertyDescriptor: (target, key) => {
2213
- if (rawHasOwnProperty.call(target, key)) {
2214
- descriptorTargetMap.set(key, 'target');
2215
- return Object.getOwnPropertyDescriptor(target, key);
2216
- }
2217
- if (rawHasOwnProperty.call(rawWindow, key)) {
2218
- descriptorTargetMap.set(key, 'rawWindow');
2219
- const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key);
2220
- if (descriptor && !descriptor.configurable) {
2221
- descriptor.configurable = true;
2222
- }
2223
- return descriptor;
2224
- }
2225
- return undefined;
2226
- },
2227
- // Object.defineProperty(window, key, Descriptor)
2228
- defineProperty: (target, key, value) => {
2229
- const from = descriptorTargetMap.get(key);
2230
- if (from === 'rawWindow') {
2231
- return Reflect.defineProperty(rawWindow, key, value);
2232
- }
2233
- return Reflect.defineProperty(target, key, value);
2234
- },
2235
- // Object.getOwnPropertyNames(window)
2236
- ownKeys: (target) => {
2237
- return unique(Reflect.ownKeys(rawWindow).concat(Reflect.ownKeys(target)));
2238
- },
2239
- deleteProperty: (target, key) => {
2240
- if (rawHasOwnProperty.call(target, key)) {
2241
- this.injectedKeys.has(key) && this.injectedKeys.delete(key);
2242
- this.escapeKeys.has(key) && Reflect.deleteProperty(rawWindow, key);
2243
- return Reflect.deleteProperty(target, key);
2244
- }
2245
- return true;
2246
- },
2247
- });
1997
+ }
1998
+ // Event center for base app
1999
+ class EventCenterForBaseApp extends EventCenterForGlobal {
2000
+ /**
2001
+ * add listener
2002
+ * @param appName app.name
2003
+ * @param cb listener
2004
+ * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
2005
+ */
2006
+ addDataListener(appName, cb, autoTrigger) {
2007
+ eventCenter.on(formatEventName(formatAppName(appName), false), cb, autoTrigger);
2248
2008
  }
2249
2009
  /**
2250
- * inject global properties to microAppWindow
2251
- * @param microAppWindow micro window
2252
- * @param appName app name
2253
- * @param url app url
2010
+ * remove listener
2011
+ * @param appName app.name
2012
+ * @param cb listener
2254
2013
  */
2255
- initMicroAppWindow(microAppWindow, appName, url) {
2256
- microAppWindow.__MICRO_APP_ENVIRONMENT__ = true;
2257
- microAppWindow.__MICRO_APP_NAME__ = appName;
2258
- microAppWindow.__MICRO_APP_PUBLIC_PATH__ = getEffectivePath(url);
2259
- microAppWindow.__MICRO_APP_WINDOW__ = microAppWindow;
2260
- microAppWindow.microApp = new EventCenterForMicroApp(appName);
2261
- microAppWindow.rawWindow = globalEnv.rawWindow;
2262
- microAppWindow.rawDocument = globalEnv.rawDocument;
2263
- microAppWindow.removeDomScope = removeDomScope;
2264
- microAppWindow.hasOwnProperty = (key) => rawHasOwnProperty.call(microAppWindow, key) || rawHasOwnProperty.call(globalEnv.rawWindow, key);
2265
- this.setMappingPropertiesWithRawDescriptor(microAppWindow);
2266
- this.setHijackProperties(microAppWindow, appName);
2014
+ removeDataListener(appName, cb) {
2015
+ isFunction(cb) && eventCenter.off(formatEventName(formatAppName(appName), false), cb);
2267
2016
  }
2268
- // properties associated with the native window
2269
- setMappingPropertiesWithRawDescriptor(microAppWindow) {
2270
- let topValue, parentValue;
2271
- const rawWindow = globalEnv.rawWindow;
2272
- if (rawWindow === rawWindow.parent) { // not in iframe
2273
- topValue = parentValue = this.proxyWindow;
2017
+ /**
2018
+ * get data from micro app or base app
2019
+ * @param appName app.name
2020
+ * @param fromBaseApp whether get data from base app, default is false
2021
+ */
2022
+ getData(appName, fromBaseApp = false) {
2023
+ return eventCenter.getData(formatEventName(formatAppName(appName), fromBaseApp));
2024
+ }
2025
+ /**
2026
+ * Dispatch data to the specified micro app
2027
+ * @param appName app.name
2028
+ * @param data data
2029
+ */
2030
+ setData(appName, data) {
2031
+ eventCenter.dispatch(formatEventName(formatAppName(appName), true), data);
2032
+ }
2033
+ /**
2034
+ * clear all listener for specified micro app
2035
+ * @param appName app.name
2036
+ */
2037
+ clearDataListener(appName) {
2038
+ eventCenter.off(formatEventName(formatAppName(appName), false));
2039
+ }
2040
+ }
2041
+ // Event center for sub app
2042
+ class EventCenterForMicroApp extends EventCenterForGlobal {
2043
+ constructor(appName) {
2044
+ super();
2045
+ this.appName = formatAppName(appName);
2046
+ !this.appName && logError(`Invalid appName ${appName}`);
2047
+ }
2048
+ /**
2049
+ * add listener, monitor the data sent by the base app
2050
+ * @param cb listener
2051
+ * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
2052
+ */
2053
+ addDataListener(cb, autoTrigger) {
2054
+ cb.__AUTO_TRIGGER__ = autoTrigger;
2055
+ eventCenter.on(formatEventName(this.appName, true), cb, autoTrigger);
2056
+ }
2057
+ /**
2058
+ * remove listener
2059
+ * @param cb listener
2060
+ */
2061
+ removeDataListener(cb) {
2062
+ isFunction(cb) && eventCenter.off(formatEventName(this.appName, true), cb);
2063
+ }
2064
+ /**
2065
+ * get data from base app
2066
+ */
2067
+ getData() {
2068
+ return eventCenter.getData(formatEventName(this.appName, true));
2069
+ }
2070
+ /**
2071
+ * dispatch data to base app
2072
+ * @param data data
2073
+ */
2074
+ dispatch(data) {
2075
+ removeDomScope();
2076
+ eventCenter.dispatch(formatEventName(this.appName, false), data);
2077
+ const app = appInstanceMap.get(this.appName);
2078
+ if ((app === null || app === void 0 ? void 0 : app.container) && isPlainObject(data)) {
2079
+ const event = new CustomEvent('datachange', {
2080
+ detail: {
2081
+ data,
2082
+ }
2083
+ });
2084
+ getRootContainer(app.container).dispatchEvent(event);
2274
2085
  }
2275
- else { // in iframe
2276
- topValue = rawWindow.top;
2277
- parentValue = rawWindow.parent;
2086
+ }
2087
+ /**
2088
+ * clear all listeners
2089
+ */
2090
+ clearDataListener() {
2091
+ eventCenter.off(formatEventName(this.appName, true));
2092
+ }
2093
+ }
2094
+ /**
2095
+ * Record UMD function before exec umdHookMount
2096
+ * @param microAppEventCenter
2097
+ */
2098
+ function recordDataCenterSnapshot(microAppEventCenter) {
2099
+ const appName = microAppEventCenter.appName;
2100
+ microAppEventCenter.umdDataListeners = { global: new Set(), normal: new Set() };
2101
+ const globalEventInfo = eventCenter.eventList.get('global');
2102
+ if (globalEventInfo) {
2103
+ for (const cb of globalEventInfo.callbacks) {
2104
+ if (appName === cb.__APP_NAME__) {
2105
+ microAppEventCenter.umdDataListeners.global.add(cb);
2106
+ }
2278
2107
  }
2279
- rawDefineProperty(microAppWindow, 'top', this.createDescriptorFormicroAppWindow('top', topValue));
2280
- rawDefineProperty(microAppWindow, 'parent', this.createDescriptorFormicroAppWindow('parent', parentValue));
2281
- globalPropertyList.forEach((key) => {
2282
- rawDefineProperty(microAppWindow, key, this.createDescriptorFormicroAppWindow(key, this.proxyWindow));
2283
- });
2284
2108
  }
2285
- createDescriptorFormicroAppWindow(key, value) {
2286
- const { configurable = true, enumerable = true, writable, set } = Object.getOwnPropertyDescriptor(globalEnv.rawWindow, key) || { writable: true };
2287
- const descriptor = {
2288
- value,
2289
- configurable,
2290
- enumerable,
2291
- writable: writable !== null && writable !== void 0 ? writable : !!set
2292
- };
2293
- return descriptor;
2109
+ const subAppEventInfo = eventCenter.eventList.get(formatEventName(appName, true));
2110
+ if (subAppEventInfo) {
2111
+ microAppEventCenter.umdDataListeners.normal = new Set(subAppEventInfo.callbacks);
2294
2112
  }
2295
- // set hijack Properties to microAppWindow
2296
- setHijackProperties(microAppWindow, appName) {
2297
- let modifiedEval, modifiedImage;
2298
- rawDefineProperties(microAppWindow, {
2299
- document: {
2300
- get() {
2301
- throttleDeferForSetAppName(appName);
2302
- return globalEnv.rawDocument;
2303
- },
2304
- configurable: false,
2305
- enumerable: true,
2306
- },
2307
- eval: {
2308
- get() {
2309
- throttleDeferForSetAppName(appName);
2310
- return modifiedEval || eval;
2311
- },
2312
- set: (value) => {
2313
- modifiedEval = value;
2314
- },
2315
- configurable: true,
2316
- enumerable: false,
2317
- },
2318
- Image: {
2319
- get() {
2320
- throttleDeferForSetAppName(appName);
2321
- return modifiedImage || globalEnv.ImageProxy;
2322
- },
2323
- set: (value) => {
2324
- modifiedImage = value;
2325
- },
2113
+ }
2114
+ /**
2115
+ * Rebind the UMD function of the record before remount
2116
+ * @param microAppEventCenter instance of EventCenterForMicroApp
2117
+ */
2118
+ function rebuildDataCenterSnapshot(microAppEventCenter) {
2119
+ for (const cb of microAppEventCenter.umdDataListeners.global) {
2120
+ microAppEventCenter.addGlobalDataListener(cb, cb.__AUTO_TRIGGER__);
2121
+ }
2122
+ for (const cb of microAppEventCenter.umdDataListeners.normal) {
2123
+ microAppEventCenter.addDataListener(cb, cb.__AUTO_TRIGGER__);
2124
+ }
2125
+ }
2126
+
2127
+ /* eslint-disable no-return-assign */
2128
+ function isBoundedFunction(value) {
2129
+ if (isBoolean(value.__MICRO_APP_IS_BOUND_FUNCTION__))
2130
+ return value.__MICRO_APP_IS_BOUND_FUNCTION__;
2131
+ return value.__MICRO_APP_IS_BOUND_FUNCTION__ = isBoundFunction(value);
2132
+ }
2133
+ function isConstructor(value) {
2134
+ var _a;
2135
+ if (isBoolean(value.__MICRO_APP_IS_CONSTRUCTOR__))
2136
+ return value.__MICRO_APP_IS_CONSTRUCTOR__;
2137
+ const valueStr = value.toString();
2138
+ const result = (((_a = value.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === value &&
2139
+ Object.getOwnPropertyNames(value.prototype).length > 1) ||
2140
+ /^function\s+[A-Z]/.test(valueStr) ||
2141
+ /^class\s+/.test(valueStr);
2142
+ return value.__MICRO_APP_IS_CONSTRUCTOR__ = result;
2143
+ }
2144
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
2145
+ function bindFunctionToRawWindow(rawWindow, value) {
2146
+ if (value.__MICRO_APP_BOUND_WINDOW_FUNCTION__)
2147
+ return value.__MICRO_APP_BOUND_WINDOW_FUNCTION__;
2148
+ if (!isConstructor(value) && !isBoundedFunction(value)) {
2149
+ const bindRawWindowValue = value.bind(rawWindow);
2150
+ for (const key in value) {
2151
+ bindRawWindowValue[key] = value[key];
2152
+ }
2153
+ if (value.hasOwnProperty('prototype')) {
2154
+ rawDefineProperty(bindRawWindowValue, 'prototype', {
2155
+ value: value.prototype,
2326
2156
  configurable: true,
2327
2157
  enumerable: false,
2328
- },
2329
- });
2158
+ writable: true,
2159
+ });
2160
+ }
2161
+ return value.__MICRO_APP_BOUND_WINDOW_FUNCTION__ = bindRawWindowValue;
2330
2162
  }
2163
+ return value;
2331
2164
  }
2332
- SandBox.activeCount = 0; // number of active sandbox
2333
2165
 
2334
- function formatEventInfo(event, element) {
2335
- Object.defineProperties(event, {
2336
- currentTarget: {
2337
- get() {
2338
- return element;
2339
- }
2340
- },
2341
- target: {
2342
- get() {
2343
- return element;
2344
- }
2345
- },
2346
- });
2347
- }
2166
+ // document.onclick binding list, the binding function of each application is unique
2167
+ const documentClickListMap = new Map();
2168
+ let hasRewriteDocumentOnClick = false;
2348
2169
  /**
2349
- * dispatch lifeCycles event to base app
2350
- * created, beforemount, mounted, unmount, error
2351
- * @param element container
2352
- * @param appName app.name
2353
- * @param lifecycleName lifeCycle name
2354
- * @param error param from error hook
2170
+ * Rewrite document.onclick and execute it only once
2355
2171
  */
2356
- function dispatchLifecyclesEvent(element, appName, lifecycleName, error) {
2357
- var _a;
2358
- if (!element) {
2359
- return logError(`element does not exist in lifecycle ${lifecycleName}`, appName);
2172
+ function overwriteDocumentOnClick() {
2173
+ hasRewriteDocumentOnClick = true;
2174
+ if (Object.getOwnPropertyDescriptor(document, 'onclick')) {
2175
+ return logWarn('Cannot redefine document property onclick');
2360
2176
  }
2361
- element = getRootContainer(element);
2362
- // clear dom scope before dispatch lifeCycles event to base app, especially mounted & unmount
2363
- removeDomScope();
2364
- const detail = Object.assign({
2365
- name: appName,
2366
- container: element,
2367
- }, error && {
2368
- error
2369
- });
2370
- const event = new CustomEvent(lifecycleName, {
2371
- detail,
2372
- });
2373
- formatEventInfo(event, element);
2374
- // global hooks
2375
- // @ts-ignore
2376
- if (isFunction((_a = microApp.lifeCycles) === null || _a === void 0 ? void 0 : _a[lifecycleName])) {
2377
- // @ts-ignore
2378
- microApp.lifeCycles[lifecycleName](event);
2177
+ const rawOnClick = document.onclick;
2178
+ document.onclick = null;
2179
+ let hasDocumentClickInited = false;
2180
+ function onClickHandler(e) {
2181
+ documentClickListMap.forEach((f) => {
2182
+ isFunction(f) && f.call(document, e);
2183
+ });
2379
2184
  }
2380
- element.dispatchEvent(event);
2185
+ rawDefineProperty(document, 'onclick', {
2186
+ configurable: true,
2187
+ enumerable: true,
2188
+ get() {
2189
+ const appName = getCurrentAppName();
2190
+ return appName ? documentClickListMap.get(appName) : documentClickListMap.get('base');
2191
+ },
2192
+ set(f) {
2193
+ const appName = getCurrentAppName();
2194
+ if (appName) {
2195
+ documentClickListMap.set(appName, f);
2196
+ }
2197
+ else {
2198
+ documentClickListMap.set('base', f);
2199
+ }
2200
+ if (!hasDocumentClickInited && isFunction(f)) {
2201
+ hasDocumentClickInited = true;
2202
+ globalEnv.rawDocumentAddEventListener.call(globalEnv.rawDocument, 'click', onClickHandler, false);
2203
+ }
2204
+ }
2205
+ });
2206
+ rawOnClick && (document.onclick = rawOnClick);
2381
2207
  }
2382
2208
  /**
2383
- * Dispatch custom event to micro app
2384
- * @param eventName event name
2385
- * @param appName app name
2386
- * @param detail event detail
2209
+ * The document event is globally, we need to clear these event bindings when micro application unmounted
2387
2210
  */
2388
- function dispatchCustomEventToMicroApp(eventName, appName, detail = {}) {
2389
- const event = new CustomEvent(`${eventName}-${appName}`, {
2390
- detail,
2391
- });
2392
- window.dispatchEvent(event);
2393
- }
2394
-
2395
- // micro app instances
2396
- const appInstanceMap = new Map();
2397
- class CreateApp {
2398
- constructor({ name, url, ssrUrl, container, inline, scopecss, useSandbox, baseroute, }) {
2399
- this.state = appStates.NOT_LOADED;
2400
- this.keepAliveState = null;
2401
- this.keepAliveContainer = null;
2402
- this.loadSourceLevel = 0;
2403
- this.umdHookMount = null;
2404
- this.umdHookUnmount = null;
2405
- this.libraryName = null;
2406
- this.umdMode = false;
2407
- this.isPrefetch = false;
2408
- this.container = null;
2409
- this.baseroute = '';
2410
- this.sandBox = null;
2411
- this.container = container !== null && container !== void 0 ? container : null;
2412
- this.inline = inline !== null && inline !== void 0 ? inline : false;
2413
- this.baseroute = baseroute !== null && baseroute !== void 0 ? baseroute : '';
2414
- this.ssrUrl = ssrUrl !== null && ssrUrl !== void 0 ? ssrUrl : '';
2415
- // optional during init👆
2416
- this.name = name;
2417
- this.url = url;
2418
- this.useSandbox = useSandbox;
2419
- this.scopecss = this.useSandbox && scopecss;
2420
- this.source = {
2421
- links: new Map(),
2422
- scripts: new Map(),
2423
- };
2424
- this.loadSourceCode();
2425
- this.useSandbox && (this.sandBox = new SandBox(name, url));
2426
- }
2427
- // Load resources
2428
- loadSourceCode() {
2429
- this.state = appStates.LOADING_SOURCE_CODE;
2430
- extractHtml(this);
2431
- }
2432
- /**
2433
- * When resource is loaded, mount app if it is not prefetch or unmount
2434
- */
2435
- onLoad(html) {
2436
- if (++this.loadSourceLevel === 2) {
2437
- this.source.html = html;
2438
- if (this.isPrefetch || appStates.UNMOUNT === this.state)
2439
- return;
2440
- this.state = appStates.LOAD_SOURCE_FINISHED;
2441
- this.mount();
2211
+ const documentEventListenerMap = new Map();
2212
+ function effectDocumentEvent() {
2213
+ const { rawDocument, rawDocumentAddEventListener, rawDocumentRemoveEventListener, } = globalEnv;
2214
+ !hasRewriteDocumentOnClick && overwriteDocumentOnClick();
2215
+ document.addEventListener = function (type, listener, options) {
2216
+ var _a;
2217
+ const appName = getCurrentAppName();
2218
+ /**
2219
+ * ignore bound function of document event in umd mode, used to solve problem of react global events
2220
+ */
2221
+ if (appName && !(((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.umdMode) && isBoundFunction(listener))) {
2222
+ const appListenersMap = documentEventListenerMap.get(appName);
2223
+ if (appListenersMap) {
2224
+ const appListenerList = appListenersMap.get(type);
2225
+ if (appListenerList) {
2226
+ appListenerList.add(listener);
2227
+ }
2228
+ else {
2229
+ appListenersMap.set(type, new Set([listener]));
2230
+ }
2231
+ }
2232
+ else {
2233
+ documentEventListenerMap.set(appName, new Map([[type, new Set([listener])]]));
2234
+ }
2235
+ listener && (listener.__MICRO_APP_MARK_OPTIONS__ = options);
2442
2236
  }
2443
- }
2444
- /**
2445
- * Error loading HTML
2446
- * @param e Error
2447
- */
2448
- onLoadError(e) {
2449
- this.loadSourceLevel = -1;
2450
- if (appStates.UNMOUNT !== this.state) {
2451
- this.onerror(e);
2452
- this.state = appStates.LOAD_SOURCE_ERROR;
2237
+ rawDocumentAddEventListener.call(rawDocument, type, listener, options);
2238
+ };
2239
+ document.removeEventListener = function (type, listener, options) {
2240
+ var _a;
2241
+ const appName = getCurrentAppName();
2242
+ if (appName && !(((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.umdMode) && isBoundFunction(listener))) {
2243
+ const appListenersMap = documentEventListenerMap.get(appName);
2244
+ if (appListenersMap) {
2245
+ const appListenerList = appListenersMap.get(type);
2246
+ if ((appListenerList === null || appListenerList === void 0 ? void 0 : appListenerList.size) && appListenerList.has(listener)) {
2247
+ appListenerList.delete(listener);
2248
+ }
2249
+ }
2453
2250
  }
2251
+ rawDocumentRemoveEventListener.call(rawDocument, type, listener, options);
2252
+ };
2253
+ }
2254
+ // Clear the document event agent
2255
+ function releaseEffectDocumentEvent() {
2256
+ document.addEventListener = globalEnv.rawDocumentAddEventListener;
2257
+ document.removeEventListener = globalEnv.rawDocumentRemoveEventListener;
2258
+ }
2259
+ // this events should be sent to the specified app
2260
+ const formatEventList = ['unmount', 'appstate-change'];
2261
+ /**
2262
+ * Format event name
2263
+ * @param type event name
2264
+ * @param microAppWindow micro window
2265
+ */
2266
+ function formatEventType(type, microAppWindow) {
2267
+ if (formatEventList.includes(type)) {
2268
+ return `${type}-${microAppWindow.__MICRO_APP_NAME__}`;
2454
2269
  }
2455
- /**
2456
- * mount app
2457
- * @param container app container
2458
- * @param inline js runs in inline mode
2459
- * @param baseroute route prefix, default is ''
2460
- */
2461
- mount(container, inline, baseroute) {
2462
- var _a, _b, _c;
2463
- if (isBoolean(inline) && inline !== this.inline) {
2464
- this.inline = inline;
2270
+ return type;
2271
+ }
2272
+ /**
2273
+ * Rewrite side-effect events
2274
+ * @param microAppWindow micro window
2275
+ */
2276
+ function effect(microAppWindow) {
2277
+ const appName = microAppWindow.__MICRO_APP_NAME__;
2278
+ const eventListenerMap = new Map();
2279
+ const intervalIdMap = new Map();
2280
+ const timeoutIdMap = new Map();
2281
+ const { rawWindow, rawDocument, rawWindowAddEventListener, rawWindowRemoveEventListener, rawSetInterval, rawSetTimeout, rawClearInterval, rawClearTimeout, rawDocumentRemoveEventListener, } = globalEnv;
2282
+ // listener may be null, e.g test-passive
2283
+ microAppWindow.addEventListener = function (type, listener, options) {
2284
+ type = formatEventType(type, microAppWindow);
2285
+ const listenerList = eventListenerMap.get(type);
2286
+ if (listenerList) {
2287
+ listenerList.add(listener);
2465
2288
  }
2466
- this.container = (_a = this.container) !== null && _a !== void 0 ? _a : container;
2467
- this.baseroute = baseroute !== null && baseroute !== void 0 ? baseroute : this.baseroute;
2468
- if (this.loadSourceLevel !== 2) {
2469
- this.state = appStates.LOADING_SOURCE_CODE;
2470
- return;
2289
+ else {
2290
+ eventListenerMap.set(type, new Set([listener]));
2471
2291
  }
2472
- dispatchLifecyclesEvent(this.container, this.name, lifeCycles.BEFOREMOUNT);
2473
- this.state = appStates.MOUNTING;
2474
- cloneContainer(this.source.html, this.container, !this.umdMode);
2475
- (_b = this.sandBox) === null || _b === void 0 ? void 0 : _b.start(this.baseroute);
2476
- let umdHookMountResult; // result of mount function
2477
- if (!this.umdMode) {
2478
- let hasDispatchMountedEvent = false;
2479
- // if all js are executed, param isFinished will be true
2480
- execScripts(this.source.scripts, this, (isFinished) => {
2481
- var _a;
2482
- if (!this.umdMode) {
2483
- const { mount, unmount } = this.getUmdLibraryHooks();
2484
- // if mount & unmount is function, the sub app is umd mode
2485
- if (isFunction(mount) && isFunction(unmount)) {
2486
- this.umdHookMount = mount;
2487
- this.umdHookUnmount = unmount;
2488
- this.umdMode = true;
2489
- (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.recordUmdSnapshot();
2490
- try {
2491
- umdHookMountResult = this.umdHookMount();
2492
- }
2493
- catch (e) {
2494
- logError('an error occurred in the mount function \n', this.name, e);
2495
- }
2496
- }
2497
- }
2498
- if (!hasDispatchMountedEvent && (isFinished === true || this.umdMode)) {
2499
- hasDispatchMountedEvent = true;
2500
- this.handleMounted(umdHookMountResult);
2292
+ listener && (listener.__MICRO_APP_MARK_OPTIONS__ = options);
2293
+ rawWindowAddEventListener.call(rawWindow, type, listener, options);
2294
+ };
2295
+ microAppWindow.removeEventListener = function (type, listener, options) {
2296
+ type = formatEventType(type, microAppWindow);
2297
+ const listenerList = eventListenerMap.get(type);
2298
+ if ((listenerList === null || listenerList === void 0 ? void 0 : listenerList.size) && listenerList.has(listener)) {
2299
+ listenerList.delete(listener);
2300
+ }
2301
+ rawWindowRemoveEventListener.call(rawWindow, type, listener, options);
2302
+ };
2303
+ microAppWindow.setInterval = function (handler, timeout, ...args) {
2304
+ const intervalId = rawSetInterval.call(rawWindow, handler, timeout, ...args);
2305
+ intervalIdMap.set(intervalId, { handler, timeout, args });
2306
+ return intervalId;
2307
+ };
2308
+ microAppWindow.setTimeout = function (handler, timeout, ...args) {
2309
+ const timeoutId = rawSetTimeout.call(rawWindow, handler, timeout, ...args);
2310
+ timeoutIdMap.set(timeoutId, { handler, timeout, args });
2311
+ return timeoutId;
2312
+ };
2313
+ microAppWindow.clearInterval = function (intervalId) {
2314
+ intervalIdMap.delete(intervalId);
2315
+ rawClearInterval.call(rawWindow, intervalId);
2316
+ };
2317
+ microAppWindow.clearTimeout = function (timeoutId) {
2318
+ timeoutIdMap.delete(timeoutId);
2319
+ rawClearTimeout.call(rawWindow, timeoutId);
2320
+ };
2321
+ const umdWindowListenerMap = new Map();
2322
+ const umdDocumentListenerMap = new Map();
2323
+ let umdIntervalIdMap = new Map();
2324
+ let umdTimeoutIdMap = new Map();
2325
+ let umdOnClickHandler;
2326
+ // record event and timer before exec umdMountHook
2327
+ const recordUmdEffect = () => {
2328
+ // record window event
2329
+ eventListenerMap.forEach((listenerList, type) => {
2330
+ if (listenerList.size) {
2331
+ umdWindowListenerMap.set(type, new Set(listenerList));
2332
+ }
2333
+ });
2334
+ // record timers
2335
+ if (intervalIdMap.size) {
2336
+ umdIntervalIdMap = new Map(intervalIdMap);
2337
+ }
2338
+ if (timeoutIdMap.size) {
2339
+ umdTimeoutIdMap = new Map(timeoutIdMap);
2340
+ }
2341
+ // record onclick handler
2342
+ umdOnClickHandler = documentClickListMap.get(appName);
2343
+ // record document event
2344
+ const documentAppListenersMap = documentEventListenerMap.get(appName);
2345
+ if (documentAppListenersMap) {
2346
+ documentAppListenersMap.forEach((listenerList, type) => {
2347
+ if (listenerList.size) {
2348
+ umdDocumentListenerMap.set(type, new Set(listenerList));
2501
2349
  }
2502
2350
  });
2503
2351
  }
2504
- else {
2505
- (_c = this.sandBox) === null || _c === void 0 ? void 0 : _c.rebuildUmdSnapshot();
2506
- try {
2507
- umdHookMountResult = this.umdHookMount();
2352
+ };
2353
+ // rebuild event and timer before remount umd app
2354
+ const rebuildUmdEffect = () => {
2355
+ // rebuild window event
2356
+ umdWindowListenerMap.forEach((listenerList, type) => {
2357
+ for (const listener of listenerList) {
2358
+ microAppWindow.addEventListener(type, listener, listener === null || listener === void 0 ? void 0 : listener.__MICRO_APP_MARK_OPTIONS__);
2508
2359
  }
2509
- catch (e) {
2510
- logError('an error occurred in the mount function \n', this.name, e);
2360
+ });
2361
+ // rebuild timer
2362
+ umdIntervalIdMap.forEach((info) => {
2363
+ microAppWindow.setInterval(info.handler, info.timeout, ...info.args);
2364
+ });
2365
+ umdTimeoutIdMap.forEach((info) => {
2366
+ microAppWindow.setTimeout(info.handler, info.timeout, ...info.args);
2367
+ });
2368
+ // rebuild onclick event
2369
+ umdOnClickHandler && documentClickListMap.set(appName, umdOnClickHandler);
2370
+ // rebuild document event
2371
+ setCurrentAppName(appName);
2372
+ umdDocumentListenerMap.forEach((listenerList, type) => {
2373
+ for (const listener of listenerList) {
2374
+ document.addEventListener(type, listener, listener === null || listener === void 0 ? void 0 : listener.__MICRO_APP_MARK_OPTIONS__);
2511
2375
  }
2512
- this.handleMounted(umdHookMountResult);
2376
+ });
2377
+ setCurrentAppName(null);
2378
+ };
2379
+ // release all event listener & interval & timeout when unmount app
2380
+ const releaseEffect = () => {
2381
+ // Clear window binding events
2382
+ if (eventListenerMap.size) {
2383
+ eventListenerMap.forEach((listenerList, type) => {
2384
+ for (const listener of listenerList) {
2385
+ rawWindowRemoveEventListener.call(rawWindow, type, listener);
2386
+ }
2387
+ });
2388
+ eventListenerMap.clear();
2513
2389
  }
2514
- }
2515
- /**
2516
- * handle for promise umdHookMount
2517
- * @param umdHookMountResult result of umdHookMount
2518
- */
2519
- handleMounted(umdHookMountResult) {
2520
- if (isPromise(umdHookMountResult)) {
2521
- umdHookMountResult
2522
- .then(() => this.dispatchMountedEvent())
2523
- .catch((e) => this.onerror(e));
2390
+ // Clear timers
2391
+ if (intervalIdMap.size) {
2392
+ intervalIdMap.forEach((_, intervalId) => {
2393
+ rawClearInterval.call(rawWindow, intervalId);
2394
+ });
2395
+ intervalIdMap.clear();
2524
2396
  }
2525
- else {
2526
- this.dispatchMountedEvent();
2397
+ if (timeoutIdMap.size) {
2398
+ timeoutIdMap.forEach((_, timeoutId) => {
2399
+ rawClearTimeout.call(rawWindow, timeoutId);
2400
+ });
2401
+ timeoutIdMap.clear();
2527
2402
  }
2528
- }
2529
- /**
2530
- * dispatch mounted event when app run finished
2531
- */
2532
- dispatchMountedEvent() {
2533
- if (appStates.UNMOUNT !== this.state) {
2534
- this.state = appStates.MOUNTED;
2535
- dispatchLifecyclesEvent(this.container, this.name, lifeCycles.MOUNTED);
2403
+ // Clear the function bound by micro application through document.onclick
2404
+ documentClickListMap.delete(appName);
2405
+ // Clear document binding event
2406
+ const documentAppListenersMap = documentEventListenerMap.get(appName);
2407
+ if (documentAppListenersMap) {
2408
+ documentAppListenersMap.forEach((listenerList, type) => {
2409
+ for (const listener of listenerList) {
2410
+ rawDocumentRemoveEventListener.call(rawDocument, type, listener);
2411
+ }
2412
+ });
2413
+ documentAppListenersMap.clear();
2536
2414
  }
2415
+ };
2416
+ return {
2417
+ recordUmdEffect,
2418
+ rebuildUmdEffect,
2419
+ releaseEffect,
2420
+ };
2421
+ }
2422
+
2423
+ // Variables that can escape to rawWindow
2424
+ const staticEscapeProperties = [
2425
+ 'System',
2426
+ '__cjsWrapper',
2427
+ ];
2428
+ // Variables that can only assigned to rawWindow
2429
+ const escapeSetterKeyList = [
2430
+ 'location',
2431
+ ];
2432
+ const globalPropertyList = ['window', 'self', 'globalThis'];
2433
+ class SandBox {
2434
+ constructor(appName, url) {
2435
+ // Scoped global Properties(Properties that can only get and set in microAppWindow, will not escape to rawWindow)
2436
+ this.scopeProperties = ['webpackJsonp'];
2437
+ // Properties that can be escape to rawWindow
2438
+ this.escapeProperties = [];
2439
+ // Properties newly added to microAppWindow
2440
+ this.injectedKeys = new Set();
2441
+ // Properties escape to rawWindow, cleared when unmount
2442
+ this.escapeKeys = new Set();
2443
+ // sandbox state
2444
+ this.active = false;
2445
+ this.microAppWindow = {}; // Proxy target
2446
+ // get scopeProperties and escapeProperties from plugins
2447
+ this.getSpecialProperties(appName);
2448
+ // create proxyWindow with Proxy(microAppWindow)
2449
+ this.proxyWindow = this.createProxyWindow(appName);
2450
+ // inject global properties
2451
+ this.initMicroAppWindow(this.microAppWindow, appName, url);
2452
+ // Rewrite global event listener & timeout
2453
+ Object.assign(this, effect(this.microAppWindow));
2537
2454
  }
2538
- /**
2539
- * unmount app
2540
- * @param destroy completely destroy, delete cache resources
2541
- * @param unmountcb callback of unmount
2542
- */
2543
- unmount(destroy, unmountcb) {
2544
- if (this.state === appStates.LOAD_SOURCE_ERROR) {
2545
- destroy = true;
2546
- }
2547
- this.state = appStates.UNMOUNT;
2548
- this.keepAliveState = null;
2549
- this.keepAliveContainer = null;
2550
- // result of unmount function
2551
- let umdHookUnmountResult;
2552
- /**
2553
- * send an unmount event to the micro app or call umd unmount hook
2554
- * before the sandbox is cleared
2555
- */
2556
- if (this.umdHookUnmount) {
2557
- try {
2558
- umdHookUnmountResult = this.umdHookUnmount();
2559
- }
2560
- catch (e) {
2561
- logError('an error occurred in the unmount function \n', this.name, e);
2455
+ start(baseRoute) {
2456
+ if (!this.active) {
2457
+ this.active = true;
2458
+ this.microAppWindow.__MICRO_APP_BASE_ROUTE__ = this.microAppWindow.__MICRO_APP_BASE_URL__ = baseRoute;
2459
+ // BUG FIX: bable-polyfill@6.x
2460
+ globalEnv.rawWindow._babelPolyfill && (globalEnv.rawWindow._babelPolyfill = false);
2461
+ if (++SandBox.activeCount === 1) {
2462
+ effectDocumentEvent();
2463
+ patchElementPrototypeMethods();
2562
2464
  }
2563
2465
  }
2564
- // dispatch unmount event to micro app
2565
- dispatchCustomEventToMicroApp('unmount', this.name);
2566
- this.handleUnmounted(destroy, umdHookUnmountResult, unmountcb);
2567
2466
  }
2568
- /**
2569
- * handle for promise umdHookUnmount
2570
- * @param destroy completely destroy, delete cache resources
2571
- * @param umdHookUnmountResult result of umdHookUnmount
2572
- * @param unmountcb callback of unmount
2573
- */
2574
- handleUnmounted(destroy, umdHookUnmountResult, unmountcb) {
2575
- if (isPromise(umdHookUnmountResult)) {
2576
- umdHookUnmountResult
2577
- .then(() => this.actionsForUnmount(destroy, unmountcb))
2578
- .catch(() => this.actionsForUnmount(destroy, unmountcb));
2579
- }
2580
- else {
2581
- this.actionsForUnmount(destroy, unmountcb);
2467
+ stop() {
2468
+ if (this.active) {
2469
+ this.active = false;
2470
+ this.releaseEffect();
2471
+ this.microAppWindow.microApp.clearDataListener();
2472
+ this.microAppWindow.microApp.clearGlobalDataListener();
2473
+ this.injectedKeys.forEach((key) => {
2474
+ Reflect.deleteProperty(this.microAppWindow, key);
2475
+ });
2476
+ this.injectedKeys.clear();
2477
+ this.escapeKeys.forEach((key) => {
2478
+ Reflect.deleteProperty(globalEnv.rawWindow, key);
2479
+ });
2480
+ this.escapeKeys.clear();
2481
+ if (--SandBox.activeCount === 0) {
2482
+ releaseEffectDocumentEvent();
2483
+ releasePatches();
2484
+ }
2582
2485
  }
2583
2486
  }
2487
+ // record umd snapshot before the first execution of umdHookMount
2488
+ recordUmdSnapshot() {
2489
+ this.microAppWindow.__MICRO_APP_UMD_MODE__ = true;
2490
+ this.recordUmdEffect();
2491
+ recordDataCenterSnapshot(this.microAppWindow.microApp);
2492
+ this.recordUmdInjectedValues = new Map();
2493
+ this.injectedKeys.forEach((key) => {
2494
+ this.recordUmdInjectedValues.set(key, Reflect.get(this.microAppWindow, key));
2495
+ });
2496
+ }
2497
+ // rebuild umd snapshot before remount umd app
2498
+ rebuildUmdSnapshot() {
2499
+ this.recordUmdInjectedValues.forEach((value, key) => {
2500
+ Reflect.set(this.proxyWindow, key, value);
2501
+ });
2502
+ this.rebuildUmdEffect();
2503
+ rebuildDataCenterSnapshot(this.microAppWindow.microApp);
2504
+ }
2584
2505
  /**
2585
- * actions for unmount app
2586
- * @param destroy completely destroy, delete cache resources
2587
- * @param unmountcb callback of unmount
2506
+ * get scopeProperties and escapeProperties from plugins
2507
+ * @param appName app name
2588
2508
  */
2589
- actionsForUnmount(destroy, unmountcb) {
2509
+ getSpecialProperties(appName) {
2590
2510
  var _a;
2591
- (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.stop();
2592
- if (destroy) {
2593
- this.actionsForCompletelyDestroy();
2594
- }
2595
- else if (this.umdMode && this.container.childElementCount) {
2596
- cloneContainer(this.container, this.source.html, false);
2597
- }
2598
- // dispatch unmount event to base app
2599
- dispatchLifecyclesEvent(this.container, this.name, lifeCycles.UNMOUNT);
2600
- this.container.innerHTML = '';
2601
- this.container = null;
2602
- unmountcb && unmountcb();
2511
+ if (!isPlainObject(microApp.plugins))
2512
+ return;
2513
+ this.commonActionForSpecialProperties(microApp.plugins.global);
2514
+ this.commonActionForSpecialProperties((_a = microApp.plugins.modules) === null || _a === void 0 ? void 0 : _a[appName]);
2603
2515
  }
2604
- // actions for completely destroy
2605
- actionsForCompletelyDestroy() {
2606
- if (!this.useSandbox && this.umdMode) {
2607
- delete window[this.libraryName];
2516
+ // common action for global plugins and module plugins
2517
+ commonActionForSpecialProperties(plugins) {
2518
+ if (isArray(plugins)) {
2519
+ for (const plugin of plugins) {
2520
+ if (isPlainObject(plugin)) {
2521
+ if (isArray(plugin.scopeProperties)) {
2522
+ this.scopeProperties = this.scopeProperties.concat(plugin.scopeProperties);
2523
+ }
2524
+ if (isArray(plugin.escapeProperties)) {
2525
+ this.escapeProperties = this.escapeProperties.concat(plugin.escapeProperties);
2526
+ }
2527
+ }
2528
+ }
2608
2529
  }
2609
- appInstanceMap.delete(this.name);
2610
- }
2611
- // hidden app when disconnectedCallback called with keep-alive
2612
- hiddenKeepAliveApp() {
2613
- const oldContainer = this.container;
2614
- cloneContainer(this.container, this.keepAliveContainer ? this.keepAliveContainer : (this.keepAliveContainer = document.createElement('div')), false);
2615
- this.container = this.keepAliveContainer;
2616
- this.keepAliveState = keepAliveStates.KEEP_ALIVE_HIDDEN;
2617
- // event should dispatch before clone node
2618
- // dispatch afterhidden event to micro-app
2619
- dispatchCustomEventToMicroApp('appstate-change', this.name, {
2620
- appState: 'afterhidden',
2621
- });
2622
- // dispatch afterhidden event to base app
2623
- dispatchLifecyclesEvent(oldContainer, this.name, lifeCycles.AFTERHIDDEN);
2624
2530
  }
2625
- // show app when connectedCallback called with keep-alive
2626
- showKeepAliveApp(container) {
2627
- // dispatch beforeshow event to micro-app
2628
- dispatchCustomEventToMicroApp('appstate-change', this.name, {
2629
- appState: 'beforeshow',
2630
- });
2631
- // dispatch beforeshow event to base app
2632
- dispatchLifecyclesEvent(container, this.name, lifeCycles.BEFORESHOW);
2633
- cloneContainer(this.container, container, false);
2634
- this.container = container;
2635
- this.keepAliveState = keepAliveStates.KEEP_ALIVE_SHOW;
2636
- // dispatch aftershow event to micro-app
2637
- dispatchCustomEventToMicroApp('appstate-change', this.name, {
2638
- appState: 'aftershow',
2531
+ // create proxyWindow with Proxy(microAppWindow)
2532
+ createProxyWindow(appName) {
2533
+ const rawWindow = globalEnv.rawWindow;
2534
+ const descriptorTargetMap = new Map();
2535
+ // window.xxx will trigger proxy
2536
+ return new Proxy(this.microAppWindow, {
2537
+ get: (target, key) => {
2538
+ throttleDeferForSetAppName(appName);
2539
+ if (Reflect.has(target, key) ||
2540
+ (isString(key) && /^__MICRO_APP_/.test(key)) ||
2541
+ this.scopeProperties.includes(key))
2542
+ return Reflect.get(target, key);
2543
+ const rawValue = Reflect.get(rawWindow, key);
2544
+ return isFunction(rawValue) ? bindFunctionToRawWindow(rawWindow, rawValue) : rawValue;
2545
+ },
2546
+ set: (target, key, value) => {
2547
+ if (this.active) {
2548
+ if (escapeSetterKeyList.includes(key)) {
2549
+ Reflect.set(rawWindow, key, value);
2550
+ }
2551
+ else if (
2552
+ // target.hasOwnProperty has been rewritten
2553
+ !rawHasOwnProperty.call(target, key) &&
2554
+ rawHasOwnProperty.call(rawWindow, key) &&
2555
+ !this.scopeProperties.includes(key)) {
2556
+ const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key);
2557
+ const { configurable, enumerable, writable, set } = descriptor;
2558
+ // set value because it can be set
2559
+ rawDefineProperty(target, key, {
2560
+ value,
2561
+ configurable,
2562
+ enumerable,
2563
+ writable: writable !== null && writable !== void 0 ? writable : !!set,
2564
+ });
2565
+ this.injectedKeys.add(key);
2566
+ }
2567
+ else {
2568
+ Reflect.set(target, key, value);
2569
+ this.injectedKeys.add(key);
2570
+ }
2571
+ if ((this.escapeProperties.includes(key) ||
2572
+ (staticEscapeProperties.includes(key) && !Reflect.has(rawWindow, key))) &&
2573
+ !this.scopeProperties.includes(key)) {
2574
+ Reflect.set(rawWindow, key, value);
2575
+ this.escapeKeys.add(key);
2576
+ }
2577
+ }
2578
+ return true;
2579
+ },
2580
+ has: (target, key) => {
2581
+ if (this.scopeProperties.includes(key))
2582
+ return key in target;
2583
+ return key in target || key in rawWindow;
2584
+ },
2585
+ // Object.getOwnPropertyDescriptor(window, key)
2586
+ getOwnPropertyDescriptor: (target, key) => {
2587
+ if (rawHasOwnProperty.call(target, key)) {
2588
+ descriptorTargetMap.set(key, 'target');
2589
+ return Object.getOwnPropertyDescriptor(target, key);
2590
+ }
2591
+ if (rawHasOwnProperty.call(rawWindow, key)) {
2592
+ descriptorTargetMap.set(key, 'rawWindow');
2593
+ const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key);
2594
+ if (descriptor && !descriptor.configurable) {
2595
+ descriptor.configurable = true;
2596
+ }
2597
+ return descriptor;
2598
+ }
2599
+ return undefined;
2600
+ },
2601
+ // Object.defineProperty(window, key, Descriptor)
2602
+ defineProperty: (target, key, value) => {
2603
+ const from = descriptorTargetMap.get(key);
2604
+ if (from === 'rawWindow') {
2605
+ return Reflect.defineProperty(rawWindow, key, value);
2606
+ }
2607
+ return Reflect.defineProperty(target, key, value);
2608
+ },
2609
+ // Object.getOwnPropertyNames(window)
2610
+ ownKeys: (target) => {
2611
+ return unique(Reflect.ownKeys(rawWindow).concat(Reflect.ownKeys(target)));
2612
+ },
2613
+ deleteProperty: (target, key) => {
2614
+ if (rawHasOwnProperty.call(target, key)) {
2615
+ this.injectedKeys.has(key) && this.injectedKeys.delete(key);
2616
+ this.escapeKeys.has(key) && Reflect.deleteProperty(rawWindow, key);
2617
+ return Reflect.deleteProperty(target, key);
2618
+ }
2619
+ return true;
2620
+ },
2639
2621
  });
2640
- // dispatch aftershow event to base app
2641
- dispatchLifecyclesEvent(this.container, this.name, lifeCycles.AFTERSHOW);
2642
2622
  }
2643
2623
  /**
2644
- * app rendering error
2645
- * @param e Error
2624
+ * inject global properties to microAppWindow
2625
+ * @param microAppWindow micro window
2626
+ * @param appName app name
2627
+ * @param url app url
2646
2628
  */
2647
- onerror(e) {
2648
- dispatchLifecyclesEvent(this.container, this.name, lifeCycles.ERROR, e);
2629
+ initMicroAppWindow(microAppWindow, appName, url) {
2630
+ microAppWindow.__MICRO_APP_ENVIRONMENT__ = true;
2631
+ microAppWindow.__MICRO_APP_NAME__ = appName;
2632
+ microAppWindow.__MICRO_APP_PUBLIC_PATH__ = getEffectivePath(url);
2633
+ microAppWindow.__MICRO_APP_WINDOW__ = microAppWindow;
2634
+ microAppWindow.microApp = new EventCenterForMicroApp(appName);
2635
+ microAppWindow.rawWindow = globalEnv.rawWindow;
2636
+ microAppWindow.rawDocument = globalEnv.rawDocument;
2637
+ microAppWindow.removeDomScope = removeDomScope;
2638
+ microAppWindow.hasOwnProperty = (key) => rawHasOwnProperty.call(microAppWindow, key) || rawHasOwnProperty.call(globalEnv.rawWindow, key);
2639
+ this.setMappingPropertiesWithRawDescriptor(microAppWindow);
2640
+ this.setHijackProperties(microAppWindow, appName);
2649
2641
  }
2650
- // get app state
2651
- getAppState() {
2652
- return this.state;
2642
+ // properties associated with the native window
2643
+ setMappingPropertiesWithRawDescriptor(microAppWindow) {
2644
+ let topValue, parentValue;
2645
+ const rawWindow = globalEnv.rawWindow;
2646
+ if (rawWindow === rawWindow.parent) { // not in iframe
2647
+ topValue = parentValue = this.proxyWindow;
2648
+ }
2649
+ else { // in iframe
2650
+ topValue = rawWindow.top;
2651
+ parentValue = rawWindow.parent;
2652
+ }
2653
+ rawDefineProperty(microAppWindow, 'top', this.createDescriptorForMicroAppWindow('top', topValue));
2654
+ rawDefineProperty(microAppWindow, 'parent', this.createDescriptorForMicroAppWindow('parent', parentValue));
2655
+ globalPropertyList.forEach((key) => {
2656
+ rawDefineProperty(microAppWindow, key, this.createDescriptorForMicroAppWindow(key, this.proxyWindow));
2657
+ });
2653
2658
  }
2654
- // get keep-alive state
2655
- getKeepAliveState() {
2656
- return this.keepAliveState;
2659
+ createDescriptorForMicroAppWindow(key, value) {
2660
+ const { configurable = true, enumerable = true, writable, set } = Object.getOwnPropertyDescriptor(globalEnv.rawWindow, key) || { writable: true };
2661
+ const descriptor = {
2662
+ value,
2663
+ configurable,
2664
+ enumerable,
2665
+ writable: writable !== null && writable !== void 0 ? writable : !!set
2666
+ };
2667
+ return descriptor;
2657
2668
  }
2658
- // get umd library, if it not exist, return empty object
2659
- getUmdLibraryHooks() {
2660
- var _a, _b;
2661
- // after execScripts, the app maybe unmounted
2662
- if (appStates.UNMOUNT !== this.state) {
2663
- const global = ((_b = (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.proxyWindow) !== null && _b !== void 0 ? _b : globalEnv.rawWindow);
2664
- this.libraryName = getRootContainer(this.container).getAttribute('library') || `micro-app-${this.name}`;
2665
- // do not use isObject
2666
- return typeof global[this.libraryName] === 'object' ? global[this.libraryName] : {};
2667
- }
2668
- return {};
2669
+ // set hijack Properties to microAppWindow
2670
+ setHijackProperties(microAppWindow, appName) {
2671
+ let modifiedEval, modifiedImage;
2672
+ rawDefineProperties(microAppWindow, {
2673
+ document: {
2674
+ get() {
2675
+ throttleDeferForSetAppName(appName);
2676
+ return globalEnv.rawDocument;
2677
+ },
2678
+ configurable: false,
2679
+ enumerable: true,
2680
+ },
2681
+ eval: {
2682
+ get() {
2683
+ throttleDeferForSetAppName(appName);
2684
+ return modifiedEval || eval;
2685
+ },
2686
+ set: (value) => {
2687
+ modifiedEval = value;
2688
+ },
2689
+ configurable: true,
2690
+ enumerable: false,
2691
+ },
2692
+ Image: {
2693
+ get() {
2694
+ throttleDeferForSetAppName(appName);
2695
+ return modifiedImage || globalEnv.ImageProxy;
2696
+ },
2697
+ set: (value) => {
2698
+ modifiedImage = value;
2699
+ },
2700
+ configurable: true,
2701
+ enumerable: false,
2702
+ },
2703
+ });
2669
2704
  }
2670
2705
  }
2706
+ SandBox.activeCount = 0; // number of active sandbox
2671
2707
 
2672
- // Record element and map element
2673
- const dynamicElementInMicroAppMap = new WeakMap();
2708
+ function formatEventInfo(event, element) {
2709
+ Object.defineProperties(event, {
2710
+ currentTarget: {
2711
+ get() {
2712
+ return element;
2713
+ }
2714
+ },
2715
+ target: {
2716
+ get() {
2717
+ return element;
2718
+ }
2719
+ },
2720
+ });
2721
+ }
2674
2722
  /**
2675
- * Process the new node and format the style, link and script element
2676
- * @param parent parent node
2677
- * @param child new node
2678
- * @param app app
2723
+ * dispatch lifeCycles event to base app
2724
+ * created, beforemount, mounted, unmount, error
2725
+ * @param element container
2726
+ * @param appName app.name
2727
+ * @param lifecycleName lifeCycle name
2728
+ * @param error param from error hook
2679
2729
  */
2680
- function handleNewNode(parent, child, app) {
2681
- if (child instanceof HTMLStyleElement) {
2682
- if (child.hasAttribute('exclude')) {
2683
- const replaceComment = document.createComment('style element with exclude attribute ignored by micro-app');
2684
- dynamicElementInMicroAppMap.set(child, replaceComment);
2685
- return replaceComment;
2686
- }
2687
- else if (app.scopecss && !child.hasAttribute('ignore')) {
2688
- return scopedCSS(child, app);
2689
- }
2690
- return child;
2730
+ function dispatchLifecyclesEvent(element, appName, lifecycleName, error) {
2731
+ var _a;
2732
+ if (!element) {
2733
+ return logError(`element does not exist in lifecycle ${lifecycleName}`, appName);
2691
2734
  }
2692
- else if (child instanceof HTMLLinkElement) {
2693
- if (child.hasAttribute('exclude')) {
2694
- const linkReplaceComment = document.createComment('link element with exclude attribute ignored by micro-app');
2695
- dynamicElementInMicroAppMap.set(child, linkReplaceComment);
2696
- return linkReplaceComment;
2697
- }
2698
- else if (child.hasAttribute('ignore')) {
2699
- return child;
2700
- }
2701
- const { url, info, replaceComment } = extractLinkFromHtml(child, parent, app, true);
2702
- if (url && info) {
2703
- const replaceStyle = pureCreateElement('style');
2704
- replaceStyle.__MICRO_APP_LINK_PATH__ = url;
2705
- foramtDynamicLink(url, info, app, child, replaceStyle);
2706
- dynamicElementInMicroAppMap.set(child, replaceStyle);
2707
- return replaceStyle;
2708
- }
2709
- else if (replaceComment) {
2710
- dynamicElementInMicroAppMap.set(child, replaceComment);
2711
- return replaceComment;
2712
- }
2713
- return child;
2735
+ element = getRootContainer(element);
2736
+ // clear dom scope before dispatch lifeCycles event to base app, especially mounted & unmount
2737
+ removeDomScope();
2738
+ const detail = Object.assign({
2739
+ name: appName,
2740
+ container: element,
2741
+ }, error && {
2742
+ error
2743
+ });
2744
+ const event = new CustomEvent(lifecycleName, {
2745
+ detail,
2746
+ });
2747
+ formatEventInfo(event, element);
2748
+ // global hooks
2749
+ // @ts-ignore
2750
+ if (isFunction((_a = microApp.lifeCycles) === null || _a === void 0 ? void 0 : _a[lifecycleName])) {
2751
+ // @ts-ignore
2752
+ microApp.lifeCycles[lifecycleName](event);
2714
2753
  }
2715
- else if (child instanceof HTMLScriptElement) {
2716
- const { replaceComment, url, info } = extractScriptElement(child, parent, app, true) || {};
2717
- if (url && info) {
2718
- if (!info.isExternal) { // inline script
2719
- const replaceElement = runScript(url, app, info, true);
2720
- dynamicElementInMicroAppMap.set(child, replaceElement);
2721
- return replaceElement;
2722
- }
2723
- else { // remote script
2724
- const replaceElement = runDynamicRemoteScript(url, info, app, child);
2725
- dynamicElementInMicroAppMap.set(child, replaceElement);
2726
- return replaceElement;
2727
- }
2728
- }
2729
- else if (replaceComment) {
2730
- dynamicElementInMicroAppMap.set(child, replaceComment);
2731
- return replaceComment;
2732
- }
2733
- return child;
2754
+ element.dispatchEvent(event);
2755
+ }
2756
+ /**
2757
+ * Dispatch custom event to micro app
2758
+ * @param eventName event name
2759
+ * @param appName app name
2760
+ * @param detail event detail
2761
+ */
2762
+ function dispatchCustomEventToMicroApp(eventName, appName, detail = {}) {
2763
+ const event = new CustomEvent(`${eventName}-${appName}`, {
2764
+ detail,
2765
+ });
2766
+ window.dispatchEvent(event);
2767
+ }
2768
+
2769
+ // micro app instances
2770
+ const appInstanceMap = new Map();
2771
+ class CreateApp {
2772
+ constructor({ name, url, ssrUrl, container, inline, scopecss, useSandbox, baseroute, }) {
2773
+ this.state = appStates.NOT_LOADED;
2774
+ this.keepAliveState = null;
2775
+ this.keepAliveContainer = null;
2776
+ this.loadSourceLevel = 0;
2777
+ this.umdHookMount = null;
2778
+ this.umdHookUnmount = null;
2779
+ this.libraryName = null;
2780
+ this.umdMode = false;
2781
+ this.isPrefetch = false;
2782
+ this.container = null;
2783
+ this.baseroute = '';
2784
+ this.sandBox = null;
2785
+ this.container = container !== null && container !== void 0 ? container : null;
2786
+ this.inline = inline !== null && inline !== void 0 ? inline : false;
2787
+ this.baseroute = baseroute !== null && baseroute !== void 0 ? baseroute : '';
2788
+ this.ssrUrl = ssrUrl !== null && ssrUrl !== void 0 ? ssrUrl : '';
2789
+ // optional during init👆
2790
+ this.name = name;
2791
+ this.url = url;
2792
+ this.useSandbox = useSandbox;
2793
+ this.scopecss = this.useSandbox && scopecss;
2794
+ this.source = {
2795
+ links: new Map(),
2796
+ scripts: new Map(),
2797
+ };
2798
+ this.loadSourceCode();
2799
+ this.useSandbox && (this.sandBox = new SandBox(name, url));
2800
+ }
2801
+ // Load resources
2802
+ loadSourceCode() {
2803
+ this.state = appStates.LOADING_SOURCE_CODE;
2804
+ extractHtml(this);
2734
2805
  }
2735
- return child;
2736
- }
2737
- /**
2738
- * Handle the elements inserted into head and body, and execute normally in other cases
2739
- * @param app app
2740
- * @param method raw method
2741
- * @param parent parent node
2742
- * @param targetChild target node
2743
- * @param passiveChild second param of insertBefore and replaceChild
2744
- */
2745
- function invokePrototypeMethod(app, rawMethod, parent, targetChild, passiveChild) {
2746
2806
  /**
2747
- * If passiveChild is not the child node, insertBefore replaceChild will have a problem, at this time, it will be degraded to appendChild
2748
- * E.g: document.head.insertBefore(targetChild, document.head.childNodes[0])
2807
+ * When resource is loaded, mount app if it is not prefetch or unmount
2749
2808
  */
2750
- if (parent === document.head) {
2751
- const microAppHead = app.container.querySelector('micro-app-head');
2752
- /**
2753
- * 1. If passivechild exists, it must be insertBefore or replacechild
2754
- * 2. When removeChild, targetChild may not be in microAppHead or head
2755
- */
2756
- if (passiveChild && !microAppHead.contains(passiveChild)) {
2757
- return globalEnv.rawAppendChild.call(microAppHead, targetChild);
2758
- }
2759
- else if (rawMethod === globalEnv.rawRemoveChild && !microAppHead.contains(targetChild)) {
2760
- if (parent.contains(targetChild)) {
2761
- return rawMethod.call(parent, targetChild);
2762
- }
2763
- return targetChild;
2764
- }
2765
- else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
2766
- return rawMethod.call(microAppHead, targetChild);
2809
+ onLoad(html) {
2810
+ if (++this.loadSourceLevel === 2) {
2811
+ this.source.html = html;
2812
+ if (this.isPrefetch || appStates.UNMOUNT === this.state)
2813
+ return;
2814
+ this.state = appStates.LOAD_SOURCE_FINISHED;
2815
+ this.mount();
2767
2816
  }
2768
- return rawMethod.call(microAppHead, targetChild, passiveChild);
2769
2817
  }
2770
- else if (parent === document.body) {
2771
- const microAppBody = app.container.querySelector('micro-app-body');
2772
- if (passiveChild && !microAppBody.contains(passiveChild)) {
2773
- return globalEnv.rawAppendChild.call(microAppBody, targetChild);
2774
- }
2775
- else if (rawMethod === globalEnv.rawRemoveChild && !microAppBody.contains(targetChild)) {
2776
- if (parent.contains(targetChild)) {
2777
- return rawMethod.call(parent, targetChild);
2778
- }
2779
- return targetChild;
2780
- }
2781
- else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
2782
- return rawMethod.call(microAppBody, targetChild);
2818
+ /**
2819
+ * Error loading HTML
2820
+ * @param e Error
2821
+ */
2822
+ onLoadError(e) {
2823
+ this.loadSourceLevel = -1;
2824
+ if (appStates.UNMOUNT !== this.state) {
2825
+ this.onerror(e);
2826
+ this.state = appStates.LOAD_SOURCE_ERROR;
2783
2827
  }
2784
- return rawMethod.call(microAppBody, targetChild, passiveChild);
2785
2828
  }
2786
- else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
2787
- return rawMethod.call(parent, targetChild);
2788
- }
2789
- return rawMethod.call(parent, targetChild, passiveChild);
2790
- }
2791
- // Get the map element
2792
- function getMappingNode(node) {
2793
- var _a;
2794
- return (_a = dynamicElementInMicroAppMap.get(node)) !== null && _a !== void 0 ? _a : node;
2795
- }
2796
- /**
2797
- * method of handle new node
2798
- * @param parent parent node
2799
- * @param newChild new node
2800
- * @param passiveChild passive node
2801
- * @param rawMethodraw method
2802
- */
2803
- function commonElementHander(parent, newChild, passiveChild, rawMethod) {
2804
- if (newChild === null || newChild === void 0 ? void 0 : newChild.__MICRO_APP_NAME__) {
2805
- const app = appInstanceMap.get(newChild.__MICRO_APP_NAME__);
2806
- if (app === null || app === void 0 ? void 0 : app.container) {
2807
- return invokePrototypeMethod(app, rawMethod, parent, handleNewNode(parent, newChild, app), passiveChild && getMappingNode(passiveChild));
2829
+ /**
2830
+ * mount app
2831
+ * @param container app container
2832
+ * @param inline js runs in inline mode
2833
+ * @param baseroute route prefix, default is ''
2834
+ */
2835
+ mount(container, inline, baseroute) {
2836
+ var _a, _b, _c;
2837
+ if (isBoolean(inline) && inline !== this.inline) {
2838
+ this.inline = inline;
2808
2839
  }
2809
- else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
2810
- return rawMethod.call(parent, newChild);
2840
+ this.container = (_a = this.container) !== null && _a !== void 0 ? _a : container;
2841
+ this.baseroute = baseroute !== null && baseroute !== void 0 ? baseroute : this.baseroute;
2842
+ if (this.loadSourceLevel !== 2) {
2843
+ this.state = appStates.LOADING_SOURCE_CODE;
2844
+ return;
2811
2845
  }
2812
- return rawMethod.call(parent, newChild, passiveChild);
2813
- }
2814
- else if (rawMethod === globalEnv.rawAppend || rawMethod === globalEnv.rawPrepend) {
2815
- const appName = getCurrentAppName();
2816
- if (!(newChild instanceof Node) && appName) {
2817
- const app = appInstanceMap.get(appName);
2818
- if (app === null || app === void 0 ? void 0 : app.container) {
2819
- if (parent === document.head) {
2820
- return rawMethod.call(app.container.querySelector('micro-app-head'), newChild);
2846
+ dispatchLifecyclesEvent(this.container, this.name, lifeCycles.BEFOREMOUNT);
2847
+ this.state = appStates.MOUNTING;
2848
+ cloneContainer(this.source.html, this.container, !this.umdMode);
2849
+ (_b = this.sandBox) === null || _b === void 0 ? void 0 : _b.start(this.baseroute);
2850
+ let umdHookMountResult; // result of mount function
2851
+ if (!this.umdMode) {
2852
+ let hasDispatchMountedEvent = false;
2853
+ // if all js are executed, param isFinished will be true
2854
+ execScripts(this.source.scripts, this, (isFinished) => {
2855
+ var _a;
2856
+ if (!this.umdMode) {
2857
+ const { mount, unmount } = this.getUmdLibraryHooks();
2858
+ // if mount & unmount is function, the sub app is umd mode
2859
+ if (isFunction(mount) && isFunction(unmount)) {
2860
+ this.umdHookMount = mount;
2861
+ this.umdHookUnmount = unmount;
2862
+ this.umdMode = true;
2863
+ (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.recordUmdSnapshot();
2864
+ try {
2865
+ umdHookMountResult = this.umdHookMount();
2866
+ }
2867
+ catch (e) {
2868
+ logError('an error occurred in the mount function \n', this.name, e);
2869
+ }
2870
+ }
2821
2871
  }
2822
- else if (parent === document.body) {
2823
- return rawMethod.call(app.container.querySelector('micro-app-body'), newChild);
2872
+ if (!hasDispatchMountedEvent && (isFinished === true || this.umdMode)) {
2873
+ hasDispatchMountedEvent = true;
2874
+ this.handleMounted(umdHookMountResult);
2824
2875
  }
2825
- }
2826
- }
2827
- return rawMethod.call(parent, newChild);
2828
- }
2829
- return rawMethod.call(parent, newChild, passiveChild);
2830
- }
2831
- /**
2832
- * Rewrite element prototype method
2833
- */
2834
- function patchElementPrototypeMethods() {
2835
- patchDocument();
2836
- // Rewrite setAttribute
2837
- Element.prototype.setAttribute = function setAttribute(key, value) {
2838
- if (/^micro-app(-\S+)?/i.test(this.tagName) && key === 'data') {
2839
- if (isPlainObject(value)) {
2840
- const cloneValue = {};
2841
- Object.getOwnPropertyNames(value).forEach((propertyKey) => {
2842
- if (!(isString(propertyKey) && propertyKey.indexOf('__') === 0)) {
2843
- // @ts-ignore
2844
- cloneValue[propertyKey] = value[propertyKey];
2845
- }
2846
- });
2847
- this.data = cloneValue;
2848
- }
2849
- else if (value !== '[object Object]') {
2850
- logWarn('property data must be an object', this.getAttribute('name'));
2851
- }
2852
- }
2853
- else if ((((key === 'src' || key === 'srcset') && /^(img|script)$/i.test(this.tagName)) ||
2854
- (key === 'href' && /^link$/i.test(this.tagName))) &&
2855
- this.__MICRO_APP_NAME__ &&
2856
- appInstanceMap.has(this.__MICRO_APP_NAME__)) {
2857
- const app = appInstanceMap.get(this.__MICRO_APP_NAME__);
2858
- globalEnv.rawSetAttribute.call(this, key, CompletionPath(value, app.url));
2876
+ });
2859
2877
  }
2860
2878
  else {
2861
- globalEnv.rawSetAttribute.call(this, key, value);
2862
- }
2863
- };
2864
- // prototype methods of add element👇
2865
- Element.prototype.appendChild = function appendChild(newChild) {
2866
- return commonElementHander(this, newChild, null, globalEnv.rawAppendChild);
2867
- };
2868
- Element.prototype.insertBefore = function insertBefore(newChild, refChild) {
2869
- return commonElementHander(this, newChild, refChild, globalEnv.rawInsertBefore);
2870
- };
2871
- Element.prototype.replaceChild = function replaceChild(newChild, oldChild) {
2872
- return commonElementHander(this, newChild, oldChild, globalEnv.rawReplaceChild);
2873
- };
2874
- Element.prototype.append = function append(...nodes) {
2875
- let i = 0;
2876
- const length = nodes.length;
2877
- while (i < length) {
2878
- commonElementHander(this, nodes[i], null, globalEnv.rawAppend);
2879
- i++;
2880
- }
2881
- };
2882
- Element.prototype.prepend = function prepend(...nodes) {
2883
- let i = nodes.length;
2884
- while (i > 0) {
2885
- commonElementHander(this, nodes[i - 1], null, globalEnv.rawPrepend);
2886
- i--;
2887
- }
2888
- };
2889
- // prototype methods of delete element👇
2890
- Element.prototype.removeChild = function removeChild(oldChild) {
2891
- if (oldChild === null || oldChild === void 0 ? void 0 : oldChild.__MICRO_APP_NAME__) {
2892
- const app = appInstanceMap.get(oldChild.__MICRO_APP_NAME__);
2893
- if (app === null || app === void 0 ? void 0 : app.container) {
2894
- return invokePrototypeMethod(app, globalEnv.rawRemoveChild, this, getMappingNode(oldChild));
2895
- }
2896
- return globalEnv.rawRemoveChild.call(this, oldChild);
2897
- }
2898
- return globalEnv.rawRemoveChild.call(this, oldChild);
2899
- };
2900
- // patch cloneNode
2901
- Element.prototype.cloneNode = function cloneNode(deep) {
2902
- const clonedNode = globalEnv.rawCloneNode.call(this, deep);
2903
- this.__MICRO_APP_NAME__ && (clonedNode.__MICRO_APP_NAME__ = this.__MICRO_APP_NAME__);
2904
- return clonedNode;
2905
- };
2906
- }
2907
- /**
2908
- * Mark the newly created element in the micro application
2909
- * @param element new element
2910
- */
2911
- function markElement(element) {
2912
- const appName = getCurrentAppName();
2913
- appName && (element.__MICRO_APP_NAME__ = appName);
2914
- return element;
2915
- }
2916
- // methods of document
2917
- function patchDocument() {
2918
- const rawDocument = globalEnv.rawDocument;
2919
- // create element 👇
2920
- Document.prototype.createElement = function createElement(tagName, options) {
2921
- const element = globalEnv.rawCreateElement.call(this, tagName, options);
2922
- return markElement(element);
2923
- };
2924
- Document.prototype.createElementNS = function createElementNS(namespaceURI, name, options) {
2925
- const element = globalEnv.rawCreateElementNS.call(this, namespaceURI, name, options);
2926
- return markElement(element);
2927
- };
2928
- Document.prototype.createDocumentFragment = function createDocumentFragment() {
2929
- const element = globalEnv.rawCreateDocumentFragment.call(this);
2930
- return markElement(element);
2931
- };
2932
- // query element👇
2933
- function querySelector(selectors) {
2934
- var _a, _b, _c;
2935
- const appName = getCurrentAppName();
2936
- if (!appName ||
2937
- !selectors ||
2938
- isUniqueElement(selectors) ||
2939
- // see https://github.com/micro-zoe/micro-app/issues/56
2940
- rawDocument !== this) {
2941
- return globalEnv.rawQuerySelector.call(this, selectors);
2879
+ (_c = this.sandBox) === null || _c === void 0 ? void 0 : _c.rebuildUmdSnapshot();
2880
+ try {
2881
+ umdHookMountResult = this.umdHookMount();
2882
+ }
2883
+ catch (e) {
2884
+ logError('an error occurred in the mount function \n', this.name, e);
2885
+ }
2886
+ this.handleMounted(umdHookMountResult);
2942
2887
  }
2943
- return (_c = (_b = (_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.container) === null || _b === void 0 ? void 0 : _b.querySelector(selectors)) !== null && _c !== void 0 ? _c : null;
2944
2888
  }
2945
- function querySelectorAll(selectors) {
2946
- var _a, _b, _c;
2947
- const appName = getCurrentAppName();
2948
- if (!appName ||
2949
- !selectors ||
2950
- isUniqueElement(selectors) ||
2951
- rawDocument !== this) {
2952
- return globalEnv.rawQuerySelectorAll.call(this, selectors);
2889
+ /**
2890
+ * handle for promise umdHookMount
2891
+ * @param umdHookMountResult result of umdHookMount
2892
+ */
2893
+ handleMounted(umdHookMountResult) {
2894
+ if (isPromise(umdHookMountResult)) {
2895
+ umdHookMountResult
2896
+ .then(() => this.dispatchMountedEvent())
2897
+ .catch((e) => this.onerror(e));
2953
2898
  }
2954
- return (_c = (_b = (_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.container) === null || _b === void 0 ? void 0 : _b.querySelectorAll(selectors)) !== null && _c !== void 0 ? _c : [];
2955
- }
2956
- Document.prototype.querySelector = querySelector;
2957
- Document.prototype.querySelectorAll = querySelectorAll;
2958
- Document.prototype.getElementById = function getElementById(key) {
2959
- if (!getCurrentAppName() || isInvalidQuerySelectorKey(key)) {
2960
- return globalEnv.rawGetElementById.call(this, key);
2899
+ else {
2900
+ this.dispatchMountedEvent();
2961
2901
  }
2962
- try {
2963
- return querySelector.call(this, `#${key}`);
2902
+ }
2903
+ /**
2904
+ * dispatch mounted event when app run finished
2905
+ */
2906
+ dispatchMountedEvent() {
2907
+ if (appStates.UNMOUNT !== this.state) {
2908
+ this.state = appStates.MOUNTED;
2909
+ dispatchLifecyclesEvent(this.container, this.name, lifeCycles.MOUNTED);
2964
2910
  }
2965
- catch (_a) {
2966
- return globalEnv.rawGetElementById.call(this, key);
2911
+ }
2912
+ /**
2913
+ * unmount app
2914
+ * @param destroy completely destroy, delete cache resources
2915
+ * @param unmountcb callback of unmount
2916
+ */
2917
+ unmount(destroy, unmountcb) {
2918
+ if (this.state === appStates.LOAD_SOURCE_ERROR) {
2919
+ destroy = true;
2967
2920
  }
2968
- };
2969
- Document.prototype.getElementsByClassName = function getElementsByClassName(key) {
2970
- if (!getCurrentAppName() || isInvalidQuerySelectorKey(key)) {
2971
- return globalEnv.rawGetElementsByClassName.call(this, key);
2921
+ this.state = appStates.UNMOUNT;
2922
+ this.keepAliveState = null;
2923
+ this.keepAliveContainer = null;
2924
+ // result of unmount function
2925
+ let umdHookUnmountResult;
2926
+ /**
2927
+ * send an unmount event to the micro app or call umd unmount hook
2928
+ * before the sandbox is cleared
2929
+ */
2930
+ if (this.umdHookUnmount) {
2931
+ try {
2932
+ umdHookUnmountResult = this.umdHookUnmount();
2933
+ }
2934
+ catch (e) {
2935
+ logError('an error occurred in the unmount function \n', this.name, e);
2936
+ }
2972
2937
  }
2973
- try {
2974
- return querySelectorAll.call(this, `.${key}`);
2938
+ // dispatch unmount event to micro app
2939
+ dispatchCustomEventToMicroApp('unmount', this.name);
2940
+ this.handleUnmounted(destroy, umdHookUnmountResult, unmountcb);
2941
+ }
2942
+ /**
2943
+ * handle for promise umdHookUnmount
2944
+ * @param destroy completely destroy, delete cache resources
2945
+ * @param umdHookUnmountResult result of umdHookUnmount
2946
+ * @param unmountcb callback of unmount
2947
+ */
2948
+ handleUnmounted(destroy, umdHookUnmountResult, unmountcb) {
2949
+ if (isPromise(umdHookUnmountResult)) {
2950
+ umdHookUnmountResult
2951
+ .then(() => this.actionsForUnmount(destroy, unmountcb))
2952
+ .catch(() => this.actionsForUnmount(destroy, unmountcb));
2975
2953
  }
2976
- catch (_a) {
2977
- return globalEnv.rawGetElementsByClassName.call(this, key);
2954
+ else {
2955
+ this.actionsForUnmount(destroy, unmountcb);
2978
2956
  }
2979
- };
2980
- Document.prototype.getElementsByTagName = function getElementsByTagName(key) {
2957
+ }
2958
+ /**
2959
+ * actions for unmount app
2960
+ * @param destroy completely destroy, delete cache resources
2961
+ * @param unmountcb callback of unmount
2962
+ */
2963
+ actionsForUnmount(destroy, unmountcb) {
2981
2964
  var _a;
2982
- const appName = getCurrentAppName();
2983
- if (!appName ||
2984
- isUniqueElement(key) ||
2985
- isInvalidQuerySelectorKey(key) ||
2986
- (!((_a = appInstanceMap.get(appName)) === null || _a === void 0 ? void 0 : _a.inline) && /^script$/i.test(key))) {
2987
- return globalEnv.rawGetElementsByTagName.call(this, key);
2988
- }
2989
- try {
2990
- return querySelectorAll.call(this, key);
2991
- }
2992
- catch (_b) {
2993
- return globalEnv.rawGetElementsByTagName.call(this, key);
2965
+ if (destroy) {
2966
+ this.actionsForCompletelyDestroy();
2994
2967
  }
2995
- };
2996
- Document.prototype.getElementsByName = function getElementsByName(key) {
2997
- if (!getCurrentAppName() || isInvalidQuerySelectorKey(key)) {
2998
- return globalEnv.rawGetElementsByName.call(this, key);
2968
+ else if (this.umdMode && this.container.childElementCount) {
2969
+ cloneContainer(this.container, this.source.html, false);
2999
2970
  }
3000
- try {
3001
- return querySelectorAll.call(this, `[name=${key}]`);
2971
+ // this.container maybe contains micro-app element, stop sandbox should exec after cloneContainer
2972
+ (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.stop();
2973
+ if (!getActiveApps().length) {
2974
+ releasePatchSetAttribute();
3002
2975
  }
3003
- catch (_a) {
3004
- return globalEnv.rawGetElementsByName.call(this, key);
2976
+ // dispatch unmount event to base app
2977
+ dispatchLifecyclesEvent(this.container, this.name, lifeCycles.UNMOUNT);
2978
+ this.container.innerHTML = '';
2979
+ this.container = null;
2980
+ unmountcb && unmountcb();
2981
+ }
2982
+ // actions for completely destroy
2983
+ actionsForCompletelyDestroy() {
2984
+ if (!this.useSandbox && this.umdMode) {
2985
+ delete window[this.libraryName];
3005
2986
  }
3006
- };
3007
- }
3008
- function releasePatchDocument() {
3009
- Document.prototype.createElement = globalEnv.rawCreateElement;
3010
- Document.prototype.createElementNS = globalEnv.rawCreateElementNS;
3011
- Document.prototype.createDocumentFragment = globalEnv.rawCreateDocumentFragment;
3012
- Document.prototype.querySelector = globalEnv.rawQuerySelector;
3013
- Document.prototype.querySelectorAll = globalEnv.rawQuerySelectorAll;
3014
- Document.prototype.getElementById = globalEnv.rawGetElementById;
3015
- Document.prototype.getElementsByClassName = globalEnv.rawGetElementsByClassName;
3016
- Document.prototype.getElementsByTagName = globalEnv.rawGetElementsByTagName;
3017
- Document.prototype.getElementsByName = globalEnv.rawGetElementsByName;
3018
- }
3019
- // release patch
3020
- function releasePatches() {
3021
- setCurrentAppName(null);
3022
- releasePatchDocument();
3023
- Element.prototype.setAttribute = globalEnv.rawSetAttribute;
3024
- Element.prototype.appendChild = globalEnv.rawAppendChild;
3025
- Element.prototype.insertBefore = globalEnv.rawInsertBefore;
3026
- Element.prototype.replaceChild = globalEnv.rawReplaceChild;
3027
- Element.prototype.removeChild = globalEnv.rawRemoveChild;
3028
- Element.prototype.append = globalEnv.rawAppend;
3029
- Element.prototype.prepend = globalEnv.rawPrepend;
3030
- Element.prototype.cloneNode = globalEnv.rawCloneNode;
3031
- }
3032
- // Set the style of micro-app-head and micro-app-body
3033
- let hasRejectMicroAppStyle = false;
3034
- function rejectMicroAppStyle() {
3035
- if (!hasRejectMicroAppStyle) {
3036
- hasRejectMicroAppStyle = true;
3037
- const style = pureCreateElement('style');
3038
- style.setAttribute('type', 'text/css');
3039
- style.textContent = `\n${microApp.tagName}, micro-app-body { display: block; } \nmicro-app-head { display: none; }`;
3040
- globalEnv.rawDocument.head.appendChild(style);
2987
+ appInstanceMap.delete(this.name);
3041
2988
  }
3042
- }
3043
-
3044
- function unmountNestedApp() {
3045
- releaseUnmountOfNestedApp();
3046
- appInstanceMap.forEach(app => {
3047
- // @ts-ignore
3048
- app.container && getRootContainer(app.container).disconnectedCallback();
3049
- });
3050
- !window.__MICRO_APP_UMD_MODE__ && appInstanceMap.clear();
3051
- if (elementInstanceMap.size) {
3052
- elementInstanceMap.clear();
3053
- releasePatches();
2989
+ // hidden app when disconnectedCallback called with keep-alive
2990
+ hiddenKeepAliveApp() {
2991
+ const oldContainer = this.container;
2992
+ cloneContainer(this.container, this.keepAliveContainer ? this.keepAliveContainer : (this.keepAliveContainer = document.createElement('div')), false);
2993
+ this.container = this.keepAliveContainer;
2994
+ this.keepAliveState = keepAliveStates.KEEP_ALIVE_HIDDEN;
2995
+ // event should dispatch before clone node
2996
+ // dispatch afterhidden event to micro-app
2997
+ dispatchCustomEventToMicroApp('appstate-change', this.name, {
2998
+ appState: 'afterhidden',
2999
+ });
3000
+ // dispatch afterhidden event to base app
3001
+ dispatchLifecyclesEvent(oldContainer, this.name, lifeCycles.AFTERHIDDEN);
3054
3002
  }
3055
- }
3056
- // if micro-app run in micro application, delete all next generation application when unmount event received
3057
- function listenUmountOfNestedApp() {
3058
- if (window.__MICRO_APP_ENVIRONMENT__) {
3059
- window.addEventListener('unmount', unmountNestedApp, false);
3003
+ // show app when connectedCallback called with keep-alive
3004
+ showKeepAliveApp(container) {
3005
+ // dispatch beforeshow event to micro-app
3006
+ dispatchCustomEventToMicroApp('appstate-change', this.name, {
3007
+ appState: 'beforeshow',
3008
+ });
3009
+ // dispatch beforeshow event to base app
3010
+ dispatchLifecyclesEvent(container, this.name, lifeCycles.BEFORESHOW);
3011
+ cloneContainer(this.container, container, false);
3012
+ this.container = container;
3013
+ this.keepAliveState = keepAliveStates.KEEP_ALIVE_SHOW;
3014
+ // dispatch aftershow event to micro-app
3015
+ dispatchCustomEventToMicroApp('appstate-change', this.name, {
3016
+ appState: 'aftershow',
3017
+ });
3018
+ // dispatch aftershow event to base app
3019
+ dispatchLifecyclesEvent(this.container, this.name, lifeCycles.AFTERSHOW);
3060
3020
  }
3061
- }
3062
- // release listener
3063
- function releaseUnmountOfNestedApp() {
3064
- if (window.__MICRO_APP_ENVIRONMENT__) {
3065
- window.removeEventListener('unmount', unmountNestedApp, false);
3021
+ /**
3022
+ * app rendering error
3023
+ * @param e Error
3024
+ */
3025
+ onerror(e) {
3026
+ dispatchLifecyclesEvent(this.container, this.name, lifeCycles.ERROR, e);
3027
+ }
3028
+ // get app state
3029
+ getAppState() {
3030
+ return this.state;
3031
+ }
3032
+ // get keep-alive state
3033
+ getKeepAliveState() {
3034
+ return this.keepAliveState;
3035
+ }
3036
+ // get umd library, if it not exist, return empty object
3037
+ getUmdLibraryHooks() {
3038
+ var _a, _b;
3039
+ // after execScripts, the app maybe unmounted
3040
+ if (appStates.UNMOUNT !== this.state) {
3041
+ const global = ((_b = (_a = this.sandBox) === null || _a === void 0 ? void 0 : _a.proxyWindow) !== null && _b !== void 0 ? _b : globalEnv.rawWindow);
3042
+ this.libraryName = getRootContainer(this.container).getAttribute('library') || `micro-app-${this.name}`;
3043
+ // do not use isObject
3044
+ return typeof global[this.libraryName] === 'object' ? global[this.libraryName] : {};
3045
+ }
3046
+ return {};
3066
3047
  }
3067
3048
  }
3068
3049
 
3069
- // record all micro-app elements
3070
- const elementInstanceMap = new Map();
3071
3050
  /**
3072
3051
  * define element
3073
3052
  * @param tagName element name
@@ -3076,7 +3055,7 @@ function defineElement(tagName) {
3076
3055
  class MicroAppElement extends HTMLElement {
3077
3056
  constructor() {
3078
3057
  super();
3079
- this.isWating = false;
3058
+ this.isWaiting = false;
3080
3059
  this.cacheData = null;
3081
3060
  this.hasConnected = false;
3082
3061
  this.appName = ''; // app name
@@ -3084,10 +3063,10 @@ function defineElement(tagName) {
3084
3063
  this.ssrUrl = ''; // html path in ssr mode
3085
3064
  this.version = version;
3086
3065
  /**
3087
- * handle for change of name an url after element inited
3066
+ * handle for change of name an url after element init
3088
3067
  */
3089
3068
  this.handleAttributeUpdate = () => {
3090
- this.isWating = false;
3069
+ this.isWaiting = false;
3091
3070
  const formatAttrName = formatAppName(this.getAttribute('name'));
3092
3071
  const formatAttrUrl = formatAppURL(this.getAttribute('url'), this.appName);
3093
3072
  if (this.legalAttribute('name', formatAttrName) && this.legalAttribute('url', formatAttrUrl)) {
@@ -3122,10 +3101,7 @@ function defineElement(tagName) {
3122
3101
  this.setAttribute('name', this.appName);
3123
3102
  }
3124
3103
  };
3125
- // cloned node of umd container also trigger constructor, we should skip
3126
- if (!this.querySelector('micro-app-head')) {
3127
- this.performWhenFirstCreated();
3128
- }
3104
+ patchSetAttribute();
3129
3105
  }
3130
3106
  static get observedAttributes() {
3131
3107
  return ['name', 'url'];
@@ -3142,9 +3118,6 @@ function defineElement(tagName) {
3142
3118
  // keep-alive: open keep-alive mode
3143
3119
  connectedCallback() {
3144
3120
  this.hasConnected = true;
3145
- if (!elementInstanceMap.has(this)) {
3146
- this.performWhenFirstCreated();
3147
- }
3148
3121
  defer(() => dispatchLifecyclesEvent(this, this.appName, lifeCycles.CREATED));
3149
3122
  this.initialMount();
3150
3123
  }
@@ -3155,12 +3128,7 @@ function defineElement(tagName) {
3155
3128
  this.handleHiddenKeepAliveApp();
3156
3129
  }
3157
3130
  else {
3158
- elementInstanceMap.delete(this);
3159
- this.handleUnmount(this.getDestroyCompatibleResult(), () => {
3160
- if (elementInstanceMap.size === 0) {
3161
- releasePatches();
3162
- }
3163
- });
3131
+ this.handleUnmount(this.getDestroyCompatibleResult());
3164
3132
  }
3165
3133
  }
3166
3134
  attributeChangedCallback(attr, _oldVal, newVal) {
@@ -3189,8 +3157,8 @@ function defineElement(tagName) {
3189
3157
  }
3190
3158
  this.handleInitialNameAndUrl();
3191
3159
  }
3192
- else if (!this.isWating) {
3193
- this.isWating = true;
3160
+ else if (!this.isWaiting) {
3161
+ this.isWaiting = true;
3194
3162
  defer(this.handleAttributeUpdate);
3195
3163
  }
3196
3164
  }
@@ -3199,15 +3167,6 @@ function defineElement(tagName) {
3199
3167
  handleInitialNameAndUrl() {
3200
3168
  this.hasConnected && this.initialMount();
3201
3169
  }
3202
- // Perform global initialization when the element count is 1
3203
- performWhenFirstCreated() {
3204
- if (elementInstanceMap.set(this, true).size === 1) {
3205
- patchElementPrototypeMethods();
3206
- rejectMicroAppStyle();
3207
- releaseUnmountOfNestedApp();
3208
- listenUmountOfNestedApp();
3209
- }
3210
- }
3211
3170
  /**
3212
3171
  * first mount of this app
3213
3172
  */
@@ -3369,7 +3328,7 @@ function defineElement(tagName) {
3369
3328
  }
3370
3329
  // show app when connectedCallback called with keep-alive
3371
3330
  handleShowKeepAliveApp(app) {
3372
- // must be asnyc
3331
+ // must be async
3373
3332
  defer(() => { var _a; return app.showKeepAliveApp((_a = this.shadowRoot) !== null && _a !== void 0 ? _a : this); });
3374
3333
  }
3375
3334
  /**
@@ -3379,7 +3338,7 @@ function defineElement(tagName) {
3379
3338
  */
3380
3339
  getDisposeResult(name) {
3381
3340
  // @ts-ignore
3382
- return (this.compatibleSpecialProperties(name) || microApp[name]) && this.compatibleDisablSpecialProperties(name);
3341
+ return (this.compatibleSpecialProperties(name) || microApp[name]) && this.compatibleDisableSpecialProperties(name);
3383
3342
  }
3384
3343
  // compatible of disableScopecss & disableSandbox
3385
3344
  compatibleSpecialProperties(name) {
@@ -3392,7 +3351,7 @@ function defineElement(tagName) {
3392
3351
  return this.hasAttribute(name);
3393
3352
  }
3394
3353
  // compatible of disableScopecss & disableSandbox
3395
- compatibleDisablSpecialProperties(name) {
3354
+ compatibleDisableSpecialProperties(name) {
3396
3355
  if (name === 'disableScopecss') {
3397
3356
  return this.getAttribute('disableScopecss') !== 'false' && this.getAttribute('disable-scopecss') !== 'false';
3398
3357
  }
@@ -3541,7 +3500,7 @@ function getGlobalAssets(assets) {
3541
3500
 
3542
3501
  /**
3543
3502
  * if app not prefetch & not unmount, then app is active
3544
- * @param excludeHiddenApp exclude hidden keep-alive app
3503
+ * @param excludeHiddenApp exclude hidden keep-alive app, default is false
3545
3504
  * @returns active apps
3546
3505
  */
3547
3506
  function getActiveApps(excludeHiddenApp) {
@@ -3561,30 +3520,30 @@ function getAllApps() {
3561
3520
  return Array.from(appInstanceMap.keys());
3562
3521
  }
3563
3522
  /**
3564
- * unmount app by appname
3523
+ * unmount app by appName
3565
3524
  * @param appName
3566
3525
  * @param options unmountAppParams
3567
3526
  * @returns Promise<void>
3568
3527
  */
3569
3528
  function unmountApp(appName, options) {
3570
3529
  const app = appInstanceMap.get(formatAppName(appName));
3571
- return new Promise((reslove) => {
3530
+ return new Promise((resolve) => {
3572
3531
  if (app) {
3573
3532
  if (app.getAppState() === appStates.UNMOUNT || app.isPrefetch) {
3574
3533
  if (options === null || options === void 0 ? void 0 : options.destroy) {
3575
3534
  app.actionsForCompletelyDestroy();
3576
3535
  }
3577
- reslove();
3536
+ resolve();
3578
3537
  }
3579
3538
  else if (app.getKeepAliveState() === keepAliveStates.KEEP_ALIVE_HIDDEN) {
3580
3539
  if (options === null || options === void 0 ? void 0 : options.destroy) {
3581
- app.unmount(true, reslove);
3540
+ app.unmount(true, resolve);
3582
3541
  }
3583
3542
  else if (options === null || options === void 0 ? void 0 : options.clearAliveState) {
3584
- app.unmount(false, reslove);
3543
+ app.unmount(false, resolve);
3585
3544
  }
3586
3545
  else {
3587
- reslove();
3546
+ resolve();
3588
3547
  }
3589
3548
  }
3590
3549
  else {
@@ -3592,12 +3551,12 @@ function unmountApp(appName, options) {
3592
3551
  const unmountHandler = () => {
3593
3552
  container.removeEventListener('unmount', unmountHandler);
3594
3553
  container.removeEventListener('afterhidden', afterhiddenHandler);
3595
- reslove();
3554
+ resolve();
3596
3555
  };
3597
3556
  const afterhiddenHandler = () => {
3598
3557
  container.removeEventListener('unmount', unmountHandler);
3599
3558
  container.removeEventListener('afterhidden', afterhiddenHandler);
3600
- reslove();
3559
+ resolve();
3601
3560
  };
3602
3561
  container.addEventListener('unmount', unmountHandler);
3603
3562
  container.addEventListener('afterhidden', afterhiddenHandler);
@@ -3624,7 +3583,7 @@ function unmountApp(appName, options) {
3624
3583
  }
3625
3584
  else {
3626
3585
  logWarn(`app ${appName} does not exist`);
3627
- reslove();
3586
+ resolve();
3628
3587
  }
3629
3588
  });
3630
3589
  }