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 @@ +personal settings \ 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 } +}