diff --git a/cypress/e2e/1-the-whole-process/visit-sub-app.spec.js b/cypress/e2e/1-the-whole-process/visit-sub-app.spec.js index 3c65a56e8..0adc2b038 100644 --- a/cypress/e2e/1-the-whole-process/visit-sub-app.spec.js +++ b/cypress/e2e/1-the-whole-process/visit-sub-app.spec.js @@ -16,7 +16,7 @@ describe('whole process app render', () => { }, sandbox: { snapshot: false, - } + }, }, }); @@ -148,8 +148,8 @@ describe('whole process app render', () => { cy.wait(4000); cy.contains('[data-test=title]', HomeTitle) .then(() => { - cy.get('[data-test=vite-count-btn]').dblclick(); - cy.contains('button', 'count is: 2'); + cy.get('[data-test=vite-count-btn]').click({ force: true }); + cy.contains('button', 'count is: 1'); }) .then(() => { win.Garfish.router.push({ path: '/vue2/home' }); diff --git a/dev/app-main/src/constant.ts b/dev/app-main/src/constant.ts index 6faeea49d..87a4e0c85 100644 --- a/dev/app-main/src/constant.ts +++ b/dev/app-main/src/constant.ts @@ -17,6 +17,7 @@ export const localApps: AppInfo = [ activeWhen: '/react17', sandbox: { fixStaticResourceBaseUrl: false, + disableLinkTransformToStyle: false, }, // 子应用的入口地址,可以为 HTML 地址和 JS 地址 // 注意:entry 地址不可以与主应用+子应用激活地址相同,否则刷新时将会直接返回子应用内容 diff --git a/dev/app-react-17/public/a.css b/dev/app-react-17/public/a.css new file mode 100644 index 000000000..bcc3583df --- /dev/null +++ b/dev/app-react-17/public/a.css @@ -0,0 +1,3 @@ +.hello { + color: red; +} diff --git a/dev/app-react-17/public/b.css b/dev/app-react-17/public/b.css new file mode 100644 index 000000000..c021f52a0 --- /dev/null +++ b/dev/app-react-17/public/b.css @@ -0,0 +1,3 @@ +.world { + color: blue; +} diff --git a/dev/app-react-17/public/index.html b/dev/app-react-17/public/index.html index a58d73c00..82f1b3947 100644 --- a/dev/app-react-17/public/index.html +++ b/dev/app-react-17/public/index.html @@ -3,6 +3,14 @@ app react v17 + + diff --git a/packages/browser-vm/__tests__/dynamic-link.spec.ts b/packages/browser-vm/__tests__/dynamic-link.spec.ts index 4770144db..05a4b6008 100644 --- a/packages/browser-vm/__tests__/dynamic-link.spec.ts +++ b/packages/browser-vm/__tests__/dynamic-link.spec.ts @@ -42,7 +42,7 @@ describe('Sandbox: dynamic link', () => { [linkOrder3]: { 'Content-Type': styleType, timeConsuming: 300, - } + }, }, }); @@ -90,7 +90,7 @@ describe('Sandbox: dynamic link', () => { go(` const dynamicLink = document.createElement('link'); dynamicLink.href = "${withSuffix}"; - dynamicLink.rel = 'stylesheet'; + dynamicLink.setAttribute('rel', 'stylesheet'); dynamicLink.onload = function () { expect(document.body).toMatchSnapshot(); jestDone(); @@ -106,7 +106,7 @@ describe('Sandbox: dynamic link', () => { go(` const dynamicLink = document.createElement('link'); dynamicLink.href = "${withoutSuffix}"; - dynamicLink.rel = 'stylesheet'; + dynamicLink.setAttribute('rel', 'stylesheet'); dynamicLink.onload = function () { expect(document.body).toMatchSnapshot(); jestDone(); @@ -122,7 +122,7 @@ describe('Sandbox: dynamic link', () => { go(` const dynamicLink = document.createElement('link'); dynamicLink.href = "${withoutSuffixAndContentType}"; - dynamicLink.rel = 'stylesheet'; + dynamicLink.setAttribute('rel', 'stylesheet'); dynamicLink.onload = function () { expect(document.body).toMatchSnapshot(); jestDone(); diff --git a/packages/browser-vm/src/dynamicNode/processor.ts b/packages/browser-vm/src/dynamicNode/processor.ts index 7b8f2a574..a8d77636f 100644 --- a/packages/browser-vm/src/dynamicNode/processor.ts +++ b/packages/browser-vm/src/dynamicNode/processor.ts @@ -368,9 +368,12 @@ export class DynamicNodeProcessor { this.monitorChangesOfStyle(); } // The link node of the request css needs to be changed to style node - else if (this.is('link')) { + else if ( + this.is('link') && + this.sandbox.options.disableLinkTransformToStyle !== true + ) { parentNode = this.findParentNodeInApp(context, 'head'); - if (this.el.rel === 'stylesheet' && this.el.href) { + if (this.el.getAttribute('rel') === 'stylesheet' && this.el.href) { convertedNode = this.addDynamicLinkNode((styleNode) => { this.nativeAppend.call(parentNode, styleNode); }); diff --git a/packages/browser-vm/src/pluginify.ts b/packages/browser-vm/src/pluginify.ts index ffcf81687..7b463ab1a 100644 --- a/packages/browser-vm/src/pluginify.ts +++ b/packages/browser-vm/src/pluginify.ts @@ -134,6 +134,9 @@ function createOptions(Garfish: interfaces.Garfish) { fixOwnerDocument: Boolean(appInfo.sandbox?.fixOwnerDocument), disableWith: Boolean(appInfo.sandbox?.disableWith), disableElementtiming: Boolean(appInfo.sandbox?.disableElementtiming), + disableLinkTransformToStyle: Boolean( + appInfo.sandbox?.disableLinkTransformToStyle, + ), strictIsolation: Boolean(appInfo.sandbox?.strictIsolation), excludeAssetFilter: appInfo.sandbox?.excludeAssetFilter, // 缓存模式,不收集副作用 diff --git a/packages/browser-vm/src/sandbox.ts b/packages/browser-vm/src/sandbox.ts index 026605be3..5e6c6b15a 100644 --- a/packages/browser-vm/src/sandbox.ts +++ b/packages/browser-vm/src/sandbox.ts @@ -97,6 +97,7 @@ export class Sandbox { fixStaticResourceBaseUrl: true, disableWith: false, strictIsolation: false, + disableLinkTransformToStyle: false, disableCollect: false, el: () => null, styleScopeId: () => '', diff --git a/packages/browser-vm/src/types.ts b/packages/browser-vm/src/types.ts index 0786b09ed..9ba67a8e7 100644 --- a/packages/browser-vm/src/types.ts +++ b/packages/browser-vm/src/types.ts @@ -27,6 +27,7 @@ export interface SandboxOptions { disableWith?: boolean; strictIsolation?: boolean; disableElementtiming?: boolean; + disableLinkTransformToStyle?: boolean; disableCollect?: boolean; modules?: Array; excludeAssetFilter?: (url: string) => boolean; diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 23d49105d..40d0c4231 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -125,6 +125,7 @@ export const createDefaultOptions = () => { disableWith: false, strictIsolation: false, disableElementtiming: false, + disableLinkTransformToStyle: false, fixOwnerDocument: false, }, // global hooks diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 03ae9b43f..01e2f336d 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -81,6 +81,7 @@ export namespace interfaces { disableWith?: boolean; strictIsolation?: boolean; disableElementtiming?: boolean; + disableLinkTransformToStyle?: boolean; fixOwnerDocument?: boolean; excludeAssetFilter?: (url: string) => boolean; } diff --git a/packages/core/src/module/app.ts b/packages/core/src/module/app.ts index a403d0aee..46da21646 100644 --- a/packages/core/src/module/app.ts +++ b/packages/core/src/module/app.ts @@ -765,6 +765,14 @@ export class App { }, link: (node) => { + if ( + this.appInfo.sandbox && + typeof this.appInfo.sandbox === 'object' && + this.appInfo.sandbox.disableLinkTransformToStyle + ) { + return DOMApis.createElement(node); + } + if (DOMApis.isCssLinkNode(node)) { const styleManager = this.resources.link.find((manager) => manager.isSameOrigin(node), diff --git a/packages/core/src/module/resource.ts b/packages/core/src/module/resource.ts index d998d107c..0131527ff 100644 --- a/packages/core/src/module/resource.ts +++ b/packages/core/src/module/resource.ts @@ -10,7 +10,7 @@ import type { interfaces } from '../interface'; // Fetch `script`, `link` and `module meta` elements function fetchStaticResources( - appName: string, + appInfo: AppInfo, loader: Loader, entryManager: TemplateManager, sandboxConfig: false | interfaces.SandboxConfig | undefined, @@ -50,7 +50,7 @@ function fetchStaticResources( // we have a preload mechanism, so we don’t need to deal with it. return loader .load({ - scope: appName, + scope: appInfo.name, url: fetchUrl, crossOrigin, defaultContentType: type, @@ -63,7 +63,7 @@ function fetchStaticResources( jsManager.setDefferAttribute(toBoolean(defer)); return jsManager; } else { - warn(`[${appName}] Failed to load script: ${fetchUrl}`); + warn(`[${appInfo.name}] Failed to load script: ${fetchUrl}`); } }) .catch(() => null); @@ -86,20 +86,26 @@ function fetchStaticResources( .findAllLinkNodes() .map((node) => { if (!entryManager.DOMApis.isCssLinkNode(node)) return; + if ( + appInfo.sandbox && + typeof appInfo.sandbox === 'object' && + appInfo.sandbox.disableLinkTransformToStyle + ) + return; const href = entryManager.findAttributeValue(node, 'href'); if (href) { const fetchUrl = entryManager.url ? transformUrl(entryManager.url, href) : href; return loader - .load({ scope: appName, url: fetchUrl }) + .load({ scope: appInfo.name, url: fetchUrl }) .then(({ resourceManager: styleManager }) => { if (styleManager) { styleManager.setDep(node); styleManager?.correctPath(); return styleManager; } else { - warn(`${appName} Failed to load link: ${fetchUrl}`); + warn(`${appInfo.name} Failed to load link: ${fetchUrl}`); } }) .catch(() => null); @@ -155,7 +161,7 @@ export async function processAppResources(loader: Loader, appInfo: AppInfo) { if (entryManager instanceof loader.TemplateManager) { isHtmlMode = true; const [js, link, modules] = await fetchStaticResources( - appInfo.name, + appInfo, loader, entryManager, appInfo.sandbox, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ef71aa87..3771df748 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3975,7 +3975,7 @@ packages: css-minimizer-webpack-plugin: 4.2.2_z2fhrcdfcwgcfi7cwpl76xfc2y cssnano: 5.1.15_postcss@8.4.36 del: 6.1.1 - detect-port: 1.6.1 + detect-port: 1.5.1 escape-html: 1.0.3 eta: 1.14.2 file-loader: 6.2.0_webpack@5.90.3 @@ -4076,7 +4076,7 @@ packages: css-minimizer-webpack-plugin: 4.2.2_z2fhrcdfcwgcfi7cwpl76xfc2y cssnano: 5.1.15_postcss@8.4.36 del: 6.1.1 - detect-port: 1.6.1 + detect-port: 1.5.1 escape-html: 1.0.3 eta: 1.14.2 file-loader: 6.2.0_webpack@5.90.3 @@ -4177,7 +4177,7 @@ packages: css-minimizer-webpack-plugin: 4.2.2_z2fhrcdfcwgcfi7cwpl76xfc2y cssnano: 5.1.15_postcss@8.4.36 del: 6.1.1 - detect-port: 1.6.1 + detect-port: 1.5.1 escape-html: 1.0.3 eta: 2.2.0 file-loader: 6.2.0_webpack@5.90.3 @@ -4438,7 +4438,7 @@ packages: '@types/react-router-dom': 5.3.3 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 - react-helmet-async: 2.0.5_react@17.0.2 + react-helmet-async: 2.0.4_sfoxds7t5ydpegc3knd667wn6m react-loadable: /@docusaurus/react-loadable/5.5.2_react@17.0.2 transitivePeerDependencies: - '@swc/core' @@ -4461,7 +4461,7 @@ packages: '@types/react-router-dom': 5.3.3 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 - react-helmet-async: 2.0.5_react@17.0.2 + react-helmet-async: 2.0.4_sfoxds7t5ydpegc3knd667wn6m react-loadable: /@docusaurus/react-loadable/5.5.2_react@17.0.2 transitivePeerDependencies: - '@swc/core' @@ -4878,7 +4878,7 @@ packages: fs-extra: 10.1.0 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 - sitemap: 7.1.2 + sitemap: 7.1.1 tslib: 2.6.2 transitivePeerDependencies: - '@parcel/css' @@ -5135,7 +5135,7 @@ packages: '@docusaurus/utils': 2.0.0-rc.1_fh5bds7ccduypwmpulad3xjppm '@docusaurus/utils-validation': 2.0.0-rc.1_fh5bds7ccduypwmpulad3xjppm algoliasearch: 4.23.3 - algoliasearch-helper: 3.20.0_algoliasearch@4.23.3 + algoliasearch-helper: 3.19.0_algoliasearch@4.23.3 clsx: 1.2.1 eta: 1.14.2 fs-extra: 10.1.0 @@ -5534,25 +5534,16 @@ packages: - webpack-cli dev: false - /@emnapi/core/1.2.0: - resolution: {integrity: sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==} + /@emnapi/core/1.1.1: + resolution: {integrity: sha512-eu4KjHfXg3I+UUR7vSuwZXpRo4c8h4Rtb5Lu2F7Z4JqJFl/eidquONEBiRs6viXKpWBC3BaJBy68xGJ2j56idw==} requiresBuild: true dependencies: - '@emnapi/wasi-threads': 1.0.1 tslib: 2.6.2 dev: false optional: true - /@emnapi/runtime/1.2.0: - resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} - requiresBuild: true - dependencies: - tslib: 2.6.2 - dev: false - optional: true - - /@emnapi/wasi-threads/1.0.1: - resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} + /@emnapi/runtime/1.1.1: + resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==} requiresBuild: true dependencies: tslib: 2.6.2 @@ -6063,13 +6054,13 @@ packages: glob-to-regexp: 0.3.0 dev: true - /@napi-rs/wasm-runtime/0.2.4: - resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + /@napi-rs/wasm-runtime/0.2.3: + resolution: {integrity: sha512-e4qmGDzXu2MYjj/XiKSgJ7XS7Z83MYVRN1yYaYXeQNVEO56zmshqmzFaELfdb612sLq/GmiPfRIwSji+bIlyCw==} requiresBuild: true dependencies: - '@emnapi/core': 1.2.0 - '@emnapi/runtime': 1.2.0 - '@tybys/wasm-util': 0.9.0 + '@emnapi/core': 1.1.1 + '@emnapi/runtime': 1.1.1 + '@tybys/wasm-util': 0.8.3 dev: false optional: true @@ -6189,7 +6180,7 @@ packages: cpu: [wasm32] requiresBuild: true dependencies: - '@napi-rs/wasm-runtime': 0.2.4 + '@napi-rs/wasm-runtime': 0.2.3 dev: false optional: true @@ -6815,8 +6806,8 @@ packages: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} dev: true - /@tybys/wasm-util/0.9.0: - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + /@tybys/wasm-util/0.8.3: + resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} requiresBuild: true dependencies: tslib: 2.6.2 @@ -9210,8 +9201,8 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /algoliasearch-helper/3.20.0_algoliasearch@4.23.3: - resolution: {integrity: sha512-6EVhAmVug0+hdRHWbubF7hLHHhLoQ8NjLk6iS6d4k5chWawpS5EDexrF6Jx/hPZvUKIeNrzsbTpjAkcvrjNLHg==} + /algoliasearch-helper/3.19.0_algoliasearch@4.23.3: + resolution: {integrity: sha512-AaSb5DZDMZmDQyIy6lf4aL0OZGgyIdqvLIIvSuVQOIOqfhrYSY7TvotIFI2x0Q3cP3xUpTd7lI1astUC4aXBJw==} peerDependencies: algoliasearch: '>= 3.1 < 6' dependencies: @@ -9384,7 +9375,6 @@ packages: /are-we-there-yet/2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} - deprecated: This package is no longer supported. dependencies: delegates: 1.0.0 readable-stream: 3.6.2 @@ -12958,9 +12948,8 @@ packages: - supports-color dev: false - /detect-port/1.6.1: - resolution: {integrity: sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==} - engines: {node: '>= 4.0.0'} + /detect-port/1.5.1: + resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==} hasBin: true dependencies: address: 1.2.2 @@ -15174,7 +15163,6 @@ packages: /gauge/3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} - deprecated: This package is no longer supported. dependencies: aproba: 2.0.0 color-support: 1.1.3 @@ -20231,7 +20219,6 @@ packages: /npmlog/5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. dependencies: are-we-there-yet: 2.0.0 console-control-strings: 1.1.0 @@ -22858,13 +22845,15 @@ packages: shallowequal: 1.1.0 dev: false - /react-helmet-async/2.0.5_react@17.0.2: - resolution: {integrity: sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==} + /react-helmet-async/2.0.4_sfoxds7t5ydpegc3knd667wn6m: + resolution: {integrity: sha512-yxjQMWposw+akRfvpl5+8xejl4JtUlHnEBcji6u8/e6oc7ozT+P9PNTWMhCbz2y9tc5zPegw2BvKjQA+NwdEjQ==} peerDependencies: react: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 dependencies: invariant: 2.2.4 react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 react-fast-compare: 3.2.2 shallowequal: 1.1.0 dev: false @@ -24580,8 +24569,8 @@ packages: /sisteransi/1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - /sitemap/7.1.2: - resolution: {integrity: sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==} + /sitemap/7.1.1: + resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} engines: {node: '>=12.0.0', npm: '>=5.6.0'} hasBin: true dependencies: diff --git a/website/src/components/config/_sandbox.mdx b/website/src/components/config/_sandbox.mdx index 1bd813fdb..2eda8dafb 100644 --- a/website/src/components/config/_sandbox.mdx +++ b/website/src/components/config/_sandbox.mdx @@ -20,6 +20,8 @@ interface SandboxConfig { disableElementtiming?: boolean; // fixOwnerDocument 1.17.2 版本提供 ,默认值 false,目前可能会存在 ownerDocument 逃逸的情况,设置为 true 之后将会避免 ownerDocument 逃逸 fixOwnerDocument?: boolean; + // disableLinkTransformToStyle 1.18.0 版本提供 ,默认值 false,禁用掉 link 自动 transform 成 style 的行为 + disableLinkTransformToStyle?: boolean; // excludeAssetFilter 1.18.0 版本提供,默认值为 undefined,用于过滤不需要再子应用沙箱中执行的资源例如 jsonp,url 参数为对应 script 的地址,返回 true 则会过滤掉该资源 excludeAssetFilter?: (url: string) => boolean; }