Skip to content

feat: add custom HMR FS icon loader - #439

Closed
userquin wants to merge 10 commits into
mainfrom
userquin/feat-add-custom-hmr-icon-fs-loader
Closed

userquin wants to merge 10 commits into
mainfrom
userquin/feat-add-custom-hmr-icon-fs-loader

Conversation

@userquin

@userquin userquin commented Sep 4, 2026

Copy link
Copy Markdown
Member

Description

This PR adds a new FileSystemHMRIconLoader to allow use HMR when using a custom collection from filesystem:

  • ✔️ vite working (I need to review if we can just use the server.moduleGraph with env. api)
  • ✔️ webpack working
  • ❌ rspack not working : it seems there was a bug fixed at unplugin.webpack logic in this.addWatchFile => rspack should use the same change applied in this PR v3.3.0 has the fix 🤞
  • ❌ add how to use the new FileSystemHMRIconLoader at README file
  • ✔️ fixed typecheck script
  • ✔️ added webpack html plugin to check the new loader in the browser
  • ❌ test webpack with nuxt4 example (using builder option): still not tested on my local
  • ❌ test rspack with nuxt4 example (using builder option): still not tested on my local, we need to fix first rspack (maybe requires some updating nuxt to latest 4.4/4.5, some rspack/rsbuild changes to fix some issues)

Linked Issues

closes #424
supersedes ##425

Additional context

I need to check it here, once tested will be moved to iconify utils (the new FileSystemHMRIconLoader loader).

NOTE: we need to update some dependencies here adding some new stuff (will try to send some PR this weekend 🤞 ):

  • unplugin@2.33.11 => current version 3.3.0
  • vite 7 => update to vite 8
  • pnpm@10.28.1 => update to 11 (maybe to latest)
  • typescript@5 => update to 6 o 7 and review tsconfig.json options
  • update nuxt to latest 4.5: include also nuxt 5 example
  • create nuxt module: since we dont need any runtime we can just use some hack and build the module using nuxt module builder layout (I did it in some repos like devframes or @unvuetify)
  • review vite env. api. support: maybe unplugin requires a review

/cc @cyberalien @antfu

@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/unplugin-icons@439

commit: 3ccfe8a

Comment thread src/core/hmr.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are correctness/type-safety issues that can break TS consumption and HMR behavior (type mismatch in options typing, overly-permissive HMR loader detection, and a Vite watch/meta safety concern).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces a new filesystem-backed custom collection loader that can participate in HMR by tracking resolved SVG paths and invalidating the corresponding ~icons/... virtual modules across supported bundlers.

Changes:

  • Added FileSystemHMRIconLoader (and supporting HMR plumbing) to enable HMR for filesystem custom collections.
  • Updated the unplugin entry to register watched SVG files (non-Vite) and to invalidate Vite modules via handleHotUpdate.
  • Updated multiple examples (Vite, Webpack, Rspack, Nuxt) to demonstrate the new loader.
