Skip to content

Instantly share code, notes, and snippets.

@ZempTime
Last active June 12, 2020 22:19
Show Gist options
  • Save ZempTime/e3241a66eac8f10228f484b3d6ee4820 to your computer and use it in GitHub Desktop.
Save ZempTime/e3241a66eac8f10228f484b3d6ee4820 to your computer and use it in GitHub Desktop.
Chameleon Conversion Script - place in root, then run with default vscode node launcher
const fs = require("fs");
const path = require("path");
/**
for each package
Convert src/* kebab-case to ClassCase
Look inside, remove any customElements.define usage
for each camelCase name not containing style or .d.ts or Index.js -
write a kebab-cased export that defines custom element
for each camelCase name not containing Index.js
export in root-level index.js
update test imports to refer to newly written customElement definitions
*/
const convert = async (packageDir) => {
const packages = await fs.readdirSync(packageDir);
const conversions = Promise.all(
packages.map((name) => convertPackage(packageDir, name))
);
await conversions;
console.log("converted");
};
const convertPackage = async (packageDir, packageName) => {
const srcPath = path.join(packageDir, packageName, "src");
if (!fs.existsSync(srcPath)) {
return;
}
// convert to CamelCase
const kebabs = fs.readdirSync(srcPath);
await convertDirToCamelCase(srcPath, kebabs);
const filenames = fs.readdirSync(srcPath);
// remove CustomElements definitions
await removeCustomElementDeclarations(srcPath, filenames);
// for each camelCase name not containing style or .d.ts -
// export in index.js
// create customElements def at root level
const customElementClasses = filenames
.filter((name) => !name.includes("Style"))
.filter((name) => !name.includes("Index"))
.filter((name) => !name.includes(".d.ts"));
await writeCustomElementImports(srcPath, customElementClasses);
const exportableClassFiles = filenames
.filter((name) => !name.includes(".d.ts"))
.filter((name) => !name.includes("Index"));
await writeClassExports(srcPath, exportableClassFiles);
// update test imports to pull in from customElements.define imports
// replaces ../src with import from relevant packages/${package}/index.js
await updateTestImports(srcPath, exportableClassFiles, customElementClasses);
};
const convertDirToCamelCase = async (dirPath, kebabs) => {
const renames = kebabs.map(async (kebabedName) => {
const newName = toCamelCase(kebabedName);
const oldPath = path.join(dirPath, kebabedName);
const newPath = path.join(dirPath, newName);
// console.log(`renaming ${oldPath} to ${newPath}`);
fs.renameSync(oldPath, newPath);
});
await Promise.all(renames);
};
// https://stackoverflow.com/questions/57556471/convert-kebab-case-to-camelcase-javascript
const toCamelCase = (str) => {
let arr = str.split("-");
let capital = arr.map((item, index) =>
index ? item.charAt(0).toUpperCase() + item.slice(1).toLowerCase() : item
);
// ^-- change here.
let capitalString = capital.join("");
return capitalString.replace(/^\w/, (c) => c.toUpperCase());
};
const removeCustomElementDeclarations = async (dirPath, filenames) => {
const removals = filenames.map(async (filename) => {
const filepath = path.join(dirPath, filename);
await fs.readFile(filepath, "utf8", (err, data) => {
if (err) throw err;
const filteredFile = data
.split("\n")
.filter((line) => !line.includes("window.customElements"))
.join("\n");
fs.writeFileSync(filepath, filteredFile, "utf8");
});
});
await Promise.all(removals);
};
// https://github.com/30-seconds/30-seconds-of-code/blob/master/snippets/toKebabCase.md
const toKebabCase = (str) =>
str &&
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map((x) => x.toLowerCase())
.join("-");
const writeCustomElementImports = async (srcPath, filenames) => {
const packagePath = `${srcPath}`.replace("/src", "");
const customElementDefinitionsToWrite = filenames.map(async (camelName) => {
const className = path.basename(camelName).split(".")[0];
const kebabName = toKebabCase(className);
const code = `import { ${className} } from './src/${camelName}';
customElements.define('${kebabName}', ${className});`;
const customElementDefinitionPath = path.join(packagePath, kebabName);
fs.writeFileSync(`${customElementDefinitionPath}.js`, code, "utf8");
});
await Promise.all(customElementDefinitionsToWrite);
};
const writeClassExports = async (srcPath, filenames) => {
const packagePath = `${srcPath}`.replace("/src", "");
const code = filenames
.map((filename) => {
const className = path.basename(filename).split(".")[0];
return `export { ${className} } from "./src/${className}.js";`;
})
.join("\n");
const indexPath = path.join(packagePath, "index.js");
fs.writeFileSync(indexPath, code, "utf8");
};
const updateTestImports = async (
srcPath,
classesToImport,
customElementsToImport
) => {
const packagePath = `${srcPath}`.replace("/src", "");
const testPath = path.join(packagePath, "__tests__");
const testFiles = fs.readdirSync(testPath);
const updates = testFiles.map(async (filename) => {
const filepath = path.join(testPath, filename);
await fs.readFile(filepath, "utf8", (err, data) => {
if (err) throw err;
const classNames = classesToImport.map((filename) => {
const withoutJs = `${filename}`.replace(".js", "");
return withoutJs;
});
const localClassImports = `import {${classNames.join(
", "
)}} from "../index.js\n`;
const customElementClasses = customElementsToImport.map((filename) => {
const withoutJs = `${filename}`.replace(".js", "");
return withoutJs;
});
const customElementDefinitionImports = customElementClasses
.map((camelName) => {
return `import "../${toKebabCase(camelName)}.js";\n`;
})
.join("");
const withoutPreviousImports = data
.split("\n")
.filter((line) => !line.includes("../src"))
.join("\n");
const updatedTestFile = [
localClassImports,
customElementDefinitionImports,
withoutPreviousImports,
].join("");
debugger;
fs.writeFileSync(filepath, updatedTestFile, "utf8");
});
});
await Promise.all(updates);
};
convert("./packages");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment