// 从 @iconify-json/ph 与 @iconify-json/solar 中抽取项目实际用到的图标,
// 生成一个精简的 iconify 数据文件,供 weapp-svg-icon 组件使用,避免全量打包。
import { readFile, readdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, '..')
const SRC_DIR = path.join(root, 'src')
const PKG_DIR = path.join(root, 'node_modules', 'weapp-svg-icon')
const OUTPUT = path.join(PKG_DIR, 'miniprogram_dist', 'index.js')
const ICON_PREFIXES = ['ph', 'solar']
const ICON_RE = /(?:^|[^\w])(ph|solar):([a-z0-9-]+)/g
const SCAN_EXTENSIONS = new Set(['.ts', '.js', '.wxml', '.vue', '.json'])
async function walk(dir) {
const entries = await readdir(dir, { withFileTypes: true })
const files = []
for (const entry of entries) {
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
const full = path.join(dir, entry.name)
if (entry.isDirectory()) files.push(...(await walk(full)))
else files.push(full)
}
return files
}
async function collectUsedIcons() {
const files = await walk(SRC_DIR)
const used = new Set()
for (const file of files) {
if (!SCAN_EXTENSIONS.has(path.extname(file))) continue
const content = await readFile(file, 'utf8')
for (const match of content.matchAll(ICON_RE)) {
used.add(`${match[1]}:${match[2]}`)
}
}
return [...used].sort()
}
async function loadIconifyJson(packageName) {
const p = path.join(root, 'node_modules', '@iconify-json', packageName, 'icons.json')
return JSON.parse(await readFile(p, 'utf8'))
}
async function main() {
const used = await collectUsedIcons()
const byPrefix = { ph: new Set(), solar: new Set() }
for (const id of used) {
const [prefix, icon] = id.split(':')
byPrefix[prefix]?.add(icon)
}
const sets = {}
const svgNameSwitch = (id) =>
id
.split(/:|-|_/)
.filter(Boolean)
.map((s) => s[0].toUpperCase() + s.slice(1).toLowerCase())
.join('')
for (const prefix of ICON_PREFIXES) {
const source = await loadIconifyJson(prefix)
const icons = {}
for (const icon of [...byPrefix[prefix]].sort()) {
if (source.icons[icon]) icons[icon] = source.icons[icon]
}
sets[svgNameSwitch(prefix)] = {
prefix: source.prefix ?? prefix,
width: source.width,
height: source.height,
icons,
}
}
const template = `// Generated by scripts/generate-svg-icons.mjs — DO NOT EDIT.
// Minimal iconify data extracted from @iconify-json/ph + @iconify-json/solar.
var iconifyPresets = ${JSON.stringify(sets)}
function svgNameSwitch(id) {
return id
.split(/:|-|_/)
.filter(Boolean)
.map(function (s) {
return s[0].toUpperCase() + s.slice(1).toLowerCase()
})
.join('')
}
function encodeSvg(svg) {
return svg
.replace('/g, '%3E')
}
function getDataUri(data) {
return 'url("data:image/svg+xml;utf8,' + data + '")'
}
function presetsIconify(id) {
var parts = id.split(':')
var prefix = ''
var icon = ''
if (parts.length === 2) {
prefix = svgNameSwitch(parts[0])
icon = parts[1]
} else if (parts.length === 1) {
parts = parts[0].split('-')
if (parts.length > 1) {
prefix = svgNameSwitch(parts.shift())
icon = parts.join('-')
}
}
var setData = iconifyPresets[prefix] || null
var width = setData && setData.width ? setData.width : 32
var height = setData && setData.height ? setData.height : 32
var iconData = setData && setData.icons && setData.icons[icon] ? setData.icons[icon] : null
if (!iconData) {
return ''
}
var svgAttributes = {
xmlns: 'http://www.w3.org/2000/svg',
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
width: '1em',
height: '1em',
preserveAspectRatio: 'xMidYMid meet',
viewBox: '0 0 ' + width + ' ' + height
}
var svgAttributesStr = Object.keys(svgAttributes)
.map(function (attr) {
return attr + '="' + svgAttributes[attr] + '"'
})
.join(' ')
return ''
}
function getLocalSVG(id) {
var name = svgNameSwitch(id)
var data = presetsIconify(id)
if (data) {
return getDataUri(encodeSvg(data))
}
data = wx.getStorageSync(name)
if (data) {
return getDataUri(data)
}
return ''
}
Component({
properties: {
name: {
type: String,
value: ''
},
color: {
type: String,
value: ''
},
size: {
type: String
}
},
data: {
width: '1em',
height: '1em',
iconUrl: '',
class: ''
},
lifetimes: {
attached: function attached() {
this.setData({
iconUrl: getLocalSVG(this.data.name),
class: this.data.color ? 'icon' : 'default-icon'
})
if (this.data.size) {
this.setData({
width: this.data.size,
height: this.data.size
})
}
}
}
})
`
await writeFile(OUTPUT, template, 'utf8')
console.log(`generated ${path.relative(root, OUTPUT)} with ${used.length} icons`)
console.log(used.join('\n'))
}
main().catch((err) => {
console.error(err)
process.exit(1)
})