Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added css to css module script #277

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions src/scripts/cssToCssModule.js
Original file line number Diff line number Diff line change
@@ -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 || [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you make an argument "optional" it's actually a union-type between being "something" or null. Union types can be complicated because you must implement logic to handle both types. For these reasons, it is important that, whenever you have a union type or optional argument, you should provide some documentation indicating it as such.


files.forEach((file) => {
if (fs.statSync(`${dirPath }/${ file}`).isDirectory()) {
arrayOfFiles = filesToChange(`${dirPath }/${ file}`, arrayOfFiles);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good use of recursion.

} else if (file.split(".").slice(-1)[0] === "jsx") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that you have an "if/else if" here, which implies that your function is actually doing more than one thing, and it might be helpful to split it into a function for each thing it's doing.

Here, I would say that the function filesToChange is doing two things:

  1. producing a list of all subpaths under a path
  2. filtering a list of (sub)paths to produce a list of all files which end with .jsx

One option would be to use a library (or a different programming language): https://github.com/isaacs/node-glob

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try to use the functions which are available to you, such as String.prototype.endsWith

arrayOfFiles.push(path.join(dirPath, "/", file));
}
});
return arrayOfFiles;
};
Copy link
Collaborator

@jennydaman jennydaman May 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's how I would've done it:

const fs = require('fs');
const path = require('path');

/**
 * @param {string} startingPath an existing path
 * @returns list of all subpaths under startingPath, including itself
 */
function findAllPaths(startingPath) {
  if (fs.statSync(startingPath).isDirectory()) {
    const subpaths = fs.readdirSync(startingPath).map((subpath) => {
      return findAllPaths(path.join(startingPath, subpath));
    });
    return Array.prototype.concat.apply([startingPath], subpaths);
  }
  return [startingPath];
}

// this helper function here has BOTH a self-explanatory name AND a simple implementation,
// so I am choosing to not add documentation
function filterByFileExtension(paths, suffix) {
  return paths.filter((p) => {
    return fs.statSync(p).isFile() && p.endsWith(suffix);
  });
}

// example

const exampleDirectory = 'ChRIS_store_ui/src';

filterByFileExtension(findAllPaths(exampleDirectory), '.jsx').forEach(p => {
  console.log(p);
});

I've identified the individual units of functionality of this task and created a helper for each one. This way, each function is individually testable, so if either one is doing the wrong thing, it's much easier to identify where the bug is in my code, and also the fix for my code will be much smaller. It can also be easier for others to read, understand, and review my code, because the names of my functions are descriptive and there is docstring where necessary.


// 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));
56 changes: 56 additions & 0 deletions src/scripts/cssToCssModule.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {convertCssString, convertCssClasses, convertSingleCssClass} from "./cssToCssModule";

describe('css module conversion script', () => {
it('should work with one class', () => {
const exampleData = '<div className="hello-world" />';
const expected = "<div className={`${styles['hello-world']}`} />";
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 = '<div className="hello-world bottom-footer" />';
const expected = "<div className={`hello-world ${styles['bottom-footer']}`} />";
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']");
});

});

85 changes: 85 additions & 0 deletions src/scripts/cssToCssModule_old.js
Original file line number Diff line number Diff line change
@@ -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));