aspect-build / aspect-build/rules_js

[Bug]: vite 8 (rolldown) fails under the sandbox because native-fs path canonicalization bypasses the rules_js `fs` patches

Open
#2,888 2 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Starlark
Stars
378
Forks
183
Avg merge
1d 9h
Merged PRs (30d)
32

Description

## What happened?

I have a Bazel project that uses rules_js to build a vite app. It builds fine with vite 7, but fails with vite 8. To isolate it I made a repro with two packages that are identical except for the vite major version (and the matching `@vitejs/plugin-react` major, since plugin-react 6.x requires vite 8).

`bazel build //vite8:build` fails in `vite:build-html`:

```
[plugin vite:build-html]
Error: The "fileName" or "name" properties of emitted chunks and assets must
be strings that are neither absolute nor relative paths, received
"../../../../../../../../../execroot/_main/bazel-out/darwin_arm64-fastbuild/bin/vite8/index.html".
at PluginContextImpl.emitFile (.../rolldown/dist/shared/bindingify-input-options-....mjs:978:23)
at PluginContextImpl.generateBundle (.../vite/dist/node/chunks/node.js:22686:10)
```

The `../../../...` part is the giveaway: the emitted `index.html` fileName has been computed against a root that lives outside the sandbox working directory.

## What I think is going on

As far as I can tell, rules_js launches node with patched `fs` APIs (`lstat`, `readlink`, `realpath`, `readdir`, and so on) so that JS code can't resolve the sandbox input symlinks back out to the real output tree. In effect this emulates `--preserve-symlinks` for everything in the execroot, which is what keeps Node tooling working inside the sandbox.

vite 7 and earlier bundle through rollup plus esbuild's JS pipeline, whose path handling goes through Node's (patched) `fs`. Paths stay inside the sandbox, the `index.html` fileName is computed relative to the project root, and the build works.

vite 8 bundles through rolldown, whose resolver and fs access are native Rust. It seems the native code calls the OS `realpath` directly, which bypasses the rules_js patches. The `index.html` entry, which is a sandbox symlink, gets canonicalized to its real path under the output tree, while `config.root` stays the unresolved sandbox working directory. `vite:build-html` then emits with `path.relative(root, id)`, which produces the `../../../...` escape above, and rolldown rejects it.

So from the rules_js side, the surface seems to be this: the `fs`-patching strategy that emulates `--preserve-symlinks` doesn't reach a bundler that does its fs access in native code. rolldown is the first mainstream one I've hit that does this, but it probably won't be the last.

I'm not assuming this is a rules_js bug to fix. The canonicalization could also be addressed in rolldown (an option not to canonicalize entry/module ids) or in vite (compute the html fileName against the canonicalized root). I'm filing here because the `--preserve-symlinks` emulation is a rules_js mechanism, so you'll have the clearest read on whether there's a fix possible on this side.

## Versions

| Component | Version |
| --- | --- |
| Bazel | 9.1.1 |
| aspect_rules_js | 3.2.2 |
| rules_nodejs / Node | 6.7.4 / 22.22.2 |
| pnpm | 11.5.2 |
| vite (failing) | 8.0.16 (rolldown 1.0.3) |
| vite (passing) | 7.3.5 |
| @vitejs/plugin-react | 6.0.2 (vite8) / 5.2.0 (vite7) |

## Development (host) and target OS/architectures

macOS, arm64. Observed under Bazel's `darwin-sandbox`. The mechanism (action inputs presented as symlinks into the real output tree) should apply to the Linux sandbox as well, but I haven't reproduced it there.

```
$ bazel --version
bazel 9.1.1
```

## Language(s) and/or frameworks involved

JavaScript / vite / rolldown, with a pnpm `node_modules` layout.

## How to reproduce

Minimal repro (requires `bazelisk` and `pnpm`; lockfile is checked in): https://github.com/macourteau/vite8-rolldown-bazel-repro

bazel build //vite7:build # PASSES
bazel build //vite8:build # FAILS

The two packages are identical apps apart from the vite major. The `.bazelrc` disables disk and remote caches so a previously cached unsandboxed success can't mask the sandboxed failure.

## Workarounds I tried (both fail, differently)

1. `resolve: { preserveSymlinks: true }`. This fixes the entry canonicalization, but with a pnpm `node_modules` layout the transitive deps of direct deps live behind symlinks into the pnpm virtual store, and they stop resolving:

Error: [vite]: Rolldown failed to resolve import "ms" from
".../bin/vite8/node_modules/debug/src/common.js".

(`debug` depends on `ms`; it's in the repro specifically to show this.)

2. `tags = ["no-sandbox"]`. This removes the symlink indirection, so the canonicalization problem disappears and the minimal app builds. But in the larger real project this was extracted from, the unsandboxed build then failed a third way: rolldown wrote `dist/index.html` carrying the read-only permission bits of the Bazel input file it was derived from, then couldn't overwrite its own output:

[UNHANDLEABLE_ERROR] Something went wrong inside rolldown ...
Failed to write file in .../bin/.../frontend/dist/index.html
Caused by: Permission denied (os error 13)

(Bazel marks action inputs and outputs read-only, so any tool that preserves source permissions on derived files and then re-opens them for writing will break.) I haven't minimized that third failure in the repro since it needed a larger app (CSS, fonts, `public/` assets) to trigger, but I'm noting it because it closes off the obvious escape hatch.

## Any other information?

The concrete question for rules_js: is there an existing or planned strategy for bundlers that do fs access in native code (rolldown today, probably others as more JS tooling moves to Rust), given that the `fs`-patch approach to emulating `--preserve-symlinks` doesn't reach them? Happy to test patches against the repro.

Contributor guide

Open the contributing guide

Research direction

Start with the linked vite8-rolldown-bazel-repro and run `bazel build //vite7:build` and `bazel build //vite8:build` under the sandbox. Trace the `vite:build-html` failure alongside the rules_js patched `fs` APIs and rolldown's native filesystem access. Done means identifying and validating a rules_js-side strategy, or documenting why the fix belongs in vite or rolldown.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.