Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 优化转换配置、逻辑; 适配 vscode 插件; 适配 unocss ; #15

Merged
merged 6 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 100 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,107 @@
# unplugin-iconify

[![NPM version](https://img.shields.io/npm/v/unplugin-iconify?color=a1b858&label=)](https://www.npmjs.com/package/unplugin-iconify)

Iconify template for [unplugin](https://github.com/unjs/unplugin).
[![NPM version](https://img.shields.io/npm/v/@waset/unplugin-iconify?color=blue)](https://www.npmjs.com/package/@waset/unplugin-iconify)

## Install

```bash
npm i -D unplugin-iconify
pnpm i -D @waset/unplugin-iconify
```

## Configuration

```ts
Iconify({
convert: {
icon: './icons',
svg: {
path: './icons',
noColor: true,
},
suffix: {
path: './icons',
noColor: true,
suffix: 'color',
},
},
output: 'dist/icons', // @default 'node_modules/.unplugin-iconify'
iconifyIntelliSense: true, // @default false
})
```

- 如果开启 `iconifyIntelliSense`,将自动创建/更新 `.vscode/settings.json` 文件,用于 VSCode 插件 [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify)

- `convert` 选项用于将图标转换为图标集,请查看 `src/core/types.ts` 获取更多类型信息。

## Usage

<details>
<summary>Vite</summary>

```ts
// vite.config.ts
import Iconify from '@waset/unplugin-iconify/vite'

export default defineConfig({
plugins: [
Iconify({
// ...
})
],
})
```
</details>

<details>
<summary>Nuxt</summary>

```ts
// nuxt.config.ts
import { defineNuxtConfig } from 'nuxt/config'

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
modules: [
// ...
'@waset/unplugin-iconify/nuxt'
],
Iconify: {
// ...
},
})
```
</details>

<details>
<summary>unocss</summary>

```ts
// uno.config.ts
import { UnocssLoader } from '@waset/unplugin-iconify/loader'
import { defineConfig, presetIcons } from 'unocss'

export default defineConfig({
presets: [
// ...
presetIcons({
scale: 1.2,
warn: true,
extraProperties: {
'display': 'inline-block',
'vertical-align': 'middle',
},
collections: {
...UnocssLoader(),
},
}),
],
// ...
})
```
</details>
waset marked this conversation as resolved.
Show resolved Hide resolved

## Thanks

- [unplugin](https://github.com/unjs/unplugin)
- [unplugin/unplugin-icons](https://github.com/unplugin/unplugin-icons)
- [unocss](https://github.com/unocss/unocss)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"eslint": "^9.12.0",
"esno": "^4.8.0",
"fast-glob": "^3.3.2",
"jsonc-parser": "^3.3.1",
waset marked this conversation as resolved.
Show resolved Hide resolved
"nodemon": "^3.1.7",
"rollup": "^4.24.0",
"tsup": "^8.3.0",
Expand Down
23 changes: 7 additions & 16 deletions playground/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import { defineConfig } from 'vite'
import Inspect from 'vite-plugin-inspect'
/**
* package.json
*
* "unplugin-iconify": "workspaces:*",
*/
// import Iconify from '../src/vite'
import Iconify from '@waset/unplugin-iconify/vite'
import Iconify from '../src/vite'
waset marked this conversation as resolved.
Show resolved Hide resolved

export default defineConfig({
plugins: [
Inspect(),
Iconify({
convert: [
{
convert: {
svg: {
path: './icons',
prefix: 'icon',
noColor: true,
},
{
path: './icons',
prefix: 'icon-color',
},
],
}) as any,
icon: './icons',
},
iconifyIntelliSense: true,
}),
],
})
13 changes: 8 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 41 additions & 13 deletions src/core/convert.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
import type { Convert } from './types'
import type { Convert, Options } from './types'
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { cwd } from 'node:process'
import { isEmptyColor, parseColors } from '@iconify/tools/lib/colors/parse'
import { importDirectory } from '@iconify/tools/lib/import/directory'
import { importDirectorySync } from '@iconify/tools/lib/import/directory'
import { runSVGO } from '@iconify/tools/lib/optimise/svgo'
waset marked this conversation as resolved.
Show resolved Hide resolved

import { cleanupSVG } from '@iconify/tools/lib/svg/cleanup'

export async function Generated(option: Convert): Promise<void> {
const { path, out, prefix, noColor } = option
/**
* 转换多个图表集
* @param options 转换配置
*/
export async function Generateds(options: Required<Options>): Promise<void> {
if (!options.convert) {
throw new Error('No convert option')
}
for (const key in options.convert) {
await Generated(key, options.convert[key], options.output)
}

// eslint-disable-next-line no-console
console.log(`\n\x1B[32m Converted \x1B[0m \x1B[31m ${Object.keys(options.convert).length} \x1B[0m \x1B[32m collections \x1B[0m`)
}

waset marked this conversation as resolved.
Show resolved Hide resolved
/**
* 转换单个图表集
* @param name 图表集名称
* @param stting 图表集路径或转换配置
* @param output 输出路径
*/
export async function Generated(name: string, stting: string | Convert, output: string): Promise<void> {
const convert = typeof stting === 'string' ? { path: stting } : { ...stting }
waset marked this conversation as resolved.
Show resolved Hide resolved
const { path, noColor, suffix } = convert

if (!existsSync(path)) {
throw new Error(`Path ${path} does not exist`)
}

// Import icons
const iconSet = await importDirectory(path, {
prefix,
const iconSet = importDirectorySync(path, {
prefix: name,
includeSubDirs: true,
keyword: (_, name) => {
return `${name}${(suffix) ? `-${suffix}` : ''}`
},
})

// Validate, clean up, fix palette and optimise
await iconSet.forEach(async (name, type) => {
iconSet.forEach(async (name, type) => {
if (type !== 'icon')
return

Expand Down Expand Up @@ -58,14 +86,14 @@ export async function Generated(option: Convert): Promise<void> {
const exported = `${JSON.stringify(iconSet.export(), null, '\t')}\n`

// 构建 manifest 文件路径
const srcDir = join(cwd(), out || 'temp/icons')
if (!existsSync(srcDir)) {
mkdirSync(srcDir, { recursive: true })
const out_dir = join(cwd(), output)
if (!existsSync(out_dir)) {
mkdirSync(out_dir, { recursive: true })
}

// Save to file
writeFileSync(`${srcDir}/${iconSet.prefix}.json`, exported, 'utf8')
writeFileSync(`${out_dir}/${iconSet.prefix}.json`, exported, 'utf8')

// eslint-disable-next-line no-console
console.log(`\x1B[32m Imported icons: \x1B[0m \x1B[31m ${Object.keys(iconSet.entries).length} \x1B[0m`)
console.log(`\x1B[32m Imported ${name}: \x1B[0m \x1B[31m ${Object.keys(iconSet.entries).length} \x1B[0m`)
}
110 changes: 110 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { Convert, Loaders, Optional, Options } from './types'
import { getOutputFiles, UnocssLoader } from '../loader'
import { Generateds } from './convert'
import { IconifyIntelliSenseSettings } from './vscode'

export class Iconify {
/**
* 配置
*/
options: Required<Options>

/**
* 默认配置
*
* @description 统一管理默认值,避免使用 `if` 判断
*/
defaultOptions: Optional<Options> = {
output: 'node_modules/.unplugin-iconify',
iconifyIntelliSense: false,
convert: {},
}

/**
* 默认转换配置
*
* @description 统一管理默认值,避免使用 `if` 判断
*/
defaultConvert: Optional<Convert> = {
suffix: '',
noColor: false,
}

/**
* 构造器
* @param options Options
*/
constructor(options: Options) {
this.options = { ...this.defaultOptions, ...options }
this.setOptions(this.options)
}

/**
* 设置配置
* @param options Options
*/
private setOptions(options: Required<Options>): void {
if (!Object.keys(options.convert).length) {
return
}

/**
* 处理 convert 配置并设置默认值
*/
for (const key in options.convert) {
const value = options.convert[key]
if (typeof value === 'string') {
options.convert[key] = {
path: value,
...this.defaultConvert,
}
}
else if (typeof value === 'object') {
options.convert[key] = {
...this.defaultConvert,
...value,
}
}
}

// 更新配置
this.options = options
}

/**
* 转换
*/
async toConvert(): Promise<void> {
await Generateds(this.options)
}
waset marked this conversation as resolved.
Show resolved Hide resolved

/**
* 生成的 JSON 文件
*/
outputs: string[] = []

/**
* 获取输出文件
*/
async toLoad(): Promise<void> {
this.outputs = getOutputFiles(this.options.output)
}
waset marked this conversation as resolved.
Show resolved Hide resolved

/**
* 获取加载器
*
* @param dir 目录
*/
getLoaders(dir: string = this.options.output): Record<Loaders, any> {
return {
unocss: UnocssLoader(dir),
}
}

/**
* 生成 Iconify IntelliSense 配置
*/
async toIntelliSense(): Promise<void> {
await IconifyIntelliSenseSettings(this.outputs)
}
waset marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading