vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
67.17k stars 6.04k forks source link

SVG Icons Not Rendering Consistently with Vite Due to Path Resolution Issues #17893

Closed otabekoff closed 3 weeks ago

otabekoff commented 3 weeks ago

Describe the bug

I am experiencing inconsistent behavior with SVG icons in my Vue application using Vite. When using the Icon.vue component to display SVG icons from a sprite file, some icons intermittently do not render. The issue seems related to path resolution or caching behavior in Vite.

I am doing:

What I expect is:

What actually happening is:

Reproduction

https://stackblitz.com/~/github.com/otabekoff/vitejs-vite-tco99a

Steps to reproduce

Reproducing the issue involves creating a Vue component that uses SVG sprites and observing the inconsistent rendering of icons.

So, I've provided a ready code example on StackBlitz. After installing the project, follow the instructions in the instructions.md file. The issue persists there as well, not just on my local setup.

System Info

System:
    OS: Windows 11 10.0.22631
    CPU: (8) x64 Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz
    Memory: 4.96 GB / 15.74 GB
  Binaries:
    Node: 20.11.1 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.22 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 10.8.2 - C:\Program Files\nodejs\npm.CMD
    pnpm: 8.14.1 - ~\AppData\Local\pnpm\pnpm.CMD
  Browsers:
    Edge: Chromium (127.0.2651.74)
    Internet Explorer: 11.0.22621.3527

But I've also Chrome latest and using it. Whatever the browser is, having the same issue. I've tested it on Edge too.

Used Package Manager

npm

Logs

