UNPKG

@toktokhan-dev/cli-plugin-gen-icon-chakra

Version:
203 lines (193 loc) 8.43 kB
import path from 'path'; import { defineCommand } from '@toktokhan-dev/cli'; import { convertFilePathToObject, readFileSync, cwd, generateCodeFile } from '@toktokhan-dev/node'; import { flatObject, awaited, prefix } from '@toktokhan-dev/universal'; import camelCase from 'lodash/camelCase.js'; import isUndefined from 'lodash/isUndefined.js'; import omitBy from 'lodash/omitBy.js'; import startCase from 'lodash/startCase.js'; import entries from 'lodash/fp/entries.js'; import flow from 'lodash/fp/flow.js'; import join from 'lodash/fp/join.js'; import map from 'lodash/fp/map.js'; import mapKeys from 'lodash/fp/mapKeys.js'; import replace from 'lodash/fp/replace.js'; import tail from 'lodash/fp/tail.js'; import trim from 'lodash/fp/trim.js'; import { parse, stringify } from 'svgson'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * 지정된 경로의 `svg`파일 기반으로 **Chakra UI Icon Component** 를 생성합니다. * * @packageDocumentation */ const PARENT_PROPS_FLAG = `parentProps="true"`; const STYLE_REGEX = /style="([^"]*)"/g; const isMatchStyle = (svgString) => STYLE_REGEX.test(svgString); const toPascalCase = (str) => startCase(camelCase(str)).replace(/ /g, ''); const parseElement = (element) => { const newElement = Object.assign(Object.assign({}, element), { attributes: mapKeys(camelCase)(element.attributes), children: element.children.map(parseElement) }); return newElement; }; const toJsxSyntaxV2 = (svg) => { const { children } = svg, rest = __rest(svg, ["children"]); const attributes = { viewBox: svg.attributes.viewBox, fill: svg.attributes.fill, parentProps: 'true', }; const transformed = Object.assign(Object.assign({}, rest), { name: 'Icon', attributes: omitBy(attributes, isUndefined), children: map(parseElement, children) }); return transformed; }; const toJsxSyntaxV3 = (svg) => { const { children } = svg, rest = __rest(svg, ["children"]); const attributes = { parentProps: 'true', }; const transformed = Object.assign(Object.assign({}, rest), { name: 'Icon', attributes: omitBy(attributes, isUndefined), children: [ { type: 'element', name: 'svg', value: '', attributes: { viewBox: svg.attributes.viewBox, fill: svg.attributes.fill, }, children: map(parseElement, children), }, ] }); return transformed; }; const convertStyleToJSX = (styleString) => { return styleString .split(';') .filter(trim) .map((style) => { const [key, value] = style.split(':').map(trim); return `${camelCase(key)}: '${value}'`; }) .join(', '); }; const styleToJsx = (svgString) => { if (!isMatchStyle(svgString)) return svgString; return svgString.replace(STYLE_REGEX, (_, styleString) => { return `style={{ ${convertStyleToJSX(styleString)} }}`; }); }; /** * @category Commands */ const genIcon = defineCommand({ name: 'gen:icon', description: 'Generate Chakra-UI Icon Component from svg files in the folder.', cliOptions: [ { name: 'input', alias: 'i', type: 'string', description: '조회할 svg 파일들이 포함되어있는 폴더 입니다.', }, { name: 'output', alias: 'o', type: 'string', description: '생성될 파일이 위치할 경로입니다.', }, { name: 'ignored', alias: 'ig', type: 'string[]', description: '제외 될 아이콘 컴포넌트 파일을 판별하는 패턴으로써, 파일이름이 패턴과 일치할 경우에 객체에서 제외 됩니다.', }, { name: 'version', alias: 'v', type: 'string', description: 'Chakra UI 버전입니다. "v2" 또는 "v3"를 지정할 수 있습니다.', }, ], default: { input: 'public', output: path.resolve('src', 'generated', 'icons', 'MyIcons.tsx'), ignored: ['*node_module*'], version: 'v2', }, run: (config) => __awaiter(void 0, void 0, void 0, function* () { const { // input, output, ignored, version = 'v2', } = config; if (!input) throw new Error('input is required'); if (!output) throw new Error('output is required'); const svgRegex = ['*.svg']; const pathObj = convertFilePathToObject({ includingPattern: svgRegex === null || svgRegex === void 0 ? void 0 : svgRegex.map((pattern) => path.join('**', pattern)), ignoredPattern: ignored, basePath: '', formatValue: (data) => { const svgContent = readFileSync('utf-8', path.join(input, data.path)); return svgContent; }, }, cwd(input)); const flatten = flatObject({ formatKey: (parentKey, currentKey) => { return toPascalCase([parentKey, currentKey].join(' ')); }, isValueType: (value) => { return typeof value === 'string'; }, }, pathObj); const toJsxSyntax = version === 'v2' ? toJsxSyntaxV2 : toJsxSyntaxV3; const transformSvgContent = (_a) => __awaiter(void 0, [_a], void 0, function* ([key, val]) { const transformed = yield flow(parse, awaited(flow(toJsxSyntax, stringify, styleToJsx, replace(PARENT_PROPS_FLAG, `{...props}`), (identity) => `export const ${key + 'Icon'} = (props: IconProps) => (${identity}); `)))(val); return [key, transformed]; }); flow(entries, map(transformSvgContent), (value) => Promise.all(value), awaited(map(tail)), awaited(flow(join('\n'), prefix(`import { Icon, IconProps } from '@chakra-ui/react';\n\n`))), awaited(generateCodeFile({ outputPath: output, prettier: { parser: 'babel-ts', configPath: 'auto', }, })))(flatten); }), }); export { genIcon };