@@ -12,7 +12,11 @@ const snakeToCamelCase = (s) => s.split('_').map(capitalize).join('')
1212const componentNameToRubyClass = ( componentName ) =>
1313 'Primer::' + componentName . split ( '/' ) . map ( snakeToCamelCase ) . join ( '::' )
1414
15- const exportSelectors = ( folder ) => {
15+ // Collects every unique bare class name (without the leading ".") across all
16+ // processed folders so we can emit a flat set in static/classnames.js.
17+ const allClassNames = new Set ( )
18+
19+ const exportSelectors = ( folder , { classNameExcludedFiles = [ ] } = { } ) => {
1620 const folderGlob = `${ folder } /**/*.css`
1721 const componentNameRegex = new RegExp ( `${ folder . replace ( '/' , '\\/' ) } \\/(.*).css` )
1822
@@ -23,6 +27,7 @@ const exportSelectors = (folder) => {
2327 const css = await readFile ( file , 'utf8' )
2428 const root = postcss . parse ( css )
2529 const componentName = componentNameRegex . exec ( file ) [ 1 ]
30+ const collectClassNames = ! classNameExcludedFiles . includes ( file )
2631
2732 const selectors = [ ]
2833 const classFiles = [ ]
@@ -38,11 +43,24 @@ const exportSelectors = (folder) => {
3843
3944 rule . selectors . forEach ( ruleSelector => {
4045 selectors . push ( ruleSelector )
41- const ruleObj = CSSwhat . parse ( ruleSelector ) [ 0 ] [ 0 ]
46+ const parsed = CSSwhat . parse ( ruleSelector )
47+ const ruleObj = parsed [ 0 ] [ 0 ]
4248 if ( ruleObj . type === 'attribute' && ruleObj . name === 'class' ) {
4349 const baseHtmlClass = ruleObj . value
4450 classFiles . push ( [ baseHtmlClass , componentNameToRubyClass ( componentName ) ] )
4551 }
52+
53+ // Collect every class token from every compound of the selector
54+ // (not just the first) for the complete classnames.js set.
55+ if ( collectClassNames ) {
56+ parsed . forEach ( compound => {
57+ compound . forEach ( token => {
58+ if ( token . type === 'attribute' && token . name === 'class' ) {
59+ allClassNames . add ( token . value )
60+ }
61+ } )
62+ } )
63+ }
4664 } )
4765 } )
4866
@@ -66,7 +84,9 @@ const exportSelectors = (folder) => {
6684const classShouldBeReserved = className =>
6785 ( className [ 0 ] . toUpperCase ( ) === className [ 0 ] )
6886
69- exportSelectors ( 'app/components/primer' )
87+ const componentsBuild = exportSelectors ( 'app/components/primer' )
88+
89+ componentsBuild
7090 . then ( classLists => {
7191 const htmlClassToRubyClasses = { }
7292
@@ -103,5 +123,26 @@ exportSelectors('app/components/primer')
103123 . catch ( error => console . error ( "failed to write static/classes.json" , { error } ) )
104124
105125// stylesheets under app/lib/primer/css need their individual
106- // json files generated
107- exportSelectors ( 'app/lib/primer/css' )
126+ // json files generated. The utility and layout classes in these files are
127+ // intentionally excluded from the flat classnames.js set.
128+ const libBuild = exportSelectors ( 'app/lib/primer/css' , {
129+ classNameExcludedFiles : [
130+ 'app/lib/primer/css/utilities.css' ,
131+ 'app/lib/primer/css/layout.css'
132+ ]
133+ } )
134+
135+ // Once both folders have been processed, write the complete, flat set of every
136+ // unique class name in the library to static/classnames.js.
137+ Promise . all ( [ componentsBuild , libBuild ] )
138+ . then ( ( ) => {
139+ const sortedClassNames = [ ...allClassNames ] . sort ( )
140+ const body = sortedClassNames . map ( className => ` '${ className } ',` ) . join ( '\n' )
141+
142+ console . log ( 'Writing static/classnames.js' )
143+ return writeFile (
144+ 'static/classnames.js' ,
145+ `const classNames = new Set([\n${ body } \n])\n\nexport default classNames\n`
146+ )
147+ } )
148+ . catch ( error => console . error ( "failed to write static/classnames.js" , { error } ) )
0 commit comments