feat: 离线图标+在线图标
parent
256884d15b
commit
c117a90750
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 25 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10a10 10 0 0 0 10-10c0-5.53-4.5-10-10-10M6.76 8.82l1.06-1.06l1.06 1.06l1.06-1.06L11 8.82L9.94 9.88L11 10.94L9.94 12l-1.06-1.06L7.82 12l-1.06-1.06l1.06-1.06l-1.06-1.06m.13 8.68C7.69 15.46 9.67 14 12 14c2.33 0 4.31 1.46 5.11 3.5H6.89m10.35-6.56L16.18 12l-1.06-1.06L14.06 12L13 10.94l1.06-1.06L13 8.82l1.06-1.06l1.06 1.06l1.06-1.06l1.06 1.06l-1.06 1.06l1.06 1.06Z"/></svg>
|
After Width: | Height: | Size: 517 B |
@ -0,0 +1 @@
|
||||
保持此文件夹,用于生成文件
|
@ -0,0 +1,18 @@
|
||||
import { addCollection } from '@iconify/vue'
|
||||
import antdesignIcons from '@iconify/json/json/ant-design.json'
|
||||
import gridiconsIcons from '@iconify/json/json/gridicons.json'
|
||||
import mdiIcons from '@iconify/json/json/mdi.json'
|
||||
import ionIcons from '@iconify/json/json/ion.json'
|
||||
import carbonIcons from '@iconify/json/json/carbon.json'
|
||||
import emojioneIcons from '@iconify/json/json/emojione.json'
|
||||
|
||||
import CustomSvgJSON from '/build/_generated/svg.json'
|
||||
|
||||
addCollection(antdesignIcons)
|
||||
addCollection(gridiconsIcons)
|
||||
addCollection(mdiIcons)
|
||||
addCollection(ionIcons)
|
||||
addCollection(carbonIcons)
|
||||
addCollection(emojioneIcons)
|
||||
|
||||
addCollection(CustomSvgJSON)
|
@ -0,0 +1,15 @@
|
||||
import antdesignIcons from '@iconify/json/json/ant-design.json'
|
||||
import gridiconsIcons from '@iconify/json/json/gridicons.json'
|
||||
import mdiIcons from '@iconify/json/json/mdi.json'
|
||||
import ionIcons from '@iconify/json/json/ion.json'
|
||||
import carbonIcons from '@iconify/json/json/carbon.json'
|
||||
import emojioneIcons from '@iconify/json/json/emojione.json'
|
||||
|
||||
import CustomSvgJSON from '/build/_generated/svg.json'
|
||||
|
||||
const collections = [antdesignIcons, gridiconsIcons, mdiIcons, ionIcons, carbonIcons, emojioneIcons, CustomSvgJSON]
|
||||
const ret: string[] = []
|
||||
collections.forEach((item) => {
|
||||
ret.push(...Object.keys(item.icons).map(key => `${item.prefix}:${key}`))
|
||||
})
|
||||
export default ret
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"icons": {},
|
||||
"prefix": "nl-svg"
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
/**
|
||||
* This is an advanced example for creating icon bundles for Iconify SVG Framework.
|
||||
*
|
||||
* It creates a bundle from:
|
||||
* - All SVG files in a directory.
|
||||
* - Custom JSON files.
|
||||
* - Iconify icon sets.
|
||||
* - SVG framework.
|
||||
*
|
||||
* This example uses Iconify Tools to import and clean up icons.
|
||||
* For Iconify Tools documentation visit https://docs.iconify.design/tools/tools2/
|
||||
*/
|
||||
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { dirname } from 'node:path'
|
||||
import type { IconifyJSON, IconifyMetaData } from '@iconify/types'
|
||||
|
||||
// Installation: npm install --save-dev @iconify/tools @iconify/utils @iconify/json @iconify/iconify
|
||||
import {
|
||||
cleanupSVG,
|
||||
importDirectory,
|
||||
isEmptyColor,
|
||||
parseColors,
|
||||
runSVGO,
|
||||
} from '@iconify/tools'
|
||||
import { getIcons, minifyIconSet, stringToIcon } from '@iconify/utils'
|
||||
|
||||
import { buildUtilsReadFile, buildUtilsWriteFile } from '../../utils'
|
||||
import {
|
||||
IconLog,
|
||||
SvgPrefix,
|
||||
iconBundlePath,
|
||||
iconListPath,
|
||||
iconSVGPath,
|
||||
} from '../src'
|
||||
|
||||
/**
|
||||
* Script configuration
|
||||
*/
|
||||
interface BundleScriptCustomSVGConfig {
|
||||
// Path to SVG files
|
||||
dir: string
|
||||
|
||||
// True if icons should be treated as monotone: colors replaced with currentColor
|
||||
monotone: boolean
|
||||
|
||||
// Icon set prefix
|
||||
prefix: string
|
||||
}
|
||||
|
||||
interface BundleScriptCustomJSONConfig {
|
||||
// Path to JSON file
|
||||
filename: string
|
||||
|
||||
// List of icons to import. If missing, all icons will be imported
|
||||
icons?: string[]
|
||||
}
|
||||
|
||||
interface BundleScriptConfig {
|
||||
// Custom SVG to import and bundle
|
||||
svg?: BundleScriptCustomSVGConfig[]
|
||||
|
||||
// Icons to bundled from @iconify/json packages
|
||||
icons?: string[]
|
||||
|
||||
// List of JSON files to bundled
|
||||
// Entry can be a string, pointing to filename or a BundleScriptCustomJSONConfig object (see type above)
|
||||
// If entry is a string or object without 'icons' property, an entire JSON file will be bundled
|
||||
json?: (string | BundleScriptCustomJSONConfig)[]
|
||||
}
|
||||
|
||||
// Iconify component (this changes import statement in generated file)
|
||||
// Available options: '@iconify/react' for React, '@iconify/vue' for Vue 3, '@iconify/vue2' for Vue 2, '@iconify/svelte' for Svelte
|
||||
const component = '@iconify/vue'
|
||||
|
||||
// Set to true to use require() instead of import
|
||||
const CommonJS = false
|
||||
|
||||
// File to save bundle to
|
||||
const target = iconBundlePath
|
||||
|
||||
/**
|
||||
* Do stuff!
|
||||
*/
|
||||
export async function generateIconUsedBundle() {
|
||||
const iconPools = await buildUtilsReadFile(iconListPath)
|
||||
|
||||
const allIconsArr = Array.from<string>(
|
||||
JSON.parse(iconPools.replace('export default ', '')),
|
||||
)
|
||||
|
||||
const sources: BundleScriptConfig = {
|
||||
// svg: [
|
||||
// {
|
||||
// dir: 'svg',
|
||||
// monotone: true,
|
||||
// prefix: 'svg',
|
||||
// },
|
||||
// {
|
||||
// dir: 'emojis',
|
||||
// monotone: false,
|
||||
// prefix: 'emoji',
|
||||
// },
|
||||
// ],
|
||||
|
||||
icons: allIconsArr,
|
||||
|
||||
// json: [
|
||||
// // Custom JSON file
|
||||
// 'json/gg.json',
|
||||
// // Iconify JSON file (@iconify/json is a package name, /json/ is directory where files are, then filename)
|
||||
// require.resolve('@iconify/json/json/tabler.json'),
|
||||
// // Custom file with only few icons
|
||||
// {
|
||||
// filename: require.resolve('@iconify/json/json/line-md.json'),
|
||||
// icons: [
|
||||
// 'home-twotone-alt',
|
||||
// 'github',
|
||||
// 'document-list',
|
||||
// 'document-code',
|
||||
// 'image-twotone',
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
|
||||
let bundle = CommonJS
|
||||
? `const { addCollection } = require('${component}');\n\n`
|
||||
: `import { addCollection } from '${component}';\n\n`
|
||||
|
||||
// Create directory for output if missing
|
||||
const dir = dirname(target)
|
||||
try {
|
||||
await fs.mkdir(dir, {
|
||||
recursive: true,
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert sources.icons to sources.json
|
||||
*/
|
||||
if (sources.icons) {
|
||||
const sourcesJSON = sources.json ? sources.json : (sources.json = [])
|
||||
|
||||
// Sort icons by prefix
|
||||
const organizedList = organizeIconsList(sources.icons)
|
||||
for (const prefix in organizedList) {
|
||||
if (prefix !== SvgPrefix) {
|
||||
const filename = require.resolve(`@iconify/json/json/${prefix}.json`)
|
||||
sourcesJSON.push({
|
||||
filename,
|
||||
icons: organizedList[prefix],
|
||||
})
|
||||
}
|
||||
else {
|
||||
sourcesJSON.push({
|
||||
filename: iconSVGPath,
|
||||
icons: organizedList[prefix],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle JSON files
|
||||
*/
|
||||
if (sources.json) {
|
||||
for (let i = 0; i < sources.json.length; i++) {
|
||||
const item = sources.json[i]
|
||||
|
||||
// Load icon set
|
||||
const filename = typeof item === 'string' ? item : item.filename
|
||||
let content = JSON.parse(
|
||||
await buildUtilsReadFile(filename),
|
||||
) as IconifyJSON
|
||||
|
||||
// Filter icons
|
||||
if (typeof item !== 'string' && item.icons?.length) {
|
||||
const filteredContent = getIcons(content, item.icons)
|
||||
if (!filteredContent)
|
||||
throw new Error(`Cannot find required icons in ${filename}`)
|
||||
|
||||
content = filteredContent
|
||||
}
|
||||
|
||||
// Remove metadata and add to bundle
|
||||
removeMetaData(content)
|
||||
minifyIconSet(content)
|
||||
bundle += `addCollection(${JSON.stringify(content)});\n`
|
||||
IconLog('Icon Bundle', `Bundled icons from ${filename}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom SVG
|
||||
*/
|
||||
if (sources.svg) {
|
||||
for (let i = 0; i < sources.svg.length; i++) {
|
||||
const source = sources.svg[i]
|
||||
|
||||
// Import icons
|
||||
const iconSet = await importDirectory(source.dir, {
|
||||
prefix: source.prefix,
|
||||
})
|
||||
|
||||
// Validate, clean up, fix palette and optimise
|
||||
await iconSet.forEach(async (name, type) => {
|
||||
if (type !== 'icon')
|
||||
return
|
||||
|
||||
// Get SVG instance for parsing
|
||||
const svg = iconSet.toSVG(name)
|
||||
if (!svg) {
|
||||
// Invalid icon
|
||||
iconSet.remove(name)
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up and optimise icons
|
||||
try {
|
||||
// Clean up icon code
|
||||
await cleanupSVG(svg)
|
||||
|
||||
if (source.monotone) {
|
||||
// Replace color with currentColor, add if missing
|
||||
// If icon is not monotone, remove this code
|
||||
await parseColors(svg, {
|
||||
defaultColor: 'currentColor',
|
||||
callback: (attr, colorStr, color) => {
|
||||
return !color || isEmptyColor(color) ? colorStr : 'currentColor'
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Optimise
|
||||
runSVGO(svg)
|
||||
}
|
||||
catch (err) {
|
||||
// Invalid icon
|
||||
console.error(`Error parsing ${name} from ${source.dir}:`, err)
|
||||
iconSet.remove(name)
|
||||
return
|
||||
}
|
||||
|
||||
// Update icon from SVG instance
|
||||
iconSet.fromSVG(name, svg)
|
||||
})
|
||||
console.log(`Bundled ${iconSet.count()} icons from ${source.dir}`)
|
||||
|
||||
// Export to JSON
|
||||
const content = iconSet.export()
|
||||
bundle += `addCollection(${JSON.stringify(content)});\n`
|
||||
}
|
||||
}
|
||||
|
||||
// Save to file
|
||||
await buildUtilsWriteFile(target, bundle)
|
||||
|
||||
IconLog(
|
||||
'Icon Bundle',
|
||||
`Saved bundle icons at: ${target} (${Number(bundle.length / 1024).toFixed(
|
||||
2,
|
||||
)} KB)`,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove metadata from icon set
|
||||
*/
|
||||
function removeMetaData(iconSet: IconifyJSON) {
|
||||
const props: (keyof IconifyMetaData)[] = [
|
||||
'info',
|
||||
'chars',
|
||||
'categories',
|
||||
'themes',
|
||||
'prefixes',
|
||||
'suffixes',
|
||||
]
|
||||
props.forEach((prop) => {
|
||||
delete iconSet[prop]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort icon names by prefix
|
||||
*/
|
||||
function organizeIconsList(icons: string[]): Record<string, string[]> {
|
||||
const sorted: Record<string, string[]> = Object.create(null)
|
||||
icons.forEach((icon) => {
|
||||
const item = stringToIcon(icon)
|
||||
if (!item)
|
||||
return
|
||||
|
||||
const prefix = item.prefix
|
||||
const prefixList = sorted[prefix] ? sorted[prefix] : (sorted[prefix] = [])
|
||||
|
||||
const name = item.name
|
||||
if (!prefixList.includes(name))
|
||||
prefixList.push(name)
|
||||
})
|
||||
|
||||
return sorted
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { buildUtilsWriteFile } from '../../utils'
|
||||
import {
|
||||
IconBundleConfig,
|
||||
SvgPrefix,
|
||||
iconBundlePath,
|
||||
iconListPath,
|
||||
iconSVGPath,
|
||||
} from '../src'
|
||||
|
||||
export async function generateIconDev() {
|
||||
const names = IconBundleConfig.list.filter(i => i !== SvgPrefix)
|
||||
|
||||
const JSONName = (i: string) => `${i.replace('-', '')}Icons`
|
||||
|
||||
const customJSONName = 'CustomSvgJSON'
|
||||
|
||||
const importJSON = names.map(
|
||||
i =>
|
||||
`import ${JSONName(i)
|
||||
} from '@iconify/json/json/${i}.json' \n`,
|
||||
)
|
||||
|
||||
const addCollections = names.map(
|
||||
i => `addCollection(${JSONName(i)}) \n`,
|
||||
)
|
||||
|
||||
const generateListFromJSON = `const collections = [${names.map(i => JSONName(i))}, ${customJSONName}];
|
||||
const ret: string[] = []
|
||||
collections.forEach((item) => {
|
||||
ret.push(...Object.keys(item.icons).map(key => \`\${item.prefix}:\${key}\`))
|
||||
})
|
||||
export default ret
|
||||
`
|
||||
|
||||
const addImport = 'import { addCollection } from \'@iconify/vue\''
|
||||
|
||||
const importString = `
|
||||
${importJSON.join('')}
|
||||
import ${customJSONName} from '/${iconSVGPath}'\n
|
||||
`
|
||||
|
||||
const addCollection = `${addCollections.join('')}
|
||||
addCollection(${customJSONName})`
|
||||
|
||||
// addCollection bundle
|
||||
await buildUtilsWriteFile(iconBundlePath, addImport + importString + addCollection)
|
||||
|
||||
// generate icon list
|
||||
await buildUtilsWriteFile(iconListPath, importString + generateListFromJSON)
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import fg from 'fast-glob'
|
||||
import { IconSet } from '@iconify/tools'
|
||||
import type { IconifyJSON } from '@iconify/types'
|
||||
|
||||
import { buildUtilsReadFile, buildUtilsWriteFile } from '../../utils'
|
||||
|
||||
import {
|
||||
IconBundleConfig,
|
||||
IconLog,
|
||||
SvgPrefix,
|
||||
iconListPath,
|
||||
iconSVGPath,
|
||||
} from '../src'
|
||||
|
||||
export async function getIconListAllArray() {
|
||||
let arr: string[] = []
|
||||
|
||||
const allCollectionPaths = await fg(
|
||||
'node_modules/@iconify/json/json/*.json',
|
||||
{},
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
IconBundleConfig.list.map(async (i) => {
|
||||
const res = allCollectionPaths
|
||||
.map(path => path.split('/').slice(-1)[0])
|
||||
.filter(p => p === `${i}.json`)
|
||||
|
||||
const file = await buildUtilsReadFile(
|
||||
i === SvgPrefix
|
||||
? iconSVGPath
|
||||
: `node_modules/@iconify/json/json/${res[0]}`,
|
||||
)
|
||||
|
||||
const fileJSON: IconifyJSON = JSON.parse(file.toString())
|
||||
|
||||
const iconSet = new IconSet(fileJSON)
|
||||
|
||||
const iconArr = iconSet.list().map(i => `${fileJSON.prefix}:${i}`)
|
||||
|
||||
arr = arr.concat(iconArr)
|
||||
}),
|
||||
)
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
export async function generateIconListAll() {
|
||||
const arr = await getIconListAllArray()
|
||||
|
||||
const str = JSON.stringify(arr)
|
||||
|
||||
await buildUtilsWriteFile(iconListPath, `export default ${str}`)
|
||||
|
||||
IconLog(
|
||||
'Icon List',
|
||||
`Generating icon list... Total number: ${arr.length}, writing into file: ${iconListPath}`,
|
||||
)
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import fg from 'fast-glob'
|
||||
|
||||
import { buildUtilsReadFile, buildUtilsWriteFile } from '../../utils'
|
||||
import { IconLog, iconListPath } from '../src/index'
|
||||
import { getIconListAllArray } from './icon-list-all'
|
||||
|
||||
export async function generateIconListScan() {
|
||||
const iconPool = await getIconListAllArray()
|
||||
|
||||
const files = await fg('src/**/*.{vue,ts,tsx}', { dot: true })
|
||||
|
||||
IconLog(
|
||||
'Icon Scan',
|
||||
`Scanning all 'vue, ts, tsx' files under 'src'. File number: ${files.length}`,
|
||||
)
|
||||
|
||||
const buffers = await Promise.all(files.map(i => buildUtilsReadFile(i)))
|
||||
|
||||
const ret = [
|
||||
...new Set(
|
||||
buffers
|
||||
.map(i =>
|
||||
iconPool
|
||||
.map(q => i.toString().match(new RegExp(q, 'gm')))
|
||||
.filter(i => i),
|
||||
)
|
||||
.filter(i => i.length !== 0)
|
||||
.flat(Number.POSITIVE_INFINITY),
|
||||
),
|
||||
]
|
||||
|
||||
await buildUtilsWriteFile(
|
||||
iconListPath,
|
||||
`export default ${JSON.stringify(ret)}`,
|
||||
)
|
||||
|
||||
IconLog(
|
||||
'Icon Scan',
|
||||
`Detected ${ret.length} matched icons, writing into file: ${iconListPath}`,
|
||||
)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { buildUtilsReadFile, buildUtilsWriteFile } from '../../utils'
|
||||
import { IconLog, SvgPrefix, iconBundlePath, iconListPath, iconSVGPath } from '../src'
|
||||
import { generateSvgJSON } from './icon-svg-json'
|
||||
|
||||
export async function writeSvgJSONBundle() {
|
||||
const result = `import { addCollection } from '@iconify/vue'
|
||||
import CustomSvgJson from '/${iconSVGPath}'\n
|
||||
addCollection(CustomSvgJson);`
|
||||
|
||||
IconLog('Svg Bundle', `Bundle svg icons from ${iconSVGPath}`)
|
||||
|
||||
await buildUtilsWriteFile(iconBundlePath, result)
|
||||
}
|
||||
|
||||
export async function rewriteSvgJSON() {
|
||||
const usedIconList = await buildUtilsReadFile(iconListPath)
|
||||
|
||||
const arr = Array.from<string>(JSON.parse(usedIconList.replace('export default ', '')))
|
||||
|
||||
const usedSvgIcons: string[] = arr.filter(i => i.startsWith(SvgPrefix))
|
||||
|
||||
await generateSvgJSON(usedSvgIcons.map(i => i.replace(`${SvgPrefix}:`, '')))
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
import {
|
||||
cleanupSVG,
|
||||
importDirectory,
|
||||
parseColors,
|
||||
runSVGO,
|
||||
} from '@iconify/tools'
|
||||
|
||||
import { buildUtilsReadFile, buildUtilsWriteFile } from '../../utils'
|
||||
import { IconLog, SvgPrefix, iconSVGPath } from '../src'
|
||||
|
||||
export async function generateSvgJSON(whiteList?: string[]) {
|
||||
// build the empty json file
|
||||
await buildUtilsWriteFile(
|
||||
iconSVGPath,
|
||||
JSON.stringify({ icons: {}, prefix: SvgPrefix }),
|
||||
)
|
||||
|
||||
// Import icons
|
||||
const iconSet = await importDirectory('.svg', {
|
||||
prefix: SvgPrefix,
|
||||
})
|
||||
|
||||
// Validate, clean up, fix palette and optimise
|
||||
await iconSet.forEach(async (name, type) => {
|
||||
if (type !== 'icon')
|
||||
return
|
||||
|
||||
// white list limit
|
||||
if (whiteList && !whiteList.includes(name))
|
||||
iconSet.remove(name)
|
||||
|
||||
const svg = iconSet.toSVG(name)
|
||||
if (!svg) {
|
||||
// Invalid icon
|
||||
iconSet.remove(name)
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up and optimise icons
|
||||
try {
|
||||
// Cleanup icon code
|
||||
await cleanupSVG(svg)
|
||||
|
||||
// Assume icon is monotone: replace color with currentColor, add if missing
|
||||
// If icon is not monotone, remove this code
|
||||
await parseColors(svg, {
|
||||
defaultColor: 'currentColor',
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
callback: (attr, colorStr, color) => {
|
||||
// return !color || isEmptyColor(color) ? colorStr : 'currentColor'
|
||||
return colorStr || 'currentColor'
|
||||
},
|
||||
})
|
||||
|
||||
// Optimise
|
||||
runSVGO(svg)
|
||||
}
|
||||
catch (err) {
|
||||
// Invalid icon
|
||||
console.error(`Error parsing ${name}:`, err)
|
||||
iconSet.remove(name)
|
||||
return
|
||||
}
|
||||
|
||||
// Update icon
|
||||
iconSet.fromSVG(name, svg)
|
||||
})
|
||||
|
||||
const data = JSON.parse(await buildUtilsReadFile(iconSVGPath))
|
||||
|
||||
// Generate to icon list
|
||||
await iconSet.forEach((name) => {
|
||||
// auto remove width and height
|
||||
const body = String(iconSet.toString(name))
|
||||
.replaceAll(/width="(.[0-9]*)"/gi, '')
|
||||
.replaceAll(/height="(.[0-9]*)"/gi, '')
|
||||
|
||||
data.icons[name] = {
|
||||
body,
|
||||
}
|
||||
})
|
||||
|
||||
// write into json
|
||||
await buildUtilsWriteFile(iconSVGPath, JSON.stringify(data, null, 2))
|
||||
|
||||
IconLog(
|
||||
'Icon SVG',
|
||||
`Detecting ${iconSet.count()} custom svg icon, writing into file: ${iconSVGPath}`,
|
||||
)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import process from 'node:process'
|
||||
import { writeIntoLog } from '../utils'
|
||||
import { iconLogPath } from './src/path'
|
||||
|
||||
writeIntoLog(
|
||||
'Icon Bundle',
|
||||
`npx esno build/icon/src/core.ts ${process.argv.slice(-1)[0]}`,
|
||||
iconLogPath,
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
export const SvgPrefix = 'nl'
|
||||
|
||||
export const IconBundleConfig: IconConfig = {
|
||||
online: false,
|
||||
treeShake: true,
|
||||
cache: false,
|
||||
// see full icon in https://icon-sets.iconify.design
|
||||
list: [
|
||||
SvgPrefix,
|
||||
'ant-design',
|
||||
'gridicons',
|
||||
'mdi',
|
||||
'ion',
|
||||
'carbon',
|
||||
'emojione',
|
||||
],
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import process from 'node:process'
|
||||
import { generateIconUsedBundle } from '../generate/icon-bundle'
|
||||
import { generateIconListAll } from '../generate/icon-list-all'
|
||||
import { generateIconListScan } from '../generate/icon-list-scan'
|
||||
import { generateSvgJSON } from '../generate/icon-svg-json'
|
||||
import { generateIconDev } from '../generate/icon-dev'
|
||||
import { rewriteSvgJSON, writeSvgJSONBundle } from '../generate/icon-svg-bundle'
|
||||
import { buildUtilsWarn } from '../../utils'
|
||||
import { IconBundleConfig } from './config'
|
||||
|
||||
(async () => {
|
||||
const arg = process.argv.slice(-1)[0]
|
||||
|
||||
// always excute the logic to transform custom svg icons into iconify json
|
||||
await generateSvgJSON()
|
||||
|
||||
// dev env, just bundle all collection icons
|
||||
if (arg === 'dev') {
|
||||
// when dev, just import json from `node_modules` and call `addCollection` api
|
||||
// also generate icon list from imported jsons
|
||||
await generateIconDev()
|
||||
}
|
||||
else {
|
||||
if (IconBundleConfig.online) {
|
||||
// online but not treeshake
|
||||
if (!IconBundleConfig.treeShake) {
|
||||
// Step 1 - Icon List should be all
|
||||
await generateIconListAll()
|
||||
}
|
||||
|
||||
// online also treeshake
|
||||
if (IconBundleConfig.treeShake) {
|
||||
// Step 1 - Use fast-glob to scan and write the used icon into icon list
|
||||
await generateIconListScan()
|
||||
|
||||
// Step 2 - Used last step generated icon list to rewrite the svg json file
|
||||
await rewriteSvgJSON()
|
||||
}
|
||||
|
||||
// Add svg icons with `addCollection`
|
||||
await writeSvgJSONBundle()
|
||||
}
|
||||
else {
|
||||
// not online and not treeshake
|
||||
// WARNING - highly not recommended to do so
|
||||
// cause it will bundle all icons into final output which would increase the bundle size a lot
|
||||
if (!IconBundleConfig.treeShake) {
|
||||
// not recomended to do so
|
||||
buildUtilsWarn('Not online and Not treeshake \n WARNING - It\'s highly not recommended to do so \n Cause it will bundle all icons into final output which would increase the bundle size a lot \n Normally more than 1MB')
|
||||
process.exitCode = 1
|
||||
}
|
||||
|
||||
// not online but treeshake
|
||||
if (IconBundleConfig.treeShake) {
|
||||
// Step 1 - Use fast-glob to scan and write the used icon into icon list
|
||||
await generateIconListScan()
|
||||
|
||||
// Step 2 - Excute the iconify bundle logic from
|
||||
// https://docs.iconify.design/icon-components/bundles/examples/component-full.html
|
||||
await generateIconUsedBundle()
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
@ -0,0 +1,3 @@
|
||||
export * from './path'
|
||||
export * from './log'
|
||||
export * from './config'
|
@ -0,0 +1,15 @@
|
||||
import { buildUtilsLog } from '../../utils'
|
||||
|
||||
export function IconLog(title: string, data: string) {
|
||||
buildUtilsLog(
|
||||
`
|
||||
/**
|
||||
* ==============================================
|
||||
* =============== ${title} ==================
|
||||
* ==============================================
|
||||
* ${data}
|
||||
* ==============================================
|
||||
*/
|
||||
`,
|
||||
)
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export const iconListPath = 'build/_generated/icon-list.ts'
|
||||
export const iconBundlePath = 'build/_generated/icon-bundle.ts'
|
||||
export const iconSVGPath = 'build/_generated/svg.json'
|
||||
export const iconLogPath = 'build/_generated/icon-build.log'
|
@ -0,0 +1,10 @@
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
interface IconConfig {
|
||||
online: boolean
|
||||
treeShake: boolean
|
||||
cache: boolean
|
||||
list: string[]
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { promisify } from 'node:util'
|
||||
import { exec } from 'node:child_process'
|
||||
import { buildUtilsLog } from './'
|
||||
|
||||
const promisifyExec = promisify(exec)
|
||||
|
||||
export async function buildUtilsExec(file: string) {
|
||||
const { stdout, stderr } = await promisifyExec(file)
|
||||
|
||||
buildUtilsLog(stdout)
|
||||
|
||||
if (stderr) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { buildUtilsLog } from './'
|
||||
|
||||
const stage = 'File'
|
||||
|
||||
export async function buildUtilsReadFile(path: string) {
|
||||
buildUtilsLog(`Reading File: ${path}`, stage)
|
||||
return await fs.readFile(path, 'utf8')
|
||||
}
|
||||
|
||||
export async function buildUtilsWriteFile(path: string, data: any) {
|
||||
buildUtilsLog(`Writing data into File: ${path}`, stage)
|
||||
return await fs.writeFile(path, data, 'utf8')
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export * from './log'
|
||||
export * from './cp'
|
||||
export * from './fs'
|
||||
export * from './paths'
|
@ -0,0 +1,83 @@
|
||||
import process from 'node:process'
|
||||
import fs from 'node:fs'
|
||||
import cp from 'node:child_process'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import { getNow } from 'easy-fns-ts/dist/lib'
|
||||
import pkg from '../../package.json'
|
||||
|
||||
function title(stage: string) {
|
||||
return chalk.magenta.bgBlack(
|
||||
`[${pkg.name.toUpperCase()}] - [${getNow()}] - [${stage}]`,
|
||||
)
|
||||
}
|
||||
|
||||
export function buildUtilsLog(msg: string, stage = 'log') {
|
||||
console.log(`${title(stage)}: ${msg}`)
|
||||
}
|
||||
|
||||
export function buildUtilsWarn(warns: string) {
|
||||
console.warn(chalk.yellow(`[${pkg.name.toUpperCase()} Warning] - [${getNow()}] \n ${warns}`))
|
||||
}
|
||||
|
||||
export function writeIntoLog(title: string, command: string, path: string) {
|
||||
const prefix = (msg: string, emoji: string) => `
|
||||
/**
|
||||
* ==============================================
|
||||
* ==============================================
|
||||
* ${title} - ${msg} - ${emoji} - ${getNow()}
|
||||
* ==============================================
|
||||
* ==============================================
|
||||
*/
|
||||
`
|
||||
|
||||
const log_file = fs.createWriteStream(path, {
|
||||
flags: 'w',
|
||||
})
|
||||
|
||||
const start = new Date().getTime()
|
||||
|
||||
cp.exec(command, (error, stdout, stderr) => {
|
||||
prefix(`Excuting command: ${command}`, '⚡⚡⚡⚡⚡')
|
||||
|
||||
const end = new Date().getTime()
|
||||
|
||||
const cost = new Date(end - start).getSeconds()
|
||||
|
||||
buildUtilsLog(chalk.blue(`${title} done in ${cost}s`))
|
||||
|
||||
if (error) {
|
||||
process.exitCode = 1
|
||||
log_file.write(
|
||||
prefix('Error', '😈😈😈😈😈') + JSON.stringify(error),
|
||||
() => {
|
||||
// // recover icon file if failed
|
||||
// cp.execSync('npm run postbuild')
|
||||
|
||||
buildUtilsLog(
|
||||
chalk.red.bgBlack(`${title} Failed, see more in ${path}`),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
else {
|
||||
buildUtilsLog(chalk.green.bgBlack(`${title} Successfully! ✨✨✨✨✨ `))
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
log_file.write(prefix('Std Out', '✨✨✨✨✨') + stdout, () => {
|
||||
buildUtilsLog(
|
||||
chalk.magenta.bgBlack(`${title} Std out, see more in ${path}`),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
log_file.write(prefix('Std Err', '💊💊💊💊💊') + stderr, () => {
|
||||
buildUtilsLog(
|
||||
chalk.yellow.bgBlack(`${title} Std Error, see more in ${path}`),
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
export const stagingBuildLogPath = 'report/stage.log'
|
||||
export const typesCheckLogPath = 'report/tsc.log'
|
||||
export const bundleSizeStatsLogPath = 'report/stats.html'
|
||||
|
||||
// paths.ts
|
||||
export const generatedPathsFilePath = 'build/_generated/paths.ts'
|
||||
|
||||
// publish.bat
|
||||
export const generatedPublishBatFileName = 'build/_generated/publish.bat'
|
||||
|
||||
// vscode setting file path
|
||||
export const VScodeSettingsFilePath = '.vscode/settings.json'
|
||||
|
||||
// app setting file path
|
||||
export const AppSettingsJSONFilePath = 'src/settings.json'
|
||||
|
||||
// app setting interface file path
|
||||
export const AppSettingsInterfaceFilePath = 'types/settings.d.ts'
|
@ -0,0 +1,12 @@
|
||||
import { IconBundleConfig } from '/build/icon/src/config'
|
||||
import { disableCache, enableCache } from '@iconify/vue'
|
||||
import '/build/_generated/icon-bundle.ts'
|
||||
|
||||
if (!IconBundleConfig.cache) {
|
||||
disableCache('all')
|
||||
}
|
||||
else {
|
||||
enableCache('local')
|
||||
}
|
||||
|
||||
export { default } from './src/icon.vue'
|
@ -0,0 +1,175 @@
|
||||
<script setup lang="ts" name="MyIcon">
|
||||
import { Icon as Iconify, iconExists, loadIcon } from '@iconify/vue'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { createNamespace } from '~/utils'
|
||||
|
||||
defineOptions({ name: 'LIcon' })
|
||||
|
||||
const props = defineProps({
|
||||
icon: { type: String as PropType<string> },
|
||||
size: { type: [String, Number] as PropType<string | number>, default: 16 },
|
||||
color: { type: String },
|
||||
depth: { type: Number as PropType<1 | 2 | 3 | 4 | 5> },
|
||||
spin: { type: Boolean, default: false },
|
||||
transition: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const { VITE_ICON_UNOCSS_PREFIX: UnoCSSPrefix, VITE_ICON_LOCAL_PREFIX: LocalPrefix } = import.meta.env
|
||||
|
||||
function getPrefixRegex(prefix: string) {
|
||||
return new RegExp(`^${prefix}[:-]`)
|
||||
}
|
||||
// ^nl[-:]
|
||||
const UnoCSSPrefixRegex = getPrefixRegex(UnoCSSPrefix)
|
||||
const LocalPrefixRegex = getPrefixRegex(LocalPrefix)
|
||||
|
||||
enum IconType {
|
||||
Unknown = 0,
|
||||
UnoCSS = 1,
|
||||
Local = 2,
|
||||
Iconify = 3,
|
||||
}
|
||||
|
||||
let loaded = $ref(false)
|
||||
|
||||
function click(e: MouseEvent) {
|
||||
emit('click', e)
|
||||
}
|
||||
|
||||
const getIconTypeRef = computed((): IconType => {
|
||||
const { icon } = props
|
||||
if (!icon) {
|
||||
return IconType.Unknown
|
||||
}
|
||||
if (LocalPrefixRegex.test(icon)) {
|
||||
return IconType.Local
|
||||
}
|
||||
if (UnoCSSPrefixRegex.test(icon)) {
|
||||
return IconType.UnoCSS
|
||||
}
|
||||
return IconType.Iconify
|
||||
})
|
||||
|
||||
function checkAndLoad(icon: string) {
|
||||
if (unref(getIconTypeRef) === IconType.Iconify) {
|
||||
const isLoaded = (loaded = iconExists(icon))
|
||||
if (!isLoaded) {
|
||||
loadIcon(icon)
|
||||
.then(() => loaded = true)
|
||||
.catch((_) => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检测 icon 变化
|
||||
watch(
|
||||
() => props.icon,
|
||||
v => checkAndLoad(v!),
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const getIconRef = computed((): string => {
|
||||
const iconType = unref(getIconTypeRef)
|
||||
switch (iconType) {
|
||||
case IconType.Local:
|
||||
{
|
||||
let icon = props.icon || 'no-icon'
|
||||
if (icon.startsWith(LocalPrefix)) {
|
||||
icon = icon.slice(icon.indexOf(LocalPrefix) + LocalPrefix.length + 1)
|
||||
}
|
||||
return `#${LocalPrefix}-${icon}`
|
||||
}
|
||||
case IconType.UnoCSS:
|
||||
{
|
||||
let icon = props.icon!
|
||||
if (icon.startsWith(UnoCSSPrefix)) {
|
||||
icon = icon.slice(icon.indexOf(UnoCSSPrefix) + UnoCSSPrefix.length + 1)
|
||||
}
|
||||
return `${UnoCSSPrefix}-${icon}`
|
||||
}
|
||||
case IconType.Iconify:
|
||||
return props.icon!
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = useAttrs()
|
||||
const bindAttrs = computed<{ class: string; style: string }>(() => ({
|
||||
class: (attrs.class as string) || '',
|
||||
style: (attrs.style as string) || '',
|
||||
}))
|
||||
const getStyle = computed((): CSSProperties => {
|
||||
const { size } = props
|
||||
let s = `${size}`
|
||||
s = `${s.replace('px', '')}px`
|
||||
return {
|
||||
width: s,
|
||||
height: s,
|
||||
}
|
||||
})
|
||||
const getClass = computed(() => {
|
||||
return {
|
||||
'animate-spin animate-duration-1000': props.spin,
|
||||
'transition-all ease-in-out-300': props.transition,
|
||||
}
|
||||
})
|
||||
const { bem } = createNamespace('l-icon')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$attrs.class || {}" @click="click($event)">
|
||||
<template v-if="getIconTypeRef === IconType.Unknown">
|
||||
<NSkeleton animated circle :width="size" :height="size" class="inline-block" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="getIconTypeRef === IconType.Local">
|
||||
<svg
|
||||
:class="[bem(), getClass]"
|
||||
:style="getStyle"
|
||||
aria-hidden="true"
|
||||
v-bind="bindAttrs"
|
||||
>
|
||||
<use :xlink:href="getIconRef" />
|
||||
</svg>
|
||||
</template>
|
||||
<template v-if="getIconTypeRef === IconType.Iconify">
|
||||
<NIcon
|
||||
v-if="loaded"
|
||||
:class="[bem(), getClass]"
|
||||
:style="getStyle"
|
||||
:color="color"
|
||||
:depth="depth"
|
||||
:size="size"
|
||||
v-bind="bindAttrs"
|
||||
>
|
||||
<Iconify :icon="getIconRef" />
|
||||
</NIcon>
|
||||
<NSkeleton v-else animated circle :width="size" :height="size" class="inline-block" />
|
||||
</template>
|
||||
<template v-if="getIconTypeRef === IconType.UnoCSS">
|
||||
<NIcon
|
||||
:class="[bem(), getClass]"
|
||||
:style="getStyle"
|
||||
:color="color"
|
||||
:depth="depth"
|
||||
:size="size"
|
||||
v-bind="bindAttrs"
|
||||
>
|
||||
<div :class="getIconRef" />
|
||||
</NIcon>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.l-icon {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
fill: currentcolor;
|
||||
vertical-align: -0.15em;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,29 @@
|
||||
import LIcon from '~/components/LIcon'
|
||||
|
||||
export function useIcon() {
|
||||
interface IconRenderOption {
|
||||
icon?: string
|
||||
color?: string
|
||||
size?: string | number
|
||||
depth?: 1 | 2 | 3 | 4 | 5
|
||||
spin?: boolean
|
||||
transition?: boolean
|
||||
}
|
||||
|
||||
function renderIcon(val: IconRenderOption | string) {
|
||||
let opt: IconRenderOption = {}
|
||||
if (val instanceof String) {
|
||||
opt = { icon: val as string }
|
||||
}
|
||||
else {
|
||||
opt = val as IconRenderOption
|
||||
}
|
||||
|
||||
if (opt.icon) {
|
||||
return h(LIcon, { ...opt })
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return { renderIcon }
|
||||
}
|
Loading…
Reference in New Issue