Peter V. Mørch
Peter V. Mørch

Reputation: 15967

How to get list of assets into js code (for cache.addAll(assets) for PWA)

I'm building a PWA and part of the startup in the service worker is to cache all the assets. But since vite (by default) inserts the hash of the generated assets into their file names, the exact list of assets to cache varies from run to run, making it impossible to cache the assets.

So how can I access an array of built assets from inside a *.js file at run-time, so I can cache them with cache.addAll(assets)?

While that is what I'd really like, I do know there are (at least) two workarounds:

How does VitePWA do it?

The source code for the VitePWA plugin is really an array of 4 plugins that interact in a way I haven't been able to understand.

I'm guessing these are rollup plugins and the path forward is to create a rollup plugin that somehow investigates what assets got created by the other plugins and then in a post step creates a assets.js or something containing something like:

export const assets = [
  "/assets/index-4f46e888.js",
  "/assets/index-ef4f98ff.css",
  "/assets/link-192-a96fcf4c.png",
  "/assets/logo-277e0e97.svg",
  "/assets/modulepreload-polyfill-3cfb730f.js",
  "/assets/otherPage-892792b9.js",
  "/favicon.ico",
  "/index.html",
  "/otherPage.html",
]

That leaves me with two questions about a "like-VitePWA" approach:

Remove -[hash] from build.rollupOptions.output.(asset|entry|chunk)Filenames

This was relatively easy. Put this under build.rollupOptions in vite.config.js:

      output: {
        assetFileNames: "assets/[name][extname]",
        entryFileNames: "assets/[name].js",
        chunkFileNames: "assets/[name].js",
        sourcemap: true,
      }

And voila, the output from npm build in dist/ becomes predictable.

But it is no longer cache-busting because the -[hash] has been removed. Which is why I'm looking for a way to keep the -[hash] so it remains cache-busting.

Upvotes: 2

Views: 725

Answers (1)

Peter V. Mørch
Peter V. Mørch

Reputation: 15967

Here is a plugin that prepends a global window.assetList to the code of the index chunk when used as:

import assetListPlugin from './assetListPlugin.js'

export default defineConfig({
  plugins: [
    ...,
    assetListPlugin('assetList', 'index'),
    ...,
  ],
})

And here is ./assetListPlugin.js`:

export default (global, chunkName) => ({
    name: 'assetList',

    generateBundle(outputOptions, bundle) {
        let chunkToModify
        const allAssetFileNames = []
        Object.values(bundle).forEach((asset) => {
            if (asset.name == chunkName && asset.type == 'chunk')
                chunkToModify = asset
            allAssetFileNames.push(asset.fileName)
        })
        if (! chunkToModify) {
            throw new Error(`No "${chunkName}" chunk found`)
        }
        chunkToModify.code = `window.${global} = ${JSON.stringify(allAssetFileNames)}; ${chunkToModify.code}`
    },
})

Use it in any code in the index chunk as:

// assetList is now a global variable
console.log(assetList)

What suprised me a little was that it only works for vite build, not vite (dev server). But it makes sense. dev only knows about the modules it has served thus far, since it doesn't build everything up-front, so it can't populate the full asset array. This is also documented as:

Output Generation Hooks (except closeBundle) are not called during dev.

(So generateBundle() is never called during vite (dev server))

So if I want to do this, run vite build -w in one terminal and vite preview in another.

Upvotes: 1

Related Questions