ElMassimo / ElMassimo/vite_ruby
Vite 8: manifest.json and manifest-assets.json disagree on content hash for assets referenced from both JS and Ruby
- Dominant language
- Ruby
- Stars
- 1.6k
- Forks
- 149
- Avg merge
- 5h 13m
- Merged PRs (30d)
- 2
Description
### Description 📖
After upgrading `vite` from `7.3.x` to `8.1.3` (keeping `vite-plugin-ruby@5.2.2`), a static asset that is **both** imported from application JS/Vue code **and** referenced directly from Ruby via `vite_asset_path` ends up with two *different* content hashes across `public/vite/.vite/manifest.json` and `public/vite/.vite/manifest-assets.json`.
Since `ViteRuby::Manifest#load_manifest` merges all manifest files with `.inject({}, &:merge)` (later files win), the entry from `manifest-assets.json` overrides the one from `manifest.json`. Only the file referenced by `manifest.json` actually exists on disk (with the correct/deduped hash), so `vite_asset_path` resolves to a hashed filename that was never written to `public/vite/assets`, and the app 404s on that asset.
On Vite `7.3.x` with the exact same source files and the exact same `vite-plugin-ruby` version, both manifests agree on the same hash and everything works.
### Reproduction
- `assets/bluethumb-nav-logo.svg` lives under `app/javascript/assets/`.
- It is referenced directly from Ruby: `vite_asset_path("assets/bluethumb-nav-logo.svg")` (used inside a Rails helper/component to render ``).
- It is *also* imported as a URL from a Vue SFC template: ``.
Running a clean production build (`RAILS_ENV=production bin/vite build` after `rake vite:clobber`) with Vite 8.1.3:
```
$ grep -o 'bluethumb-nav-logo[^"]*' public/vite/.vite/manifest.json | sort -u
bluethumb-nav-logo-hxTv2K1u.svg
bluethumb-nav-logo.svg
$ grep -o 'bluethumb-nav-logo[^"]*' public/vite/.vite/manifest-assets.json | sort -u
bluethumb-nav-logo-hxTv2K1u2.svg
bluethumb-nav-logo.svg
$ find public/vite -iname '*nav-logo*'
public/vite/assets/bluethumb-nav-logo-hxTv2K1u.svg
```
Note the hash in `manifest-assets.json` (`hxTv2K1u2`) differs from the one in `manifest.json` (`hxTv2K1u`) by one extra character, for what should be byte-identical source content. Only the `manifest.json` hash's file is actually written to disk. Since `manifest-assets.json` is loaded after `manifest.json` and merged with `Hash#merge` (later wins), `vite_asset_path("assets/bluethumb-nav-logo.svg")` returns the path to the missing file, and the app serves a 404 for that asset in production.
This reproduces on every clean build (tried 3 times in a row, same result each time). Reverting `vite` to `^7.3.0` with no other changes makes both manifests agree on the same hash immediately, and the asset resolves correctly.
I believe this is caused by [`fingerprintRemainingAssets`](https://github.com/ElMassimo/vite_ruby/blob/main/vite-plugin-ruby/src/manifest.ts) calling `ctx.emitFile` a second time (to build `manifest-assets.json`) for an asset that Rollup/Vite's own asset pipeline already fingerprinted once (because it's also referenced from JS/Vue). On Vite 7 both `emitFile` calls apparently produced the same content hash for the same buffer, so Rollup deduped them into one physical file referenced consistently by both manifests. Something in Vite 8's bundled Rollup appears to make these two `emitFile` calls for identical content produce different filenames, breaking the assumption that `manifest-assets.json` and `manifest.json` stay in sync for shared assets.
### System info
```
vite: 8.1.3
vite-plugin-ruby: 5.2.2
vite_ruby: 3.10.2
vite_rails: 3.11.0
rails: 8.1.1
ruby: 3.4.5
node: 22.22.3
```
- [x] I have tried upgrading by running `bundle update vite_ruby` (already on latest 3.10.2).
- [x] I have read the troubleshooting section before opening an issue.
Contributor guide
Assessment
This issue has not been assessed yet.