diff --git a/.changeset/loud-penguins-crash.md b/.changeset/loud-penguins-crash.md new file mode 100644 index 000000000..f7c6b3dd7 --- /dev/null +++ b/.changeset/loud-penguins-crash.md @@ -0,0 +1,5 @@ +--- +"@qiankunjs/sandbox": patch +--- + +feat: set proxy appendChild/insertBefore method for every sandbox rather than modify prototype on HTMLElement diff --git a/packages/sandbox/src/core/membrane/index.ts b/packages/sandbox/src/core/membrane/index.ts index da689894b..79fe2c619 100644 --- a/packages/sandbox/src/core/membrane/index.ts +++ b/packages/sandbox/src/core/membrane/index.ts @@ -124,7 +124,7 @@ export class Membrane { return true; }, - get: (membraneTarget, p) => { + get: (membraneTarget, p, receiver) => { if (p === Symbol.unscopables) return unscopables; // properties in endowments returns directly @@ -155,7 +155,7 @@ export class Membrane { proxyFetch('https://qiankun.com'); */ const boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : incubatorContext; - return getTargetValue(boundTarget, value); + return getTargetValue(boundTarget, value, receiver); }, // trap in operator diff --git a/packages/sandbox/src/core/membrane/utils.ts b/packages/sandbox/src/core/membrane/utils.ts index 96556dbc0..b7e1604fa 100644 --- a/packages/sandbox/src/core/membrane/utils.ts +++ b/packages/sandbox/src/core/membrane/utils.ts @@ -3,7 +3,7 @@ import { isBoundedFunction, isCallable, isConstructable } from '../../utils'; const functionBoundedValueMap = new WeakMap(); -export function getTargetValue(target: unknown, value: T): T { +export function getTargetValue(target: unknown, value: T, receiver: unknown): T { /* 仅绑定 isCallable && !isBoundedFunction && !isConstructable 的函数对象,如 window.console、window.atob 这类,不然微应用中调用时会抛出 Illegal invocation 异常 目前没有完美的检测方式,这里通过 prototype 中是否还有可枚举的拓展方法的方式来判断 @@ -16,7 +16,13 @@ export function getTargetValue(target: unknown, value: T): T { return cachedBoundFunction as T; } - const boundValue = Function.prototype.bind.call(typedValue, target) as CallableFunction; + const boundValue = function proxyFunction(...args: unknown[]): unknown { + return Function.prototype.apply.call( + typedValue, + target, + args.map((arg) => (arg === receiver ? target : arg)), + ); + }; // some callable function has custom fields, we need to copy the own props to boundValue. such as moment function. getOwnPropertyNames(typedValue).forEach((key) => { diff --git a/packages/sandbox/src/patchers/dynamicAppend/common.ts b/packages/sandbox/src/patchers/dynamicAppend/common.ts index 3bf8d5f8e..b559dfaab 100644 --- a/packages/sandbox/src/patchers/dynamicAppend/common.ts +++ b/packages/sandbox/src/patchers/dynamicAppend/common.ts @@ -3,7 +3,7 @@ * @author Kuitos * @since 2019-10-21 */ -import { transpileAssets } from '@qiankunjs/shared'; +import { QiankunError, transpileAssets } from '@qiankunjs/shared'; import { qiankunHeadTagName } from '../../consts'; import type { SandboxConfig } from './types'; @@ -51,7 +51,7 @@ export function isHijackingTag(tagName?: string) { * Such as the style element generated by styled-components and emotion. * @param element */ -export function isStyledComponentsLike(element: HTMLStyleElement) { +export function isStyledComponentsLike(element: HTMLStyleElement): boolean { return !element.textContent && (element.sheet?.cssRules.length || getStyledElementCSSRules(element)?.length); } @@ -116,25 +116,32 @@ export function getStyledElementCSSRules(styledElement: HTMLStyleElement): CSSRu return styledComponentCSSRulesMap.get(styledElement); } -function getOverwrittenAppendChildOrInsertBefore(opts: { - rawDOMAppendOrInsertBefore: typeof HTMLElement.prototype.insertBefore; - isInvokedByMicroApp: (element: HTMLElement) => boolean; - getSandboxConfig: (element: HTMLElement) => SandboxConfig; - target: DynamicDomMutationTarget; -}) { - function appendChildOrInsertBefore( +export function getOverwrittenAppendChildOrInsertBefore( + opType: 'appendChild' | 'insertBefore', + getSandboxConfig: (element: HTMLElement) => SandboxConfig | undefined, + target: DynamicDomMutationTarget = 'body', + isInvokedByMicroApp: (element: HTMLElement) => boolean, +) { + function appendChildInSandbox( this: HTMLHeadElement | HTMLBodyElement, newChild: T, refChild: Node | null = null, - ) { + ): T { + const appendChild = this[opType]; + const element = newChild as unknown as HTMLElement; - const { rawDOMAppendOrInsertBefore, isInvokedByMicroApp, getSandboxConfig, target = 'body' } = opts; if (!isHijackingTag(element.tagName) || !isInvokedByMicroApp(element)) { - return rawDOMAppendOrInsertBefore.call(this, element, refChild) as T; + return appendChild.call(this, element, refChild) as T; } if (element.tagName) { const containerConfig = getSandboxConfig(element); + if (!containerConfig) { + throw new QiankunError( + `You haven't set the container for ${element.tagName} element while calling appendChild/insertBefore!`, + ); + } + const { getContainer, dynamicStyleSheetElements, sandbox } = containerConfig; switch (element.tagName) { @@ -156,7 +163,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { refNo = Array.from(mountDOM.childNodes).indexOf(referenceNode as ChildNode); } - const result = rawDOMAppendOrInsertBefore.call(mountDOM, stylesheetElement, referenceNode); + const result = appendChild.call(mountDOM, stylesheetElement, referenceNode); // record refNo thus we can keep order while remounting if (typeof refNo === 'number' && refNo !== -1) { @@ -164,7 +171,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { } // record dynamic style elements after insert succeed dynamicStyleSheetElements.push(stylesheetElement); - return result; + return result as T; } case SCRIPT_TAG_NAME: { @@ -175,7 +182,7 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { // TODO paas fetch configuration and current entry url as baseURI const node = transpileAssets(element, location.href, { fetch, sandbox, rawNode: element }); - return rawDOMAppendOrInsertBefore.call(mountDOM, node, referenceNode); + return appendChild.call(mountDOM, node, referenceNode) as T; } default: @@ -183,28 +190,38 @@ function getOverwrittenAppendChildOrInsertBefore(opts: { } } - return rawDOMAppendOrInsertBefore.call(this, element, refChild); + return appendChild.call(this, element, refChild) as T; } - appendChildOrInsertBefore[overwrittenSymbol] = true; + appendChildInSandbox[overwrittenSymbol] = true; - return appendChildOrInsertBefore; + return appendChildInSandbox; } -function getNewRemoveChild( - rawRemoveChild: typeof HTMLElement.prototype.removeChild, - containerConfigGetter: (element: HTMLElement) => SandboxConfig, +export function getNewRemoveChild( + opType: 'removeChild', + containerConfigGetter: (element: HTMLElement) => SandboxConfig | undefined, target: DynamicDomMutationTarget, isInvokedByMicroApp: (element: HTMLElement) => boolean, ) { - function removeChild(this: HTMLHeadElement | HTMLBodyElement, child: T) { + function removeChildInSandbox(this: HTMLHeadElement | HTMLBodyElement, child: T) { + const removeChild = this[opType]; + const childElement = child as unknown as HTMLElement; const { tagName } = childElement; - if (!isHijackingTag(tagName) || !isInvokedByMicroApp(childElement)) return rawRemoveChild.call(this, child) as T; + if (!isHijackingTag(tagName) || !isInvokedByMicroApp(childElement)) { + return removeChild.call(this, child) as T; + } try { let attachedElement: Node; - const { getContainer, dynamicStyleSheetElements } = containerConfigGetter(childElement); + + const containerConfig = containerConfigGetter(childElement); + if (!containerConfig) { + throw new QiankunError(`You haven't set the container for ${tagName} element while calling removeChild!`); + } + + const { getContainer, dynamicStyleSheetElements } = containerConfig; switch (tagName) { case STYLE_TAG_NAME: @@ -234,80 +251,17 @@ function getNewRemoveChild( const mountDOM = target === 'head' ? getContainerHeadElement(container) : container; // container might have been removed while app unmounting if the removeChild action was async if (mountDOM.contains(attachedElement)) { - return rawRemoveChild.call(attachedElement.parentNode, attachedElement) as T; + return removeChild.call(attachedElement.parentNode, attachedElement) as T; } } catch (e) { console.warn(e); } - return rawRemoveChild.call(this, child) as T; + return removeChild.call(this, child) as T; } - removeChild[overwrittenSymbol] = true; - return removeChild; -} - -export function patchHTMLDynamicAppendPrototypeFunctions( - isInvokedByMicroApp: (element: HTMLElement) => boolean, - getSandboxConfig: (element: HTMLElement) => SandboxConfig, -) { - const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild; - const rawBodyAppendChild = HTMLBodyElement.prototype.appendChild; - const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore; - - // Just overwrite it while it have not been overwritten - if ( - rawHeadAppendChild[overwrittenSymbol] !== true && - rawBodyAppendChild[overwrittenSymbol] !== true && - rawHeadInsertBefore[overwrittenSymbol] !== true - ) { - HTMLHeadElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({ - rawDOMAppendOrInsertBefore: rawHeadAppendChild, - getSandboxConfig: getSandboxConfig, - isInvokedByMicroApp, - target: 'head', - }) as typeof rawHeadAppendChild; - HTMLBodyElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({ - rawDOMAppendOrInsertBefore: rawBodyAppendChild, - getSandboxConfig: getSandboxConfig, - isInvokedByMicroApp, - target: 'body', - }) as typeof rawBodyAppendChild; - - HTMLHeadElement.prototype.insertBefore = getOverwrittenAppendChildOrInsertBefore({ - rawDOMAppendOrInsertBefore: rawHeadInsertBefore, - getSandboxConfig: getSandboxConfig, - isInvokedByMicroApp, - target: 'head', - }) as typeof rawHeadInsertBefore; - } - - const rawHeadRemoveChild = HTMLHeadElement.prototype.removeChild; - const rawBodyRemoveChild = HTMLBodyElement.prototype.removeChild; - // Just overwrite it while it have not been overwritten - if (rawHeadRemoveChild[overwrittenSymbol] !== true && rawBodyRemoveChild[overwrittenSymbol] !== true) { - HTMLHeadElement.prototype.removeChild = getNewRemoveChild( - rawHeadRemoveChild, - getSandboxConfig, - 'head', - isInvokedByMicroApp, - ); - HTMLBodyElement.prototype.removeChild = getNewRemoveChild( - rawBodyRemoveChild, - getSandboxConfig, - 'body', - isInvokedByMicroApp, - ); - } - - return function unpatch() { - HTMLHeadElement.prototype.appendChild = rawHeadAppendChild; - HTMLHeadElement.prototype.removeChild = rawHeadRemoveChild; - HTMLBodyElement.prototype.appendChild = rawBodyAppendChild; - HTMLBodyElement.prototype.removeChild = rawBodyRemoveChild; - - HTMLHeadElement.prototype.insertBefore = rawHeadInsertBefore; - }; + removeChildInSandbox[overwrittenSymbol] = true; + return removeChildInSandbox; } export function rebuildCSSRules( diff --git a/packages/sandbox/src/patchers/dynamicAppend/forStandardSandbox.ts b/packages/sandbox/src/patchers/dynamicAppend/forStandardSandbox.ts index 4832941f8..32ed34141 100644 --- a/packages/sandbox/src/patchers/dynamicAppend/forStandardSandbox.ts +++ b/packages/sandbox/src/patchers/dynamicAppend/forStandardSandbox.ts @@ -4,15 +4,17 @@ * @since 2020-10-13 */ +import type { noop } from 'lodash'; import { nativeDocument, nativeGlobal } from '../../consts'; +import { getTargetValue } from '../../core/membrane/utils'; import type { Sandbox } from '../../core/sandbox'; -import { isBoundedFunction, isCallable } from '../../utils'; import type { Free } from '../types'; import { calcAppCount, getContainerHeadElement, + getNewRemoveChild, + getOverwrittenAppendChildOrInsertBefore, isAllAppsUnmounted, - patchHTMLDynamicAppendPrototypeFunctions, rebuildCSSRules, recordStyledComponentsCSSRules, styleElementRefNodeNo, @@ -45,17 +47,31 @@ Object.defineProperty(nativeGlobal, '__currentLockingSandbox__', { configurable: true, }); -const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore; -const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild; - // Share sandboxConfigWeakMap between multiple qiankun instance, thus they could access the same record nativeGlobal.__sandboxConfigWeakMap__ = nativeGlobal.__sandboxConfigWeakMap__ || new WeakMap(); const sandboxConfigWeakMap = nativeGlobal.__sandboxConfigWeakMap__; const elementAttachSandboxConfigMap = new WeakMap(); -const patchMap = new WeakMap(); +const patchCacheWeakMap = new WeakMap(); + +const getSandboxConfig = (element: HTMLElement) => elementAttachSandboxConfigMap.get(element); +const isInvokedByMicroApp = (element: HTMLElement) => elementAttachSandboxConfigMap.has(element); + +function patchDocument(sandbox: Sandbox): void { + if (patchCacheWeakMap.has(sandbox)) { + return; + } + + const proxyDocumentFnsCache = new Map< + | 'appendChildOnHead' + | 'insertBeforeOnHead' + | 'removeChildOnHead' + | 'appendChildOnBody' + | 'insertBeforeOnBody' + | 'removeChildOnBody', + CallableFunction + >(); -function patchDocument(sandbox: Sandbox) { const attachElementToSandbox = (element: HTMLElement) => { const sandboxConfig = sandboxConfigWeakMap.get(sandbox); if (sandboxConfig) { @@ -65,7 +81,7 @@ function patchDocument(sandbox: Sandbox) { const proxyDocument = new Proxy(document, { set: (target, p, value) => { - target[p as string] = value; + target[p as keyof Document] = value; return true; }, get: (target, p, receiver) => { @@ -92,6 +108,125 @@ function patchDocument(sandbox: Sandbox) { }; } + case 'head': { + const headElement = target.head; + return new Proxy(headElement, { + set: (headElementTarget, p, value) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + headElementTarget[p] = value; + return true; + }, + get(headElementTarget, p, headReceiver) { + switch (p) { + case 'appendChild': { + let cachedAppendChild = proxyDocumentFnsCache.get('appendChildOnHead'); + if (!cachedAppendChild) { + cachedAppendChild = getOverwrittenAppendChildOrInsertBefore( + 'appendChild', + getSandboxConfig, + 'head', + isInvokedByMicroApp, + ).bind(headElementTarget); + proxyDocumentFnsCache.set('appendChildOnHead', cachedAppendChild); + } + return cachedAppendChild; + } + + case 'insertBefore': { + let cachedInsertBefore = proxyDocumentFnsCache.get('insertBeforeOnHead'); + if (!cachedInsertBefore) { + cachedInsertBefore = getOverwrittenAppendChildOrInsertBefore( + 'insertBefore', + getSandboxConfig, + 'head', + isInvokedByMicroApp, + ).bind(headElementTarget); + proxyDocumentFnsCache.set('insertBeforeOnHead', cachedInsertBefore); + } + return cachedInsertBefore; + } + + case 'removeChild': { + let cachedRemoveChild = proxyDocumentFnsCache.get('removeChildOnHead'); + if (!cachedRemoveChild) { + cachedRemoveChild = getNewRemoveChild( + 'removeChild', + getSandboxConfig, + 'head', + isInvokedByMicroApp, + ).bind(headElementTarget); + proxyDocumentFnsCache.set('removeChildOnHead', cachedRemoveChild); + } + return cachedRemoveChild; + } + + default: { + const value = headElementTarget[p as keyof HTMLHeadElement]; + return getTargetValue(headElementTarget, value, headReceiver); + } + } + }, + }); + } + + case 'body': { + const bodyElement = target.body; + return new Proxy(bodyElement, { + get(bodyElementTarget, p, bodyReceiver) { + switch (p) { + case 'appendChild': { + let cachedAppendChild = proxyDocumentFnsCache.get('appendChildOnBody'); + if (!cachedAppendChild) { + cachedAppendChild = getOverwrittenAppendChildOrInsertBefore( + 'appendChild', + getSandboxConfig, + 'body', + isInvokedByMicroApp, + ).bind(bodyElementTarget); + proxyDocumentFnsCache.set('appendChildOnBody', cachedAppendChild); + } + return cachedAppendChild; + } + + case 'insertBefore': { + let cachedInsertBefore = proxyDocumentFnsCache.get('insertBeforeOnBody'); + if (!cachedInsertBefore) { + cachedInsertBefore = getOverwrittenAppendChildOrInsertBefore( + 'insertBefore', + getSandboxConfig, + 'body', + isInvokedByMicroApp, + ).bind(bodyElementTarget); + proxyDocumentFnsCache.set('insertBeforeOnBody', cachedInsertBefore); + } + return cachedInsertBefore; + } + + case 'removeChild': { + let cachedRemoveChild = proxyDocumentFnsCache.get('removeChildOnBody'); + if (!cachedRemoveChild) { + cachedRemoveChild = getNewRemoveChild( + 'removeChild', + getSandboxConfig, + 'body', + isInvokedByMicroApp, + ).bind(bodyElementTarget); + proxyDocumentFnsCache.set('removeChildOnBody', cachedRemoveChild); + } + return cachedRemoveChild; + } + + default: { + const value = bodyElementTarget[p as keyof HTMLHeadElement]; + return getTargetValue(bodyElementTarget, value, bodyReceiver); + } + } + }, + }); + } + case 'querySelector': { const targetQuerySelector = target.querySelector; return function querySelector(...args: Parameters) { @@ -103,9 +238,9 @@ function patchDocument(sandbox: Sandbox) { const qiankunHead = getContainerHeadElement(containerConfig.getContainer()); // proxied head in micro app should use the proxied appendChild/removeChild/insertBefore methods - qiankunHead.appendChild = HTMLHeadElement.prototype.appendChild; - qiankunHead.insertBefore = HTMLHeadElement.prototype.insertBefore; - qiankunHead.removeChild = HTMLHeadElement.prototype.removeChild; + qiankunHead.appendChild = proxyDocument.head.appendChild; + qiankunHead.insertBefore = proxyDocument.head.insertBefore; + qiankunHead.removeChild = proxyDocument.head.removeChild; return qiankunHead; } @@ -122,50 +257,45 @@ function patchDocument(sandbox: Sandbox) { const value = target[p as string]; // must rebind the function to the target otherwise it will cause illegal invocation error - if (isCallable(value) && !isBoundedFunction(value)) { - return function proxyFunction(...args: unknown[]): unknown { - return Function.prototype.apply.call( - value, - target, - args.map((arg) => (arg === receiver ? target : arg)), - ); - }; - } - - return value; + return getTargetValue(target, value, receiver); }, }); - sandbox.addIntrinsics({ document: { value: proxyDocument, writable: false, enumerable: true, configurable: true } }); + sandbox.addIntrinsics({ + document: { value: proxyDocument, writable: false, enumerable: true, configurable: true }, + }); + patchCacheWeakMap.set(sandbox, true); +} +function patchDOMPrototypeFns(): typeof noop { // patch MutationObserver.prototype.observe to avoid type error // https://github.com/umijs/qiankun/issues/2406 const nativeMutationObserverObserveFn = MutationObserver.prototype.observe; - if (!patchMap.has(nativeMutationObserverObserveFn)) { + if (!patchCacheWeakMap.has(nativeMutationObserverObserveFn)) { const observe = function observe(this: MutationObserver, target: Node, options: MutationObserverInit) { const realTarget = target instanceof Document ? nativeDocument : target; return nativeMutationObserverObserveFn.call(this, realTarget, options); }; MutationObserver.prototype.observe = observe; - patchMap.set(nativeMutationObserverObserveFn, observe); + patchCacheWeakMap.set(nativeMutationObserverObserveFn, observe); } // patch Node.prototype.compareDocumentPosition to avoid type error const prevCompareDocumentPosition = Node.prototype.compareDocumentPosition; - if (!patchMap.has(prevCompareDocumentPosition)) { + if (!patchCacheWeakMap.has(prevCompareDocumentPosition)) { Node.prototype.compareDocumentPosition = function compareDocumentPosition(this: Node, node) { const realNode = node instanceof Document ? nativeDocument : node; return prevCompareDocumentPosition.call(this, realNode); }; - patchMap.set(prevCompareDocumentPosition, Node.prototype.compareDocumentPosition); + patchCacheWeakMap.set(prevCompareDocumentPosition, Node.prototype.compareDocumentPosition); } // TODO https://github.com/umijs/qiankun/pull/2415 Not support yet as getCurrentRunningApp api is not reliable // patch parentNode getter to avoid document === html.parentNode // https://github.com/umijs/qiankun/issues/2408#issuecomment-1446229105 // const parentNodeDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, 'parentNode'); - // if (parentNodeDescriptor && !patchMap.has(parentNodeDescriptor)) { + // if (parentNodeDescriptor && !patchCacheWeakMap.has(parentNodeDescriptor)) { // const { get: parentNodeGetter, configurable } = parentNodeDescriptor; // if (parentNodeGetter && configurable) { // const patchedParentNodeDescriptor = { @@ -184,24 +314,28 @@ function patchDocument(sandbox: Sandbox) { // }; // Object.defineProperty(Node.prototype, 'parentNode', patchedParentNodeDescriptor); // - // patchMap.set(parentNodeDescriptor, patchedParentNodeDescriptor); + // patchCacheWeakMap.set(parentNodeDescriptor, patchedParentNodeDescriptor); // } // } return () => { MutationObserver.prototype.observe = nativeMutationObserverObserveFn; - patchMap.delete(nativeMutationObserverObserveFn); + patchCacheWeakMap.delete(nativeMutationObserverObserveFn); Node.prototype.compareDocumentPosition = prevCompareDocumentPosition; - patchMap.delete(prevCompareDocumentPosition); + patchCacheWeakMap.delete(prevCompareDocumentPosition); // if (parentNodeDescriptor) { // Object.defineProperty(Node.prototype, 'parentNode', parentNodeDescriptor); - // patchMap.delete(parentNodeDescriptor); + // patchCacheWeakMap.delete(parentNodeDescriptor); // } }; } +// FIXME should not use global variable, should get it every time it is used, otherwise it may miss the runtime container or the business itself monkey patch logic +const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore; +const rawHeadAppendChild = HTMLHeadElement.prototype.appendChild; + export function patchStandardSandbox( appName: string, getContainer: () => HTMLElement, @@ -216,7 +350,7 @@ export function patchStandardSandbox( sandboxConfig = { appName, sandbox, - getContainer: getContainer, + getContainer, dynamicStyleSheetElements: [], }; sandboxConfigWeakMap.set(sandbox, sandboxConfig); @@ -224,12 +358,8 @@ export function patchStandardSandbox( // all dynamic style sheets are stored in proxy container const { dynamicStyleSheetElements } = sandboxConfig; - const unpatchDynamicAppendPrototypeFunctions = patchHTMLDynamicAppendPrototypeFunctions( - (element) => elementAttachSandboxConfigMap.has(element), - (element) => elementAttachSandboxConfigMap.get(element)!, - ); - - const unpatchDocument = patchDocument(sandbox); + patchDocument(sandbox); + const unpatchDOMPrototype = patchDOMPrototypeFns(); if (!mounting) calcAppCount(appName, 'increase', 'bootstrapping'); if (mounting) calcAppCount(appName, 'increase', 'mounting'); @@ -240,8 +370,7 @@ export function patchStandardSandbox( // release the overwritten prototype after all the micro apps unmounted if (isAllAppsUnmounted()) { - unpatchDynamicAppendPrototypeFunctions(); - unpatchDocument(); + unpatchDOMPrototype(); } recordStyledComponentsCSSRules(dynamicStyleSheetElements as HTMLStyleElement[]);