Click to expand! ```shell > vite --debug vite:config bundled config file loaded in 1359.34ms +0ms vite:config using resolved config: { vite:config plugins: [ vite:config 'vite:optimized-deps', vite:config 'vite:watch-package-data', vite:config 'vite:pre-alias', vite:config 'alias', vite:config 'vite-plugin-inspect', vite:config 'vite-plugin-vue-inspector', vite:config 'vite-plugin-vue-devtools', vite:config 'vite:modulepreload-polyfill', vite:config 'vite:resolve', vite:config 'vite:html-inline-proxy', vite:config 'vite:css', vite:config 'vite:esbuild', vite:config 'vite:json', vite:config 'vite:wasm-helper', vite:config 'vite:worker', vite:config 'vite:asset', vite:config 'vite:vue', vite:config 'vite:wasm-fallback', vite:config 'vite:define', vite:config 'vite:css-post', vite:config 'vite:worker-import-meta-url', vite:config 'vite:asset-import-meta-url', vite:config 'vite:dynamic-import-vars', vite:config 'vite:import-glob', vite:config 'vite-plugin-vue-inspector:post', vite:config 'vite:client-inject', vite:config 'vite:css-analysis', vite:config 'vite:import-analysis' vite:config ], vite:config css: { preprocessorOptions: { scss: [Object] }, lightningcss: undefined }, vite:config resolve: { vite:config mainFields: [ 'browser', 'module', 'jsnext:main', 'jsnext' ], vite:config conditions: [], vite:config extensions: [ vite:config '.mjs', '.js', vite:config '.mts', '.ts', vite:config '.jsx', '.tsx', vite:config '.json' vite:config ], vite:config dedupe: [ 'vue' ], vite:config preserveSymlinks: false, vite:config alias: [ [Object], [Object], [Object], [Object] ] vite:config }, vite:config optimizeDeps: { vite:config holdUntilCrawlEnd: true, vite:config force: undefined, vite:config esbuildOptions: { preserveSymlinks: false } vite:config }, vite:config server: { vite:config preTransformRequests: true, vite:config host: undefined, vite:config sourcemapIgnoreList: [Function: isInNodeModules$1], vite:config middlewareMode: false, vite:config fs: { vite:config strict: true, vite:config allow: [Array], vite:config deny: [Array], vite:config cachedChecks: undefined vite:config } vite:config }, vite:config define: { vite:config __VUE_OPTIONS_API__: true, vite:config __VUE_PROD_DEVTOOLS__: false, vite:config __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false vite:config }, vite:config ssr: { vite:config target: 'node', vite:config external: [], vite:config optimizeDeps: { noDiscovery: true, esbuildOptions: [Object] } vite:config }, vite:config configFile: 'C:/vueprojects/bekky-dev/vite.config.js', vite:config configFileDependencies: [ 'C:/vueprojects/bekky-dev/vite.config.js' ], vite:config inlineConfig: { vite:config root: undefined, vite:config base: undefined, vite:config mode: undefined, vite:config configFile: undefined, vite:config logLevel: undefined, vite:config clearScreen: undefined, vite:config optimizeDeps: { force: undefined }, vite:config server: { host: undefined } vite:config }, vite:config root: 'C:/vueprojects/bekky-dev', vite:config base: '/', vite:config decodedBase: '/', vite:config rawBase: '/', vite:config publicDir: 'C:/vueprojects/bekky-dev/public', vite:config cacheDir: 'C:/vueprojects/bekky-dev/node_modules/.vite', vite:config command: 'serve', vite:config mode: 'development', vite:config isWorker: false, vite:config mainConfig: null, vite:config bundleChain: [], vite:config isProduction: false, vite:config esbuild: { jsxDev: true }, vite:config build: { vite:config target: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari14' ], vite:config cssTarget: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari14' ], vite:config outDir: 'dist', vite:config assetsDir: 'assets', vite:config assetsInlineLimit: 4096, vite:config cssCodeSplit: true, vite:config sourcemap: false, vite:config rollupOptions: {}, vite:config minify: 'esbuild', vite:config terserOptions: {}, vite:config write: true, vite:config emptyOutDir: null, vite:config copyPublicDir: true, vite:config manifest: false, vite:config lib: false, vite:config ssr: false, vite:config ssrManifest: false, vite:config ssrEmitAssets: false, vite:config reportCompressedSize: true, vite:config chunkSizeWarningLimit: 500, vite:config watch: null, vite:config commonjsOptions: { include: [Array], extensions: [Array] }, vite:config dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] }, vite:config modulePreload: { polyfill: true }, vite:config cssMinify: true vite:config }, vite:config preview: { vite:config port: undefined, vite:config strictPort: undefined, vite:config host: undefined, vite:config https: undefined, vite:config open: undefined, vite:config proxy: undefined, vite:config cors: undefined, vite:config headers: undefined vite:config }, vite:config envDir: 'C:/vueprojects/bekky-dev', vite:config env: { BASE_URL: '/', MODE: 'development', DEV: true, PROD: false }, vite:config assetsInclude: [Function: assetsInclude], vite:config logger: { vite:config hasWarned: false, vite:config info: [Function: info], vite:config warn: [Function: warn], vite:config warnOnce: [Function: warnOnce], vite:config error: [Function: error], vite:config clearScreen: [Function: clearScreen], vite:config hasErrorLogged: [Function: hasErrorLogged] vite:config }, vite:config packageCache: Map(1) { vite:config 'fnpd_C:/vueprojects/bekky-dev' => { vite:config dir: 'C:/vueprojects/bekky-dev', vite:config data: [Object], vite:config hasSideEffects: [Function: hasSideEffects], vite:config webResolvedImports: {}, vite:config nodeResolvedImports: {}, vite:config setResolvedCache: [Function: setResolvedCache], vite:config getResolvedCache: [Function: getResolvedCache] vite:config }, vite:config set: [Function (anonymous)] vite:config }, vite:config createResolver: [Function (anonymous)], vite:config worker: { format: 'iife', plugins: '() => plugins', rollupOptions: {} }, vite:config appType: 'spa', vite:config experimental: { importGlobRestoreExtension: false, hmrPartialAccept: false }, vite:config getSortedPlugins: [Function: getSortedPlugins], vite:config getSortedPluginHooks: [Function: getSortedPluginHooks] vite:config } +28ms vite:deps Hash is consistent. Skipping. Use --force to override. +0ms Port 5173 is in use, trying another one... VITE v5.4.1 ready in 1955 ms ➜ Local: http://localhost:5174/ ➜ Network: use --host to expose ➜ Vue DevTools: Open http://localhost:5174/__devtools__/ as a separate window ➜ Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools ➜ press h + enter to show help ```

