generate-svg-icons.mjs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // 从 @iconify-json/ph 与 @iconify-json/solar 中抽取项目实际用到的图标,
  2. // 生成一个精简的 iconify 数据文件,供 weapp-svg-icon 组件使用,避免全量打包。
  3. import { readFile, readdir, writeFile } from 'node:fs/promises'
  4. import path from 'node:path'
  5. import { fileURLToPath } from 'node:url'
  6. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  7. const root = path.resolve(__dirname, '..')
  8. const SRC_DIR = path.join(root, 'src')
  9. const PKG_DIR = path.join(root, 'node_modules', 'weapp-svg-icon')
  10. const OUTPUT = path.join(PKG_DIR, 'miniprogram_dist', 'index.js')
  11. const ICON_PREFIXES = ['ph', 'solar']
  12. const ICON_RE = /(?:^|[^\w])(ph|solar):([a-z0-9-]+)/g
  13. const SCAN_EXTENSIONS = new Set(['.ts', '.js', '.wxml', '.vue', '.json'])
  14. async function walk(dir) {
  15. const entries = await readdir(dir, { withFileTypes: true })
  16. const files = []
  17. for (const entry of entries) {
  18. if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
  19. const full = path.join(dir, entry.name)
  20. if (entry.isDirectory()) files.push(...(await walk(full)))
  21. else files.push(full)
  22. }
  23. return files
  24. }
  25. async function collectUsedIcons() {
  26. const files = await walk(SRC_DIR)
  27. const used = new Set()
  28. for (const file of files) {
  29. if (!SCAN_EXTENSIONS.has(path.extname(file))) continue
  30. const content = await readFile(file, 'utf8')
  31. for (const match of content.matchAll(ICON_RE)) {
  32. used.add(`${match[1]}:${match[2]}`)
  33. }
  34. }
  35. return [...used].sort()
  36. }
  37. async function loadIconifyJson(packageName) {
  38. const p = path.join(root, 'node_modules', '@iconify-json', packageName, 'icons.json')
  39. return JSON.parse(await readFile(p, 'utf8'))
  40. }
  41. async function main() {
  42. const used = await collectUsedIcons()
  43. const byPrefix = { ph: new Set(), solar: new Set() }
  44. for (const id of used) {
  45. const [prefix, icon] = id.split(':')
  46. byPrefix[prefix]?.add(icon)
  47. }
  48. const sets = {}
  49. const svgNameSwitch = (id) =>
  50. id
  51. .split(/:|-|_/)
  52. .filter(Boolean)
  53. .map((s) => s[0].toUpperCase() + s.slice(1).toLowerCase())
  54. .join('')
  55. for (const prefix of ICON_PREFIXES) {
  56. const source = await loadIconifyJson(prefix)
  57. const icons = {}
  58. for (const icon of [...byPrefix[prefix]].sort()) {
  59. if (source.icons[icon]) icons[icon] = source.icons[icon]
  60. }
  61. sets[svgNameSwitch(prefix)] = {
  62. prefix: source.prefix ?? prefix,
  63. width: source.width,
  64. height: source.height,
  65. icons,
  66. }
  67. }
  68. const template = `// Generated by scripts/generate-svg-icons.mjs — DO NOT EDIT.
  69. // Minimal iconify data extracted from @iconify-json/ph + @iconify-json/solar.
  70. var iconifyPresets = ${JSON.stringify(sets)}
  71. function svgNameSwitch(id) {
  72. return id
  73. .split(/:|-|_/)
  74. .filter(Boolean)
  75. .map(function (s) {
  76. return s[0].toUpperCase() + s.slice(1).toLowerCase()
  77. })
  78. .join('')
  79. }
  80. function encodeSvg(svg) {
  81. return svg
  82. .replace('<svg', ~svg.indexOf('xmlns') ? '<svg' : '<svg xmlns="http://www.w3.org/2000/svg"')
  83. .replace(/"/g, "'")
  84. .replace(/%/g, '%25')
  85. .replace(/#/g, '%23')
  86. .replace(/{/g, '%7B')
  87. .replace(/}/g, '%7D')
  88. .replace(/</g, '%3C')
  89. .replace(/>/g, '%3E')
  90. }
  91. function getDataUri(data) {
  92. return 'url("data:image/svg+xml;utf8,' + data + '")'
  93. }
  94. function presetsIconify(id) {
  95. var parts = id.split(':')
  96. var prefix = ''
  97. var icon = ''
  98. if (parts.length === 2) {
  99. prefix = svgNameSwitch(parts[0])
  100. icon = parts[1]
  101. } else if (parts.length === 1) {
  102. parts = parts[0].split('-')
  103. if (parts.length > 1) {
  104. prefix = svgNameSwitch(parts.shift())
  105. icon = parts.join('-')
  106. }
  107. }
  108. var setData = iconifyPresets[prefix] || null
  109. var width = setData && setData.width ? setData.width : 32
  110. var height = setData && setData.height ? setData.height : 32
  111. var iconData = setData && setData.icons && setData.icons[icon] ? setData.icons[icon] : null
  112. if (!iconData) {
  113. return ''
  114. }
  115. var svgAttributes = {
  116. xmlns: 'http://www.w3.org/2000/svg',
  117. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  118. width: '1em',
  119. height: '1em',
  120. preserveAspectRatio: 'xMidYMid meet',
  121. viewBox: '0 0 ' + width + ' ' + height
  122. }
  123. var svgAttributesStr = Object.keys(svgAttributes)
  124. .map(function (attr) {
  125. return attr + '="' + svgAttributes[attr] + '"'
  126. })
  127. .join(' ')
  128. return '<svg ' + svgAttributesStr + '>' + iconData.body + '</svg>'
  129. }
  130. function getLocalSVG(id) {
  131. var name = svgNameSwitch(id)
  132. var data = presetsIconify(id)
  133. if (data) {
  134. return getDataUri(encodeSvg(data))
  135. }
  136. data = wx.getStorageSync(name)
  137. if (data) {
  138. return getDataUri(data)
  139. }
  140. return ''
  141. }
  142. Component({
  143. properties: {
  144. name: {
  145. type: String,
  146. value: ''
  147. },
  148. color: {
  149. type: String,
  150. value: ''
  151. },
  152. size: {
  153. type: String
  154. }
  155. },
  156. data: {
  157. width: '1em',
  158. height: '1em',
  159. iconUrl: '',
  160. class: ''
  161. },
  162. lifetimes: {
  163. attached: function attached() {
  164. this.setData({
  165. iconUrl: getLocalSVG(this.data.name),
  166. class: this.data.color ? 'icon' : 'default-icon'
  167. })
  168. if (this.data.size) {
  169. this.setData({
  170. width: this.data.size,
  171. height: this.data.size
  172. })
  173. }
  174. }
  175. }
  176. })
  177. `
  178. await writeFile(OUTPUT, template, 'utf8')
  179. console.log(`generated ${path.relative(root, OUTPUT)} with ${used.length} icons`)
  180. console.log(used.join('\n'))
  181. }
  182. main().catch((err) => {
  183. console.error(err)
  184. process.exit(1)
  185. })