diff --git a/src/scripts/cssToCssModule.js b/src/scripts/cssToCssModule.js new file mode 100644 index 00000000..b8a47bbf --- /dev/null +++ b/src/scripts/cssToCssModule.js @@ -0,0 +1,162 @@ +const fs = require("fs"); +const path = require("path"); + +/** + * List of blacklisted classes representing classNames not to convert + * + */ +const blackListedClasses = [ + "row", + "cards-pf", + "pficon ", + "pficon-info", + "fa", + "fa-database", + "fa-file", + "btn", + "btn-large", + "login-pf-signup", + "text-light", + "no-flex", + "row-cards-pf", + "container-fluid", + "container-cards-pf", + "pf-c-button", + "pf-m-primary", + "pf-m-link", + "pf-c-button__icon", + "pf-m-start", + "fas", + "fa-user", +]; + +const filesToChange = function (dirPath, fileList) { + const files = fs.readdirSync(dirPath); + + let arrayOfFiles = fileList || []; + + files.forEach((file) => { + if (fs.statSync(`${dirPath }/${ file}`).isDirectory()) { + arrayOfFiles = filesToChange(`${dirPath }/${ file}`, arrayOfFiles); + } else if (file.split(".").slice(-1)[0] === "jsx") { + arrayOfFiles.push(path.join(dirPath, "/", file)); + } + }); + return arrayOfFiles; +}; + +// const pathToComponents = path.resolve(__dirname, '../src/components') +// const arrayOfFiles = filesToChange(pathToComponents); + +/** + * Converts .css to .module.css file format and CSS classes to use CSS module object syntax + * + * @param filePath path to the files to be changed + * @return files converted to CSS module object syntax + */ +function convertFile(filePath) { + fs.readFile(filePath, "utf8", (err, textData) => { + const cssFileName = filePath.replace(".jsx", ".css"); + const cssModuleName = filePath.replace(".jsx", ".module.css"); + fs.rename(cssFileName, cssModuleName, () => {}); + let data = textData.replace( + `import './${cssFileName.split("/").slice(-1)}'`, + `import styles from './${cssModuleName.split("/").slice(-1)}'` + ); + data = convertCssClasses(data, blackListedClasses); + fs.writeFileSync(filePath, data, { encoding: "utf8", flag: "w" }); + }); +} + +/** + * Convert all classes in a className to use CSS module object syntax. + * + * @param data string containing JSX file contents + * @param blackListedClasses list of strings representing class names not to convert + * @returns a classname using CSS module object syntax + */ +export function convertCssClasses(data, blackListedClasses) { + return data.replace(/(?<=className=)".+?"/g, (classNames) => { + const className = classNames.slice(1, -1).split(/[, ]+/); + let res; + if (className.length > 1) { + res = "{`"; + className.forEach((cls, index) => { + if (Number(index) !== 0) { + res += " "; + } + if (blackListedClasses.includes(cls)) { + res += cls; + } else { + res += `\${styles['${cls}']}`; + } + }); + res += "`}"; + } else if (blackListedClasses.includes(className[0])) { + res = `{\`${className[0]}\`}`; + } else { + res = "{`$"; + res += `{styles['${className[0]}']}`; + res += "`}"; + } + return res; + }); +} + +/** + * Convert all CSS classes to use object syntax as if they were imported from a CSS module. + * + * @param data string containing JSX file contents + * @param blackListedClasses list of strings representing class names not to convert + * @return data where CSS classes were changed to use CSS module object syntax + */ +export function processFile(data, blackListedClasses) { + return data.replace(/(?<=className=)".+?"/g, ); +} + +/** + * Convert a className to use CSS module syntax. + * + * @param className a class from the className of a JSX object + * @param blackListedClasses list of strings representing class names not to convert + * @returns className using CSS module syntax + */ +export function convertCssString(classNames, blackListedClasses) { + const className = classNames.slice(1, -1).split(/[, ]+/); + let res; + if (className.length > 1) { + res = "{`"; + className.forEach((cls, index) => { + if (Number(index) !== 0) { + res += " "; + } + if (blackListedClasses.includes(cls)) { + res += cls; + } else { + res += `\${styles['${ cls }']}`; + } + }); + res += "`}"; + } else if (blackListedClasses.includes(className[0])) { + res = `{\`${ className[0] }\`}`; + } else { + res = "{`$"; + res += `{styles['${className[0]}']}`; + res += "`}"; + } + return res; +} + +/** + * Convert a className to use CSS module syntax. + * + * @param className a class from the className of a JSX object + * @param cssModuleName name of imported CSS module + * @returns className using CSS module syntax + */ +export function convertSingleCssClass(className, cssModuleName) { + return `${cssModuleName}['${className}']` +} + + +// arrayOfFiles.forEach(async (filePath) => convertFile(filePath)); \ No newline at end of file diff --git a/src/scripts/cssToCssModule.test.js b/src/scripts/cssToCssModule.test.js new file mode 100644 index 00000000..f901b349 --- /dev/null +++ b/src/scripts/cssToCssModule.test.js @@ -0,0 +1,56 @@ +import {convertCssString, convertCssClasses, convertSingleCssClass} from "./cssToCssModule"; + +describe('css module conversion script', () => { + it('should work with one class', () => { + const exampleData = '
'; + const expected = ""; + const actual = convertCssClasses(exampleData, []); + expect(actual).toBe(expected); + }); + + + it('should work with muliple class names', () => { + const exampleData = '"hello-world bottom-footer"'; + const expected = "{`${styles['hello-world']} ${styles['bottom-footer']}`}"; + const actual = convertCssString(exampleData, []); + expect(actual).toBe(expected); + }); + + it('should not process blacklisted class names', () => { + const exampleData = ''; + const expected = ""; + const actual = convertCssClasses(exampleData, ['hello-world']); + expect(actual).toBe(expected); + }); + + it('should not process multiple blacklisted classNames', () => { + const exampleData = '"fa-file fa bottom-footer"'; + const expected = "{`fa-file fa ${styles['bottom-footer']}`}"; + const actual = convertCssString(exampleData, ['fa-file', 'fa']); + expect(actual).toBe(expected); + }); + + it('should not process blacklisted classNames', () => { + const exampleData = '"fa-file fa"'; + const expected = "{`fa-file fa`}"; + const actual = convertCssString(exampleData, ['fa-file', 'fa']); + expect(actual).toBe(expected); + }); + + it('should be able to convert a single class', () => { + const exampleData = '"bottom-footer"'; + const expected = "{`${styles['bottom-footer']}`}"; + const actual = convertCssString(exampleData, []); + expect(actual).toBe(expected); + }); + + + it('should be able to convert a single class', () => { + const actual1 = convertSingleCssClass("hello-world", "styles"); + expect(actual1).toBe("styles['hello-world']"); + const actual2 = convertSingleCssClass("footer-text", "secondStyles"); + expect(actual2).toBe("secondStyles['footer-text']"); + }); + +}); + diff --git a/src/scripts/cssToCssModule_old.js b/src/scripts/cssToCssModule_old.js new file mode 100644 index 00000000..d76a37b8 --- /dev/null +++ b/src/scripts/cssToCssModule_old.js @@ -0,0 +1,85 @@ +const fs = require("fs"); +const path = require("path"); + +const blackListedClasses = [ + "row", + "cards-pf", + "pficon ", + "pficon-info", + "fa", + "fa-database", + "fa-file", + "btn", + "btn-large", + "login-pf-signup", + "text-light", + "no-flex", + "row-cards-pf", + "container-fluid", + "container-cards-pf", + "pf-c-button", + "pf-m-primary", + "pf-m-link", + "pf-c-button__icon", + "pf-m-start", + "fas", + "fa-user", +]; + +const filesToChange = function (dirPath, fileList) { + const files = fs.readdirSync(dirPath); + + let arrayOfFiles = fileList || []; + + files.forEach((file) => { + if (fs.statSync(`${dirPath }/${ file}`).isDirectory()) { + arrayOfFiles = filesToChange(`${dirPath }/${ file}`, arrayOfFiles); + } else if (file.split(".").slice(-1)[0] === "jsx") { + arrayOfFiles.push(path.join(dirPath, "/", file)); + } + }); + return arrayOfFiles; +}; + +const pathToComponents = path.resolve(__dirname, '../components') +const arrayOfFiles = filesToChange(pathToComponents); + +function convertFile(filePath) { + fs.readFile(filePath, "utf8", (err, textData) => { + const cssFileName = filePath.replace(".jsx", ".css"); + const cssModuleName = filePath.replace(".jsx", ".module.css"); + fs.rename(cssFileName, cssModuleName, () => {}); + let data = textData.replace( + `import './${cssFileName.split("/").slice(-1)}'`, + `import styles from './${cssModuleName.split("/").slice(-1)}'` + ); + data = data.replace(/(?<=className=)".+?"/g, (classNames) => { + const className = classNames.slice(1, -1).split(/[, ]+/); + let res; + if (className.length > 1) { + res = "{`"; + className.forEach((cls, index) => { + if (Number(index) !== 0) { + res += " "; + } + if (blackListedClasses.includes(cls)) { + res += cls; + } else { + res += `\${styles['${ cls }']}`; + } + }); + res += "`}"; + } else if (blackListedClasses.includes(className[0])) { + res = `{\`${ className[0] }\`}`; + } else { + res = "{`$"; + res += `{styles['${className[0]}']}`; + res += "`}"; + } + return res; + }); + fs.writeFileSync(filePath, data, { encoding: "utf8", flag: "w" }); + }); +} + +arrayOfFiles.forEach(async (filePath) => convertFile(filePath)); \ No newline at end of file