Validations

otabekoff commented 3 weeks ago

How the Issue Started and Has Evolved?

Please read further to understand the issue in detail. I ask for your patience.

The component name mentioned as Icon.vue in the bug description is actually BekIcon.vue. "Bek" is a prefix for my project components, as the ESLint plugin does not allow single-word component names. Thus, I use BekIcon for registration and do not mind this naming convention.

Project Creation Process

I created a new Vue project using npm create vue@latest and started developing my components. The issue began when working with an icon component designed to display sprites from an SVG sprite file. I am implementing an Icon component using SVG symbols.

I created a new Vue component in src/components called BekIcon.vue. The initial code was:

<template>
  <svg class="icon" :class="className" :width="size" :height="size">
    <use :xlink:href="`${props.icon}`"></use>
  </svg>
</template>

<script setup>
const props = defineProps({
  icon: {
    type: String,
    required: true
  },
  size: {
    type: [String, Number],
    default: '24'
  },
  className: {
    type: String,
    default: ''
  }
})
</script>

<style scoped>
.icon {
  display: inline-block;
  vertical-align: middle;
}
</style>

Next, I created an SVG sprite file with the icons. Initially, the SVG looked like this:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
    <!-- Notes: make sure 24x24, remove title, rename to symbol, add id, format -->
    <symbol id="search" width="24" height="24" viewBox="0 0 24 24" fill="none"
        xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" clip-rule="evenodd"
            d="M10.1408 17.2906C11.726 17.2906 13.1906 16.7737 14.3754 15.8992L19.0696 20.6638C19.5067 21.1076 20.2206 21.1127 20.6641 20.6752C21.1075 20.2378 21.1126 19.5235 20.6754 19.0797L15.9575 14.291C16.7911 13.1219 17.2816 11.6909 17.2816 10.1453C17.2816 6.19906 14.0845 3 10.1408 3C6.19704 3 3 6.19906 3 10.1453C3 14.0916 6.19704 17.2906 10.1408 17.2906ZM10.1408 15.0342C12.8391 15.0342 15.0266 12.8454 15.0266 10.1453C15.0266 7.44525 12.8391 5.25641 10.1408 5.25641C7.44243 5.25641 5.25499 7.44525 5.25499 10.1453C5.25499 12.8454 7.44243 15.0342 10.1408 15.0342Z"
            fill="black" />
    </symbol>
</svg>

I tested it in src/views/HomeView.vue like this:

<script setup></script>

<template>
  <main>
    <bek-icon icon="/ui-icons.svg#search" />
  </main>
</template>

Encountering the Issue

