33const fg = require ( 'fast-glob' ) ;
44const fs = require ( 'fs' ) ;
55const postcss = require ( 'postcss' ) ;
6+ const lastClassFromSelectorRegexp = / \. ( [ ^ \. \, \s \n \: \( \) \[ \] \' ~ \+ \> \* \\ ] * ) / gim;
67const removeDuplicatesFromArray = require ( './removeDuplicatesFromArray' ) ;
78
8- let previousGlobsResults = [ ] ;
9+ const cssFilesInfos = new Map ( ) ;
910let lastUpdate = null ;
1011let classnamesFromFiles = [ ] ;
1112
@@ -16,28 +17,61 @@ let classnamesFromFiles = [];
1617 * @returns {Array } List of classnames
1718 */
1819const 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 ;
3743 }
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 ] ) ;
3967 }
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 ) ;
4175} ;
4276
4377module . exports = generateClassnamesListSync ;
0 commit comments