script.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import path from 'node:path'
  2. import { access, appendFile, mkdir, open, readdir, rm, writeFile } from 'node:fs/promises'
  3. import { parseXml } from '@rgrove/parse-xml'
  4. import { camelCase, template } from 'lodash-es'
  5. const __dirname = path.dirname(new URL(import.meta.url).pathname)
  6. const generateDir = async (currentPath) => {
  7. try {
  8. await mkdir(currentPath, { recursive: true })
  9. }
  10. catch (err) {
  11. console.error(err.message)
  12. }
  13. }
  14. const processSvgStructure = (svgStructure, replaceFillOrStrokeColor) => {
  15. if (svgStructure?.children.length) {
  16. svgStructure.children = svgStructure.children.filter(c => c.type !== 'text')
  17. svgStructure.children.forEach((child) => {
  18. if (child?.name === 'path' && replaceFillOrStrokeColor) {
  19. if (child?.attributes?.stroke)
  20. child.attributes.stroke = 'currentColor'
  21. if (child?.attributes.fill)
  22. child.attributes.fill = 'currentColor'
  23. }
  24. if (child?.children.length)
  25. processSvgStructure(child, replaceFillOrStrokeColor)
  26. })
  27. }
  28. }
  29. const generateSvgComponent = async (fileHandle, entry, pathList, replaceFillOrStrokeColor) => {
  30. const currentPath = path.resolve(__dirname, 'src', ...pathList.slice(2))
  31. try {
  32. await access(currentPath)
  33. }
  34. catch {
  35. await generateDir(currentPath)
  36. }
  37. const svgString = await fileHandle.readFile({ encoding: 'utf8' })
  38. const svgJson = parseXml(svgString).toJSON()
  39. const svgStructure = svgJson.children[0]
  40. processSvgStructure(svgStructure, replaceFillOrStrokeColor)
  41. const prefixFileName = camelCase(entry.split('.')[0])
  42. const fileName = prefixFileName.charAt(0).toUpperCase() + prefixFileName.slice(1)
  43. const svgData = {
  44. icon: svgStructure,
  45. name: fileName,
  46. }
  47. const componentRender = template(`
  48. // GENERATE BY script
  49. // DON NOT EDIT IT MANUALLY
  50. import * as React from 'react'
  51. import data from './<%= svgName %>.json'
  52. import IconBase from '@/app/components/base/icons/IconBase'
  53. import type { IconData } from '@/app/components/base/icons/IconBase'
  54. const Icon = (
  55. {
  56. ref,
  57. ...props
  58. }: React.SVGProps<SVGSVGElement> & {
  59. ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
  60. },
  61. ) => <IconBase {...props} ref={ref} data={data as IconData} />
  62. Icon.displayName = '<%= svgName %>'
  63. export default Icon
  64. `.trim())
  65. await writeFile(path.resolve(currentPath, `${fileName}.json`), JSON.stringify(svgData, '', '\t'))
  66. await writeFile(path.resolve(currentPath, `${fileName}.tsx`), `${componentRender({ svgName: fileName })}\n`)
  67. const indexingRender = template(`
  68. export { default as <%= svgName %> } from './<%= svgName %>'
  69. `.trim())
  70. await appendFile(path.resolve(currentPath, 'index.ts'), `${indexingRender({ svgName: fileName })}\n`)
  71. }
  72. const generateImageComponent = async (entry, pathList) => {
  73. const currentPath = path.resolve(__dirname, 'src', ...pathList.slice(2))
  74. try {
  75. await access(currentPath)
  76. }
  77. catch {
  78. await generateDir(currentPath)
  79. }
  80. const prefixFileName = camelCase(entry.split('.')[0])
  81. const fileName = prefixFileName.charAt(0).toUpperCase() + prefixFileName.slice(1)
  82. const componentCSSRender = template(`
  83. .wrapper {
  84. display: inline-flex;
  85. background: url(<%= assetPath %>) center center no-repeat;
  86. background-size: contain;
  87. }
  88. `.trim())
  89. await writeFile(path.resolve(currentPath, `${fileName}.module.css`), `${componentCSSRender({ assetPath: path.join('~@/app/components/base/icons/assets', ...pathList.slice(2), entry) })}\n`)
  90. const componentRender = template(`
  91. // GENERATE BY script
  92. // DON NOT EDIT IT MANUALLY
  93. import * as React from 'react'
  94. import cn from '@/utils/classnames'
  95. import s from './<%= fileName %>.module.css'
  96. const Icon = (
  97. {
  98. ref,
  99. className,
  100. ...restProps
  101. }: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
  102. ref?: React.RefObject<HTMLSpanElement>;
  103. },
  104. ) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
  105. Icon.displayName = '<%= fileName %>'
  106. export default Icon
  107. `.trim())
  108. await writeFile(path.resolve(currentPath, `${fileName}.tsx`), `${componentRender({ fileName })}\n`)
  109. const indexingRender = template(`
  110. export { default as <%= fileName %> } from './<%= fileName %>'
  111. `.trim())
  112. await appendFile(path.resolve(currentPath, 'index.ts'), `${indexingRender({ fileName })}\n`)
  113. }
  114. const walk = async (entry, pathList, replaceFillOrStrokeColor) => {
  115. const currentPath = path.resolve(...pathList, entry)
  116. let fileHandle
  117. try {
  118. fileHandle = await open(currentPath)
  119. const stat = await fileHandle.stat()
  120. if (stat.isDirectory()) {
  121. const files = await readdir(currentPath)
  122. for (const file of files)
  123. await walk(file, [...pathList, entry], replaceFillOrStrokeColor)
  124. }
  125. if (stat.isFile() && /.+\.svg$/g.test(entry))
  126. await generateSvgComponent(fileHandle, entry, pathList, replaceFillOrStrokeColor)
  127. if (stat.isFile() && /.+\.png$/g.test(entry))
  128. await generateImageComponent(entry, pathList)
  129. }
  130. finally {
  131. fileHandle?.close()
  132. }
  133. }
  134. (async () => {
  135. await rm(path.resolve(__dirname, 'src'), { recursive: true, force: true })
  136. await walk('public', [__dirname, 'assets'])
  137. await walk('vender', [__dirname, 'assets'], true)
  138. await walk('image', [__dirname, 'assets'])
  139. })()