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

[Feature] svg 아이콘 컴포넌트 방식 변환 자동화 스크립트 작성 #31

Merged
merged 32 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
84d7adc
rename: scripts wow-ui 외부로 이동
ghdtjgus76 May 28, 2024
6edcbd0
chore: eslintcache 파일 삭제
ghdtjgus76 May 28, 2024
6ab2230
refactor: 상수값 위치 변경
ghdtjgus76 May 28, 2024
db49121
chore: tsx 의존성 루트로 이동해서 설치
ghdtjgus76 May 28, 2024
6f8a213
feat: svg 파일 리액트 컴포넌트 방식으로 변환하는 스크립트 작성
ghdtjgus76 May 28, 2024
7687ddc
feat: svg 파일 삭제 시 변환되어 있는 컴포넌트 파일도 삭제되는 로직 추가
ghdtjgus76 May 28, 2024
9cbab74
refactor: 스크립트 리팩토링
ghdtjgus76 May 29, 2024
807ab4a
chore: wow-icons 빌드 input 설정 변경
ghdtjgus76 May 29, 2024
6c14c6c
chore: 빌드 설정 변경
ghdtjgus76 May 29, 2024
2ca243f
chore: wow-icons 빌드 설정 변경
ghdtjgus76 May 29, 2024
15b5dd0
chore: 스크립트 수정
ghdtjgus76 May 29, 2024
1662cd8
chore: wow-ui 빌드 시 babel runtime external로 설정하도록 변경
ghdtjgus76 May 29, 2024
f173ef7
chore: root에 wowds-tokens dev dependency 설치
ghdtjgus76 May 29, 2024
feb0396
rename: theme 패키지 내부 폴더구조 정리
ghdtjgus76 May 29, 2024
3637b50
chore: theme 패키지 exports 속성 변경
ghdtjgus76 May 29, 2024
e9c6de7
chore: theme 패키지 tsconfig 수정
ghdtjgus76 May 29, 2024
5d29bbb
chore: 스크립트 수정 및 적용
ghdtjgus76 May 29, 2024
8aa7e42
chore: 스크립트 및 아이콘 코드 수정
ghdtjgus76 May 29, 2024
476fa30
chore: root tsconfig에 styled-system exclude하도록 설정
ghdtjgus76 May 29, 2024
e835760
chore: wow-ui 빌드 설정 변경
ghdtjgus76 May 29, 2024
d518a60
chore: wow-ui preserveModules 설정 추가
ghdtjgus76 May 29, 2024
988a436
chore: 스크립트 및 아이콘 수정
ghdtjgus76 May 29, 2024
87c8894
chore: wow-icons에 절대 경로 설정 적용
ghdtjgus76 May 30, 2024
8ea29c1
chore: 함수 이름 변경
ghdtjgus76 May 30, 2024
1ffb655
chore: 컴포넌트 디렉토리 존재하지 않는 경우 예외 처리
ghdtjgus76 May 30, 2024
fc4a5e3
chore: wow-icons 롤업 절대 경로 설정 추가
ghdtjgus76 May 30, 2024
8c1f90d
chore: 의존성 정리
ghdtjgus76 May 30, 2024
c1a56a8
chore: 아이콘 타입 추론 관련 설정 변경
ghdtjgus76 May 30, 2024
0704e9b
chore: stroke/fill 포함 여부에 따라 props 구성하도록 스크립트 수정
ghdtjgus76 May 30, 2024
7efae93
chore: 안 쓰는 import문 삭제
ghdtjgus76 May 30, 2024
4f29092
chore: format 스크립트 변경
ghdtjgus76 May 30, 2024
8d39402
chore: 이미 생성된 컴포넌트는 다시 쓰지 않도록 수정
ghdtjgus76 May 30, 2024
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
1 change: 0 additions & 1 deletion .eslintcache

This file was deleted.

1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
globals: {
React: true,
JSX: true,
console: true,
Comment on lines 30 to +31
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이건 자꾸 console이 정의되지 않은 거라고 되어 있어서 추가했습니다

},
env: {
browser: true,
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
"@changesets/changelog-git": "^0.2.0",
"@changesets/cli": "^2.27.1",
"@pandacss/dev": "^0.39.0",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-url": "^8.0.2",
"@svgr/rollup": "^8.1.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
Expand Down Expand Up @@ -69,11 +69,13 @@
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-preserve-directives": "^0.4.0",
"shared-config": "workspace:^",
"theme": "workspace:*",
"theme": "workspace:^",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tsx": "^4.11.0",
"turbo": "latest",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"wowds-tokens": "workspace:^"
},
"packageManager": "[email protected]",
"engines": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { promises as fs } from "fs";
import packageJSON from "package.json";
import path from "path";

import packageJSON from "../wow-ui/package.json";

