// 从 @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 '' + iconData.body + '' } 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) })