From d08037c5b86c4789a72f2d842b6059ed2d635d4a Mon Sep 17 00:00:00 2001 From: Hemisu Date: Wed, 23 Apr 2025 15:35:27 +0800 Subject: [PATCH] fix: retain currentScript reference when processing async/defer scripts to prevent data-* attribute read failures --- dev/app-react-18/public/index.html | 7 +- .../public/scripts/readCurrentScript.js | 5 ++ packages/core/src/module/app.ts | 81 +++++++++++++------ 3 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 dev/app-react-18/public/scripts/readCurrentScript.js diff --git a/dev/app-react-18/public/index.html b/dev/app-react-18/public/index.html index 0bb206616..0f2d9d4b6 100644 --- a/dev/app-react-18/public/index.html +++ b/dev/app-react-18/public/index.html @@ -2,7 +2,12 @@ - app react v17 + app react v18 +
diff --git a/dev/app-react-18/public/scripts/readCurrentScript.js b/dev/app-react-18/public/scripts/readCurrentScript.js new file mode 100644 index 000000000..e5e5b152f --- /dev/null +++ b/dev/app-react-18/public/scripts/readCurrentScript.js @@ -0,0 +1,5 @@ +const currentScript = document.currentScript; +if (!currentScript.dataset.testName) { + throw new Error('currentScript.dataset.testName is required') +} + diff --git a/packages/core/src/module/app.ts b/packages/core/src/module/app.ts index 46da21646..314838cc7 100644 --- a/packages/core/src/module/app.ts +++ b/packages/core/src/module/app.ts @@ -98,6 +98,7 @@ export class App { // Environment variables injected by garfish for linkage with child applications private globalEnvVariables: Record; private deferNodeMap = new Map(); + private asyncNodeMap = new Map(); private resolveAsyncProvider: () => void | undefined; private asyncProvider?: | interfaces.Provider @@ -476,18 +477,42 @@ export class App { let noEntry = false; const targetUrl = jsManager.url || this.appInfo.entry; + const mockOriginScript = document.createElement('script'); + if (type === 'defer') { const node = this.deferNodeMap.get(jsManager); if (node) { noEntry = toBoolean( this.entryManager.findAttributeValue(node, 'no-entry'), ); + + node.attributes.forEach((attribute) => { + if (attribute.key) { + mockOriginScript.setAttribute( + attribute.key, + attribute.value || '', + ); + } + }); } // Try to read the childApp global configuration if (!noEntry) { noEntry = toBoolean(this.isNoEntryScript(targetUrl)); } } + + const node = this.asyncNodeMap.get(jsManager); + if (node) { + node.attributes.forEach((attribute) => { + if (attribute.key) { + mockOriginScript.setAttribute( + attribute.key, + attribute.value || '', + ); + } + }); + } + this.execScript(jsManager.scriptCode, {}, targetUrl, { noEntry, defer: type === 'defer', @@ -703,36 +728,40 @@ export class App { } const jsManager = resources.js.find((manager) => { - return !manager.async ? manager.isSameOrigin(node) : false; + if (manager.async) { + this.asyncNodeMap.set(manager, node); + return false; + } + if (manager.defer) { + this.deferNodeMap.set(manager, node); + return false; + } + return manager.isSameOrigin(node); }); if (jsManager) { - if (jsManager.defer) { - this.deferNodeMap.set(jsManager, node); - } else { - const { url, scriptCode } = jsManager; - const mockOriginScript = document.createElement('script'); - node.attributes.forEach((attribute) => { - if (attribute.key) { - mockOriginScript.setAttribute( - attribute.key, - attribute.value || '', - ); - } - }); + const { url, scriptCode } = jsManager; + const mockOriginScript = document.createElement('script'); + node.attributes.forEach((attribute) => { + if (attribute.key) { + mockOriginScript.setAttribute( + attribute.key, + attribute.value || '', + ); + } + }); - const targetUrl = url || this.appInfo.entry; - this.execScript(scriptCode, {}, targetUrl, { - isModule, - async: false, - defer: false, - isInline: jsManager.isInlineScript(), - noEntry: - toBoolean(entryManager.findAttributeValue(node, 'no-entry')) || - toBoolean(this.isNoEntryScript(targetUrl)), - originScript: mockOriginScript, - }); - } + const targetUrl = url || this.appInfo.entry; + this.execScript(scriptCode, {}, targetUrl, { + isModule, + async: false, + defer: false, + isInline: jsManager.isInlineScript(), + noEntry: + toBoolean(entryManager.findAttributeValue(node, 'no-entry')) || + toBoolean(this.isNoEntryScript(targetUrl)), + originScript: mockOriginScript, + }); } else if (__DEV__) { const async = entryManager.findAttributeValue(node, 'async'); if (typeof async === 'undefined' || async === 'false') {