vue.jswebpackvitehttp-cachingesbuild

vite: Adding static assets(with cache busting) to the build


I have large static files which should be busted with a hash for HTTP-caching. If I put them into the public directory, vite only copies them without appending a hash to the filenames. If I put them into the assets directory, vite ignores them, because they are not referenced directly from code, but loaded via XHR requests. The directory structure is pretty standard:

/
├─ src/
│  ├─ assets/
│  │  ├─ locales/
│  │  │  ├─ en.json
│  │  │  ├─ de.json
│  │  │  ├─ ru.json
│  ├─ main.js
├─ public/
├─ dist/
├─ index.html

How do I tell vite to copy those files with hash added to filenames and how do I get the resulting filenames to use them in XHR requests?


Solution

  • One solution is to use the url suffix to import the .json:

    // BEFORE:
    // fetch('@/assets/locales/en.json').then(res => res.json()).then(json => /*...*/)
    
    // AFTER:                                   👇
    import enUrl from '@/assets/locales/en.json?url'
    fetch(enUrl).then(res => res.json()).then(json => /*...*/)
    

    If there are many locale files, you can import them all with import.meta.glob:

    async function loadLocales() {
      const localeFiles = await import.meta.glob('@/assets/locales/*.json', { as: 'url', eager: true })
    
      // get basename of file from path
      const basename = s => s.split('/').at(-1).split('.').at(0)
    
      // create map of locale name to file -> { en: '/src/assets/locales/en-abc123.json' }
      const resolvedUrls = {}
      for (const [key, filePath] of Object.entries(localeFiles)) {
        resolvedUrls[basename(key)] = filePath
      }
      return resolvedUrls
    }
    

    If your files are less than 4KB, they'll be inlined as a data URL, and you won't see the corresponding file in your build output. To disable this inlining, set build.assetsInlineLimit to 0:

    // vite.config.js
    import { defineConfig } from 'vite'
    
    export default defineConfig({
      build: {
        assetsInlineLimit: 0,
      },
    })
    

    Also, to keep the built locale files under dist/locales, you'd have to configure the asset naming:

    // vite.config.js
    import { normalizePath, defineConfig } from 'vite'
    
    export default defineConfig({
      build: {
        rollupOptions: {
          output: {
            assetFileNames(assetInfo) {
             // output src/assets/locales/*.json files to dist/locales
              const pathToFile = normalizePath(assetInfo.name)
              if (/\/src\/assets\/locales\/.*\.json$/.test(pathToFile)) {
                return 'locales/[name]-[hash].json'
              }
    
              return 'assets/[name]-[hash][extname]'
            },
          },
        },
      },
    })
    

    demo