Skip to content

Commit 893386a

Browse files
committed
Factor out flattenDeep from injectAndGetClassName
In my profiling, calling flattenDeep in here takes 1437ms out of 3418ms, and that's with already flattened arrays. By refactoring this code, we can actually avoid a lot of the work done here and make this faster. This change drops css() down from 3418ms to 2045ms.
1 parent 10b37c4 commit 893386a

3 files changed

Lines changed: 58 additions & 47 deletions

File tree

src/inject.js

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import asap from 'asap';
33

44
import OrderedElements from './ordered-elements';
55
import {generateCSS} from './generate';
6-
import {flattenDeep, hashObject} from './util';
6+
import {hashObject} from './util';
77

88
/* ::
99
import type { SheetDefinition, SheetDefinitions } from './index.js';
1010
import type { MaybeSheetDefinition } from './exports.js';
1111
import type { SelectorHandler } from './generate.js';
12+
type ProcessedStyleDefinitions = {
13+
classNameBits: Array<string>,
14+
definitionBits: Array<Object>,
15+
};
1216
*/
1317

1418
// The current <style> tag we are inserting into, or null if we haven't
@@ -136,24 +140,26 @@ let injectionBuffer = "";
136140
let isBuffering = false;
137141

138142
const injectGeneratedCSSOnce = (key, generatedCSS) => {
139-
if (!alreadyInjected[key]) {
140-
if (!isBuffering) {
141-
// We should never be automatically buffering on the server (or any
142-
// place without a document), so guard against that.
143-
if (typeof document === "undefined") {
144-
throw new Error(
145-
"Cannot automatically buffer without a document");
146-
}
143+
if (alreadyInjected[key]) {
144+
return;
145+
}
147146

148-
// If we're not already buffering, schedule a call to flush the
149-
// current styles.
150-
isBuffering = true;
151-
asap(flushToStyleTag);
147+
if (!isBuffering) {
148+
// We should never be automatically buffering on the server (or any
149+
// place without a document), so guard against that.
150+
if (typeof document === "undefined") {
151+
throw new Error(
152+
"Cannot automatically buffer without a document");
152153
}
153154

154-
injectionBuffer += generatedCSS;
155-
alreadyInjected[key] = true;
155+
// If we're not already buffering, schedule a call to flush the
156+
// current styles.
157+
isBuffering = true;
158+
asap(flushToStyleTag);
156159
}
160+
161+
injectionBuffer += generatedCSS;
162+
alreadyInjected[key] = true;
157163
}
158164

159165
export const injectStyleOnce = (
@@ -163,13 +169,15 @@ export const injectStyleOnce = (
163169
useImportant /* : boolean */,
164170
selectorHandlers /* : SelectorHandler[] */ = []
165171
) => {
166-
if (!alreadyInjected[key]) {
167-
const generated = generateCSS(
168-
selector, definitions, selectorHandlers,
169-
stringHandlers, useImportant);
170-
171-
injectGeneratedCSSOnce(key, generated);
172+
if (alreadyInjected[key]) {
173+
return;
172174
}
175+
176+
const generated = generateCSS(
177+
selector, definitions, selectorHandlers,
178+
stringHandlers, useImportant);
179+
180+
injectGeneratedCSSOnce(key, generated);
173181
};
174182

175183
export const reset = () => {
@@ -211,6 +219,25 @@ export const addRenderedClassNames = (classNames /* : string[] */) => {
211219
});
212220
};
213221

222+
const processStyleDefinitions = (
223+
styleDefinitions /* : any[] */,
224+
result /* : ProcessedStyleDefinitions */
225+
) /* : void */ => {
226+
for (let i = 0; i < styleDefinitions.length; i += 1) {
227+
// Filter out falsy values from the input, to allow for
228+
// `css(a, test && c)`
229+
if (styleDefinitions[i]) {
230+
if (Array.isArray(styleDefinitions[i])) {
231+
// We've encountered an array, so let's recurse
232+
processStyleDefinitions(styleDefinitions[i], result);
233+
} else {
234+
result.classNameBits.push(styleDefinitions[i]._name);
235+
result.definitionBits.push(styleDefinitions[i]._definition);
236+
}
237+
}
238+
}
239+
};
240+
214241
/**
215242
* Inject styles associated with the passed style definition objects, and return
216243
* an associated CSS class name.
@@ -226,28 +253,23 @@ export const injectAndGetClassName = (
226253
styleDefinitions /* : MaybeSheetDefinition[] */,
227254
selectorHandlers /* : SelectorHandler[] */
228255
) /* : string */ => {
229-
styleDefinitions = flattenDeep(styleDefinitions);
256+
const processedStyleDefinitions /* : ProcessedStyleDefinitions */ = {
257+
classNameBits: [],
258+
definitionBits: [],
259+
};
260+
// Mutates processedStyleDefinitions
261+
processStyleDefinitions(styleDefinitions, processedStyleDefinitions);
230262

231-
const classNameBits = [];
232-
const definitionBits = [];
233-
for (let i = 0; i < styleDefinitions.length; i += 1) {
234-
// Filter out falsy values from the input, to allow for
235-
// `css(a, test && c)`
236-
if (styleDefinitions[i]) {
237-
classNameBits.push(styleDefinitions[i]._name);
238-
definitionBits.push(styleDefinitions[i]._definition);
239-
}
240-
}
241263
// Break if there aren't any valid styles.
242-
if (classNameBits.length === 0) {
264+
if (processedStyleDefinitions.classNameBits.length === 0) {
243265
return "";
244266
}
245-
const className = classNameBits.join("-o_O-");
267+
const className = processedStyleDefinitions.classNameBits.join("-o_O-");
246268

247269
injectStyleOnce(
248270
className,
249271
`.${className}`,
250-
definitionBits,
272+
processedStyleDefinitions.definitionBits,
251273
useImportant,
252274
selectorHandlers
253275
);

src/util.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ export const mapObj = (
2121
return mappedObj;
2222
}
2323

24-
export const flattenDeep = (list /* : any[] */) /* : any[] */ =>
25-
list.reduce((memo, x) => memo.concat(Array.isArray(x) ? flattenDeep(x) : x), []);
26-
2724
const UPPERCASE_RE = /([A-Z])/g;
2825
const UPPERCASE_RE_TO_KEBAB = (match /* : string */) /* : string */ => `-${match.toLowerCase()}`;
2926

tests/util_test.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import {assert} from 'chai';
22

3-
import {flattenDeep, kebabifyStyleName} from '../src/util.js';
3+
import {kebabifyStyleName} from '../src/util.js';
44

55
describe('Utils', () => {
6-
describe('flattenDeep', () => {
7-
it('flattens arrays at any level', () => {
8-
assert.deepEqual(
9-
flattenDeep([[1, [2, 3, []]], 4, [[5], [6, [7]]]]),
10-
[1, 2, 3, 4, 5, 6, 7]);
11-
});
12-
});
13-
146
describe('kebabifyStyleName', () => {
157
it('kebabifies camelCase', () => {
168
assert.equal(kebabifyStyleName('fooBarBaz'), 'foo-bar-baz');

0 commit comments

Comments
 (0)