1- import { readFileSync } from 'fs'
1+ import { existsSync , readFileSync , promises as fsPromises } from 'fs'
22import path from 'path'
33
44import { Project } from 'ts-morph'
55
6+ import { isDefined } from '../../utils'
7+
68import { getCssModuleExportNameMap } from './postcss/getCssModuleExportNameMap'
79import { transformFileToCssModule } from './postcss/transformFileToCssModule'
810import { addClassNamesUtilImportIfNeeded } from './ts/classNamesUtility'
911import { getNodesWithClassName } from './ts/getNodesWithClassName'
1012import { STYLES_IDENTIFIER , processNodesWithClassName } from './ts/processNodesWithClassName'
1113
14+ interface GlobalCssToCssModuleOptions {
15+ project : Project
16+ /** If `true` persist changes made by the codemod to the filesystem. */
17+ shouldWriteFiles ?: boolean
18+ }
19+
1220interface CodemodResult {
1321 css : {
1422 source : string
@@ -18,6 +26,7 @@ interface CodemodResult {
1826 source : string
1927 path : string
2028 }
29+ fsWritePromise ?: Promise < void [ ] >
2130}
2231
2332/**
@@ -32,44 +41,86 @@ interface CodemodResult {
3241 * 7) Add `.module.scss` import to the `.tsx` file.
3342 *
3443 */
35- export function globalCssToCssModule ( project : Project ) : Promise < CodemodResult [ ] > {
36- const codemodResults = project . getSourceFiles ( ) . map ( async sourceFile => {
37- const filePath = sourceFile . getFilePath ( )
44+ export async function globalCssToCssModule ( options : GlobalCssToCssModuleOptions ) : Promise < CodemodResult [ ] > {
45+ const { project, shouldWriteFiles } = options
46+ /**
47+ * Find `.tsx` files with co-located `.scss` file.
48+ * For example `RepoHeader.tsx` should have matching `RepoHeader.scss` in the same folder.
49+ */
50+ const itemsToProcess = project
51+ . getSourceFiles ( )
52+ . map ( tsSourceFile => {
53+ const tsFilePath = tsSourceFile . getFilePath ( )
54+
55+ const parsedTsFilePath = path . parse ( tsFilePath )
56+ const cssFilePath = path . resolve ( parsedTsFilePath . dir , `${ parsedTsFilePath . name } .scss` )
57+
58+ if ( existsSync ( cssFilePath ) ) {
59+ return {
60+ tsSourceFile,
61+ cssFilePath,
62+ }
63+ }
64+
65+ return undefined
66+ } )
67+ . filter ( isDefined )
3868
39- const parsedTsFilePath = path . parse ( filePath )
40- const cssFilePath = path . resolve ( parsedTsFilePath . dir , `${ parsedTsFilePath . name } .scss` )
69+ const codemodResultPromises = itemsToProcess . map ( async ( { tsSourceFile, cssFilePath } ) => {
70+ const tsFilePath = tsSourceFile . getFilePath ( )
71+ const parsedTsFilePath = path . parse ( tsFilePath )
4172
42- // TODO: add check if SCSS file doesn't exist and exit if it's not found.
4373 const sourceCss = readFileSync ( cssFilePath , 'utf8' )
44- const { css : cssModuleSource , filePath : cssModuleFileName } = await transformFileToCssModule (
74+ const { css : cssModuleSource , filePath : cssModuleFileName } = await transformFileToCssModule ( {
4575 sourceCss,
46- cssFilePath
47- )
76+ sourceFilePath : cssFilePath ,
77+ } )
4878 const exportNameMap = await getCssModuleExportNameMap ( cssModuleSource )
4979
5080 processNodesWithClassName ( {
5181 exportNameMap,
52- nodesWithClassName : getNodesWithClassName ( sourceFile ) ,
82+ nodesWithClassName : getNodesWithClassName ( tsSourceFile ) ,
5383 } )
5484
55- addClassNamesUtilImportIfNeeded ( sourceFile )
56- sourceFile . addImportDeclaration ( {
85+ addClassNamesUtilImportIfNeeded ( tsSourceFile )
86+ tsSourceFile . addImportDeclaration ( {
5787 defaultImport : STYLES_IDENTIFIER ,
5888 moduleSpecifier : `./${ path . parse ( cssModuleFileName ) . base } ` ,
5989 } )
6090
61- // TODO: run prettier and eslint --fix over updated files.
91+ /**
92+ * If `shouldWriteFiles` is true:
93+ *
94+ * 1. Update TS file with a new source that uses CSS module.
95+ * 2. Create a new CSS module file.
96+ * 3. Delete redundant SCSS file that's replaced with CSS module.
97+ */
98+ const fsWritePromise = shouldWriteFiles
99+ ? Promise . all ( [
100+ tsSourceFile . save ( ) ,
101+ fsPromises . writeFile ( cssModuleFileName , cssModuleSource , { encoding : 'utf-8' } ) ,
102+ fsPromises . rm ( cssFilePath ) ,
103+ ] )
104+ : undefined
105+
62106 return {
107+ fsWritePromise,
63108 css : {
64109 source : cssModuleSource ,
65110 path : path . resolve ( parsedTsFilePath . dir , cssModuleFileName ) ,
66111 } ,
67112 ts : {
68- source : sourceFile . getFullText ( ) ,
69- path : sourceFile . getFilePath ( ) ,
113+ source : tsSourceFile . getFullText ( ) ,
114+ path : tsSourceFile . getFilePath ( ) ,
70115 } ,
71116 }
72117 } )
73118
74- return Promise . all ( codemodResults )
119+ const codemodResults = await Promise . all ( codemodResultPromises )
120+
121+ if ( shouldWriteFiles ) {
122+ await Promise . all ( codemodResults . map ( result => result . fsWritePromise ) )
123+ }
124+
125+ return codemodResults
75126}
0 commit comments