Skip to content

Commit

Permalink
perf(icons): optimize icons, scripts, merge declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
zettca committed Aug 9, 2024
1 parent e62de84 commit b90634c
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 167 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ lerna-debug.log*
# Build
dist
bin
packages/icons/src
packages/icons/sprites
packages/icons/src/*
!packages/icons/src/IconBase.tsx
!packages/icons/src/IconSprite.tsx
!packages/icons/src/utils.ts

*.tgz
.nx

Expand Down
2 changes: 1 addition & 1 deletion packages/icons/assets/Check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/icons/assets/Contains.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 3 additions & 7 deletions packages/icons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@
"url": "https://github.com/lumada-design/hv-uikit-react/issues"
},
"scripts": {
"optimize": "svgo -r -f assets assets",
"convert": "tsx scripts/svgToReact.ts && tsx scripts/svgToSprite.ts",
"prebuild": "run-s clean convert",
"build": "vite build",
"postbuild": "npx cpy \"sprites\" \"dist\"",
"optimize": "svgo -r -f assets assets",
"preconvert": "npx rimraf src",
"convert": "run-s convert:*",
"convert:svg": "tsx scripts/svgToReact.ts",
"convert:sprites": "tsx scripts/svgToSprite.ts",
"convert:copy": "npx cpy \"lib/*\" \"src\"",
"clean": "npx rimraf dist src sprites package",
"clean": "npx rimraf dist sprites package",
"prepare": "npm run convert",
"prepublishOnly": "npm run build && npx clean-publish"
},
Expand Down
12 changes: 4 additions & 8 deletions packages/icons/scripts/generateComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,11 @@ export const generateComponent = (
iconName: string,
colorArray: string[],
viewBox: string,
basePath = "..",
) => {
const palette = colorArray.map((c) => `"${hexColorMap[c] || c}"`).join(",");
const finalOutput = /(\/>.*){2,}/.test(svgOutput)
? `<>${svgOutput}</>`
: svgOutput;

return `import { createHvIcon } from "${basePath}/IconBase";
export const ${iconName} = createHvIcon(
"${iconName}", "${viewBox}", [${palette}],
<>${svgOutput}</>,
);
`;
return `export const ${iconName} = createHvIcon("${iconName}", "${viewBox}", [${palette}], ${finalOutput});\n`;
};
202 changes: 54 additions & 148 deletions packages/icons/scripts/svgToReact.ts
Original file line number Diff line number Diff line change
@@ -1,173 +1,79 @@
/* eslint-disable no-console */
import fs from "node:fs";
import path from "node:path";
import fs from "node:fs/promises";
import { parse, resolve } from "node:path";
import { transform } from "@svgr/core";

import { generateComponent } from "./generateComponent";
import { extractColors, extractSize, replaceFill } from "./utils";

// Resolve arguments
const inputPath = "assets";
const outputPath = "src";

const componentOutputFolder = outputPath
? path.resolve(process.cwd(), outputPath)
: path.resolve(process.cwd());

const knownSubfolders: Record<string, boolean> = {};