Initially, it worked. However, when I added more icons to the SVG file:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
    <!-- Notes: make sure 24x24, remove title, rename to symbol, add id, format -->
    <symbol id="search" width="24" height="24" viewBox="0 0 24 24" fill="none"
        xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" clip-rule="evenodd"
            d="M10.1408 17.2906C11.726 17.2906 13.1906 16.7737 14.3754 15.8992L19.0696 20.6638C19.5067 21.1076 20.2206 21.1127 20.6641 20.6752C21.1075 20.2378 21.1126 19.5235 20.6754 19.0797L15.9575 14.291C16.7911 13.1219 17.2816 11.6909 17.2816 10.1453C17.2816 6.19906 14.0845 3 10.1408 3C6.19704 3 3 6.19906 3 10.1453C3 14.0916 6.19704 17.2906 10.1408 17.2906ZM10.1408 15.0342C12.8391 15.0342 15.0266 12.8454 15.0266 10.1453C15.0266 7.44525 12.8391 5.25641 10.1408 5.25641C7.44243 5.25641 5.25499 7.44525 5.25499 10.1453C5.25499 12.8454 7.44243 15.0342 10.1408 15.0342Z"
            fill="black" />
    </symbol>

    <symbol id="hamburger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
    </symbol>

    <symbol id="notification" width="24" height="24" viewBox="0 0 24 24" fill="none"
        xmlns="http://www.w3.org/2000/svg">
        <path
            d="M21 19V20H3V19L5 17V11C5 7.9 7.03 5.17 10 4.29C10 4.19 10 4.1 10 4C10 3.46957 10.2107 2.96086 10.5858 2.58579C10.9609 2.21071 11.4696 2 12 2C12.5304 2 13.0391 2.21071 13.4142 2.58579C13.7893 2.96086 14 3.46957 14 4C14 4.1 14 4.19 14 4.29C16.97 5.17 19 7.9 19 11V17L21 19ZM14 21C14 21.5304 13.7893 22.0391 13.4142 22.4142C13.0391 22.7893 12.5304 23 12 23C11.4696 23 10.9609 22.7893 10.5858 22.4142C10.2107 22.0391 10 21.5304 10 21"
            fill="black" />
    </symbol>
</svg>

Then, the issue persisted here. The icons for hamburger and notification were not showing up. I checked the Chrome DevTools, console, and my terminal where I ran npm run dev, but there were no errors. I thought Vite might have frozen, so I stopped the npm run dev command and restarted the server.

<script setup></script>

<template>
  <main>
    <bek-icon icon="/ui-icons.svg#search" />
    <bek-icon icon="/ui-icons.svg#hamburger" />
    <bek-icon icon="/ui-icons.svg#notification" />
  </main>
</template>

But this time it was wonderful than ever. To my surprise, after restarting, the situation improved, but inconsistently. The notification icon, which was not displaying before, began to appear. However, the hamburger icon between search and notification icons was still not visible as it was earlier. This inconsistency was puzzling.

When I open Chrome Devtools and check if the button is still here it was there. Why I do this because, icon was transparent but taking up its own space.

I spent hours researching SVGs, my exports from Figma, and related issues, but none of them seemed to help. I then suspected that the problem might be related to file paths. Vite provides / to the public folder, but I tried using /public/ as well. Vite recommended me using /, so I reverted to it.

I tried to fix this issue and then reloaded the page for 100th or 1000th time, I don't know 😅. After trying various fixes, reloading the page countless times, and clearing the cache multiple times, the hamburger icon eventually appeared finally.

I thought the problem might be related to caching issues. I took it normal and continued adding more icons and didn't mind about this.

This kind of small issues may happen in development process that takes tons of time, I thought.

