3
3
const fg = require ( 'fast-glob' ) ;
4
4
const fs = require ( 'fs' ) ;
5
5
const postcss = require ( 'postcss' ) ;
6
+ const lastClassFromSelectorRegexp = / \. ( [ ^ \. \, \s \n \: \( \) \[ \] \' ~ \+ \> \* \\ ] * ) / gim;
6
7
const removeDuplicatesFromArray = require ( './removeDuplicatesFromArray' ) ;
7
8
8
- let previousGlobsResults = [ ] ;
9
+ const cssFilesInfos = new Map ( ) ;
9
10
let lastUpdate = null ;
10
11
let classnamesFromFiles = [ ] ;
11
12
@@ -16,28 +17,61 @@ let classnamesFromFiles = [];
16
17
* @returns {Array } List of classnames
17
18
*/
18
19
const generateClassnamesListSync = ( patterns , refreshRate = 5_000 ) => {
19
- const now = new Date ( ) . getTime ( ) ;
20
- const files = fg . sync ( patterns , { suppressErrors : true } ) ;
21
- const newGlobs = previousGlobsResults . flat ( ) . join ( ',' ) != files . flat ( ) . join ( ',' ) ;
22
- 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 ) ;
20
+ const now = Date . now ( ) ;
21
+ const isExpired = lastUpdate === null || now - lastUpdate > refreshRate ;
22
+
23
+ if ( ! isExpired ) {
24
+ // console.log(`generateClassnamesListSync from cache (${classnamesFromFiles.length} classes)`);
25
+ return classnamesFromFiles ;
26
+ }
27
+
28
+ // console.log('generateClassnamesListSync EXPIRED');
29
+ // Update classnames from CSS files
30
+ lastUpdate = now ;
31
+ const filesToBeRemoved = new Set ( [ ...cssFilesInfos . keys ( ) ] ) ;
32
+ const files = fg . sync ( patterns , { suppressErrors : true , stats : true } ) ;
33
+ for ( const file of files ) {
34
+ let mtime = '' ;
35
+ let canBeSkipped = cssFilesInfos . has ( file . path ) ;
36
+ if ( canBeSkipped ) {
37
+ // This file is still used
38
+ filesToBeRemoved . delete ( file . path ) ;
39
+ // Check modification date
40
+ const stats = fs . statSync ( file . path ) ;
41
+ mtime = `${ stats . mtime || '' } ` ;
42
+ canBeSkipped = cssFilesInfos . get ( file . path ) . mtime === mtime ;
37
43
}
38
- classnamesFromFiles = detectedClassnames ;
44
+ if ( canBeSkipped ) {
45
+ // File did not change since last run
46
+ continue ;
47
+ }
48
+ // Parse CSS file
49
+ const data = fs . readFileSync ( file . path , 'utf-8' ) ;
50
+ const root = postcss . parse ( data ) ;
51
+ let detectedClassnames = new Set ( ) ;
52
+ root . walkRules ( ( rule ) => {
53
+ const matches = [ ...rule . selector . matchAll ( lastClassFromSelectorRegexp ) ] ;
54
+ const classnames = matches . map ( ( arr ) => arr [ 1 ] ) ;
55
+ detectedClassnames = new Set ( [ ...detectedClassnames , ...classnames ] ) ;
56
+ } ) ;
57
+ // Save the detected classnames
58
+ cssFilesInfos . set ( file . path , {
59
+ mtime : mtime ,
60
+ classNames : [ ...detectedClassnames ] ,
61
+ } ) ;
62
+ }
63
+ // Remove erased CSS from the Map
64
+ const deletedFiles = [ ...filesToBeRemoved ] ;
65
+ for ( let i = 0 ; i < deletedFiles . length ; i ++ ) {
66
+ cssFilesInfos . delete ( deletedFiles [ i ] ) ;
39
67
}
40
- return classnamesFromFiles ;
68
+ // Build the final list
69
+ classnamesFromFiles = [ ] ;
70
+ cssFilesInfos . forEach ( ( css ) => {
71
+ classnamesFromFiles = [ ...classnamesFromFiles , ...css . classNames ] ;
72
+ } ) ;
73
+ // Unique classnames
74
+ return removeDuplicatesFromArray ( classnamesFromFiles ) ;
41
75
} ;
42
76
43
77
module . exports = generateClassnamesListSync ;
0 commit comments