Skip to content

Commit ed12be7

Browse files
authored
feat: Queue support (#337)
* feat: dynamic css support prepend queue * test: add test case * test: includes prepend * refactor: replace prepend with insertBefore * chore: force CI * chore: lock version * chore: Trigger CI again * chore: try exclude * chore: more * chore: try ignore * chore: typing * chore: sad * chore: update
1 parent 780a217 commit ed12be7

File tree

4 files changed

+128
-15
lines changed

4 files changed

+128
-15
lines changed

examples/dynaymicCSS.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react';
2+
import { updateCSS, removeCSS } from '../src/Dom/dynamicCSS';
3+
import type { Prepend } from '../src/Dom/dynamicCSS';
4+
5+
function injectStyle(id: number, prepend?: Prepend) {
6+
const randomColor = Math.floor(Math.random() * 16777215).toString(16);
7+
8+
updateCSS(`body { background: #${randomColor} }`, `style-${id}`, {
9+
prepend,
10+
});
11+
}
12+
13+
export default () => {
14+
const [id, setId] = React.useState(0);
15+
const idRef = React.useRef(id);
16+
idRef.current = id;
17+
18+
// Clean up
19+
React.useEffect(() => {
20+
return () => {
21+
for (let i = 0; i <= idRef.current; i += 1) {
22+
removeCSS(`style-${i}`);
23+
}
24+
};
25+
}, []);
26+
27+
return (
28+
<>
29+
<button
30+
onClick={() => {
31+
injectStyle(id, 'queue');
32+
setId(id + 1);
33+
}}
34+
>
35+
Prepend Queue: {id}
36+
</button>
37+
38+
<button
39+
onClick={() => {
40+
injectStyle(-1);
41+
}}
42+
>
43+
Append
44+
</button>
45+
</>
46+
);
47+
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@
3434
"@types/jest": "^25.2.3",
3535
"@types/react": "^18.0.0",
3636
"@types/react-dom": "^18.0.0",
37+
"@types/responselike": "^1.0.0",
3738
"@types/shallowequal": "^1.1.1",
3839
"@types/warning": "^3.0.0",
3940
"@umijs/fabric": "^2.0.8",
4041
"coveralls": "^3.1.0",
4142
"create-react-class": "^15.6.3",
4243
"cross-env": "^7.0.2",
4344
"eslint": "^6.6.0",
44-
"father": "^2.14.0",
45+
"father": "^2.29.9",
4546
"np": "^6.2.3",
4647
"react": "^18.0.0",
4748
"react-dom": "^18.0.0",

src/Dom/dynamicCSS.ts

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import canUseDom from './canUseDom';
22

3+
const APPEND_ORDER = '_rc_util_order';
34
const MARK_KEY = `rc-util-key`;
45

6+
const containerCache = new Map<Element, Node & ParentNode>();
7+
8+
export type Prepend = boolean | 'queue';
9+
export type AppendType = 'prependQueue' | 'append' | 'prepend';
10+
511
interface Options {
612
attachTo?: Element;
713
csp?: { nonce?: string };
8-
prepend?: boolean;
14+
prepend?: Prepend;
915
mark?: string;
1016
}
1117

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

34+
function getOrder(prepend?: Prepend): AppendType {
35+
if (prepend === 'queue') {
36+
return 'prependQueue';
37+
}
38+
39+
return prepend ? 'prepend' : 'append';
40+
}
41+
42+
/**
43+
* Find style which inject by rc-util
44+
*/
45+
function findStyles(container: Element) {
46+
return Array.from(
47+
(containerCache.get(container) || container).children,
48+
).filter(
49+
node => node.tagName === 'STYLE' && node[APPEND_ORDER],
50+
) as HTMLStyleElement[];
51+
}
52+
2853
export function injectCSS(css: string, option: Options = {}) {
2954
if (!canUseDom()) {
3055
return null;
3156
}
3257

58+
const { csp, prepend } = option;
59+
3360
const styleNode = document.createElement('style');
34-
if (option.csp?.nonce) {
35-
styleNode.nonce = option.csp?.nonce;
61+
styleNode[APPEND_ORDER] = getOrder(prepend);
62+
63+
if (csp?.nonce) {
64+
styleNode.nonce = csp?.nonce;
3665
}
3766
styleNode.innerHTML = css;
3867

3968
const container = getContainer(option);
4069
const { firstChild } = container;
4170

42-
if (option.prepend && container.prepend) {
43-
// Use `prepend` first
44-
container.prepend(styleNode);
45-
} else if (option.prepend && firstChild) {
46-
// Fallback to `insertBefore` like IE not support `prepend`
71+
if (prepend) {
72+
// If is queue `prepend`, it will prepend first style and then append rest style
73+
if (prepend === 'queue') {
74+
const existStyle = findStyles(container).filter(node =>
75+
['prepend', 'prependQueue'].includes(node[APPEND_ORDER]),
76+
);
77+
if (existStyle.length) {
78+
container.insertBefore(
79+
styleNode,
80+
existStyle[existStyle.length - 1].nextSibling,
81+
);
82+
83+
return styleNode;
84+
}
85+
}
86+
87+
// Use `insertBefore` as `prepend`
4788
container.insertBefore(styleNode, firstChild);
4889
} else {
4990
container.appendChild(styleNode);
@@ -52,15 +93,12 @@ export function injectCSS(css: string, option: Options = {}) {
5293
return styleNode;
5394
}
5495

55-
const containerCache = new Map<Element, Node & ParentNode>();
56-
5796
function findExistNode(key: string, option: Options = {}) {
5897
const container = getContainer(option);
5998

60-
return Array.from(containerCache.get(container).children).find(
61-
node =>
62-
node.tagName === 'STYLE' && node.getAttribute(getMark(option)) === key,
63-
) as HTMLStyleElement;
99+
return findStyles(container).find(
100+
node => node.getAttribute(getMark(option)) === key,
101+
);
64102
}
65103

66104
export function removeCSS(key: string, option: Options = {}) {

tests/dynamicCSS.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ describe('dynamicCSS', () => {
5454

5555
head.prepend = originPrepend;
5656
});
57+
58+
it('prepend with queue', () => {
59+
const head = document.querySelector('head');
60+
61+
const styles = [
62+
injectCSS(TEST_STYLE, { prepend: 'queue' }),
63+
injectCSS(TEST_STYLE, { prepend: 'queue' }),
64+
];
65+
66+
const styleNodes = Array.from(head.querySelectorAll('style'));
67+
expect(styleNodes).toHaveLength(2);
68+
69+
for (let i = 0; i < styleNodes.length; i += 1) {
70+
expect(styles[i]).toBe(styleNodes[i]);
71+
}
72+
73+
// Should not after append
74+
const appendStyle = injectCSS(TEST_STYLE);
75+
const prependStyle = injectCSS(TEST_STYLE, { prepend: 'queue' });
76+
const nextStyleNodes = Array.from(head.querySelectorAll('style'));
77+
78+
expect(nextStyleNodes).toHaveLength(4);
79+
expect(nextStyleNodes[0]).toBe(styles[0]);
80+
expect(nextStyleNodes[1]).toBe(styles[1]);
81+
expect(nextStyleNodes[2]).toBe(prependStyle);
82+
expect(nextStyleNodes[3]).toBe(appendStyle);
83+
});
5784
});
5885
});
5986

0 commit comments

Comments
 (0)