3
3
const fg = require ( 'fast-glob' ) ;
4
4
const fs = require ( 'fs' ) ;
5
5
const postcss = require ( 'postcss' ) ;
6
- const removeDuplicatesFromArray = require ( './removeDuplicatesFromArray' ) ;
7
6
8
- let previousGlobsResults = [ ] ;
7
+ const classRegexp = / \. ( [ ^ \. \, \s \n \: \( \) \[ \] \' ~ \+ \> \* \\ ] * ) / gim;
8
+
9
9
let lastUpdate = null ;
10
10
let classnamesFromFiles = [ ] ;
11
11
12
+ /**
13
+ * @type {Map<string, number }
14
+ */
15
+ const prevEditedTimestamp = new Map ( )
16
+
12
17
/**
13
18
* Read CSS files and extract classnames
14
19
* @param {Array } patterns Glob patterns to locate files
@@ -17,27 +22,56 @@ let classnamesFromFiles = [];
17
22
*/
18
23
const generateClassnamesListSync = ( patterns , refreshRate = 5_000 ) => {
19
24
const now = new Date ( ) . getTime ( ) ;
20
- const files = fg . sync ( patterns , { suppressErrors : true } ) ;
21
- const newGlobs = previousGlobsResults . flat ( ) . join ( ',' ) != files . flat ( ) . join ( ',' ) ;
22
25
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 ) ;
37
49
}
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
+ } ) ;
39
57
}
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
41
75
} ;
42
76
43
77
module . exports = generateClassnamesListSync ;
0 commit comments