Then, I added more icons, including dots-vert and play, to the SVG:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
    <!-- Notes: make sure 24x24, remove title, rename to symbol, add id, format -->
    <symbol id="search" width="24" height="24" viewBox="0 0 24 24" fill="none"
        xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" clip-rule="evenodd"
            d="M10.1408 17.2906C11.726 17.2906 13.1906 16.7737 14.3754 15.8992L19.0696 20.6638C19.5067 21.1076 20.2206 21.1127 20.6641 20.6752C21.1075 20.2378 21.1126 19.5235 20.6754 19.0797L15.9575 14.291C16.7911 13.1219 17.2816 11.6909 17.2816 10.1453C17.2816 6.19906 14.0845 3 10.1408 3C6.19704 3 3 6.19906 3 10.1453C3 14.0916 6.19704 17.2906 10.1408 17.2906ZM10.1408 15.0342C12.8391 15.0342 15.0266 12.8454 15.0266 10.1453C15.0266 7.44525 12.8391 5.25641 10.1408 5.25641C7.44243 5.25641 5.25499 7.44525 5.25499 10.1453C5.25499 12.8454 7.44243 15.0342 10.1408 15.0342Z"
            fill="black" />
    </symbol>

    <symbol id="hamburger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
    </symbol>

    <symbol id="notification" width="24" height="24" viewBox="0 0 24 24" fill="none"
        xmlns="http://www.w3.org/2000/svg">
        <path
            d="M21 19V20H3V19L5 17V11C5 7.9 7.03 5.17 10 4.29C10 4.19 10 4.1 10 4C10 3.46957 10.2107 2.96086 10.5858 2.58579C10.9609 2.21071 11.4696 2 12 2C12.5304 2 13.0391 2.21071 13.4142 2.58579C13.7893 2.96086 14 3.46957 14 4C14 4.1 14 4.19 14 4.29C16.97 5.17 19 7.9 19 11V17L21 19ZM14 21C14 21.5304 13.7893 22.0391 13.4142 22.4142C13.0391 22.7893 12.5304 23 12 23C11.4696 23 10.9609 22.7893 10.5858 22.4142C10.2107 22.0391 10 21.5304 10 21"
            fill="black" />
    </symbol>

    <symbol id="play" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M8,5.14V19.14L19,12.14L8,5.14Z" />
    </symbol>
    <symbol id="dots-vert" width="24" height="24" viewBox="0 0 24 24" fill="none"
        xmlns="http://www.w3.org/2000/svg">
        <path
            d="M12 16C12.5304 16 13.0391 16.2107 13.4142 16.5858C13.7893 16.9609 14 17.4696 14 18C14 18.5304 13.7893 19.0391 13.4142 19.4142C13.0391 19.7893 12.5304 20 12 20C11.4696 20 10.9609 19.7893 10.5858 19.4142C10.2107 19.0391 10 18.5304 10 18C10 17.4696 10.2107 16.9609 10.5858 16.5858C10.9609 16.2107 11.4696 16 12 16ZM12 10C12.5304 10 13.0391 10.2107 13.4142 10.5858C13.7893 10.9609 14 11.4696 14 12C14 12.5304 13.7893 13.0391 13.4142 13.4142C13.0391 13.7893 12.5304 14 12 14C11.4696 14 10.9609 13.7893 10.5858 13.4142C10.2107 13.0391 10 12.5304 10 12C10 11.4696 10.2107 10.9609 10.5858 10.5858C10.9609 10.2107 11.4696 10 12 10ZM12 4C12.5304 4 13.0391 4.21071 13.4142 4.58579C13.7893 4.96086 14 5.46957 14 6C14 6.53043 13.7893 7.03914 13.4142 7.41421C13.0391 7.78929 12.5304 8 12 8C11.4696 8 10.9609 7.78929 10.5858 7.41421C10.2107 7.03914 10 6.53043 10 6C10 5.46957 10.2107 4.96086 10.5858 4.58579C10.9609 4.21071 11.4696 4 12 4Z"
            fill="black" />
    </symbol>
</svg>

And updated the HomeView.vue component to:

<script setup></script>

<template>
  <main>
    <bek-icon icon="/ui-icons.svg#hamburger" />
    <bek-icon icon="/ui-icons.svg#search" />
    <bek-icon icon="/ui-icons.svg#play" />
    <bek-icon icon="/ui-icons.svg#dots-vert" />
    <bek-icon icon="/ui-icons.svg#notification" />
  </main>
</template>

When I noticed that the play and dots-vert icons weren't showing up, I initially thought it was a caching issue again. So I tried

None worked. When I reverted to using /public/ for icon paths, the icons started displaying correctly. This behavior made me suspect a caching issue or a problem with Vite's handling of paths.

Up to this point, I believed the issue was related to caching and didn't consider that changing / to /public/ and then reverting to / might have already resolved the issue.

I started thinking about how the problem was solved earlier when I first encountered this issue. Almost as a last-ditch effort, I decided to change the path from / to /public/, even though it felt counterintuitive. I had full faith in Vite, so this felt like a shot in the dark. However, to my surprise, when I refreshed the page, all my icons appeared correctly. This made me question why I believed in Vite 100% — perhaps I should have left room for that 0.1% of doubt.

