Skip to content

Commit 6d853ba

Browse files
committed
no-custom-classname: perf optimization
1 parent 7070c34 commit 6d853ba

File tree

1 file changed

+54
-20
lines changed

1 file changed

+54
-20
lines changed

lib/util/cssFiles.js

+54-20
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
const fg = require('fast-glob');
44
const fs = require('fs');
55
const postcss = require('postcss');
6-
const removeDuplicatesFromArray = require('./removeDuplicatesFromArray');
76

8-
let previousGlobsResults = [];
7+
const classRegexp = /\.([^\.\,\s\n\:\(\)\[\]\'~\+\>\*\\]*)/gim;
8+
99
let lastUpdate = null;
1010
let classnamesFromFiles = [];
1111

12+
/**
13+
* @type {Map<string, number}
14+
*/
15+
const prevEditedTimestamp = new Map()
16+
1217
/**
1318
* Read CSS files and extract classnames
1419
* @param {Array} patterns Glob patterns to locate files
@@ -17,27 +22,56 @@ let classnamesFromFiles = [];
1722
*/
1823
const generateClassnamesListSync = (patterns, refreshRate = 5_000) => {
1924
const now = new Date().getTime();
20-
const files = fg.sync(patterns, { suppressErrors: true });
21-
const newGlobs = previousGlobsResults.flat().join(',') != files.flat().join(',');
2225
const expired = lastUpdate === null || now - lastUpdate > refreshRate;
23-
if (newGlobs || expired) {
24-
previousGlobsResults = files;
25-
lastUpdate = now;
26-
let detectedClassnames = [];
27-
for (const file of files) {
28-
const data = fs.readFileSync(file, 'utf-8');
29-
const root = postcss.parse(data);
30-
root.walkRules((rule) => {
31-
const regexp = /\.([^\.\,\s\n\:\(\)\[\]\'~\+\>\*\\]*)/gim;
32-
const matches = [...rule.selector.matchAll(regexp)];
33-
const classnames = matches.map((arr) => arr[1]);
34-
detectedClassnames.push(...classnames);
35-
});
36-
detectedClassnames = removeDuplicatesFromArray(detectedClassnames);
26+
27+
if (!expired) {
28+
return classnamesFromFiles;
29+
}
30+
const files = fg.sync(patterns, { suppressErrors: true, stats: true });
31+
lastUpdate = now;
32+
33+
/**
34+
* @type {Set<string}
35+
*/
36+
const detectedClassnames = new Set();
37+
/**
38+
* @type {Set<string>}
39+
*/
40+
const filesSet = new Set();
41+
for (const { path: file, stats } of files) {
42+
filesSet.add(file);
43+
if (!stats) {}
44+
// file is not changed -> we do need to do extra work
45+
else if (prevEditedTimestamp.get(file) === stats.mtimeMs) {
46+
continue;
47+
} else {
48+
prevEditedTimestamp.set(file, stats.mtimeMs);
3749
}
38-
classnamesFromFiles = detectedClassnames;
50+
const data = fs.readFileSync(file, 'utf-8');
51+
const root = postcss.parse(data);
52+
root.walkRules((rule) => {
53+
for (const match of rule.selector.matchAll(classRegexp)) {
54+
detectedClassnames.add(match[1]);
55+
}
56+
});
3957
}
40-
return classnamesFromFiles;
58+
// avoiding memory leak
59+
{
60+
/**
61+
* @type {string[]}
62+
*/
63+
const keysToDelete = []
64+
for (const cachedFilePath of prevEditedTimestamp.keys()) {
65+
if (!filesSet.has(cachedFilePath)) {
66+
keysToDelete.push(cachedFilePath);
67+
}
68+
}
69+
for (const key of keysToDelete) {
70+
prevEditedTimestamp.delete(key);
71+
}
72+
}
73+
classnamesFromFiles = [...detectedClassnames];
74+
return classnamesFromFiles
4175
};
4276

4377
module.exports = generateClassnamesListSync;

0 commit comments

Comments
 (0)