type ExportItem =
| {
types: string;
Expand All @@ -13,8 +14,6 @@ type ExportObject = { [key: string]: ExportItem };
type EntryFileObject = { [key: string]: string };

const COMPONENT_PATH = "./src/components";
const ROLLUP_CONFIG_PATH = "rollup.config.js";
const PACKAGEJSON_PATH = "package.json";

const generateExports = (files: string[]) => {
const exportsObj: ExportObject = {
Expand All @@ -36,6 +35,8 @@ const generateExports = (files: string[]) => {
};

const applyExportsToPackageJSON = async (exportsObj: ExportObject) => {
const PACKAGEJSON_PATH = "package.json";

packageJSON.exports = packageJSON.exports || {};
Object.assign(packageJSON.exports, exportsObj);

Expand All @@ -53,6 +54,8 @@ const generateRollupEntryFiles = (files: string[]) => {
};

const applyEntryFilesToRollupConfig = async (entryFileObj: EntryFileObject) => {
const ROLLUP_CONFIG_PATH = "rollup.config.js";

const dirname = path.resolve();
const rollupConfigPath = path.join(dirname, ROLLUP_CONFIG_PATH);
let rollupConfigContent = await fs.readFile(rollupConfigPath, "utf-8");
Expand Down
140 changes: 140 additions & 0 deletions packages/scripts/generateReactComponentFromSvg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { existsSync, promises as fs } from "fs";
import path from "path";

const SVG_DIR = "../wow-icons/src/svg";
const COMPONENT_DIR = "../wow-icons/src/react";
Copy link
Member

Choose a reason for hiding this comment

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

image

디렉토리가 없어서 만들어주고 generate 해봤습니다!! 넘모 신기해요.
잘 되는데 요렇게 react 폴더 내부에 만들어지는걸 의도하신게 맞을까요?! 기존 UpArrow 컴포넌트 위치 때문에 쪼끔 헷갈렸네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아하 넵 일단은 svg 폴더 내부에 필요한 파일들 다 넣어두고 스크립트 실행시키면 react 폴더에 변환된 아이콘 + export 파일 생성되게 하는 걸 의도했어요
react 폴더 없는 경우는 고려하지 못한 거 같아서 그 부분도 코드 추가해서 올려놨습니다!


type SvgComponentMap = { [key: string]: string };

const generateSvgComponentMap = async () => {
const svgFiles = (await fs.readdir(SVG_DIR)).reduce<SvgComponentMap>(
(map, svgFile) => {
const componentName = path
.basename(svgFile, ".svg")
.replace(/(^\w|-\w)/g, (match) => match.replace("-", "").toUpperCase());
map[componentName] = svgFile;

return map;
},
{}
);

return svgFiles;
};

const deleteUnusedComponentFiles = async (svgComponentMap: SvgComponentMap) => {
if (!existsSync(COMPONENT_DIR)) {
fs.mkdir(COMPONENT_DIR);
return;
}

const componentFiles = await fs.readdir(COMPONENT_DIR);
const componentFilesToDelete = componentFiles.filter((componentFile) => {
const componentName = path.basename(componentFile, ".tsx");
return !(componentName in svgComponentMap);
});

await Promise.all(
componentFilesToDelete.map((file) => {
const componentFilePath = path.resolve(COMPONENT_DIR, file);
return fs.unlink(componentFilePath);
})
);
};

const createComponentContent = (
componentName: string,
svgContent: string,
svgFile: string
): string => {
const iconName = path.basename(svgFile, ".svg");
const hasStroke = svgContent.includes("stroke=");
const fillAttributes = (svgContent.match(/fill="([^"]*)"/g) || []).filter(
(attr) => attr !== 'fill="none"'
);
const hasFill = fillAttributes.length;
const propsString = `{ className, width = 24, height = 24, viewBox = "0 0 24 24"${hasStroke || hasFill ? ` ${hasStroke ? ', stroke = "white"' : ""}${hasFill ? ', fill = "white"' : ""}` : ""}, ...rest }`;
const modifiedSvgContent = svgContent
.replace(/-(\w)/g, (_, letter) => letter.toUpperCase())
.replace(/width="(\d+)"/g, `width={width}`)
.replace(/height="(\d+)"/g, `height={height}`)
.replace(/viewBox="(.*?)"/g, `viewBox={viewBox}`)
.replace(/<svg([^>]*)fill="[^"]*"([^>]*)>/, "<svg$1$2>")
.replace(/fill="([^"]+)"/g, `fill={color[fill]}`)
.replace(/stroke="([^"]+)"/g, `stroke={color[stroke]}`)
.replace(
/<svg([^>]*)>/,
`<svg$1 aria-label="${iconName} icon" fill="none" ref={ref} className={className} {...rest}>`
);

return `
import { forwardRef } from 'react';
import { color } from "wowds-tokens";

import type { IconProps } from "../types/Icon.ts";

const ${componentName} = forwardRef<SVGSVGElement, IconProps>(
(${propsString}, ref) => {
return (
${modifiedSvgContent}
);
}
);

${componentName}.displayName = '${componentName}';
export default ${componentName};
`;
};

const generateComponentFiles = async (svgComponentMap: SvgComponentMap) => {
const components: string[] = [];

for (const [componentName, svgFile] of Object.entries(svgComponentMap)) {
const componentFilePath = path.resolve(
COMPONENT_DIR,
`${componentName}.tsx`
);

if (existsSync(componentFilePath)) {
components.push(componentName);
continue;
}

const svgFilePath = path.resolve(SVG_DIR, svgFile);
const svgContent = (await fs.readFile(svgFilePath)).toString();

const componentContent = createComponentContent(
componentName,
svgContent,
svgFile
);

await fs.writeFile(componentFilePath, componentContent);
components.push(componentName);
}

return components;
};

const generateExportFile = async (components: string[]) => {
const EXPORT_FILE_PATH = "../wow-icons/src/react/index.ts";
const exportFileContent = components
.map(
(component) =>
`export { default as ${component} } from "./${component}.tsx";`
)
.join("\n");

await fs.writeFile(EXPORT_FILE_PATH, exportFileContent);
};

(async () => {
try {
const svgComponentMap = await generateSvgComponentMap();
await deleteUnusedComponentFiles(svgComponentMap);
const components = await generateComponentFiles(svgComponentMap);
await generateExportFile(components);
} catch (error) {
console.log("Error generating components:", error);
}
})();
2 changes: 1 addition & 1 deletion packages/shared-config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const baseConfig = {
format: "cjs",
},
],
external: ["react/jsx-runtime"],
external: ["react", "react-dom", "react/jsx-runtime"],
plugins: [
peerDepsExternal(),
resolve({ extensions }),
Expand Down
5 changes: 3 additions & 2 deletions packages/theme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./utils": "./utils/index.ts"
".": "./src/tokens/index.ts",
"./utils": "./src/utils/index.ts",
"./types": "./src/types/index.ts"
},
"devDependencies": {
"@pandacss/dev": "^0.39.0"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
99 changes: 99 additions & 0 deletions packages/theme/src/types/Color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
export type ColorToken =
| "red50"
| "red100"
| "red150"
| "red200"
| "red300"
| "red400"
| "red500"
| "red600"
| "red700"
| "red800"
| "red850"
| "red900"
| "red950"
| "blue50"
| "blue100"
| "blue150"
| "blue200"
| "blue300"
| "blue400"
| "blue500"
| "blue600"
| "blue700"
| "blue800"
| "blue850"
| "blue900"
| "blue950"
| "yellow50"
| "yellow100"
| "yellow150"
| "yellow200"
| "yellow300"
| "yellow400"
| "yellow500"
| "yellow600"
| "yellow700"
| "yellow800"
| "yellow850"
| "yellow900"
| "yellow950"
| "green50"
| "green100"
| "green150"
| "green200"
| "green300"
| "green400"
| "green500"
| "green600"
| "green700"
| "green800"
| "green850"
| "green900"
| "green950"
| "mono50"
| "mono100"
| "mono150"
| "mono200"
| "mono300"
| "mono400"
| "mono500"
| "mono600"
| "mono700"
| "mono800"
| "mono850"
| "mono900"
| "mono950"
| "white"
| "black"
| "whiteOpacity20"
| "whiteOpacity40"
| "whiteOpacity60"
| "whiteOpacity80"
| "blackOpacity20"
| "blackOpacity40"
| "blackOpacity60"
| "blackOpacity80"
| "primary"
| "success"
| "error"
| "backgroundNormal"
| "backgroundAlternative"
| "backgroundDimmer"
| "sub"
| "outline"
| "textBlack"
| "textWhite"
| "darkDisabled"
| "lightDisabled"
| "blueHover"
| "monoHover"
| "elevatedHover"
| "bluePressed"
| "blueBackgroundPressed"
| "monoBackgroundPressed"
| "shadowSmall"
| "shadowMedium"
| "blueShadow"
| "discord"
| "github";
1 change: 1 addition & 0 deletions packages/theme/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Color.ts";
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/theme/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"allowImportingTsExtensions": true,
"jsx": "react-jsx"
},
"include": ["src", "utils"],
"include": ["src"],
"exclude": ["node_modules"]
}
7 changes: 5 additions & 2 deletions packages/wow-icons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"types": "./dist/react/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "rm -rf dist && rollup -c --bundleConfigAsCjs && tsc --emitDeclarationOnly"
"build": "pnpm generate:icons && rm -rf dist && rollup -c --bundleConfigAsCjs && tsc --emitDeclarationOnly",
"generate:icons": "tsx ../scripts/generateReactComponentFromSvg.ts && pnpm format && pnpm lint",
"lint": "eslint --fix ./src/react/**/*.tsx",
"format": "prettier --write ./src/react/**/*"
},
"keywords": [],
"author": "gdsc-hongik",
Expand Down
5 changes: 3 additions & 2 deletions packages/wow-icons/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import baseConfig from "../shared-config/rollup.config.js";
import babel from "@rollup/plugin-babel";
import svgr from "@svgr/rollup";
import url from "@rollup/plugin-url";
import typescript from "@rollup/plugin-typescript";

const extensions = [".ts", ".tsx", ".js", ".jsx"];

export default {
...baseConfig,
input: "./src/react/index.ts",
plugins: [
...baseConfig.plugins,
babel({
Expand All @@ -15,6 +16,6 @@ export default {
babelHelpers: "runtime",
}),
url(),
svgr(),
typescript(),
],
};
Loading
Loading