| const path = require('node:path') |
| const { open, readdir, access, mkdir, writeFile, appendFile, rm } = require('node:fs/promises') |
| const { parseXml } = require('@rgrove/parse-xml') |
| const camelCase = require('lodash/camelCase') |
| const template = require('lodash/template') |
|
|
| const generateDir = async (currentPath) => { |
| try { |
| await mkdir(currentPath, { recursive: true }) |
| } |
| catch (err) { |
| console.error(err.message) |
| } |
| } |
| const processSvgStructure = (svgStructure, replaceFillOrStrokeColor) => { |
| if (svgStructure?.children.length) { |
| svgStructure.children = svgStructure.children.filter(c => c.type !== 'text') |
|
|
| svgStructure.children.forEach((child) => { |
| if (child?.name === 'path' && replaceFillOrStrokeColor) { |
| if (child?.attributes?.stroke) |
| child.attributes.stroke = 'currentColor' |
|
|
| if (child?.attributes.fill) |
| child.attributes.fill = 'currentColor' |
| } |
| if (child?.children.length) |
| processSvgStructure(child, replaceFillOrStrokeColor) |
| }) |
| } |
| } |
| const generateSvgComponent = async (fileHandle, entry, pathList, replaceFillOrStrokeColor) => { |
| const currentPath = path.resolve(__dirname, 'src', ...pathList.slice(2)) |
|
|
| try { |
| await access(currentPath) |
| } |
| catch { |
| await generateDir(currentPath) |
| } |
|
|
| const svgString = await fileHandle.readFile({ encoding: 'utf8' }) |
| const svgJson = parseXml(svgString).toJSON() |
| const svgStructure = svgJson.children[0] |
| processSvgStructure(svgStructure, replaceFillOrStrokeColor) |
| const prefixFileName = camelCase(entry.split('.')[0]) |
| const fileName = prefixFileName.charAt(0).toUpperCase() + prefixFileName.slice(1) |
| const svgData = { |
| icon: svgStructure, |
| name: fileName, |
| } |
|
|
| const componentRender = template(` |
| // GENERATE BY script |
| // DON NOT EDIT IT MANUALLY |
| |
| import * as React from 'react' |
| import data from './<%= svgName %>.json' |
| import IconBase from '@/app/components/base/icons/IconBase' |
| import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' |
| |
| const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>(( |
| props, |
| ref, |
| ) => <IconBase {...props} ref={ref} data={data as IconData} />) |
| |
| Icon.displayName = '<%= svgName %>' |
| |
| export default Icon |
| `.trim()) |
|
|
| await writeFile(path.resolve(currentPath, `${fileName}.json`), JSON.stringify(svgData, '', '\t')) |
| await writeFile(path.resolve(currentPath, `${fileName}.tsx`), `${componentRender({ svgName: fileName })}\n`) |
|
|
| const indexingRender = template(` |
| export { default as <%= svgName %> } from './<%= svgName %>' |
| `.trim()) |
|
|
| await appendFile(path.resolve(currentPath, 'index.ts'), `${indexingRender({ svgName: fileName })}\n`) |
| } |
|
|
| const generateImageComponent = async (entry, pathList) => { |
| const currentPath = path.resolve(__dirname, 'src', ...pathList.slice(2)) |
|
|
| try { |
| await access(currentPath) |
| } |
| catch { |
| await generateDir(currentPath) |
| } |
|
|
| const prefixFileName = camelCase(entry.split('.')[0]) |
| const fileName = prefixFileName.charAt(0).toUpperCase() + prefixFileName.slice(1) |
|
|
| const componentCSSRender = template(` |
| .wrapper { |
| display: inline-flex; |
| background: url(<%= assetPath %>) center center no-repeat; |
| background-size: contain; |
| } |
| `.trim()) |
|
|
| await writeFile(path.resolve(currentPath, `${fileName}.module.css`), `${componentCSSRender({ assetPath: path.join('~@/app/components/base/icons/assets', ...pathList.slice(2), entry) })}\n`) |
|
|
| const componentRender = template(` |
| // GENERATE BY script |
| // DON NOT EDIT IT MANUALLY |
| |
| import * as React from 'react' |
| import cn from '@/utils/classnames' |
| import s from './<%= fileName %>.module.css' |
| |
| const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>(( |
| { className, ...restProps }, |
| ref, |
| ) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />) |
| |
| Icon.displayName = '<%= fileName %>' |
| |
| export default Icon |
| `.trim()) |
|
|
| await writeFile(path.resolve(currentPath, `${fileName}.tsx`), `${componentRender({ fileName })}\n`) |
|
|
| const indexingRender = template(` |
| export { default as <%= fileName %> } from './<%= fileName %>' |
| `.trim()) |
|
|
| await appendFile(path.resolve(currentPath, 'index.ts'), `${indexingRender({ fileName })}\n`) |
| } |
|
|
| const walk = async (entry, pathList, replaceFillOrStrokeColor) => { |
| const currentPath = path.resolve(...pathList, entry) |
| let fileHandle |
|
|
| try { |
| fileHandle = await open(currentPath) |
| const stat = await fileHandle.stat() |
|
|
| if (stat.isDirectory()) { |
| const files = await readdir(currentPath) |
|
|
| for (const file of files) |
| await walk(file, [...pathList, entry], replaceFillOrStrokeColor) |
| } |
|
|
| if (stat.isFile() && /.+\.svg$/g.test(entry)) |
| await generateSvgComponent(fileHandle, entry, pathList, replaceFillOrStrokeColor) |
|
|
| if (stat.isFile() && /.+\.png$/g.test(entry)) |
| await generateImageComponent(entry, pathList) |
| } |
| finally { |
| fileHandle?.close() |
| } |
| } |
|
|
| (async () => { |
| await rm(path.resolve(__dirname, 'src'), { recursive: true, force: true }) |
| await walk('public', [__dirname, 'assets']) |
| await walk('vender', [__dirname, 'assets'], true) |
| await walk('image', [__dirname, 'assets']) |
| })() |
|
|