Description
Reproduction
TL;DR; Path of the input files are not considered when computing the content hashes and, this sometimes causes non-deterministic builds. Make sure relative path of the file is also part of content hash computation to prevent this.
I could not manage to create a reproduction repo, size of the project might be in play here. But we spent quite some time debugging this and I'd like to share the findings.
System Info
System:
OS: macOS 14.4.1
CPU: (10) arm64 Apple M1 Pro
Memory: 2.42 GB / 32.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.16.0 - /usr/local/bin/node
Yarn: 1.22.19 - ~/.yarn/bin/yarn
npm: 10.8.1 - /usr/local/bin/npm
pnpm: 9.7.1 - /usr/local/bin/pnpm
bun: 1.1.24 - ~/source/cerbos/spitfire/frontend/node_modules/.bin/bun
Watchman: 2024.04.15.00 - /opt/homebrew/bin/watchman
Browsers:
Chrome: 127.0.6533.122
Safari: 17.4.1
Used Package Manager
pnpm
Expected Behavior
I would expect build outputs to be consistent across different builds unless there is a change in the inputs
Actual Behavior
We have many route modules named as route.tsx
files under route folders. Some of those routes (let's assume the count is x
) have different loaders/actions but they import the exact same component from a shared folder and export is as default.
When we analyzed our build outputs, we saw that the manifest file was changing its content hash on each build. All route files were named as route-[hash].tsx
, which looks normal at first but when we look into contents of the manifest file, too, we realized that the x
routes that import and export the same component were swapping file names with each other on each build. Which was causing the contents of manifest file to change on each build.
We've made some deductions/observations on what was happening;
- When you take only the client code from the route files, you end up with exactly the same content for those
x
routes. They just import and re-export as default the same component. - content hash computation does not take the path of the file into account
Because of these two, those x
routes are supposed to get the same output file name (exact same content hash). It seems vite
is adding incremental numbers to the clashing file names to prevent collisions so they are getting non-clashing names.
BUT
those x
routes are randomly sharing those x
file names on each build. It seems they are not being processed in the same order across builds and, this is causing the change in manifest file contents and thus, we get non-deterministic builds.
To remedy this, I created a vite plugin that adds the path of the file as a comment in the route files and makes sure it is placed in a way that the comment ends up in the client code and that way we get deterministic builds.