|
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']) |
|
})() |
|
|