Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions examples/dynaymicCSS.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { updateCSS, removeCSS } from '../src/Dom/dynamicCSS';
import type { Prepend } from '../src/Dom/dynamicCSS';

function injectStyle(id: number, prepend?: Prepend) {
const randomColor = Math.floor(Math.random() * 16777215).toString(16);

updateCSS(`body { background: #${randomColor} }`, `style-${id}`, {
prepend,
});
}

export default () => {
const [id, setId] = React.useState(0);
const idRef = React.useRef(id);
idRef.current = id;

// Clean up
React.useEffect(() => {
return () => {
for (let i = 0; i <= idRef.current; i += 1) {
removeCSS(`style-${i}`);
}
};
}, []);

return (
<>
<button
onClick={() => {
injectStyle(id, 'queue');
setId(id + 1);
}}
>
Prepend Queue: {id}
</button>

<button
onClick={() => {
injectStyle(-1);
}}
>
Append
</button>
</>
);
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@
"@types/jest": "^25.2.3",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/responselike": "^1.0.0",
"@types/shallowequal": "^1.1.1",
"@types/warning": "^3.0.0",
"@umijs/fabric": "^2.0.8",
"coveralls": "^3.1.0",
"create-react-class": "^15.6.3",
"cross-env": "^7.0.2",
"eslint": "^6.6.0",
"father": "^2.14.0",
"father": "^2.29.9",
"np": "^6.2.3",
"react": "^18.0.0",
"react-dom": "^18.0.0",
Expand Down
66 changes: 52 additions & 14 deletions src/Dom/dynamicCSS.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import canUseDom from './canUseDom';

const APPEND_ORDER = '_rc_util_order';
const MARK_KEY = `rc-util-key`;

const containerCache = new Map<Element, Node & ParentNode>();

export type Prepend = boolean | 'queue';
export type AppendType = 'prependQueue' | 'append' | 'prepend';

interface Options {
attachTo?: Element;
csp?: { nonce?: string };
prepend?: boolean;
prepend?: Prepend;
mark?: string;
}

Expand All @@ -25,25 +31,60 @@ function getContainer(option: Options) {
return head || document.body;
}

function getOrder(prepend?: Prepend): AppendType {
if (prepend === 'queue') {
return 'prependQueue';
}

return prepend ? 'prepend' : 'append';
}

/**
* Find style which inject by rc-util
*/
function findStyles(container: Element) {
return Array.from(
(containerCache.get(container) || container).children,
).filter(
node => node.tagName === 'STYLE' && node[APPEND_ORDER],
) as HTMLStyleElement[];
}

export function injectCSS(css: string, option: Options = {}) {
if (!canUseDom()) {
return null;
}

const { csp, prepend } = option;

const styleNode = document.createElement('style');
if (option.csp?.nonce) {
styleNode.nonce = option.csp?.nonce;
styleNode[APPEND_ORDER] = getOrder(prepend);

if (csp?.nonce) {
styleNode.nonce = csp?.nonce;
}
styleNode.innerHTML = css;

const container = getContainer(option);
const { firstChild } = container;

if (option.prepend && container.prepend) {
// Use `prepend` first
container.prepend(styleNode);
} else if (option.prepend && firstChild) {
// Fallback to `insertBefore` like IE not support `prepend`
if (prepend) {
// If is queue `prepend`, it will prepend first style and then append rest style
if (prepend === 'queue') {
const existStyle = findStyles(container).filter(node =>
['prepend', 'prependQueue'].includes(node[APPEND_ORDER]),
);
if (existStyle.length) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

所以这里第一个 style 还是 prepend?qiankun 没劫持 prepend,会透出去,后面 exitStyle 找不到还是会继续 prepend?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prepend 让 qiankun 处理就好了,否则 rc-util 就要做框架检测了,这样就变成针对 qiankun 的耦合了。

container.insertBefore(
styleNode,
existStyle[existStyle.length - 1].nextSibling,
);

return styleNode;
}
}

// Use `insertBefore` as `prepend`
container.insertBefore(styleNode, firstChild);
} else {
container.appendChild(styleNode);
Expand All @@ -52,15 +93,12 @@ export function injectCSS(css: string, option: Options = {}) {
return styleNode;
}

const containerCache = new Map<Element, Node & ParentNode>();

function findExistNode(key: string, option: Options = {}) {
const container = getContainer(option);

return Array.from(containerCache.get(container).children).find(
node =>
node.tagName === 'STYLE' && node.getAttribute(getMark(option)) === key,
) as HTMLStyleElement;
return findStyles(container).find(
node => node.getAttribute(getMark(option)) === key,
);
}

export function removeCSS(key: string, option: Options = {}) {
Expand Down
27 changes: 27 additions & 0 deletions tests/dynamicCSS.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,33 @@ describe('dynamicCSS', () => {

head.prepend = originPrepend;
});

it('prepend with queue', () => {
const head = document.querySelector('head');

const styles = [
injectCSS(TEST_STYLE, { prepend: 'queue' }),
injectCSS(TEST_STYLE, { prepend: 'queue' }),
];

const styleNodes = Array.from(head.querySelectorAll('style'));
expect(styleNodes).toHaveLength(2);

for (let i = 0; i < styleNodes.length; i += 1) {
expect(styles[i]).toBe(styleNodes[i]);
}

// Should not after append
const appendStyle = injectCSS(TEST_STYLE);
const prependStyle = injectCSS(TEST_STYLE, { prepend: 'queue' });
const nextStyleNodes = Array.from(head.querySelectorAll('style'));

expect(nextStyleNodes).toHaveLength(4);
expect(nextStyleNodes[0]).toBe(styles[0]);
expect(nextStyleNodes[1]).toBe(styles[1]);
expect(nextStyleNodes[2]).toBe(prependStyle);
expect(nextStyleNodes[3]).toBe(appendStyle);
});
});
});

Expand Down