File summaries
File Description
src/types.ts Introduces CustomHMRIconLoader / CustomCollectionIconLoader types (but options typing still needs alignment).
src/loaders.ts Adds FileSystemHMRIconLoader implementation with path/name tracking for HMR.
src/index.ts Integrates watch-file registration and Vite handleHotUpdate invalidation hook.
src/core/options.ts Transforms HMR-aware custom collections into iconify-compatible loaders and exposes HMR helpers.
src/core/loader.ts Adds HMR loader type guard and adjusts iconify loader option shaping.
src/core/hmr.ts New helper to map changed SVG paths to virtual icon module IDs for invalidation.
examples/webpack/webpack.config.js Demonstrates FileSystemHMRIconLoader usage in Webpack and adds a minimal HTML emitter plugin.
examples/webpack/src/main.js Uses ?raw icon imports and renders both built-in and custom icons.
examples/webpack/package.json Adds a dev script for webpack serve.
examples/webpack/custom-a/car-a.svg Adds a sample custom SVG icon for the Webpack example.
examples/vite-vue3/vite.config.ts Switches the custom collection example to FileSystemHMRIconLoader.
examples/rspack-vue3/src/App.vue Demonstrates importing and rendering a custom icon in Rspack example.
examples/rspack-vue3/rspack.config.mjs Configures custom collections via FileSystemHMRIconLoader in Rspack example.
examples/rspack-vue3/custom-a/car-a.svg Adds a sample custom SVG icon for the Rspack example.
examples/nuxt4/nuxt.config.ts Wires FileSystemHMRIconLoader into Nuxt module config and icon resolver config (currently mismatched).
examples/nuxt4/app/custom-a/SteeringWheel.svg Adds a sample custom SVG icon for the Nuxt example.
examples/nuxt4/app/custom-a/car-a.svg Adds a sample custom SVG icon for the Nuxt example.
examples/nuxt4/app/app.vue Demonstrates importing and rendering a custom icon in Nuxt app.
Review details
  • Files reviewed: 14/18 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread examples/nuxt4/nuxt.config.ts Outdated
Comment thread src/core/hmr.ts
Comment thread src/core/loader.ts Outdated
Comment thread src/index.ts Outdated
Comment thread src/types.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed HMR correctness gaps (Vite watch registration/invalidation ID mismatches) and a TypeScript API typing mismatch that will block correct usage without casts.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

src/loaders.ts:70

  • resolveIcon() uses fs.lstat(). For symlinked SVGs, lstat().isFile() is false, so icons behind symlinks will never load. Using fs.stat() (follows symlinks) avoids this class of false negatives while still letting you check isFile().

src/types.ts:12

  • CustomHMRIconLoader is now part of the public types and FileSystemHMRIconLoader() returns it, but Options.customCollections is still typed as Record<string, CustomIconLoader | InlineCollection>. This makes TypeScript configs unable to pass ...FileSystemHMRIconLoader(...) without casts, even though runtime support exists in resolveOptions().
export interface CustomHMRIconLoader {
  /**
   * The name of the custom collection.
   */
  name: string
  • Files reviewed: 14/18 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread src/core/hmr.ts
Comment thread src/index.ts Outdated
Comment thread src/core/hmr.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are correctness/API issues in the new loader implementation (symlink handling and alias-to-path mapping) and a TypeScript typing mismatch that will break consumption without casts.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

src/loaders.ts:74

  • Using fs.lstat() here prevents loading icons that are symlinked (common in monorepos/workspaces), because lstat().isFile() is false for symbolic links. fs.stat() follows symlinks and keeps the behavior closer to typical file loaders while still ensuring the target is a file.
    src/loaders.ts:76
  • pathToName stores only one icon name per SVG path. If the same file is resolved through different name variants (e.g. SteeringWheel.svg imported as both SteeringWheel and steering-wheel), the later import overwrites the mapping and HMR invalidation will only refresh one of the virtual ids. To make HMR reliable, consider tracking a set/list of icon names per path and invalidating all matching virtual modules.
  • Files reviewed: 14/18 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/loaders.ts
@userquin

userquin commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

My plan:

  • Update dependencies, at least unplugin
  • Use Iconify's types: many things use the same types, and we need to add code to prevent TypeScript from complaining
  • Add the Nuxt module with the builder's layout
  • Wait for feat(utils): add custom HMR FS icon loader iconify/iconify#3562
  • Update this pull request with the new Iconify loader
  • Update all dependencies and examples: Vite 8, for example

@cyberalien

Copy link
Copy Markdown
Contributor

Published new version of Iconify Utils with new loader.

@userquin

userquin commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

superseded by #440

@userquin userquin closed this Sep 7, 2026
@userquin
userquin deleted the userquin/feat-add-custom-hmr-icon-fs-loader branch September 7, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom icons HMR failure

4 participants