From c117a907504af18f9518d02a181ad0ae5a74ad44 Mon Sep 17 00:00:00 2001
From: NoahLan <6995syu@163.com>
Date: Tue, 21 Nov 2023 10:03:55 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A6=BB=E7=BA=BF=E5=9B=BE=E6=A0=87+?=
=?UTF-8?q?=E5=9C=A8=E7=BA=BF=E5=9B=BE=E6=A0=87?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
assets/svg/net-error.svg | 1 +
assets/svg/no-data.svg | 1 +
assets/svg/no-icon.svg | 1 +
build/_generated/README.md | 1 +
build/_generated/icon-bundle.ts | 18 ++
build/_generated/icon-list.ts | 15 ++
build/_generated/svg.json | 4 +
build/icon/generate/icon-bundle.ts | 306 +++++++++++++++++++++++++
build/icon/generate/icon-dev.ts | 50 ++++
build/icon/generate/icon-list-all.ts | 59 +++++
build/icon/generate/icon-list-scan.ts | 41 ++++
build/icon/generate/icon-svg-bundle.ts | 23 ++
build/icon/generate/icon-svg-json.ts | 91 ++++++++
build/icon/index.ts | 9 +
build/icon/src/config.ts | 17 ++
build/icon/src/core.ts | 64 ++++++
build/icon/src/index.ts | 3 +
build/icon/src/log.ts | 15 ++
build/icon/src/path.ts | 4 +
build/icon/src/types.d.ts | 10 +
build/utils/cp.ts | 17 ++
build/utils/fs.ts | 14 ++
build/utils/index.ts | 4 +
build/utils/log.ts | 83 +++++++
build/utils/paths.ts | 18 ++
src/components/LIcon/index.ts | 12 +
src/components/LIcon/src/icon.vue | 175 ++++++++++++++
src/composables/icon.ts | 29 +++
28 files changed, 1085 insertions(+)
create mode 100644 assets/svg/net-error.svg
create mode 100644 assets/svg/no-data.svg
create mode 100644 assets/svg/no-icon.svg
create mode 100644 build/_generated/README.md
create mode 100644 build/_generated/icon-bundle.ts
create mode 100644 build/_generated/icon-list.ts
create mode 100644 build/_generated/svg.json
create mode 100644 build/icon/generate/icon-bundle.ts
create mode 100644 build/icon/generate/icon-dev.ts
create mode 100644 build/icon/generate/icon-list-all.ts
create mode 100644 build/icon/generate/icon-list-scan.ts
create mode 100644 build/icon/generate/icon-svg-bundle.ts
create mode 100644 build/icon/generate/icon-svg-json.ts
create mode 100644 build/icon/index.ts
create mode 100644 build/icon/src/config.ts
create mode 100644 build/icon/src/core.ts
create mode 100644 build/icon/src/index.ts
create mode 100644 build/icon/src/log.ts
create mode 100644 build/icon/src/path.ts
create mode 100644 build/icon/src/types.d.ts
create mode 100644 build/utils/cp.ts
create mode 100644 build/utils/fs.ts
create mode 100644 build/utils/index.ts
create mode 100644 build/utils/log.ts
create mode 100644 build/utils/paths.ts
create mode 100644 src/components/LIcon/index.ts
create mode 100644 src/components/LIcon/src/icon.vue
create mode 100644 src/composables/icon.ts
diff --git a/assets/svg/net-error.svg b/assets/svg/net-error.svg
new file mode 100644
index 0000000..81f2004
--- /dev/null
+++ b/assets/svg/net-error.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/no-data.svg b/assets/svg/no-data.svg
new file mode 100644
index 0000000..2b9f257
--- /dev/null
+++ b/assets/svg/no-data.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/no-icon.svg b/assets/svg/no-icon.svg
new file mode 100644
index 0000000..a3124d5
--- /dev/null
+++ b/assets/svg/no-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/build/_generated/README.md b/build/_generated/README.md
new file mode 100644
index 0000000..d0c03df
--- /dev/null
+++ b/build/_generated/README.md
@@ -0,0 +1 @@
+保持此文件夹,用于生成文件
\ No newline at end of file
diff --git a/build/_generated/icon-bundle.ts b/build/_generated/icon-bundle.ts
new file mode 100644
index 0000000..8ad59d7
--- /dev/null
+++ b/build/_generated/icon-bundle.ts
@@ -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)
diff --git a/build/_generated/icon-list.ts b/build/_generated/icon-list.ts
new file mode 100644
index 0000000..26cb37b
--- /dev/null
+++ b/build/_generated/icon-list.ts
@@ -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
diff --git a/build/_generated/svg.json b/build/_generated/svg.json
new file mode 100644
index 0000000..da817f2
--- /dev/null
+++ b/build/_generated/svg.json
@@ -0,0 +1,4 @@
+{
+ "icons": {},
+ "prefix": "nl-svg"
+}
diff --git a/build/icon/generate/icon-bundle.ts b/build/icon/generate/icon-bundle.ts
new file mode 100644
index 0000000..50db852
--- /dev/null
+++ b/build/icon/generate/icon-bundle.ts
@@ -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(
+ 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 {
+ const sorted: Record = 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
+}
diff --git a/build/icon/generate/icon-dev.ts b/build/icon/generate/icon-dev.ts
new file mode 100644
index 0000000..082f1c0
--- /dev/null
+++ b/build/icon/generate/icon-dev.ts
@@ -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)
+}
diff --git a/build/icon/generate/icon-list-all.ts b/build/icon/generate/icon-list-all.ts
new file mode 100644
index 0000000..50e5aee
--- /dev/null
+++ b/build/icon/generate/icon-list-all.ts
@@ -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}`,
+ )
+}
diff --git a/build/icon/generate/icon-list-scan.ts b/build/icon/generate/icon-list-scan.ts
new file mode 100644
index 0000000..f80df1e
--- /dev/null
+++ b/build/icon/generate/icon-list-scan.ts
@@ -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}`,
+ )
+}
diff --git a/build/icon/generate/icon-svg-bundle.ts b/build/icon/generate/icon-svg-bundle.ts
new file mode 100644
index 0000000..688c610
--- /dev/null
+++ b/build/icon/generate/icon-svg-bundle.ts
@@ -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(JSON.parse(usedIconList.replace('export default ', '')))
+
+ const usedSvgIcons: string[] = arr.filter(i => i.startsWith(SvgPrefix))
+
+ await generateSvgJSON(usedSvgIcons.map(i => i.replace(`${SvgPrefix}:`, '')))
+}
diff --git a/build/icon/generate/icon-svg-json.ts b/build/icon/generate/icon-svg-json.ts
new file mode 100644
index 0000000..3e4b0d8
--- /dev/null
+++ b/build/icon/generate/icon-svg-json.ts
@@ -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}`,
+ )
+}
diff --git a/build/icon/index.ts b/build/icon/index.ts
new file mode 100644
index 0000000..1f16253
--- /dev/null
+++ b/build/icon/index.ts
@@ -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,
+)
diff --git a/build/icon/src/config.ts b/build/icon/src/config.ts
new file mode 100644
index 0000000..4aafeb9
--- /dev/null
+++ b/build/icon/src/config.ts
@@ -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',
+ ],
+}
diff --git a/build/icon/src/core.ts b/build/icon/src/core.ts
new file mode 100644
index 0000000..2cf4a82
--- /dev/null
+++ b/build/icon/src/core.ts
@@ -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()
+ }
+ }
+ }
+})()
diff --git a/build/icon/src/index.ts b/build/icon/src/index.ts
new file mode 100644
index 0000000..2a43a61
--- /dev/null
+++ b/build/icon/src/index.ts
@@ -0,0 +1,3 @@
+export * from './path'
+export * from './log'
+export * from './config'
diff --git a/build/icon/src/log.ts b/build/icon/src/log.ts
new file mode 100644
index 0000000..bc11236
--- /dev/null
+++ b/build/icon/src/log.ts
@@ -0,0 +1,15 @@
+import { buildUtilsLog } from '../../utils'
+
+export function IconLog(title: string, data: string) {
+ buildUtilsLog(
+ `
+/**
+ * ==============================================
+ * =============== ${title} ==================
+ * ==============================================
+ * ${data}
+ * ==============================================
+ */
+`,
+ )
+}
diff --git a/build/icon/src/path.ts b/build/icon/src/path.ts
new file mode 100644
index 0000000..448f4cf
--- /dev/null
+++ b/build/icon/src/path.ts
@@ -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'
diff --git a/build/icon/src/types.d.ts b/build/icon/src/types.d.ts
new file mode 100644
index 0000000..1a4942a
--- /dev/null
+++ b/build/icon/src/types.d.ts
@@ -0,0 +1,10 @@
+export {}
+
+declare global {
+ interface IconConfig {
+ online: boolean
+ treeShake: boolean
+ cache: boolean
+ list: string[]
+ }
+}
diff --git a/build/utils/cp.ts b/build/utils/cp.ts
new file mode 100644
index 0000000..bdc16fb
--- /dev/null
+++ b/build/utils/cp.ts
@@ -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
+}
diff --git a/build/utils/fs.ts b/build/utils/fs.ts
new file mode 100644
index 0000000..b2c2b4f
--- /dev/null
+++ b/build/utils/fs.ts
@@ -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')
+}
diff --git a/build/utils/index.ts b/build/utils/index.ts
new file mode 100644
index 0000000..c23c68a
--- /dev/null
+++ b/build/utils/index.ts
@@ -0,0 +1,4 @@
+export * from './log'
+export * from './cp'
+export * from './fs'
+export * from './paths'
diff --git a/build/utils/log.ts b/build/utils/log.ts
new file mode 100644
index 0000000..fe3acd0
--- /dev/null
+++ b/build/utils/log.ts
@@ -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}`),
+ )
+ })
+ }
+ })
+}
diff --git a/build/utils/paths.ts b/build/utils/paths.ts
new file mode 100644
index 0000000..d8753f3
--- /dev/null
+++ b/build/utils/paths.ts
@@ -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'
diff --git a/src/components/LIcon/index.ts b/src/components/LIcon/index.ts
new file mode 100644
index 0000000..72ff649
--- /dev/null
+++ b/src/components/LIcon/index.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'
diff --git a/src/components/LIcon/src/icon.vue b/src/components/LIcon/src/icon.vue
new file mode 100644
index 0000000..ee11b7b
--- /dev/null
+++ b/src/components/LIcon/src/icon.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/composables/icon.ts b/src/composables/icon.ts
new file mode 100644
index 0000000..09228be
--- /dev/null
+++ b/src/composables/icon.ts
@@ -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 }
+}