From b90634c3a68818a64ab313ef5d04382998586464 Mon Sep 17 00:00:00 2001 From: Bruno Henriques Date: Fri, 9 Aug 2024 01:34:41 +0100 Subject: [PATCH] perf(icons): optimize icons, scripts, merge declarations --- .gitignore | 6 +- packages/icons/assets/Check.svg | 2 +- packages/icons/assets/Contains.svg | 2 +- packages/icons/package.json | 10 +- packages/icons/scripts/generateComponent.ts | 12 +- packages/icons/scripts/svgToReact.ts | 202 ++++++-------------- packages/icons/{lib => src}/IconBase.tsx | 0 packages/icons/{lib => src}/IconSprite.tsx | 0 packages/icons/{lib => src}/utils.ts | 0 packages/icons/svgo.config.js | 2 +- 10 files changed, 69 insertions(+), 167 deletions(-) rename packages/icons/{lib => src}/IconBase.tsx (100%) rename packages/icons/{lib => src}/IconSprite.tsx (100%) rename packages/icons/{lib => src}/utils.ts (100%) diff --git a/.gitignore b/.gitignore index a4b53e4e25..334267eda8 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/packages/icons/assets/Check.svg b/packages/icons/assets/Check.svg index 8f24f24594..30bbbbb089 100644 --- a/packages/icons/assets/Check.svg +++ b/packages/icons/assets/Check.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/icons/assets/Contains.svg b/packages/icons/assets/Contains.svg index 64e3266410..f32d325dc8 100644 --- a/packages/icons/assets/Contains.svg +++ b/packages/icons/assets/Contains.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/icons/package.json b/packages/icons/package.json index 96b3a96caa..e50a06cbb6 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -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" }, diff --git a/packages/icons/scripts/generateComponent.ts b/packages/icons/scripts/generateComponent.ts index 5a976ba30b..a034d294d9 100644 --- a/packages/icons/scripts/generateComponent.ts +++ b/packages/icons/scripts/generateComponent.ts @@ -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`; }; diff --git a/packages/icons/scripts/svgToReact.ts b/packages/icons/scripts/svgToReact.ts index c9c5dc87d4..70424438fc 100644 --- a/packages/icons/scripts/svgToReact.ts +++ b/packages/icons/scripts/svgToReact.ts @@ -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 = {}; - -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>;/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>;/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(); diff --git a/packages/icons/lib/IconBase.tsx b/packages/icons/src/IconBase.tsx similarity index 100% rename from packages/icons/lib/IconBase.tsx rename to packages/icons/src/IconBase.tsx diff --git a/packages/icons/lib/IconSprite.tsx b/packages/icons/src/IconSprite.tsx similarity index 100% rename from packages/icons/lib/IconSprite.tsx rename to packages/icons/src/IconSprite.tsx diff --git a/packages/icons/lib/utils.ts b/packages/icons/src/utils.ts similarity index 100% rename from packages/icons/lib/utils.ts rename to packages/icons/src/utils.ts diff --git a/packages/icons/svgo.config.js b/packages/icons/svgo.config.js index 8661cfff01..a376deb1e6 100644 --- a/packages/icons/svgo.config.js +++ b/packages/icons/svgo.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { multipass: true, quiet: "true", plugins: [