Skip to content

Commit 1648736

Browse files
committed
feat: Add faster styleManager
1 parent 096b823 commit 1648736

File tree

10 files changed

+168
-70
lines changed

10 files changed

+168
-70
lines changed

ROADMAP.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# RoadMap
22

3+
### New StyleManager
4+
- [ ] ProgressBar
5+
- [ ] ProgressRing
6+
- [ ] All Base Componnents
7+
- [ ] MarkdownRender
8+
- [ ] Table
9+
- [ ] NoiseTexture
10+
11+
### Dynamic BorderSize
12+
- [ ] Todo
13+
314
### Commands
415
- [x] CommandBar
516

docs/src/components/Wrapper/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ function getStyles(header: Header): {
216216

217217
return {
218218
root: prefixStyle({
219+
...theme.acrylicTexture40.style,
219220
fontSize: 14,
220221
color: theme.baseHigh,
221-
...theme.acrylicTexture40.style,
222222
boxShadow: theme.isDarkTheme ? void 0 : `0 2px 8px ${theme.listLow}`,
223223
width: "100%",
224224
height: headerHeight,

docs/src/components/WrapperWithCategories/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ function getStyles(wrapperWithCategories: WrapperWithCategories) {
8585
margin: "0 auto"
8686
}),
8787
side: prefixStyle({
88-
background: theme.altMediumLow,
89-
// ...theme.acrylicTexture60.style,
88+
...theme.acrylicTexture60.style,
9089
width: notPhoneTablet ? "calc(100% - 320px)" : "100%",
9190
...(theme.useFluentDesign ? void 0 : getStripedBackground(4, tinycolor(theme.baseHigh).setAlpha(0.025).toRgbString(), "transparent")),
9291
minHeight: "100%"

src/MarkdownRender/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ return (
155155
text-decoration: underline;
156156
}
157157
158-
.${className} h1, {
158+
.${className} h1 {
159159
line-height: 2;
160160
font-size: 24px;
161161
border-bottom: 2px solid ${theme.listAccentMedium};

src/ProgressBar/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ ${vendorPrefixes.map(str => `@${str}keyframes ProgressBar {
9494
styles: inlineStyles
9595
});
9696
const onlyClassName = this.getOnlyClassName();
97-
theme.styleManager.addCSSText(this.getCSSText(onlyClassName));
97+
// theme.styleManager.addCSSText(this.getCSSText(onlyClassName));
9898

9999
return (
100100
<div

src/ProgressRing/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ ${vendorPrefixes.map(str => `@${str}keyframes CircleLoopFade {
104104
} = this.props;
105105
const { theme } = this.context;
106106
const onlyClassName = this.getOnlyClassName();
107-
theme.styleManager.addCSSText(this.getCSSText(onlyClassName));
107+
// theme.styleManager.addCSSText(this.getCSSText(onlyClassName));
108108

109109
const inlineStyles = getStyles(this);
110110
const styles = theme.prepareStyles({

src/Theme/index.tsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import darkTheme from "../styles/darkTheme";
66
import getTheme, { Theme as ThemeType } from "../styles/getTheme";
77
import RenderToBody from "../RenderToBody";
88
import ToastWrapper from "../Toast/ToastWrapper";
9+
import { getThemeBaseCSS, getGlobalBaseCSS } from "../styles/getBaseCSSText";
910

1011
export { getTheme };
1112
export interface DataProps {
@@ -88,27 +89,34 @@ export class Theme extends React.Component<ThemeProps, ThemeState> {
8889
componentWillReceiveProps(nextProps: ThemeProps) {
8990
const currTheme = this.getThemeFromProps(nextProps);
9091
if (currTheme !== this.state.currTheme) {
91-
this.setState(() => ({ currTheme }), () => {
92-
this.setThemeHelper(currTheme);
93-
});
92+
this.setThemeHelper(currTheme);
93+
this.setState({ currTheme });
9494
}
9595
}
9696

9797
updateAllCSSToEl() {
9898
// const now = performance.now();
9999
if (this.styleEl) {
100-
this.styleEl.textContent = this.state.currTheme.styleManager.getAllCSSText();
100+
this.state.currTheme.styleManager.inserAllRule2el(this.styleEl);
101101
}
102102
// console.log(performance.now() - now);
103103
}
104104

105-
setThemeHelper(theme: ThemeType) {
106-
theme.styleManager.onRemoveCSSText = (CSSText => {
107-
if (this.styleEl && this.styleEl.textContent.includes(CSSText)) {
108-
this.styleEl.textContent += this.styleEl.style.cssText.replace(CSSText, "");
109-
}
105+
setStyleManagerUpdate(theme: ThemeType) {
106+
theme.styleManager.onAddRules = (rules => {
107+
rules.forEach((inserted, rule) => {
108+
if (!inserted) {
109+
theme.styleManager.insertRule2el(this.styleEl, rule);
110+
rules.set(rule, true);
111+
}
112+
});
110113
});
114+
}
111115

116+
setThemeHelper(theme: ThemeType) {
117+
theme.styleManager.addCSSText(getGlobalBaseCSS());
118+
theme.styleManager.addCSSText(getThemeBaseCSS(theme));
119+
this.setStyleManagerUpdate(theme);
112120
Object.assign(theme, {
113121
updateTheme: this.updateTheme,
114122
onToastsUpdate: (toasts) => {
@@ -127,10 +135,11 @@ export class Theme extends React.Component<ThemeProps, ThemeState> {
127135
}
128136

129137
updateTheme = (currTheme: ThemeType) => {
130-
currTheme.removeBaseCSSText(this.state.currTheme);
131-
this.setState({ currTheme }, () => {
132-
this.setThemeHelper(currTheme);
138+
this.state.currTheme.styleManager.allRules.forEach((inserted, rule) => {
139+
currTheme.styleManager.allRules.set(rule, inserted);
133140
});
141+
this.setThemeHelper(currTheme);
142+
this.setState({ currTheme });
134143
}
135144

136145
handleScrollReveal = (e?: Event) => {
@@ -157,6 +166,10 @@ export class Theme extends React.Component<ThemeProps, ThemeState> {
157166
styles
158167
});
159168

169+
// if (this.styleEl) {
170+
// console.log("rule length: ", this.styleEl.sheet["rules"].length);
171+
// }
172+
160173
return (
161174
<div {...attributes} {...classes.root}>
162175
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/react-uwp/1.1.0/css/segoe-mdl2-assets.css" />

src/styles/StyleManager.ts

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as createHash from "murmurhash-js/murmurhash3_gc";
22
import isUnitlessNumber from "../utils/react/isUnitlessNumber";
33
import { Theme } from "./getTheme";
4-
import { CSSProperties } from "react";
54

65
export const replace2Dashes = (key: string) => key.replace(/[A-Z]/g, $1 => `-${$1.toLowerCase()}`);
76
export const getStyleValue = (key: string, value: string) => ((typeof value === "number" && !isUnitlessNumber[key]) ? `${value}px` : value);
@@ -21,6 +20,7 @@ export interface StyleClasses {
2120
}
2221

2322
export interface Sheet {
23+
rules?: Map<string, boolean>;
2424
CSSText?: string;
2525
className?: string;
2626
classNameWithHash?: string;
@@ -39,45 +39,56 @@ export interface StyleManagerConfig {
3939

4040
export class StyleManager {
4141
prefixClassName: string;
42-
theme: Theme;
4342
sheets: {
44-
[key: string]: Sheet
43+
[key: string]: Sheet;
4544
} = {};
46-
CSSText: string = "";
45+
resultCSSText: string = "";
4746
addedCSSText: {
48-
[key: string]: boolean;
47+
[key: string]: {
48+
CSSText?: string;
49+
rules?: Map<string, boolean>;
50+
};
4951
} = {};
52+
allRules: Map<string, boolean> = new Map();
5053
onAddCSSText(CSSText?: string) {}
54+
onAddRules(rules?: Map<string, boolean>) {}
5155
onRemoveCSSText(CSSText?: string) {}
5256

5357
constructor(config?: StyleManagerConfig) {
5458
const { prefixClassName } = config || {};
5559
this.prefixClassName = prefixClassName ? `${prefixClassName}-` : "";
5660
}
5761

62+
setRules2allRules(rules: Map<string, boolean>, rule: string) {
63+
if (this.allRules.get(rule)) {
64+
rules.set(rule, true);
65+
} else {
66+
rules.set(rule, false);
67+
this.allRules.set(rule, false)
68+
}
69+
}
70+
5871
cleanAllStyles() {
5972
this.cleanSheets();
6073
this.cleanCSSText();
6174
}
6275

6376
cleanSheets = (): void => {
64-
this.theme = null;
6577
this.sheets = {};
6678
}
6779

6880
cleanCSSText() {
69-
this.theme = null;
7081
this.addedCSSText = {};
71-
this.CSSText = "";
82+
this.resultCSSText = "";
7283
}
7384

7485
style2CSSText = (style: React.CSSProperties): string => style ? Object.keys(style).map(key => (
75-
` ${replace2Dashes(key)}: ${getStyleValue(key, style[key])};`
76-
)).join("\n") : void 0
86+
`${replace2Dashes(key)}: ${getStyleValue(key, style[key])};`
87+
)).join(" ") : void 0
7788

78-
sheetsToString = () => `\n${Object.keys(this.sheets).map(id => this.sheets[id].CSSText).join("")}`;
89+
sheetsToString = () => `${Object.keys(this.sheets).map(id => this.sheets[id].CSSText).join("")}`;
7990

80-
getAllCSSText = () => `${this.sheetsToString()}\n${this.CSSText}`;
91+
getAllCSSText = () => `${this.sheetsToString()}\n${this.resultCSSText}`;
8192

8293
addStyle = (style: CustomCSSProperties, className = ""): Sheet => {
8394
const id = createHash(JSON.stringify(style));
@@ -88,27 +99,33 @@ export class StyleManager {
8899

89100
const classNameWithHash = `${this.prefixClassName}${className}-${id}`;
90101
let CSSText = "";
91-
let contentCSSText = "";
92-
let extendsCSSText = "";
102+
let currStyleText = "";
103+
let extendsRules = "";
104+
const rules = new Map<string, boolean>();
93105

94106
for (const styleKey in style) {
95107
if (extendsStyleKeys[styleKey]) {
96108
const extendsStyle = style[styleKey];
97109
if (extendsStyle) {
98-
extendsCSSText += `.${classNameWithHash}${styleKey.slice(1)} {\n${this.style2CSSText(extendsStyle)}\n}\n`;
110+
const newRules = `.${classNameWithHash}${styleKey.slice(1)} { ${this.style2CSSText(extendsStyle)} }`;
111+
this.setRules2allRules(rules, newRules);
112+
extendsRules += newRules;
99113
}
100114
} else {
101115
if (style[styleKey] !== void 0) {
102-
contentCSSText += ` ${replace2Dashes(styleKey)}: ${getStyleValue(styleKey, style[styleKey])};\n`;
116+
currStyleText += `${replace2Dashes(styleKey)}: ${getStyleValue(styleKey, style[styleKey])}; `;
103117
}
104118
}
105119
}
106120

107-
CSSText += `.${classNameWithHash} {\n${contentCSSText}\n}\n`;
108-
CSSText += extendsCSSText;
121+
const currRules = `.${classNameWithHash} { ${currStyleText}}`;
122+
this.setRules2allRules(rules, currRules);
123+
CSSText += currRules;
124+
CSSText += extendsRules;
109125

110-
this.sheets[id] = { CSSText, classNameWithHash, id, className };
111-
this.onAddCSSText(CSSText);
126+
this.sheets[id] = { CSSText, classNameWithHash, id, className, rules };
127+
this.onAddCSSText(currRules + extendsRules);
128+
this.onAddRules(rules);
112129

113130
return this.sheets[id];
114131
}
@@ -117,26 +134,41 @@ export class StyleManager {
117134
const hash = createHash(CSSText);
118135
const shouldUpdate = !this.addedCSSText[hash];
119136
if (shouldUpdate) {
120-
this.addedCSSText[hash] = true;
121-
this.CSSText += CSSText;
137+
this.resultCSSText += CSSText;
138+
const textSize = CSSText.length;
139+
let currRule = "";
140+
let leftBraces = 0;
141+
const rules = new Map<string, boolean>();
142+
for (let i = 0; i < textSize; i ++) {
143+
const char = CSSText[i];
144+
currRule += char;
145+
if (char === "{") {
146+
leftBraces += 1;
147+
}
148+
if (char === "}") {
149+
leftBraces -= 1;
150+
if (leftBraces === 0) {
151+
this.setRules2allRules(rules, currRule);
152+
currRule = "";
153+
}
154+
}
155+
}
156+
this.addedCSSText[hash] = { CSSText, rules };
122157
this.onAddCSSText(CSSText);
158+
this.onAddRules(rules);
123159
}
124160
}
125161

126162
removeCSSText = (CSSText: string) => {
127163
const hash = createHash(CSSText);
128-
this.addedCSSText[hash] = false;
129-
this.CSSText = this.CSSText.replace(CSSText, "");
164+
delete this.addedCSSText[hash];
165+
this.resultCSSText = this.resultCSSText.replace(CSSText, "");
130166
this.onRemoveCSSText(CSSText);
131167
}
132168

133-
setStyleToManager(config?: {
134-
style?: CustomCSSProperties;
135-
className?: string;
136-
}, callback?: (theme?: Theme) => StyleClasses): StyleClasses {
169+
setStyleToManager(config?: { style?: CustomCSSProperties; className?: string; }): StyleClasses {
137170
let newStyles: StyleClasses = {};
138171
let { style, className } = config || {} as StyleClasses;
139-
if (callback) style = callback(this.theme) as CustomCSSProperties;
140172

141173
const { dynamicStyle, ...styleProperties } = style;
142174
className = className || "";
@@ -149,18 +181,14 @@ export class StyleManager {
149181
return newStyles;
150182
}
151183

152-
setStylesToManager(config?: {
153-
styles: { [key: string]: StyleClasses | CustomCSSProperties };
154-
className?: string;
155-
}, callback?: (theme?: Theme) => { [key: string]: StyleClasses }): { [key: string]: StyleClasses } {
184+
setStylesToManager(config?: { styles: { [key: string]: StyleClasses | CustomCSSProperties }; className?: string; }): { [key: string]: StyleClasses } {
156185
const newStyles: {
157186
[key: string]: {
158187
className?: string;
159188
style?: React.CSSProperties;
160189
}
161190
} = {};
162191
let { className, styles } = config;
163-
if (callback) styles = callback(this.theme);
164192
className = className || "";
165193
const keys = Object.keys(styles);
166194

@@ -190,6 +218,47 @@ export class StyleManager {
190218

191219
return newStyles;
192220
}
221+
222+
insertRule2el(styleEl: HTMLStyleElement, rule: string, index?: number) {
223+
if (rule && styleEl && !this.allRules.get(rule)) {
224+
const { sheet } = styleEl;
225+
const rulesSize = sheet["rules"].size;
226+
227+
try {
228+
if ("insertRule" in sheet) {
229+
(sheet as any)["insertRule"](rule, index === void 0 ? rulesSize : index);
230+
} else if ("appendRule" in sheet) {
231+
(sheet as any)["appendRule"](rule);
232+
} else {
233+
styleEl.textContent += rule;
234+
}
235+
} catch (error) {
236+
console.error(error);
237+
}
238+
}
239+
}
240+
241+
inserAllRule2el(styleEl: HTMLStyleElement) {
242+
this.allRules.forEach((inserted, rule) => {
243+
if (!inserted) {
244+
this.insertRule2el(styleEl, rule);
245+
this.allRules.set(rule, true);
246+
}
247+
});
248+
}
249+
250+
cleanRules(styleEl: HTMLStyleElement) {
251+
if (styleEl) {
252+
const { sheet } = styleEl;
253+
const rules = sheet["rules"] as any;
254+
if (rules && rules.length > 0) {
255+
for (const rule of rules) {
256+
(sheet as any)["deleteRule"](rule);
257+
}
258+
}
259+
this.cleanAllStyles();
260+
}
261+
}
193262
}
194263

195264
export default StyleManager;

0 commit comments

Comments
 (0)