After that, I switched back to using / and checked to see if everything was working correctly, and it was, without any issues.

The problem seems to be related to how Vite resolves the path /. It appears that Vite is trying to shorten /public/ to / or handle it in a way that's not entirely transparent. This led me to realize that something isn't right with Vite's handling of this situation, and I now consider it to be a potential bug.

As a result, I’ve decided to switch to using /public/ for development to avoid any issues. However, I plan to revert to using / once the project is ready for production. Until then, I want to avoid the warnings from Vite about changing /public/ to /.

I hope I could explain what is happening.

Thank you for taking the time to read this. I hope this issue will be addressed soon so that I can develop without these workarounds and warnings. I appreciate your attention to this matter and look forward to a fix in the near future. 😊

sapphi-red commented 3 weeks ago

I wasn't able to reproduce it. Is this the same with what you did? Note that the manual refresh is necessary since the URL is same and therefore the browser won't swap the content of SVG to the newer one. For Vite to auto refresh in this case, your code needs to include a specific code (import svgUrl from '/ui-icons.svg') so that Vite knows that the code depends on /ui-icons.svg.

https://github.com/user-attachments/assets/b4d875cc-abe8-4612-848a-5b38d48a02b1

otabekoff commented 3 weeks ago

Hi @sapphi-red, could you provide a working example? I cloned the repository and tried to replicate your setup, but I wasn’t able to get it working successfully.

hi-ogawa commented 3 weeks ago

When I tested a bit, it looked like Chrome is doing odd caching when it comes to svg's <use xlink:href="/ui-icons.svg#search"></use>. What's also odd is that /ui-icons.svg request won't even appear on devtools https://issues.chromium.org/issues/348598125.

I made a repro here without Vite https://github.com/hi-ogawa/reproductions/tree/main/vite-17893-svg-xlink-href. This repro uses sirv-cli --dev to serve just two files index.html and ui-icons.svg, and a same issue reproduces on Chromium, but not on Firefox.

I'm not sure what's helping by writing /public/ui-icons.svg on Vite, but to me, Chrome's behavior looks suspicious (though it's hard to believe).

otabekoff commented 3 weeks ago

@hi-ogawa Yeah, I also tried and having the same issue. When I tested it on Firefox it is working.

My index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body>
    <h1>Test</h1>
    <svg class="icon" width="24" height="24">
      <use xlink:href="/ui-icons.svg#search"></use>
    </svg>
  </body>
</html>

and ui-icons.svg:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
    <symbol id="search" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" clip-rule="evenodd" d="M10.1408 17.2906C11.726 17.2906 13.1906 16.7737 14.3754 15.8992L19.0696 20.6638C19.5067 21.1076 20.2206 21.1127 20.6641 20.6752C21.1075 20.2378 21.1126 19.5235 20.6754 19.0797L15.9575 14.291C16.7911 13.1219 17.2816 11.6909 17.2816 10.1453C17.2816 6.19906 14.0845 3 10.1408 3C6.19704 3 3 6.19906 3 10.1453C3 14.0916 6.19704 17.2906 10.1408 17.2906ZM10.1408 15.0342C12.8391 15.0342 15.0266 12.8454 15.0266 10.1453C15.0266 7.44525 12.8391 5.25641 10.1408 5.25641C7.44243 5.25641 5.25499 7.44525 5.25499 10.1453C5.25499 12.8454 7.44243 15.0342 10.1408 15.0342Z" fill="black" />
    </symbol>
</svg>

Here is what's happening on my Chrome version 127.0.6533.120 (Official Build) (64-bit):

`symbol` commented: image
`symbol` commented out: image2
otabekoff commented 3 weeks ago

and a same issue reproduces on Chromium, but no on Firefox.

Finally has a workaround and got it working with Firefox for now as @hi-ogawa mentioned above.

