| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- // 从 @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('<svg', ~svg.indexOf('xmlns') ? '<svg' : '<svg xmlns="http://www.w3.org/2000/svg"')
- .replace(/"/g, "'")
- .replace(/%/g, '%25')
- .replace(/#/g, '%23')
- .replace(/{/g, '%7B')
- .replace(/}/g, '%7D')
- .replace(/</g, '%3C')
- .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 '<svg ' + svgAttributesStr + '>' + iconData.body + '</svg>'
- }
- 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)
- })
|