script.mjs 5.2 KB

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