I've implemented this Vite plugin for my current project to help me organize my svg files and create a sprite. But I am thinking about publishing it as an NPM package to make it publicly available.

What's your suggestions? ```javascript // plugins/vite-plugin-icon-sprite.js import { promises as fs } from 'fs' import path from 'path' /** * Reads the directory and filters out files that do not have the .svg extension. * @param {string} directory - The directory containing the SVG files. * @returns {Promise} - A promise that resolves with an array of SVG filenames. */ const getSVGFiles = async (directory) => { const files = await fs.readdir(directory) return files.filter(file => file.endsWith('.svg')) } /** * Processes a single SVG file by removing the existing id attribute and wrapping it in a element. * @param {string} file - The name of the SVG file. * @param {string} directory - The directory containing the SVG file. * @returns {Promise} - A promise that resolves with the modified SVG content as a element. */ const processSVGFile = async (file, directory) => { const svgContent = await fs.readFile(path.join(directory, file), 'utf8') const id = path.basename(file, '.svg') // Extracts the filename without extension return svgContent .replace(/id="[^"]+"/, '') // Remove existing ID if present .replace(' with and add id .replace('', '') // Close the tag } /** * Generates the content for the SVG sprite by processing all SVG files in the directory. * @param {string} directory - The directory containing the SVG files. * @returns {Promise} - A promise that resolves with the complete SVG sprite content. */ const generateSpriteContent = async (directory) => { const files = await getSVGFiles(directory) const symbolPromises = files.map(file => processSVGFile(file, directory)) const symbols = await Promise.all(symbolPromises) return `\n\n${symbols.join('\n')}\n` } /** * Writes the generated SVG sprite content to a file in the specified output path. * @param {string} sprite - The SVG sprite content to write to the file. * @param {string} outputPath - The file path where the SVG sprite should be saved. * @returns {Promise} - A promise that resolves when the file has been written. */ const writeSpriteToFile = async (sprite, outputPath) => { await fs.writeFile(outputPath, sprite) } /** * Watches for changes in SVG files and regenerates the sprite. * @param {object} server - The Vite server object. * @param {string} iconsDir - The directory containing the SVG files. * @param {string} outputPath - The file path where the SVG sprite should be saved. */ const watchAndRegenerateSprite = (server, iconsDir, outputPath) => { server.watcher.add(path.join(iconsDir, '*.svg')) // Add the directory to the watcher server.watcher.on('change', async (changedPath) => { if (changedPath.endsWith('.svg')) { await generateAndWriteSprite(iconsDir, outputPath) // Regenerate sprite on change } }) } /** * Orchestrates the SVG sprite generation process by reading SVG files from the icons directory * and writing the generated sprite to the static folder. * @param {string} iconsDir - The directory containing the SVG files. * @param {string} outputPath - The file path where the SVG sprite should be saved. * @returns {Promise} - A promise that resolves when the SVG sprite has been generated. */ const generateAndWriteSprite = async (iconsDir, outputPath) => { const sprite = await generateSpriteContent(iconsDir) await writeSpriteToFile(sprite, outputPath) } /** * Creates and returns the Vite plugin object with hooks for buildStart and configureServer. * @returns {object} - The Vite plugin object with hooks for buildStart and configureServer. */ export default function IconSpritePlugin() { const baseDir = path.resolve('static', 'icons') // Base directory for icons const iconsDir = path.join(baseDir, 'icons') // Full path to the icons directory const outputPath = path.join('static', 'icon-sprite.svg') // Path to save the sprite return { name: 'icon-sprite-plugin', // Hook that runs at the start of the build process to generate the SVG sprite. buildStart: () => generateAndWriteSprite(iconsDir, outputPath), // Hook that sets up a watcher during development to regenerate the SVG sprite on changes. configureServer: (server) => watchAndRegenerateSprite(server, iconsDir, outputPath) } } ```
otabekoff commented 3 weeks ago

But the issue is still persisting on Chrome. So, should I file a bug on Chrome and then close this?

