From 11e94fb45fceacd266f2288c99a96f59767bd317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Sep 2022 16:01:36 +0800 Subject: [PATCH 01/13] feat: dynamic css support prepend queue --- examples/dynaymicCSS.tsx | 32 +++++++++++++++++++++++++ src/Dom/dynamicCSS.ts | 50 +++++++++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 examples/dynaymicCSS.tsx diff --git a/examples/dynaymicCSS.tsx b/examples/dynaymicCSS.tsx new file mode 100644 index 00000000..367b4427 --- /dev/null +++ b/examples/dynaymicCSS.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { updateCSS, removeCSS } from '../src/Dom/dynamicCSS'; + +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 ( + + ); +}; diff --git a/src/Dom/dynamicCSS.ts b/src/Dom/dynamicCSS.ts index 186733e4..94c3507a 100644 --- a/src/Dom/dynamicCSS.ts +++ b/src/Dom/dynamicCSS.ts @@ -1,11 +1,14 @@ import canUseDom from './canUseDom'; +const UNIQUE_MARK = '_rc_util'; const MARK_KEY = `rc-util-key`; +const containerCache = new Map(); + interface Options { attachTo?: Element; csp?: { nonce?: string }; - prepend?: boolean; + prepend?: boolean | 'queue'; mark?: string; } @@ -25,24 +28,52 @@ function getContainer(option: Options) { return head || document.body; } +/** + * 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[UNIQUE_MARK], + ) 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[UNIQUE_MARK] = true; + + if (csp?.nonce) { + styleNode.nonce = csp?.nonce; } styleNode.innerHTML = css; const container = getContainer(option); const { firstChild } = container; - if (option.prepend && container.prepend) { + if (prepend && container.prepend) { + // If is queue `prepend`, it will prepend first style and then append rest style + if (prepend === 'queue') { + const existStyle = findStyles(container); + if (existStyle.length) { + container.insertBefore( + styleNode, + existStyle[existStyle.length - 1].nextSibling, + ); + + return styleNode; + } + } + // Use `prepend` first container.prepend(styleNode); - } else if (option.prepend && firstChild) { + } else if (prepend && firstChild) { // Fallback to `insertBefore` like IE not support `prepend` container.insertBefore(styleNode, firstChild); } else { @@ -52,15 +83,12 @@ export function injectCSS(css: string, option: Options = {}) { return styleNode; } -const containerCache = new Map(); - 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 = {}) { From 1a51043406187f4d40a7fce354559b02522a47e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Sep 2022 16:16:50 +0800 Subject: [PATCH 02/13] test: add test case --- examples/dynaymicCSS.tsx | 37 ++++++++++++++++++++++++++----------- src/Dom/dynamicCSS.ts | 23 ++++++++++++++++++----- tests/dynamicCSS.test.tsx | 27 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/examples/dynaymicCSS.tsx b/examples/dynaymicCSS.tsx index 367b4427..866cacce 100644 --- a/examples/dynaymicCSS.tsx +++ b/examples/dynaymicCSS.tsx @@ -1,5 +1,14 @@ 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); @@ -16,17 +25,23 @@ export default () => { }, []); return ( - - updateCSS(`body { background: #${randomColor} }`, `style-${id}`, { - prepend: 'queue', - }); - setId(id + 1); - }} - > - Inject: {id} - + + ); }; diff --git a/src/Dom/dynamicCSS.ts b/src/Dom/dynamicCSS.ts index 94c3507a..620c0a1a 100644 --- a/src/Dom/dynamicCSS.ts +++ b/src/Dom/dynamicCSS.ts @@ -1,14 +1,17 @@ import canUseDom from './canUseDom'; -const UNIQUE_MARK = '_rc_util'; +const APPEND_ORDER = '_rc_util_order'; const MARK_KEY = `rc-util-key`; const containerCache = new Map(); +export type Prepend = boolean | 'queue'; +export type AppendType = 'prependQueue' | 'append' | 'prepend'; + interface Options { attachTo?: Element; csp?: { nonce?: string }; - prepend?: boolean | 'queue'; + prepend?: Prepend; mark?: string; } @@ -28,6 +31,14 @@ 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 */ @@ -35,7 +46,7 @@ function findStyles(container: Element) { return Array.from( (containerCache.get(container) || container).children, ).filter( - node => node.tagName === 'STYLE' && node[UNIQUE_MARK], + node => node.tagName === 'STYLE' && node[APPEND_ORDER], ) as HTMLStyleElement[]; } @@ -47,7 +58,7 @@ export function injectCSS(css: string, option: Options = {}) { const { csp, prepend } = option; const styleNode = document.createElement('style'); - styleNode[UNIQUE_MARK] = true; + styleNode[APPEND_ORDER] = getOrder(prepend); if (csp?.nonce) { styleNode.nonce = csp?.nonce; @@ -60,7 +71,9 @@ export function injectCSS(css: string, option: Options = {}) { if (prepend && container.prepend) { // If is queue `prepend`, it will prepend first style and then append rest style if (prepend === 'queue') { - const existStyle = findStyles(container); + const existStyle = findStyles(container).filter( + node => node[APPEND_ORDER] === 'prependQueue', + ); if (existStyle.length) { container.insertBefore( styleNode, diff --git a/tests/dynamicCSS.test.tsx b/tests/dynamicCSS.test.tsx index 738114c2..ff793440 100644 --- a/tests/dynamicCSS.test.tsx +++ b/tests/dynamicCSS.test.tsx @@ -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); + }); }); }); From 9ac34d63f6e3ad2938931f94825b5a8a366f811d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sun, 4 Sep 2022 16:26:03 +0800 Subject: [PATCH 03/13] test: includes prepend --- src/Dom/dynamicCSS.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dom/dynamicCSS.ts b/src/Dom/dynamicCSS.ts index 620c0a1a..02279624 100644 --- a/src/Dom/dynamicCSS.ts +++ b/src/Dom/dynamicCSS.ts @@ -71,8 +71,8 @@ export function injectCSS(css: string, option: Options = {}) { if (prepend && container.prepend) { // If is queue `prepend`, it will prepend first style and then append rest style if (prepend === 'queue') { - const existStyle = findStyles(container).filter( - node => node[APPEND_ORDER] === 'prependQueue', + const existStyle = findStyles(container).filter(node => + ['prepend', 'prependQueue'].includes(node[APPEND_ORDER]), ); if (existStyle.length) { container.insertBefore( From 34a5902cd8014763b7a92c95961b20faabd78347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 14:23:10 +0800 Subject: [PATCH 04/13] refactor: replace prepend with insertBefore --- src/Dom/dynamicCSS.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Dom/dynamicCSS.ts b/src/Dom/dynamicCSS.ts index 02279624..0bbb95bc 100644 --- a/src/Dom/dynamicCSS.ts +++ b/src/Dom/dynamicCSS.ts @@ -68,7 +68,7 @@ export function injectCSS(css: string, option: Options = {}) { const container = getContainer(option); const { firstChild } = container; - if (prepend && container.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 => @@ -85,9 +85,6 @@ export function injectCSS(css: string, option: Options = {}) { } // Use `prepend` first - container.prepend(styleNode); - } else if (prepend && firstChild) { - // Fallback to `insertBefore` like IE not support `prepend` container.insertBefore(styleNode, firstChild); } else { container.appendChild(styleNode); From 5431b9d52e7b8b6b9ff3e5335e99fb11bc632933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 15:36:08 +0800 Subject: [PATCH 05/13] chore: force CI --- src/Dom/dynamicCSS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dom/dynamicCSS.ts b/src/Dom/dynamicCSS.ts index 0bbb95bc..e88726bc 100644 --- a/src/Dom/dynamicCSS.ts +++ b/src/Dom/dynamicCSS.ts @@ -84,7 +84,7 @@ export function injectCSS(css: string, option: Options = {}) { } } - // Use `prepend` first + // Use `insertBefore` as `prepend` container.insertBefore(styleNode, firstChild); } else { container.appendChild(styleNode); From 62179783e0ac43c7ea465a89d47c189c51099a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 15:44:52 +0800 Subject: [PATCH 06/13] chore: lock version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34eab9cf..38c43b6d 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "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", From 426c99dbb170938abf79b510dae5f7ba256852a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 15:50:45 +0800 Subject: [PATCH 07/13] chore: Trigger CI again --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38c43b6d..446c7a6c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "create-react-class": "^15.6.3", "cross-env": "^7.0.2", "eslint": "^6.6.0", - "father": "2.29.9", + "father": "^2.29.9", "np": "^6.2.3", "react": "^18.0.0", "react-dom": "^18.0.0", From deb11e6ac7981d138065a3dd1f894ff4cbac1dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 15:56:52 +0800 Subject: [PATCH 08/13] chore: try exclude --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index b6f727e2..0e068ccc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,6 @@ "declaration": true, "skipLibCheck": true, "esModuleInterop": true - } + }, + "exclude": [ "node_modules"] } From 28f76b54436cdd5272bfa70b38d2b8a425552559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 16:18:30 +0800 Subject: [PATCH 09/13] chore: more --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 0e068ccc..d2fa327f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,5 +8,5 @@ "skipLibCheck": true, "esModuleInterop": true }, - "exclude": [ "node_modules"] + "include": ["src"] } From 7ad88c5edb61acd41fb757f72ce2f875b88a9ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 16:44:51 +0800 Subject: [PATCH 10/13] chore: try ignore --- tsconfig.json | 3 +-- typings/index.d.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 typings/index.d.ts diff --git a/tsconfig.json b/tsconfig.json index d2fa327f..b6f727e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,5 @@ "declaration": true, "skipLibCheck": true, "esModuleInterop": true - }, - "include": ["src"] + } } diff --git a/typings/index.d.ts b/typings/index.d.ts new file mode 100644 index 00000000..0ee831cf --- /dev/null +++ b/typings/index.d.ts @@ -0,0 +1 @@ +declare module 'responselike'; \ No newline at end of file From b8bc2aab13f510727eae13e4d669d90fc52e067d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 16:56:26 +0800 Subject: [PATCH 11/13] chore: typing --- tsconfig.json | 3 ++- typings/{ => global}/index.d.ts | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename typings/{ => global}/index.d.ts (100%) diff --git a/tsconfig.json b/tsconfig.json index b6f727e2..5fae20e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "jsx": "preserve", "declaration": true, "skipLibCheck": true, - "esModuleInterop": true + "esModuleInterop": true, + "typeRoots": ["./typings", "./node_modules/@types"] } } diff --git a/typings/index.d.ts b/typings/global/index.d.ts similarity index 100% rename from typings/index.d.ts rename to typings/global/index.d.ts From ea0da94084a20ece9797a980b4ee9bcf730966f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 21:15:29 +0800 Subject: [PATCH 12/13] chore: sad --- package.json | 1 + tsconfig.json | 1 - typings/global/index.d.ts | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 typings/global/index.d.ts diff --git a/package.json b/package.json index 446c7a6c..d97911a3 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@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", diff --git a/tsconfig.json b/tsconfig.json index 5fae20e6..e70fa341 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,5 @@ "declaration": true, "skipLibCheck": true, "esModuleInterop": true, - "typeRoots": ["./typings", "./node_modules/@types"] } } diff --git a/typings/global/index.d.ts b/typings/global/index.d.ts deleted file mode 100644 index 0ee831cf..00000000 --- a/typings/global/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'responselike'; \ No newline at end of file From 804af2e8e917d6db4ef7dc27f529a4266c5f394a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 5 Sep 2022 21:15:48 +0800 Subject: [PATCH 13/13] chore: update --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index e70fa341..b6f727e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,6 @@ "jsx": "preserve", "declaration": true, "skipLibCheck": true, - "esModuleInterop": true, + "esModuleInterop": true } }