@micro-zoe/micro-app 0.8.0 → 0.8.4

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