userquin commented 3 weeks ago

Here you can find some info about svg sprites (css) and browsers support: https://github.com/unocss/unocss/pull/2675#issuecomment-1670898336

otabekoff commented 3 weeks ago

@userquin I know more about you since a few years. But you may don't know about me. But it's an honor to chat with you.

Look, I want to have my own SVG sprite file and use icons put in there inside of <symbol> tags. For this, I want to have a component. But, icons are not rendering well. When I heard from @hi-ogawa about this was Chrome specific issue but it is working on Firefox, I understood that it maybe resolved after a century. Because, Google like giant has no time to care about and hear from us about bugs or limitation complaints. So, I need another way to have my scalable icons like what I want to have with SVG. What do you recommend?

otabekoff commented 3 weeks ago

OK, new clues are coming. If I reload, hard reload, clear cache and restart the tab or page it is not working, but if I close Chrome or the tab/page then open the server at a new tab/page then it is working.

userquin commented 3 weeks ago

@otabekoff I guess it is some internal SVG cache, even moving the svg to src/assets folder with custom svg url (I'm going to try adding a middleware to add some cache headers to the svg sprite).

Add BekSpriteIcon.vue to src/components and change the import in src/main.js.

src/components/BekSpriteIcon.vue ```html ```

imagen

otabekoff commented 3 weeks ago

@userquin But when I test the BekSpriteIcon component the xlink:href value is [object Object]?t=1723995190340#file-download which is coming from import svgUrl from '@/assets/ui-icons.svg'. And I don't know why it is object on me and valid stuff on you.

userquin commented 3 weeks ago

@otabekoff change icon names, just remove the sprite svg, use just the icon name: /ui-icons.svg#<icon> => <icon>

I'm preparing reproduction fixing your problem, I guess there is small regression on watcher... give me 5 minutes

userquin commented 3 weeks ago

@otabekoff here the repo https://github.com/userquin/vite-issue-17893, link to open it on SB on readme file.

The repo using import.meta.glob and so you can use any svg sprite inside src/assets/svg-sprites (the palette-icons.svg is just a copy/paste from ui-icons.svg). You can change the BekIcon.vue importing the url should also work (will require some work on your side).

EDIT: you can try just adding the Vite plugin in your repo using the svg sprite from the assets folder.

@hi-ogawa @sapphi-red removing the watcher plugin the repo stops reloading the svgs, no idea what's happening with this.

otabekoff commented 3 weeks ago

Thank you for sharing the repository and solution!

I see that you're using import.meta.glob for SVG sprites inside src/assets/svg-sprites, and I appreciate the example with palette-icons.svg as well. Excluding the .svg extension and using a structure like ui-colors:bookmark is a great approach, and I really appreciate that.

I've reviewed the code in the repository and adapted the BekIcon.vue component to import the URL as suggested. I also tested it locally, and it's working fine. I don't have any further questions or issues at this point.

@userquin, thank you---it worked like a charm! If @sapphi-red and @hi-ogawa clarify their answers to your question, I'd like to close this issue. My great thanks to all of you, especially to you, for your quick replies, time and help with further guidance.

sapphi-red commented 3 weeks ago

Hm, https://stackblitz.com/~/github.com/otabekoff/vitejs-vite-tco99a reproduced if I ran it locally rather than on stackblitz. It feels there's a bug in Chrome.

removing the watcher plugin the repo stops reloading the svgs, no idea what's happening with this.

@userquin It worked for me even after removing the watcher plugin, so I'm not sure about that.

otabekoff commented 3 weeks ago

I have submitted a bug report on the Chromium website regarding this issue. Since it is being considered a bug specific to the Chrome browser, and not to Vite, I will be closing this issue. If you have further questions or wish to discuss this matter further, please feel free to open a new issue or request to reopen this one.

You can view the Chromium bug report here: Chromium Bug Report

Thank you for your support.

userquin commented 3 weeks ago

@sapphi-red I'm running local and SB on Windows laptop