xpe 1.0.11 → 1.0.13

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.
@@ -415,11 +415,11 @@ export function recoverShadowRootEventListeners(iframeWindow) {
415
415
  rawRemoveEventListener.call(nextElement, listener.type, listener.callback, listener.options);
416
416
  rawAddEventListener.call(nextElement, listener.type, listener.callback, listener.options);
417
417
  });
418
- recoverFrameworkEventInvokers(nextElement);
418
+ recoverFrameworkEventInvokers(nextElement, elementListenerList);
419
419
  nextElement = walker.nextNode();
420
420
  }
421
421
  }
422
- function recoverFrameworkEventInvokers(element) {
422
+ function recoverFrameworkEventInvokers(element, cachedListeners) {
423
423
  if (element.nodeType !== Node.ELEMENT_NODE)
424
424
  return;
425
425
  Object.getOwnPropertySymbols(element)
@@ -435,6 +435,11 @@ function recoverFrameworkEventInvokers(element) {
435
435
  const eventType = toDomEventType(key);
436
436
  if (!eventType)
437
437
  return;
438
+ // A tracked Vue invoker is already restored through its realm-safe
439
+ // callback. Adding the raw invoker as well executes one click twice.
440
+ if (cachedListeners?.some((listener) => listener.type === eventType && listener.handler === invoker)) {
441
+ return;
442
+ }
438
443
  rawRemoveEventListener.call(element, eventType, invoker);
439
444
  rawAddEventListener.call(element, eventType, invoker);
440
445
  });
@@ -669,6 +674,10 @@ function patchNodeEffect(iframeWindow) {
669
674
  const rawAppendChild = iframeWindow.Node.prototype.appendChild;
670
675
  const rawInsertBefore = iframeWindow.Node.prototype.insertBefore;
671
676
  const rawRemoveChild = iframeWindow.Node.prototype.removeChild;
677
+ const hostAppendChild = window.Node.prototype.appendChild;
678
+ const hostInsertBefore = window.Node.prototype.insertBefore;
679
+ const hostRemoveChild = window.Node.prototype.removeChild;
680
+ const nodeOperation = (node, iframeOperation, hostOperation) => (window.Node.prototype.isPrototypeOf(node) ? hostOperation : iframeOperation);
672
681
  iframeWindow.Node.prototype.getRootNode = function (options) {
673
682
  const rootNode = rawGetRootNode.call(this, options);
674
683
  if (rootNode === sandbox.shadowRoot)
@@ -677,13 +686,15 @@ function patchNodeEffect(iframeWindow) {
677
686
  return rootNode;
678
687
  };
679
688
  iframeWindow.Node.prototype.appendChild = function (node) {
680
- const res = rawAppendChild.call(this, node);
689
+ const operation = nodeOperation(node, rawAppendChild, hostAppendChild);
690
+ const res = operation.call(this, node);
681
691
  patchElementEffect(node, iframeWindow);
682
692
  recoverEventListeners(this, iframeWindow);
683
693
  return res;
684
694
  };
685
695
  iframeWindow.Node.prototype.insertBefore = function (node, child) {
686
- const res = rawInsertBefore.call(this, node, child);
696
+ const operation = nodeOperation(node, rawInsertBefore, hostInsertBefore);
697
+ const res = operation.call(this, node, child);
687
698
  patchElementEffect(node, iframeWindow);
688
699
  recoverEventListeners(this, iframeWindow);
689
700
  return res;
@@ -691,7 +702,8 @@ function patchNodeEffect(iframeWindow) {
691
702
  iframeWindow.Node.prototype.removeChild = function (node) {
692
703
  let res;
693
704
  try {
694
- res = rawRemoveChild.call(this, node);
705
+ const operation = nodeOperation(node, rawRemoveChild, hostRemoveChild);
706
+ res = operation.call(this, node);
695
707
  }
696
708
  catch (e) {
697
709
  console.warn(`Failed to removeChild: ${node.nodeName.toLowerCase()} is not a child of ${this.nodeName.toLowerCase()}, try again with parentNode attribute. `);
@@ -727,7 +739,11 @@ function wrapInlineScriptWithSandbox(code) {
727
739
  }
728
740
  function createExecNextScriptContent() {
729
741
  return [
730
- "const sandbox = window.__XPE_INTERNAL_SANDBOX__;",
742
+ // This helper is injected as a classic script repeatedly. `const` would
743
+ // create a global lexical binding and fail on the second queued script
744
+ // (notably when Vite's dev client precedes the app entry). `var` is
745
+ // intentionally used so each queue step remains re-entrant.
746
+ "var sandbox = window.__XPE_INTERNAL_SANDBOX__;",
731
747
  "if(sandbox.execQueue && sandbox.execQueue.length){ sandbox.execQueue.shift()()}",
732
748
  ].join("");
733
749
  }
@@ -923,7 +939,7 @@ export function syncIframeUrlToWindow(iframeWindow) {
923
939
  * @param rawElement 原始的脚本
924
940
  */
925
941
  export function insertScriptToIframe(scriptResult, iframeWindow, rawElement) {
926
- const { src, module, content, crossorigin, crossoriginType, async, attrs, callback, onload } = scriptResult;
942
+ const { src, module, isEntry, content, crossorigin, crossoriginType, async, attrs, callback, onload } = scriptResult;
927
943
  const sandbox = getActiveSandbox(iframeWindow);
928
944
  const container = rawDocumentQuerySelector.call(iframeWindow.document, "head");
929
945
  const execNextScript = () => {
@@ -1001,9 +1017,19 @@ export function insertScriptToIframe(scriptResult, iframeWindow, rawElement) {
1001
1017
  scriptElement.onerror = afterExecScript;
1002
1018
  }
1003
1019
  container.appendChild(scriptElement);
1004
- if (isOutlineScript && module) {
1020
+ // Vite injects an HMR client module before the actual application entry in
1021
+ // development HTML. That client keeps a long-lived websocket and may not
1022
+ // dispatch the module load event in the sandbox, so waiting for it would
1023
+ // prevent the real entry module from ever being inserted. It is not an
1024
+ // application lifecycle module and must not participate in the serial
1025
+ // entry gate.
1026
+ const isViteClientModule = module && /(?:^|\/)@vite\/client(?:$|\?)/.test(src);
1027
+ if (isOutlineScript && module && isEntry && !isViteClientModule) {
1005
1028
  waitForModuleLifecycleReady(iframeWindow, afterExecScript);
1006
1029
  }
1030
+ if (isOutlineScript && isViteClientModule) {
1031
+ afterExecScript();
1032
+ }
1007
1033
  // 调用回调
1008
1034
  callback?.(iframeWindow);
1009
1035
  // 执行 hooks
@@ -1,4 +1,5 @@
1
1
  import { XpeRuntimeError, } from "../core/index.js";
2
+ import { createRuntimeFetch } from "./runtime-fetch.js";
2
3
  let runtimeEngineModule;
3
4
  let runtimeEngineModulePromise;
4
5
  export function createRuntimeKernel() {
@@ -46,7 +47,7 @@ function toEnginePreloadOptions(manifest) {
46
47
  alive: runtime.alive,
47
48
  sync: runtime.sync,
48
49
  prefix: toRuntimePrefix(runtime),
49
- fetch: toRuntimeFetch(runtime.credentials),
50
+ fetch: createRuntimeFetch(runtime.credentials),
50
51
  props: {
51
52
  appCode: manifest.appCode,
52
53
  manifest,
@@ -82,8 +83,3 @@ function toRuntimePrefix(runtime) {
82
83
  return undefined;
83
84
  return { [runtime.name]: runtime.basename };
84
85
  }
85
- function toRuntimeFetch(credentials) {
86
- if (!credentials)
87
- return undefined;
88
- return (input, init) => window.fetch(input, { ...init, credentials: init?.credentials ?? credentials });
89
- }
@@ -76,7 +76,14 @@ export function proxyGenerator(iframe, urlElement, mainHostPath, appHostPath) {
76
76
  return new Proxy(document[propKey], {
77
77
  apply(_createElement, _ctx, args) {
78
78
  const rawCreateMethod = propKey === "createElement" ? rawCreateElement : rawCreateTextNode;
79
- const element = rawCreateMethod.apply(iframeDocument, args);
79
+ // Vue compiles static template blocks through one shared
80
+ // HTMLTemplateElement. A template created in the execution iframe
81
+ // exposes a fragment from that realm, which native host DOM
82
+ // insertion rejects. Keep templates and their content fragment in
83
+ // the visible host realm.
84
+ const element = propKey === "createElement" && String(args[0]).toLowerCase() === "template"
85
+ ? window.document.createElement("template")
86
+ : rawCreateMethod.apply(iframeDocument, args);
80
87
  patchElementEffect(element, iframeWindow);
81
88
  return element;
82
89
  },
@@ -276,7 +283,9 @@ export function localGenerator(iframe, urlElement, mainHostPath, appHostPath) {
276
283
  const activeIframe = getActiveIframe(iframeRef);
277
284
  const activeWindow = getIframeWindow(activeIframe);
278
285
  const activeDocument = getIframeDocument(activeIframe);
279
- const element = getXpeRawDocumentCreateElement(activeWindow).apply(activeDocument, args);
286
+ const element = String(args[0]).toLowerCase() === "template"
287
+ ? window.document.createElement("template")
288
+ : getXpeRawDocumentCreateElement(activeWindow).apply(activeDocument, args);
280
289
  patchElementEffect(element, activeWindow);
281
290
  return element;
282
291
  };
@@ -8,6 +8,8 @@ export interface ScriptObjectLoader {
8
8
  src?: string;
9
9
  /** 脚本是否为module模块 */
10
10
  module?: boolean;
11
+ /** 是否为应用入口脚本。仅入口模块需要等待 XPE 生命周期挂载。 */
12
+ isEntry?: boolean;
11
13
  /** 脚本是否为async执行 */
12
14
  async?: boolean;
13
15
  /** 脚本是否设置crossorigin */
@@ -0,0 +1,2 @@
1
+ import type { RuntimeCredentials } from "../core/index.js";
2
+ export declare function createRuntimeFetch(runtimeCredentials: RuntimeCredentials | undefined, browserFetch?: typeof fetch): ((input: RequestInfo, init?: RequestInit) => Promise<Response>) | undefined;
@@ -0,0 +1,14 @@
1
+ export function createRuntimeFetch(runtimeCredentials, browserFetch = window.fetch.bind(window)) {
2
+ if (!runtimeCredentials)
3
+ return undefined;
4
+ return (input, init) => {
5
+ // Request objects created inside an XPE child iframe belong to another
6
+ // JavaScript Realm, so `input instanceof Request` is false in the Host.
7
+ // RequestInfo is Request|string; use the contract shape instead.
8
+ const requestCredentials = typeof input === "string" ? undefined : input.credentials;
9
+ return browserFetch(input, {
10
+ ...init,
11
+ credentials: init?.credentials ?? requestCredentials ?? runtimeCredentials,
12
+ });
13
+ };
14
+ }
@@ -11,6 +11,8 @@ export interface ScriptBaseObject {
11
11
  defer?: boolean;
12
12
  /** 脚本是否为module模块 */
13
13
  module?: boolean;
14
+ /** 是否为应用入口脚本。仅入口模块需要等待 XPE 生命周期挂载。 */
15
+ isEntry?: boolean;
14
16
  /** 脚本是否设置crossorigin */
15
17
  crossorigin?: boolean;
16
18
  /** 脚本crossorigin的类型 */
@@ -212,9 +212,17 @@ export default function processTpl(tpl, baseURI, postProcessTemplate) {
212
212
  return inlineScriptReplaceSymbol;
213
213
  }
214
214
  });
215
+ const entrySrc = typeof entry === "string" ? entry : entry?.src;
216
+ const entryIndex = entrySrc
217
+ ? scripts.findIndex((script) => script.src === entrySrc)
218
+ : scripts.length - 1;
219
+ const scriptsWithEntry = scripts.map((script, index) => ({
220
+ ...script,
221
+ isEntry: index === entryIndex,
222
+ }));
215
223
  let tplResult = {
216
224
  template,
217
- scripts,
225
+ scripts: scriptsWithEntry,
218
226
  styles,
219
227
  // set the last script as entry if have not set
220
228
  entry: entry || scripts[scripts.length - 1],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xpe",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "下一代微前端应用运行底座",
5
5
  "type": "module",
6
6
  "license": "MIT",