Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 9 additions & 0 deletions examples/vite-vue3/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ const config: UserConfig = {
props.color = 'skyblue'
}
},
hmrResolver(file, folder, normalizedSVGIconName) {
console.log(file, folder, normalizedSVGIconName)
if (file.endsWith('assets/giftbox.svg')) {
return 'inline/async'
}
if (folder.endsWith('assets/custom-a')) {
return `custom/${normalizedSVGIconName}`
}
},
}),
Components({
dts: true,
Expand Down
2 changes: 2 additions & 0 deletions src/core/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export async function resolveOptions(options: Options): Promise<ResolvedOptions>
transform,
autoInstall = false,
collectionsNodeResolvePath = process.cwd(),
hmrResolver,
} = options

const webComponents = Object.assign({
Expand All @@ -38,6 +39,7 @@ export async function resolveOptions(options: Options): Promise<ResolvedOptions>
transform,
autoInstall,
collectionsNodeResolvePath,
hmrResolver,
}
}

Expand Down
39 changes: 39 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Options } from './types'
import { basename, dirname } from 'node:path'
import { camelToKebab } from '@iconify/utils/lib/misc/strings'
import { createUnplugin } from 'unplugin'
import { generateComponentFromPath, isIconPath, normalizeIconPath, resolveIconsPath } from './core/loader'
import { resolveOptions } from './core/options'
Expand Down Expand Up @@ -58,6 +60,43 @@ const unplugin = createUnplugin<Options | undefined>((options = {}) => {
}
}
},
vite: {
async handleHotUpdate({ file, server }) {
const hmrResolver = await resolved.then(({ hmrResolver }) => hmrResolver)
if (!hmrResolver) {
return undefined
}
const iconId = await hmrResolver(
file,
dirname(file).replace(/\\/g, '/'),
camelToKebab(basename(file).replace(/\.\w+$/, '')),
)
if (!iconId) {
return undefined
}

const icons = Array.isArray(iconId) ? iconId : [iconId]
const modules: import('vite').ModuleNode[] = []
for (const id of icons) {
const iconPath = isIconPath(id)
let module = iconPath ? server.moduleGraph.getModuleById(id) : undefined
if (module) {
modules.push(module)
continue
}
if (!iconPath) {
for (const prefix of ['~icons', 'virtual:icons', 'virtual/icons']) {
module = server.moduleGraph.getModuleById(`${prefix}/${id}`)
if (module) {
modules.push(module)
break
}
}
}
}
return modules.length > 0 ? modules : undefined
},
},
rollup: {
api: {
config: options,
Expand Down
50 changes: 49 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,54 @@ export interface Options {
* @deprecated no longer needed
*/
iconSource?: 'legacy' | 'modern' | 'auto'

/**
* HMR helper to resolve the icon id from local file SVG changes.
*
* **NOTE:** works only with Vite.
*
* Since there is no way to correlate the icon name with the local file SVG, we need a helper to invalidate the
* corresponding icon module.
*
* For example, we can have a custom collection using `readFile`, we cannot resolve `~icons/inline/async`
* when `<project-root>/assets/giftbox.svg` changes:
* ```ts
* customCollections: {
* inline: {
* async: () => fs.readFile('assets/giftbox.svg', 'utf-8')
* }
* }
* ```
*
* To resolve the icon in the previous example you will need to add:
* ```ts
* hmrResolver(file) => file.endsWidth('assets/giftbox.svg') ? 'inline/async' : undefined
* ```
*
* The `normalizedSVGIconName` is the SVG basename without the extension converted to kebab-case, will help you when using `FileSystemIconLoader`:
* ```ts
* customCollections: {
* custom: FileSystemIconLoader('assets/custom-a')
* }
* ```
*
* then, to resolve the icons from the `assets/custom-a` folder you only need to add the corresponding collection name:
* To resolve the icon in the previous example you will need to add:
* ```ts
* hmrResolver(file, folderName, normalizedSVGIconName) {
* if (folderName.endsWith('assets/custom-a') {
* return `custom/${normalizedSVGIconName}`
* }
* }
* ```
*
* @param file The file path received from the Vite's [handleHotUpdate](https://vite.dev/guide/api-plugin.html#handlehotupdate) hook.
* @param folderName The normalized folder containing the file.
* @param normalizedSVGIconName The normalized SVG name (basename without extension and the path).
* @return The icon collection and name to invalidate (<collection>/<icon>).
* @see https://vitejs.dev/guide/api-plugin.html#handlehotupdate
*/
hmrResolver?: (file: string, folderName: string, normalizedSVGIconName: string) => Awaitable<string | string[] | undefined>
}

export type ResolvedOptions = Omit<Required<Options>, 'iconSource' | 'transform'> & Pick<Options, 'transform'>
export type ResolvedOptions = Omit<Required<Options>, 'iconSource' | 'transform' | 'hmrResolver'> & Pick<Options, 'transform' | 'hmrResolver'>
Loading