Skip to content

Client builds are not deterministic in some casesΒ #9913

Open
@hasanayan

Description

@hasanayan

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions