├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── ci.yml ├── .npmrc ├── CONTRIBUTING.md ├── pnpm-workspace.yaml ├── eslint.config.js ├── netlify.toml ├── .gitignore ├── docs ├── .vitepress │ ├── theme │ │ ├── index.ts │ │ └── style.css │ └── config.ts └── index.md ├── test ├── fixture.ts ├── highlight.test.ts ├── output │ └── case1.html └── detect.test.ts ├── scripts └── prepare.ts ├── tsconfig.json ├── LICENSE.md ├── .vscode └── settings.json ├── README.md ├── package.json └── src ├── index.ts └── collections.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [antfu] 2 | opencollective: antfu 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | shell-emulator=true 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to https://github.com/antfu/contribute 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | - docs 4 | - packages/* 5 | - examples/* 6 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import antfu from '@antfu/eslint-config' 3 | 4 | export default antfu( 5 | { 6 | type: 'lib', 7 | }, 8 | ) 9 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "docs/.vitepress/dist" 3 | command = "pnpm run docs:build" 4 | 5 | [build.environment] 6 | NODE_VERSION = "20" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .DS_Store 3 | .idea 4 | *.log 5 | *.tgz 6 | coverage 7 | dist 8 | lib-cov 9 | logs 10 | node_modules 11 | temp 12 | cache 13 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import Theme from 'vitepress/theme' 2 | import 'uno.css' 3 | import './style.css' 4 | 5 | export default { 6 | extends: Theme, 7 | } 8 | -------------------------------------------------------------------------------- /test/fixture.ts: -------------------------------------------------------------------------------- 1 | export const fixture1 = ` 2 |
3 | 4 | 5 | 6 | ` 7 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/style.css: -------------------------------------------------------------------------------- 1 | .shiki-inlay-iconify { 2 | display: inline-block; 3 | width: 1.2em; 4 | height: 1.2em; 5 | vertical-align: sub; 6 | margin: 0 0.1em; 7 | } 8 | 9 | html.dark .shiki-inlay-iconify { 10 | color: white; 11 | } 12 | -------------------------------------------------------------------------------- /scripts/prepare.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { collections } from '@iconify/collections/index.js' 3 | 4 | fs.writeFileSync( 5 | 'src/collections.ts', 6 | `export const defaultCollections = ${JSON.stringify(Object.keys(collections).sort((a, b) => b.length - a.length), null, 2)}\n`, 7 | 'utf-8', 8 | ) 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["ESNext"], 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "skipDefaultLibCheck": true, 13 | "skipLibCheck": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/highlight.test.ts: -------------------------------------------------------------------------------- 1 | import { createHighlighter } from 'shiki' 2 | import { expect, it } from 'vitest' 3 | import { transformerInlayIconify } from '../src' 4 | import { fixture1 } from './fixture' 5 | 6 | it('case1', async () => { 7 | const shiki = await createHighlighter({ 8 | langs: ['html'], 9 | themes: ['vitesse-dark'], 10 | }) 11 | 12 | const result = shiki.codeToHtml(fixture1, { 13 | lang: 'html', 14 | theme: 'vitesse-dark', 15 | transformers: [ 16 | transformerInlayIconify(), 17 | ], 18 | }) 19 | 20 | await expect(result).toMatchFileSnapshot('./output/case1.html') 21 | }) 22 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { presetIcons } from 'unocss' 2 | import UnoCSS from 'unocss/vite' 3 | import { defineConfig } from 'vitepress' 4 | import { transformerInlayIconify } from '../../src' 5 | 6 | export default defineConfig({ 7 | title: 'Shiki Inlay Iconify', 8 | description: 'Shiki transformer that renders inline Iconify preview', 9 | themeConfig: { 10 | socialLinks: [ 11 | { icon: 'github', link: 'https://github.com/antfu/shiki-transformer-inlay-iconify' }, 12 | ], 13 | }, 14 | markdown: { 15 | theme: { 16 | light: 'vitesse-light', 17 | dark: 'vitesse-dark', 18 | }, 19 | codeTransformers: [ 20 | transformerInlayIconify(), 21 | ], 22 | }, 23 | vite: { 24 | plugins: [ 25 | UnoCSS({ 26 | presets: [ 27 | presetIcons(), 28 | ], 29 | }), 30 | ], 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | id-token: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | tags: 10 | - 'v*' 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - uses: pnpm/action-setup@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | registry-url: https://registry.npmjs.org/ 24 | 25 | - run: pnpm dlx changelogithub 26 | env: 27 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 28 | 29 | # # Uncomment the following lines to publish to npm on CI 30 | # 31 | # - run: pnpm install 32 | # - run: pnpm publish -r --access public 33 | # env: 34 | # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 35 | # NPM_CONFIG_PROVENANCE: true 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Anthony Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disable the default formatter, use eslint instead 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | 6 | // Auto fix 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit", 9 | "source.organizeImports": "never" 10 | }, 11 | 12 | // Silent the stylistic rules in you IDE, but still auto fix them 13 | "eslint.rules.customizations": [ 14 | { "rule": "style/*", "severity": "off" }, 15 | { "rule": "*-indent", "severity": "off" }, 16 | { "rule": "*-spacing", "severity": "off" }, 17 | { "rule": "*-spaces", "severity": "off" }, 18 | { "rule": "*-order", "severity": "off" }, 19 | { "rule": "*-dangle", "severity": "off" }, 20 | { "rule": "*-newline", "severity": "off" }, 21 | { "rule": "*quotes", "severity": "off" }, 22 | { "rule": "*semi", "severity": "off" } 23 | ], 24 | 25 | // Enable eslint for all supported languages 26 | "eslint.validate": [ 27 | "javascript", 28 | "javascriptreact", 29 | "typescript", 30 | "typescriptreact", 31 | "vue", 32 | "html", 33 | "markdown", 34 | "json", 35 | "jsonc", 36 | "yaml" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: pnpm/action-setup@v4 18 | with: 19 | run_install: false 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | cache: pnpm 24 | 25 | - run: pnpm i -g @antfu/ni 26 | - run: nci 27 | - run: nr lint 28 | - run: nr typecheck 29 | 30 | test: 31 | runs-on: ${{ matrix.os }} 32 | 33 | strategy: 34 | matrix: 35 | node: [lts/*] 36 | os: [ubuntu-latest, windows-latest, macos-latest] 37 | fail-fast: false 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: pnpm/action-setup@v4 42 | with: 43 | run_install: false 44 | - name: Set node ${{ matrix.node }} 45 | uses: actions/setup-node@v4 46 | with: 47 | node-version: ${{ matrix.node }} 48 | cache: pnpm 49 | 50 | - run: pnpm i -g @antfu/ni 51 | - run: nci 52 | - run: nr build 53 | - run: nr test 54 | -------------------------------------------------------------------------------- /test/output/case1.html: -------------------------------------------------------------------------------- 1 |

2 | <div class="i-carbon:car />
3 | <Icon name="i-ph:acorn-duotone />
4 | <span i-logos-vue>
5 | <i icon="i-svg-spinners-clock />
6 | 
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shiki-transformer-inlay-iconify 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![bundle][bundle-src]][bundle-href] 6 | [![JSDocs][jsdocs-src]][jsdocs-href] 7 | [![License][license-src]][license-href] 8 | 9 | Shiki transformer that renders inline Iconify preview. 10 | 11 | Please check the docs: https://shiki-inlay-iconify.netlify.app/ 12 | 13 | ## Sponsors 14 | 15 |

16 | 17 | 18 | 19 |

20 | 21 | ## License 22 | 23 | [MIT](./LICENSE) License © 2024-PRESENT [Anthony Fu](https://github.com/antfu) 24 | 25 | 26 | 27 | [npm-version-src]: https://img.shields.io/npm/v/shiki-transformer-inlay-iconify?style=flat&colorA=080f12&colorB=1fa669 28 | [npm-version-href]: https://npmjs.com/package/shiki-transformer-inlay-iconify 29 | [npm-downloads-src]: https://img.shields.io/npm/dm/shiki-transformer-inlay-iconify?style=flat&colorA=080f12&colorB=1fa669 30 | [npm-downloads-href]: https://npmjs.com/package/shiki-transformer-inlay-iconify 31 | [bundle-src]: https://img.shields.io/bundlephobia/minzip/shiki-transformer-inlay-iconify?style=flat&colorA=080f12&colorB=1fa669&label=minzip 32 | [bundle-href]: https://bundlephobia.com/result?p=shiki-transformer-inlay-iconify 33 | [license-src]: https://img.shields.io/github/license/antfu/shiki-transformer-inlay-iconify.svg?style=flat&colorA=080f12&colorB=1fa669 34 | [license-href]: https://github.com/antfu/shiki-transformer-inlay-iconify/blob/main/LICENSE 35 | [jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669 36 | [jsdocs-href]: https://www.jsdocs.io/package/shiki-transformer-inlay-iconify 37 | -------------------------------------------------------------------------------- /test/detect.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { detectIconUsage } from '../src' 3 | import { fixture1 } from './fixture' 4 | 5 | const re = /\b(?:i-)?(?:fluent-emoji-high-contrast|material-symbols-light|cryptocurrency-color|emojione-monotone|fluent-emoji-flat|heroicons-outline|icon-park-outline|icon-park-twotone|simple-line-icons|streamline-emojis|flat-color-icons|material-symbols|heroicons-solid|icon-park-solid|pepicons-pencil|cryptocurrency|pepicons-print|bitcoin-icons|devicon-plain|entypo-social|grommet-icons|pixelarticons|system-uicons|token-branded|circle-flags|fluent-emoji|icomoon-free|medical-icon|pepicons-pop|simple-icons|svg-spinners|vscode-icons|academicons|emojione-v1|fa6-regular|fluent-mdl2|healthicons|humbleicons|majesticons|radix-icons|rivet-icons|skill-icons|akar-icons|ant-design|catppuccin|fa-regular|fa6-brands|file-icons|foundation|game-icons|gravity-ui|lets-icons|lucide-lab|mono-icons|streamline|teenyicons|arcticons|dashicons|eos-icons|fa-brands|fa6-solid|fontelico|gridicons|heroicons|hugeicons|icon-park|iconamoon|mdi-light|meteocons|websymbol|zondicons|brandico|bytesize|emojione|fa-solid|flagpack|flowbite|fontisto|guidance|marketeq|mingcute|nonicons|openmoji|pepicons|si-glyph|clarity|codicon|devicon|feather|flat-ui|formkit|fxemoji|iconoir|line-md|noto-v1|octicon|pajamas|raphael|tdesign|topcoat|twemoji|carbon|circum|entypo|fluent|icons8|lucide|memory|mynaui|nimbus|subway|tabler|vaadin|basil|charm|covid|logos|prime|quill|solar|token|typcn|bpmn|flag|gala|iwwa|mage|maki|noto|ooui|unjs|weui|zmdi|bxl|bxs|cbi|cib|cif|cil|eva|fad|geo|gis|ion|jam|map|mdi|oui|uil|uim|uis|uit|uiw|whh|wpf|bi|bx|ci|ei|el|ep|et|f7|fa|fe|gg|ic|il|la|ls|mi|oi|ph|ps|ri|vs|wi)[:-][-\w.]+\b/g 6 | it('detect', () => { 7 | expect( 8 | detectIconUsage( 9 | fixture1, 10 | re, 11 | ), 12 | ).toMatchInlineSnapshot(` 13 | [ 14 | { 15 | "end": 25, 16 | "name": "i-carbon:car", 17 | "start": 13, 18 | }, 19 | { 20 | "end": 60, 21 | "name": "i-ph:acorn-duotone", 22 | "start": 42, 23 | }, 24 | { 25 | "end": 82, 26 | "name": "i-logos-vue", 27 | "start": 71, 28 | }, 29 | { 30 | "end": 115, 31 | "name": "i-svg-spinners-clock", 32 | "start": 95, 33 | }, 34 | ] 35 | `) 36 | }) 37 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Shiki Transformer Inlay Iconify 2 | 3 | A transformer for [Shiki](https://shiki.style) that renders [Iconify](https://iconify.design) icons in code blocks, similar to the [Iconify IntelliSense](https://github.com/antfu/vscode-iconify) 4 | 5 | ```html 6 |
7 | 8 | 9 | 10 | ``` 11 | 12 | It will insert a `` element before the matched icon name id. You can use solutions [UnoCSS Icons](https://unocss.dev/presets/icons) to render the icons. 13 | 14 | ## Install 15 | 16 | ```sh 17 | npm i shiki-transformer-inlay-iconify 18 | ``` 19 | 20 | ## Usage 21 | 22 | You might also need some CSS to assist the styling, for example: 23 | 24 | ```css 25 | .shiki-inlay-iconify { 26 | display: inline-block; 27 | width: 1.2em; 28 | height: 1.2em; 29 | vertical-align: sub; 30 | } 31 | ``` 32 | 33 | Usages for some popular frameworks: 34 | 35 | ### Shiki 36 | 37 | Here is an example of how to use the [transformer](https://shiki.style/guide/transformers) with Shiki: 38 | 39 | ```ts 40 | import { createHighlighter } from 'shiki' 41 | import { transformerInlayIconify } from 'shiki-transformer-inlay-iconify' 42 | 43 | const shiki = await createHighlighter({ 44 | themes: [/* ... */], 45 | langs: [/* ... */], 46 | }) 47 | 48 | const html = shiki.codeToHtml(code, { 49 | lang: 'css', 50 | theme: 'nord', 51 | transformers: [ 52 | transformerInlayIconify() // [!code hl] 53 | ], 54 | }) 55 | ``` 56 | 57 | ### VitePress 58 | 59 | In VitePress, you can use the transformer in the configuration file: 60 | 61 | ```ts [.vitepress/config.ts] 62 | import { transformerInlayIconify } from 'shiki-transformer-inlay-iconify' 63 | import { defineConfig } from 'vitepress' 64 | 65 | export default defineConfig({ 66 | markdown: { 67 | codeTransformers: [ 68 | transformerInlayIconify(), // [!code hl] 69 | ], 70 | }, 71 | }) 72 | ``` 73 | 74 | ### Nuxt Content 75 | 76 | In Nuxt Content, you can use the transformer by create a `mdc.config.ts` file as follows: 77 | 78 | ```ts [mdc.config.ts] 79 | import { defineConfig } from '@nuxtjs/mdc/config' 80 | import { transformerInlayIconify } from 'shiki-transformer-inlay-iconify' 81 | 82 | export default defineConfig({ 83 | shiki: { 84 | transformers: [ 85 | transformerInlayIconify(), // [!code hl] 86 | ] 87 | } 88 | }) 89 | ``` 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiki-transformer-inlay-iconify", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "packageManager": "pnpm@10.5.2", 6 | "description": "Shiki transformer that renders inline Iconify preview", 7 | "author": "Anthony Fu ", 8 | "license": "MIT", 9 | "funding": "https://github.com/sponsors/antfu", 10 | "homepage": "https://github.com/antfu/shiki-transformer-inlay-iconify#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/antfu/shiki-transformer-inlay-iconify.git" 14 | }, 15 | "bugs": "https://github.com/antfu/shiki-transformer-inlay-iconify/issues", 16 | "keywords": [ 17 | "shiki", 18 | "shiki-transformer" 19 | ], 20 | "sideEffects": false, 21 | "exports": { 22 | ".": "./dist/index.mjs" 23 | }, 24 | "main": "./dist/index.mjs", 25 | "module": "./dist/index.mjs", 26 | "types": "./dist/index.d.mts", 27 | "files": [ 28 | "dist" 29 | ], 30 | "scripts": { 31 | "build": "unbuild", 32 | "dev": "unbuild --stub", 33 | "lint": "eslint .", 34 | "prepublishOnly": "nr build", 35 | "release": "bumpp && npm publish", 36 | "start": "esno src/index.ts", 37 | "test": "vitest", 38 | "typecheck": "tsc --noEmit", 39 | "prepare": "simple-git-hooks", 40 | "docs:dev": "vitepress dev docs", 41 | "docs:build": "vitepress build docs", 42 | "docs:preview": "vitepress preview docs" 43 | }, 44 | "dependencies": { 45 | "@shikijs/core": "^3.1.0", 46 | "@shikijs/types": "^3.1.0" 47 | }, 48 | "devDependencies": { 49 | "@antfu/eslint-config": "^4.6.0", 50 | "@antfu/ni": "^23.3.1", 51 | "@antfu/utils": "^9.1.0", 52 | "@iconify-json/carbon": "^1.2.8", 53 | "@iconify-json/logos": "^1.2.4", 54 | "@iconify-json/ph": "^1.2.2", 55 | "@iconify-json/svg-spinners": "^1.2.2", 56 | "@iconify/collections": "^1.0.525", 57 | "@types/node": "^22.13.9", 58 | "bumpp": "^10.0.3", 59 | "eslint": "^9.21.0", 60 | "esno": "^4.8.0", 61 | "lint-staged": "^15.4.3", 62 | "pnpm": "^10.5.2", 63 | "shiki": "^3.1.0", 64 | "simple-git-hooks": "^2.11.1", 65 | "tsx": "^4.19.3", 66 | "typescript": "^5.8.2", 67 | "unbuild": "^3.5.0", 68 | "unocss": "^66.0.0", 69 | "vite": "^6.2.0", 70 | "vitepress": "^2.0.0-alpha.3", 71 | "vitest": "^3.0.8" 72 | }, 73 | "simple-git-hooks": { 74 | "pre-commit": "pnpm lint-staged" 75 | }, 76 | "lint-staged": { 77 | "*": "eslint --fix" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { DecorationItem, ShikiTransformer } from '@shikijs/types' 2 | import { defaultCollections } from './collections' 3 | 4 | export { defaultCollections } 5 | 6 | export interface TransformerColorHighlightOptions { 7 | /** 8 | * Additional class to be added to the inlay element 9 | * Ignored when `getElementProps` is provided 10 | * 11 | * @default 'shiki-inlay-iconify' 12 | */ 13 | class?: string | string[] 14 | /** 15 | * The prefix of the icon name 16 | * 17 | * @default ['i-', ''] 18 | */ 19 | prefix?: string[] 20 | /** 21 | * The tag name of the inlay element 22 | * 23 | * @default 'span' 24 | */ 25 | elementTag?: string 26 | /** 27 | * Custom function to get the element props (`class`, `style`, etc) for the inlay element. 28 | * Returns `undefined` to skip the inlay element. 29 | * 30 | * @param name The icon name, raw 31 | */ 32 | getElementProps?: (name: string) => Record | undefined | null 33 | /** 34 | * The collections to be matched 35 | * 36 | * By default, it uses all collections from `@iconify/collections` 37 | */ 38 | collections?: string[] 39 | } 40 | 41 | export function transformerInlayIconify( 42 | options: TransformerColorHighlightOptions = {}, 43 | ): ShikiTransformer { 44 | const { 45 | class: classes = 'shiki-inlay-iconify', 46 | elementTag = 'span', 47 | prefix = ['i-', ''], 48 | collections = defaultCollections, 49 | getElementProps = name => ({ 50 | class: [name, ...(typeof classes === 'string' ? [classes] : classes)].join(' '), 51 | }), 52 | } = options 53 | 54 | const regex = new RegExp( 55 | `\\b(?:${prefix.sort((a, b) => b.length - a.length).join('|')})(?:${collections.sort((a, b) => b.length - a.length).join('|')})[:\-][-\\w.]+\\b`, 56 | 'g', 57 | ) 58 | 59 | return { 60 | name: 'shiki-transformer-inlay-iconify', 61 | preprocess(code) { 62 | const usages: IconUsage[] = detectIconUsage(code, regex) 63 | this.options.decorations ||= [] 64 | this.options.decorations.push( 65 | ...usages 66 | .map((i) => { 67 | const properties = getElementProps(i.name) 68 | if (!properties) 69 | return undefined 70 | return { 71 | start: i.start, 72 | end: i.end, 73 | alwaysWrap: true, 74 | transform: (el) => { 75 | el.children.unshift({ 76 | type: 'element', 77 | tagName: elementTag, 78 | properties, 79 | children: [], 80 | }) 81 | }, 82 | } 83 | }) 84 | .filter(x => !!x), 85 | ) 86 | return undefined 87 | }, 88 | } 89 | } 90 | 91 | interface IconUsage { 92 | start: number 93 | end: number 94 | name: string 95 | } 96 | 97 | export function detectIconUsage(code: string, regex: RegExp): IconUsage[] { 98 | const usages: IconUsage[] = [] 99 | 100 | for (const match of code.matchAll(regex)) { 101 | const name = match[0] 102 | const start = match.index 103 | const end = start + name.length 104 | usages.push({ start, end, name }) 105 | } 106 | 107 | return usages 108 | .sort((a, b) => a.start - b.start) 109 | } 110 | -------------------------------------------------------------------------------- /src/collections.ts: -------------------------------------------------------------------------------- 1 | export const defaultCollections = [ 2 | 'fluent-emoji-high-contrast', 3 | 'material-symbols-light', 4 | 'cryptocurrency-color', 5 | 'icon-park-outline', 6 | 'icon-park-twotone', 7 | 'fluent-emoji-flat', 8 | 'emojione-monotone', 9 | 'streamline-emojis', 10 | 'heroicons-outline', 11 | 'simple-line-icons', 12 | 'material-symbols', 13 | 'flat-color-icons', 14 | 'icon-park-solid', 15 | 'pepicons-pencil', 16 | 'heroicons-solid', 17 | 'pepicons-print', 18 | 'cryptocurrency', 19 | 'pixelarticons', 20 | 'system-uicons', 21 | 'bitcoin-icons', 22 | 'entypo-social', 23 | 'token-branded', 24 | 'devicon-plain', 25 | 'grommet-icons', 26 | 'pepicons-pop', 27 | 'svg-spinners', 28 | 'fluent-emoji', 29 | 'simple-icons', 30 | 'circle-flags', 31 | 'vscode-icons', 32 | 'medical-icon', 33 | 'icomoon-free', 34 | 'majesticons', 35 | 'radix-icons', 36 | 'humbleicons', 37 | 'fa6-regular', 38 | 'rivet-icons', 39 | 'emojione-v1', 40 | 'skill-icons', 41 | 'academicons', 42 | 'healthicons', 43 | 'fluent-mdl2', 44 | 'lucide-lab', 45 | 'teenyicons', 46 | 'ant-design', 47 | 'gravity-ui', 48 | 'akar-icons', 49 | 'lets-icons', 50 | 'streamline', 51 | 'fa6-brands', 52 | 'file-icons', 53 | 'catppuccin', 54 | 'game-icons', 55 | 'foundation', 56 | 'fa-regular', 57 | 'mono-icons', 58 | 'hugeicons', 59 | 'iconamoon', 60 | 'zondicons', 61 | 'mdi-light', 62 | 'eos-icons', 63 | 'gridicons', 64 | 'icon-park', 65 | 'heroicons', 66 | 'fa6-solid', 67 | 'meteocons', 68 | 'arcticons', 69 | 'dashicons', 70 | 'fa-brands', 71 | 'websymbol', 72 | 'fontelico', 73 | 'mingcute', 74 | 'flowbite', 75 | 'marketeq', 76 | 'bytesize', 77 | 'guidance', 78 | 'openmoji', 79 | 'emojione', 80 | 'nonicons', 81 | 'brandico', 82 | 'flagpack', 83 | 'fa-solid', 84 | 'fontisto', 85 | 'si-glyph', 86 | 'pepicons', 87 | 'iconoir', 88 | 'tdesign', 89 | 'clarity', 90 | 'octicon', 91 | 'pajamas', 92 | 'formkit', 93 | 'line-md', 94 | 'twemoji', 95 | 'noto-v1', 96 | 'fxemoji', 97 | 'codicon', 98 | 'devicon', 99 | 'raphael', 100 | 'flat-ui', 101 | 'topcoat', 102 | 'feather', 103 | 'tabler', 104 | 'carbon', 105 | 'lucide', 106 | 'memory', 107 | 'mynaui', 108 | 'circum', 109 | 'fluent', 110 | 'nimbus', 111 | 'entypo', 112 | 'icons8', 113 | 'subway', 114 | 'vaadin', 115 | 'solar', 116 | 'basil', 117 | 'typcn', 118 | 'charm', 119 | 'prime', 120 | 'quill', 121 | 'logos', 122 | 'token', 123 | 'covid', 124 | 'maki', 125 | 'weui', 126 | 'gala', 127 | 'mage', 128 | 'ooui', 129 | 'noto', 130 | 'flag', 131 | 'unjs', 132 | 'iwwa', 133 | 'zmdi', 134 | 'bpmn', 135 | 'mdi', 136 | 'ion', 137 | 'uil', 138 | 'bxs', 139 | 'cil', 140 | 'uiw', 141 | 'uim', 142 | 'uit', 143 | 'uis', 144 | 'jam', 145 | 'oui', 146 | 'bxl', 147 | 'cib', 148 | 'cbi', 149 | 'cif', 150 | 'gis', 151 | 'map', 152 | 'geo', 153 | 'fad', 154 | 'eva', 155 | 'wpf', 156 | 'whh', 157 | 'ic', 158 | 'ph', 159 | 'ri', 160 | 'bi', 161 | 'bx', 162 | 'gg', 163 | 'ci', 164 | 'ep', 165 | 'fe', 166 | 'mi', 167 | 'f7', 168 | 'ei', 169 | 'wi', 170 | 'la', 171 | 'fa', 172 | 'oi', 173 | 'et', 174 | 'el', 175 | 'ls', 176 | 'vs', 177 | 'il', 178 | 'ps', 179 | ] 180 | --------------------------------------------------------------------------------