const transformToJsx = (svgCode: string) => {
return transform.sync(svgCode, {
const transformToJsx = (fileData: string, iconName: string) => {
const svgJsx = transform.sync(fileData, {
plugins: ["@svgr/plugin-jsx"],
jsxRuntime: "automatic",
// we only care about the JSX part of the generated React component
template: (vars, { tpl }) => tpl`${vars.jsx}`,
});

// TODO: simplify this logic
const viewBox = extractSize(svgJsx);
const colorArray = extractColors(svgJsx);
const jsxSvgFixedColors = replaceFill(svgJsx, colorArray)
// remove svg tag, keeping only the content
.replace(/<svg.*?>(.*?)<\/svg>;/s, "$1");

// Wrap it up in a React component
return generateComponent(jsxSvgFixedColors, iconName, colorArray, viewBox);
};

const writeFile = (processedSVG: string, fileName: string, subFolder = ".") => {
fs.mkdirSync(path.resolve(componentOutputFolder, subFolder), {
recursive: true,
});
/** Converts all SVGs in `inputDir` to JSX & writes result to `outputFile` */
async function convertSvgFiles(inputDir: string, outputFile: string) {
const files = await fs.readdir(resolve(inputDir), { withFileTypes: true });
const svgFiles = files.filter((f) => f.isFile() && f.name.endsWith(".svg"));

const file = path.resolve(
componentOutputFolder,
subFolder,
`${fileName}.tsx`,
const data = await Promise.all(
svgFiles.map(async (f) => ({
name: `${parse(f.name).name}`.replace(/[-.]g/, ""),
data: await fs.readFile(resolve(inputDir, f.name), "utf-8"),
})),
);

fs.writeFile(file, processedSVG, { flag: "w" }, (err) => {
if (err) {
console.error(`Output file ${file} not writable ${err.code}`);
}
});
const headers = `import { createHvIcon } from "./IconBase";`;
const output = data.map((f) => transformToJsx(f.data, f.name)).join("");

const exportName = fileName.split(".").join("");
const exportString = `export { ${exportName} } from "./${fileName}";\n`;

if (subFolder === ".") {
fs.appendFile(
path.resolve(componentOutputFolder, `icons.ts`),
exportString,
() => {},
);
} else {
fs.appendFile(
path.resolve(componentOutputFolder, subFolder, `index.ts`),
exportString,
() => {},
);
}

if (!knownSubfolders[subFolder]) {
knownSubfolders[subFolder] = true;

if (subFolder === ".") {
fs.appendFile(
path.resolve(componentOutputFolder, `index.ts`),
[
`export * from "./IconBase";`,
`export * from "./IconSprite";`,
"\n",
`export * from "./icons";`,
`import * as icons from "./icons";`,
`export { icons };`,
"\n",
].join("\n"),
() => {},
);
} else {
const subFolderName = subFolder.replace("./", "");
fs.appendFile(
path.resolve(componentOutputFolder, subFolder, "..", `index.ts`),
[
`export * from "${subFolder}";`,
`import * as ${subFolderName} from "${subFolder}";`,
`export { ${subFolderName} };`,
"\n",
].join("\n"),
() => {},
);
}
}
};
await fs.writeFile(resolve(outputFile), `${headers}\n${output}`);
}

const runUtil = (
fileToRead: string,
iconName: string,
subFolder = ".",
depth = 0,
) => {
fs.readFile(fileToRead, "utf8", (err, fileData) => {
if (err) {
console.error(err);
return;
} // exit early

let output = transformToJsx(fileData);

const viewBox = extractSize(output);
const colorArray = extractColors(output);

output = replaceFill(output, colorArray)
// remove svg tag, keeping only the content
.replace(/<svg.*?>(.*?)<\/svg>;/s, "$1");

// Wrap it up in a React component
output = generateComponent(
output,
iconName,
colorArray,
viewBox,
`${".".repeat(depth + 1)}`,
);
writeFile(output, iconName, subFolder);
});
};
const makeHeaders = (name: string) => `
export * from "./${name}";
import * as ${name} from "./${name}";
export { ${name} };
`;

const isFolder = (dirPath: string) =>
fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
async function main() {
const inputPath = "assets";
const outputPath = "src";

const processFile = (file: string, subFolder = ".", depth = 0) => {
const extension = path.extname(file);
const fileName = path.basename(file, extension);
await fs.mkdir(resolve(outputPath), { recursive: true });

if (extension === ".svg") {
const componentName = fileName.replace(/[-.]g/, "");
runUtil(file, componentName, subFolder, depth);
}
};
await convertSvgFiles(inputPath, `${outputPath}/icons.tsx`);

const runUtilForJustFilesInDir = (
folder: string,
subFolder = ".",
depth = 0,
) => {
fs.readdir(folder, (err, files) => {
if (err) {
console.log(err);
return;
} // Get out early if not found

files.forEach((file) => {
const filePath = path.resolve(folder, file);
if (!isFolder(filePath)) {
processFile(filePath, subFolder, depth);
} else {
runUtilForJustFilesInDir(
filePath,
`${subFolder}/${path.basename(file)}`,
depth + 1,
);
}
});
});
};
const files = await fs.readdir(inputPath, { withFileTypes: true });
const subDirs = files.filter((f) => f.isDirectory()).map((f) => f.name);

await Promise.all(
subDirs.map((dir) =>
convertSvgFiles(`${inputPath}/${dir}`, `${outputPath}/${dir}.tsx`),
),
);

fs.mkdir(outputPath, { recursive: true }, (err) => {
if (err) throw err;
});
const allDirs = ["icons", ...subDirs];

fs.writeFile(path.resolve(process.cwd(), outputPath, `index.ts`), "", () => {});
const indexFile = `
export * from "./IconBase";
export * from "./IconSprite";
${allDirs.map(makeHeaders).join("")}
`;
await fs.writeFile(resolve("src/index.ts"), indexFile);
}

runUtilForJustFilesInDir(`${process.cwd()}/${inputPath}`);
main();
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/icons/svgo.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
multipass: true,
quiet: "true",
plugins: [
Expand Down

0 comments on commit b90634c

Please sign in to comment.