From e3bfcd96037cdd19642385edbe3bf288694f38a8 Mon Sep 17 00:00:00 2001 From: xiaobai-web715 Date: Wed, 7 Jan 2026 22:50:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E9=80=BB=E8=BE=91=E5=B9=B6=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E5=85=A5=E5=8F=82=E8=BF=9B=E8=A1=8C=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/wujie-core/src/sandbox.ts | 13 ++++++++++++- packages/wujie-core/src/utils.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/wujie-core/src/sandbox.ts b/packages/wujie-core/src/sandbox.ts index 555b847b..943a0419 100644 --- a/packages/wujie-core/src/sandbox.ts +++ b/packages/wujie-core/src/sandbox.ts @@ -134,6 +134,12 @@ export default class Wujie { mainHostPath: string; }; + /** 是否开启超时取消请求 */ + public cancelRequest: boolean; + + /** 超时时间 */ + public timeout: number; + /** 激活子应用 * 1、同步路由 * 2、动态修改iframe的fetch @@ -423,6 +429,8 @@ export default class Wujie { this.prefix = null; this.iframeAddEventListeners = null; this.iframeOnEvents = null; + this.cancelRequest = null; + this.timeout = null; // 清除 dom if (this.el) { clearChild(this.el); @@ -491,6 +499,8 @@ export default class Wujie { lifecycles: lifecycles; iframeAddEventListeners?: Array; iframeOnEvents?: Array; + cancelRequest?: boolean; + timeout?: number; }) { // 传递inject给嵌套子应用 if (window.__POWERED_BY_WUJIE__) this.inject = window.__WUJIE.inject; @@ -515,7 +525,8 @@ export default class Wujie { this.plugins = getPlugins(plugins); this.iframeAddEventListeners = options.iframeAddEventListeners; this.iframeOnEvents = options.iframeOnEvents; - + this.cancelRequest = options.cancelRequest; + this.timeout = options.timeout; // 创建目标地址的解析 const { urlElement, appHostPath, appRoutePath } = appRouteParse(url); const { mainHostPath } = this.inject; diff --git a/packages/wujie-core/src/utils.ts b/packages/wujie-core/src/utils.ts index d29672d5..7dce83f5 100644 --- a/packages/wujie-core/src/utils.ts +++ b/packages/wujie-core/src/utils.ts @@ -376,3 +376,16 @@ export function stopMainAppRun() { warn(WUJIE_TIPS_STOP_APP_DETAIL); throw new Error(WUJIE_TIPS_STOP_APP); } + +export function fetchWithTimeOut( + url: string, + fetch: (input: RequestInfo, init?: RequestInit) => Promise, + timeout: number = 8000 +) { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + + return fetch(url, { + signal: controller.signal, + }).finally(() => clearTimeout(id)); +} From a6e8ac65f212a642a8e13719429ea7a3498c2e25 Mon Sep 17 00:00:00 2001 From: liuxinghua Date: Fri, 9 Jan 2026 08:29:13 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E9=98=BB=E5=A1=9E=E6=80=A7=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E8=AF=B7=E6=B1=82=E9=80=BB=E8=BE=91=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E6=8E=A7=E5=88=B6=E5=85=A5=E5=8F=82=EF=BC=8C?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E8=AF=B7=E6=B1=82=E8=B6=85=E6=97=B6=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E9=A1=B5=E9=9D=A2=E9=95=BF=E6=97=B6=E9=97=B4=E7=99=BD?= =?UTF-8?q?=E5=B1=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/wujie-core/src/effect.ts | 62 ++++++++++++++++++------------ packages/wujie-core/src/entry.ts | 48 +++++++++++++++++------ packages/wujie-core/src/index.ts | 14 +++++++ packages/wujie-core/src/sandbox.ts | 2 +- packages/wujie-core/src/shadow.ts | 6 +-- packages/wujie-core/src/utils.ts | 20 ++++++---- packages/wujie-react/index.js | 2 + packages/wujie-vue2/index.js | 4 ++ packages/wujie-vue3/index.js | 4 ++ 9 files changed, 115 insertions(+), 47 deletions(-) diff --git a/packages/wujie-core/src/effect.ts b/packages/wujie-core/src/effect.ts index 2444df52..17889234 100644 --- a/packages/wujie-core/src/effect.ts +++ b/packages/wujie-core/src/effect.ts @@ -179,7 +179,18 @@ function rewriteAppendOrInsertChild(opts: { const { rawDOMAppendOrInsertBefore, wujieId } = opts; const sandbox = getWujieById(wujieId); - const { styleSheetElements, replace, fetch, plugins, iframe, lifecycles, proxyLocation, fiber } = sandbox; + const { + styleSheetElements, + replace, + fetch, + plugins, + iframe, + lifecycles, + proxyLocation, + fiber, + cancelRequest, + timeout, + } = sandbox; if (!isHijackingTag(element.tagName) || !wujieId) { const res = rawDOMAppendOrInsertBefore.call(this, element, refChild) as T; @@ -288,29 +299,32 @@ function rewriteAppendOrInsertChild(opts: { ignore: isMatchUrl(src, getEffectLoaders("jsIgnores", plugins)), attrs: parseTagAttributes(element.outerHTML), } as ScriptObject; - getExternalScripts([scriptOptions], fetch, lifecycles.loadError, fiber).forEach((scriptResult) => { - dynamicScriptExecStack = dynamicScriptExecStack.then(() => - scriptResult.contentPromise.then( - (content) => { - if (sandbox.execQueue === null) return warn(WUJIE_TIPS_REPEAT_RENDER); - const execQueueLength = sandbox.execQueue?.length; - sandbox.execQueue.push(() => - fiber - ? sandbox.requestIdleCallback(() => { - execScript({ ...scriptResult, content }); - }) - : execScript({ ...scriptResult, content }) - ); - // 同步脚本如果都执行完了,需要手动触发执行 - if (!execQueueLength) sandbox.execQueue.shift()(); - }, - () => { - manualInvokeElementEvent(element, "error"); - element = null; - } - ) - ); - }); + getExternalScripts([scriptOptions], fetch, lifecycles.loadError, fiber, cancelRequest, timeout).forEach( + (scriptResult) => { + dynamicScriptExecStack = dynamicScriptExecStack.then(() => + // fetch请求超时会影响后续其它任务通过then链式调用的方式推入execQueue当中 + scriptResult.contentPromise.then( + (content) => { + if (sandbox.execQueue === null) return warn(WUJIE_TIPS_REPEAT_RENDER); + const execQueueLength = sandbox.execQueue?.length; + sandbox.execQueue.push(() => + fiber + ? sandbox.requestIdleCallback(() => { + execScript({ ...scriptResult, content }); + }) + : execScript({ ...scriptResult, content }) + ); + // 同步脚本如果都执行完了,需要手动触发执行 + if (!execQueueLength) sandbox.execQueue.shift()(); + }, + () => { + manualInvokeElementEvent(element, "error"); + element = null; + } + ) + ); + } + ); } else { const execQueueLength = sandbox.execQueue?.length; sandbox.execQueue.push(() => diff --git a/packages/wujie-core/src/entry.ts b/packages/wujie-core/src/entry.ts index 2a3614f2..efab3681 100644 --- a/packages/wujie-core/src/entry.ts +++ b/packages/wujie-core/src/entry.ts @@ -5,7 +5,15 @@ import processTpl, { ScriptBaseObject, StyleObject, } from "./template"; -import { defaultGetPublicPath, getInlineCode, requestIdleCallback, error, compose, getCurUrl } from "./utils"; +import { + defaultGetPublicPath, + getInlineCode, + requestIdleCallback, + error, + compose, + getCurUrl, + fetchWithTimeOut, +} from "./utils"; import { WUJIE_TIPS_NO_FETCH, WUJIE_TIPS_SCRIPT_ERROR_REQUESTED, @@ -34,6 +42,8 @@ type ImportEntryOpts = { fiber?: boolean; plugins?: Array; loadError?: loadErrorHandler; + cancelRequest?: boolean; + timeout?: number; }; const styleCache = {}; @@ -105,10 +115,12 @@ const fetchAssets = ( cache: Object, fetch: (input: RequestInfo, init?: RequestInit) => Promise, cssFlag?: boolean, - loadError?: loadErrorHandler + loadError?: loadErrorHandler, + cancelRequest?: boolean, + timeout?: number ) => cache[src] || - (cache[src] = fetch(src) + (cache[src] = fetchWithTimeOut(src, fetch, cancelRequest, timeout) .then((response) => { // usually browser treats 4xx and 5xx response of script loading as an error and will fire a script error event // https://stackoverflow.com/questions/5625420/what-http-headers-responses-trigger-the-onerror-handler-on-a-script-tag/5625603 @@ -143,7 +155,9 @@ const fetchAssets = ( export function getExternalStyleSheets( styles: StyleObject[], fetch: (input: RequestInfo, init?: RequestInit) => Promise = defaultFetch, - loadError: loadErrorHandler + loadError: loadErrorHandler, + cancelRequest?: boolean, + timeout?: number ): StyleResultList { return styles.map(({ src, content, ignore }) => { // 内联 @@ -157,7 +171,9 @@ export function getExternalStyleSheets( return { src, ignore, - contentPromise: ignore ? Promise.resolve("") : fetchAssets(src, styleCache, fetch, true, loadError), + contentPromise: ignore + ? Promise.resolve("") + : fetchAssets(src, styleCache, fetch, true, loadError, cancelRequest, timeout), }; } }); @@ -168,7 +184,9 @@ export function getExternalScripts( scripts: ScriptObject[], fetch: (input: RequestInfo, init?: RequestInit) => Promise = defaultFetch, loadError: loadErrorHandler, - fiber: boolean + fiber: boolean, + cancelRequest?: boolean, + timeout?: number ): ScriptResultList { // module should be requested in iframe return scripts.map((script) => { @@ -178,8 +196,10 @@ export function getExternalScripts( if ((async || defer) && src && !module) { contentPromise = new Promise((resolve, reject) => fiber - ? requestIdleCallback(() => fetchAssets(src, scriptCache, fetch, false, loadError).then(resolve, reject)) - : fetchAssets(src, scriptCache, fetch, false, loadError).then(resolve, reject) + ? requestIdleCallback(() => + fetchAssets(src, scriptCache, fetch, false, loadError, cancelRequest, timeout).then(resolve, reject) + ) + : fetchAssets(src, scriptCache, fetch, false, loadError, cancelRequest, timeout).then(resolve, reject) ); // module || ignore } else if ((module && src) || ignore) { @@ -189,7 +209,7 @@ export function getExternalScripts( contentPromise = Promise.resolve(script.content); // outline } else { - contentPromise = fetchAssets(src, scriptCache, fetch, false, loadError); + contentPromise = fetchAssets(src, scriptCache, fetch, false, loadError, cancelRequest, timeout); } // refer https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer if (module && !async) script.defer = true; @@ -205,7 +225,7 @@ export default function importHTML(params: { const { url, opts, html } = params; const fetch = opts.fetch ?? defaultFetch; const fiber = opts.fiber ?? true; - const { plugins, loadError } = opts; + const { plugins, loadError, cancelRequest, timeout } = opts; const htmlLoader = plugins ? compose(plugins.map((plugin) => plugin.htmlLoader)) : defaultGetTemplate; const jsExcludes = getEffectLoaders("jsExcludes", plugins); const cssExcludes = getEffectLoaders("cssExcludes", plugins); @@ -243,7 +263,9 @@ export default function importHTML(params: { .map((script) => ({ ...script, ignore: script.src && isMatchUrl(script.src, jsIgnores) })), fetch, loadError, - fiber + fiber, + cancelRequest, + timeout ), getExternalStyleSheets: () => getExternalStyleSheets( @@ -251,7 +273,9 @@ export default function importHTML(params: { .filter((style) => !style.src || !isMatchUrl(style.src, cssExcludes)) .map((style) => ({ ...style, ignore: style.src && isMatchUrl(style.src, cssIgnores) })), fetch, - loadError + loadError, + cancelRequest, + timeout ), }; }); diff --git a/packages/wujie-core/src/index.ts b/packages/wujie-core/src/index.ts index 0bf908ee..3c36fbf5 100644 --- a/packages/wujie-core/src/index.ts +++ b/packages/wujie-core/src/index.ts @@ -118,6 +118,10 @@ type baseOptions = { iframeAddEventListeners?: Array; /** 子应用iframe on事件 */ iframeOnEvents?: Array; + /** 是否取消请求 */ + cancelRequest?: boolean; + /** 请求超时时间 */ + timeout?: number; /** 子应用生命周期 */ beforeLoad?: lifecycle; beforeMount?: lifecycle; @@ -212,6 +216,8 @@ export async function startApp(startOptions: startOptions): Promise { const document = sandbox.iframe.contentDocument; - const { plugins, replace, proxyLocation } = sandbox; + const { plugins, replace, proxyLocation, cancelRequest, timeout } = sandbox; const cssLoader = getCssLoader({ plugins, replace }); const cssBeforeLoaders = getPresetLoaders("cssBeforeLoaders", plugins); const cssAfterLoaders = getPresetLoaders("cssAfterLoaders", plugins); @@ -116,7 +116,7 @@ async function processCssLoaderForTemplate(sandbox: Wujie, html: HTMLHtmlElement return await Promise.all([ Promise.all( - getExternalStyleSheets(cssBeforeLoaders, sandbox.fetch, sandbox.lifecycles.loadError).map( + getExternalStyleSheets(cssBeforeLoaders, sandbox.fetch, sandbox.lifecycles.loadError, cancelRequest, timeout).map( ({ src, contentPromise }) => contentPromise.then((content) => ({ src, content })) ) ).then((contentList) => { @@ -131,7 +131,7 @@ async function processCssLoaderForTemplate(sandbox: Wujie, html: HTMLHtmlElement }); }), Promise.all( - getExternalStyleSheets(cssAfterLoaders, sandbox.fetch, sandbox.lifecycles.loadError).map( + getExternalStyleSheets(cssAfterLoaders, sandbox.fetch, sandbox.lifecycles.loadError, cancelRequest, timeout).map( ({ src, contentPromise }) => contentPromise.then((content) => ({ src, content })) ) ).then((contentList) => { diff --git a/packages/wujie-core/src/utils.ts b/packages/wujie-core/src/utils.ts index 7dce83f5..c4736498 100644 --- a/packages/wujie-core/src/utils.ts +++ b/packages/wujie-core/src/utils.ts @@ -355,6 +355,8 @@ export function mergeOptions(options: cacheOptions, cacheOptions: cacheOptions) deactivated: options.deactivated || cacheOptions?.deactivated, loadError: options.loadError || cacheOptions?.loadError, }, + cancelRequest: options.cancelRequest || cacheOptions?.cancelRequest, + timeout: options.timeout || cacheOptions?.timeout, }; } @@ -380,12 +382,16 @@ export function stopMainAppRun() { export function fetchWithTimeOut( url: string, fetch: (input: RequestInfo, init?: RequestInit) => Promise, - timeout: number = 8000 + cancelRequest?: boolean, + timeout: number = 5000 ) { - const controller = new AbortController(); - const id = setTimeout(() => controller.abort(), timeout); - - return fetch(url, { - signal: controller.signal, - }).finally(() => clearTimeout(id)); + if (cancelRequest) { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + return fetch(url, { + signal: controller.signal, + }).finally(() => clearTimeout(id)); + } else { + return fetch(url); + } } diff --git a/packages/wujie-react/index.js b/packages/wujie-react/index.js index e8a9c4d3..afa31afb 100644 --- a/packages/wujie-react/index.js +++ b/packages/wujie-react/index.js @@ -92,4 +92,6 @@ const propTypes = { style: PropTypes.object, iframeAddEventListeners: PropTypes.arrayOf(PropTypes.string), iframeOnEvents: PropTypes.arrayOf(PropTypes.string), + cancelRequest: PropTypes.bool, + timeout: PropTypes.number, }; diff --git a/packages/wujie-vue2/index.js b/packages/wujie-vue2/index.js index 623661ed..b19d02b6 100644 --- a/packages/wujie-vue2/index.js +++ b/packages/wujie-vue2/index.js @@ -30,6 +30,8 @@ const wujieVueOptions = { style: { type: Object, default: undefined }, iframeAddEventListeners: { type: Array, default: null }, iframeOnEvents: { type: Array, default: null }, + cancelRequest: { type: Boolean, default: undefined }, + timeout: { type: Number, default: undefined }, }, data() { return { @@ -89,6 +91,8 @@ const wujieVueOptions = { loadError: this.loadError, iframeAddEventListeners: this.iframeAddEventListeners, iframeOnEvents: this.iframeOnEvents, + cancelRequest: this.cancelRequest, + timeout: this.timeout, }); } catch (error) { console.log(error); diff --git a/packages/wujie-vue3/index.js b/packages/wujie-vue3/index.js index 5cd8c4dd..af2218c6 100644 --- a/packages/wujie-vue3/index.js +++ b/packages/wujie-vue3/index.js @@ -30,6 +30,8 @@ const wujieVueOptions = { style: { type: Object, default: undefined }, iframeAddEventListeners: { type: Array, default: null }, iframeOnEvents: { type: Array, default: null }, + cancelRequest: { type: Boolean, default: undefined }, + timeout: { type: Number, default: undefined }, }, data() { return { @@ -88,6 +90,8 @@ const wujieVueOptions = { loadError: this.loadError, iframeAddEventListeners: this.iframeAddEventListeners, iframeOnEvents: this.iframeOnEvents, + cancelRequest: this.cancelRequest, + timeout: this.timeout, }); } catch (error) { console.log(error); From be5ee1070483033aae8192d75e92e68964213fd0 Mon Sep 17 00:00:00 2001 From: liuxinghua Date: Fri, 9 Jan 2026 10:36:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0docs/api=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E6=8E=A7=E5=88=B6=E5=AD=97=E6=AE=B5=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/preloadApp.md | 4 ++++ docs/api/setupApp.md | 4 ++++ docs/api/startApp.md | 6 +++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/api/preloadApp.md b/docs/api/preloadApp.md index fc2c99f4..6f190acd 100644 --- a/docs/api/preloadApp.md +++ b/docs/api/preloadApp.md @@ -51,6 +51,10 @@ type preOptions { deactivated?: lifecycle; /** 子应用资源加载失败后调用 */ loadError?: loadErrorHandler + /** 超时取消开关 */ + cancelRequest?: boolean; + /** 超时等待时间 */ + timeout?: number; }; ``` diff --git a/docs/api/setupApp.md b/docs/api/setupApp.md index 1cadc610..3e6dc79e 100644 --- a/docs/api/setupApp.md +++ b/docs/api/setupApp.md @@ -48,6 +48,10 @@ type baseOptions = { activated?: lifecycle; deactivated?: lifecycle; loadError?: loadErrorHandler; + /** 超时取消开关 */ + cancelRequest?: boolean; + /** 超时等待时间 */ + timeout?: number; }; type preOptions = baseOptions & { diff --git a/docs/api/startApp.md b/docs/api/startApp.md index 91d1b723..ba7644ec 100644 --- a/docs/api/startApp.md +++ b/docs/api/startApp.md @@ -58,7 +58,11 @@ type startOption { activated?: lifecycle; deactivated?: lifecycle; /** 子应用资源加载失败后调用 */ - loadError?: loadErrorHandler + loadError?: loadErrorHandler; + /** 超时取消开关 */ + cancelRequest?: boolean; + /** 超时等待时间 */ + timeout?: number; }; ```