Skip to content

bundler: emit posix-relative paths in the HTML-import manifest on Windows - #34557

Open
robobun wants to merge 5 commits into
mainfrom
farm/64291784/html-manifest-windows-paths
Open

bundler: emit posix-relative paths in the HTML-import manifest on Windows#34557
robobun wants to merge 5 commits into
mainfrom
farm/64291784/html-manifest-windows-paths

Conversation

@robobun

@robobun robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Problem

On Windows, importing an HTML file from JS and bundling for target: "bun" emits a manifest whose files[].input and file-loader files[].path fields are wrong:

// Windows, before
{ "input": "../C:\\Users\\...\\Temp\\...\\client.html", "path": ".\\favicon-wjepk3hq.png", ... }
// Linux/macOS, and expected
{ "input": "client.html",                               "path": "./favicon-wjepk3hq.png", ... }

The manifest is meant to be handed to a static file server, so leaking a build-machine absolute path (and using \ separators) makes it unusable on Windows builds. This is the path-format symptom reported in #21283.

Cause

Two separate gaps.

input: HTMLImportManifest::write derives the key with

relative_normalized::<bun_paths::platform::Posix, false>(root_dir, source.path.text)

Both operands are native absolute paths (C:\... with backslashes on Windows). relative_normalized does not normalise its inputs, and under Platform::Posix \ is not a separator and C:\... is not absolute, so each side is treated as a single path segment. The result is "../" + source.path.text.

path: additional_output_files[].dest_path is produced by PathTemplate::print, which emits native separators. Chunk.final_rel_path is posix-converted right after templating (generateChunksInParallel.rs:372), but the parallel code for additional output files at bundle_v2.rs:4154-4158 skips that step. Three consumers had already independently re-normalised the same field to work around this:

  • Chunk.rs:946-956 copied into a scratch buffer and posix-converted before emitting an asset URL;
  • StandaloneModuleGraph.rs:778-786 carried a #[cfg(windows)] platform_to_posix_buf with a comment pointing back at the Chunk.rs workaround;
  • bake/production.rs:273-279,757-763 special-cased a .\ prefix when building bake:/ module keys.

Other readers (static-route registration in HTMLBundle.rs, asset URLs in bake/production.rs, the CLI summary in build_command.rs:1059) emitted the \ form on Windows.

Fix

  • Compute the manifest input with relative_platform_buf::<platform::Auto, true> (host semantics, normalised inputs) into a pooled buffer and platform_to_posix_in_place the result, matching final_rel_path / sourcemap sources.
  • Posix-convert additional_output_files[].dest_path once at the producer, immediately after template.print(...) in bundle_v2.rs, so it carries the same invariant as Chunk.final_rel_path. Every reader either already re-normalised, routes through platform::Auto/Loose path helpers whose separator test accepts /, or wants a URL/module key where / is required; Windows openat/mkdirat normalise / before the NT syscall.
  • Drop the now-redundant consumer workarounds in Chunk.rs, StandaloneModuleGraph.rs, and bake/production.rs.

All path helpers touched are no-ops on POSIX, so Linux/macOS output is byte-identical.

Verification

New html-import/manifest-paths-are-posix-relative in test/bundler/html-import-manifest.test.ts builds with Bun.build (so it runs on Windows today, independent of the itBundled harness) and asserts every input/path is /-separated and project-relative.

Windows x64:

# fail-before (canary)
Expected: "page/index.html"
Received: "../C:\Users\azureuser\AppData\Local\Temp\html-manifest-paths_...\page\index.html"
# pass-after (this branch)
(pass) bundler > html-import/manifest-paths-are-posix-relative

Also verified on Windows with this branch:

  • bun build entry.ts --outdir out --asset-naming "[dir]/[name]-[hash].[ext]" emits var icon_default = "./sub/icon-neqd1z7a.txt" and prints sub/icon-neqd1z7a.txt in the CLI summary (was .\sub\...);
  • bun build --compile entry.ts with a file-loader asset embeds and reads the asset correctly (exercises the StandaloneModuleGraph key).

Linux x64: all 6 tests in test/bundler/html-import-manifest.test.ts pass with the two inline snapshots unchanged; bundler_loader.test.ts and bundler_naming.test.ts pass.

The fix only affects Windows output, so the Linux-only fail-before gate will see the new test pass both ways; Windows CI is the direct proof.

Note on the itBundled snapshot tests

#34552 re-enables itBundled on Windows and gates html-import/manifest-with-metadata and html-import/multiple-manifests as todo: isWindows. With this fix applied those tests still fail there, because the harness's temp root is an 8.3 short path (C:\Users\AZUREU~1\...) while options.root_dir is canonicalised to the long form via GetFinalPathNameByHandleW, so relative(root_dir, source) walks up and back down. That is the same root_dir-vs-source canonicalisation mismatch that breaks naming/AssetNamingDir and is tracked separately; once it is fixed the todo: isWindows gates on these two tests can be dropped.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 003da366-71e3-4f90-aea3-9b8461a568f5

📥 Commits

Reviewing files that changed from the base of the PR and between c4f6cb8 and 08930b7.

📒 Files selected for processing (6)
  • src/bundler/Chunk.rs
  • src/bundler/HTMLImportManifest.rs
  • src/bundler/bundle_v2.rs
  • src/runtime/bake/production.rs
  • src/standalone_graph/StandaloneModuleGraph.rs
  • test/bundler/html-import-manifest.test.ts

Walkthrough

Generated paths across bundler manifests, copied assets, standalone metadata, sourcemaps, and module maps now use POSIX separators and consistent leading ./ handling. A new test validates relative manifest paths for nested HTML imports and multiple loader types.

Changes

Path normalization across generated outputs

Layer / File(s) Summary
HTML manifest path generation
src/bundler/HTMLImportManifest.rs, test/bundler/html-import-manifest.test.ts
Manifest input and output paths use pooled platform-relative buffers, POSIX conversion, and leading dot-slash removal; tests validate nested loader paths.
Output and runtime metadata paths
src/bundler/bundle_v2.rs, src/standalone_graph/StandaloneModuleGraph.rs, src/runtime/bake/production.rs
Copied asset paths, standalone metadata, sourcemap keys, and module-map keys normalize separators and remove leading dot-slashes.
Source-map shifted path emission
src/bundler/Chunk.rs
Standalone path emission passes resolved paths directly to prefix normalization while retaining source-map shift advancement and additional-file key setup.

Possibly related PRs

  • oven-sh/bun#33860: Both adjust source-map shift cursor handling across path or output boundaries.
  • oven-sh/bun#34188: Both modify IntermediateOutput::code_with_source_map_shifts path and shift handling.
  • oven-sh/bun#34552: Both address Windows backslashes in bundler output paths and tests.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately states the main change: emitting POSIX-relative HTML-import manifest paths on Windows.
Description check ✅ Passed The description includes the problem, fix, and verification details, covering the required PR description content.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 12:11 AM PT - Jul 18th, 2026

@robobun, your commit 08930b7 has 1 failures in Build #75104 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34557

That installs a local version of the PR into your bun-34557 executable, so you can run:

bun-34557 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Bun.build does not include <link>ed assets into "index" (Fullstack Dev Server) #21283 - Reporter on Windows sees absolute backslash-separated input paths in the HTML import manifest and missing file-loader assets, matching both bugs this PR fixes in HTMLImportManifest::write

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #21283

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — targeted Windows path fix that mirrors the existing final_rel_path / sourcemap-sources pattern.

What was reviewed:

  • Verified relative_platform_buf::<Auto, true> + platform_to_posix_in_place matches the pattern at generateChunksInParallel.rs:372 and LinkerContext.rs:1087; both helpers early-return on POSIX so Linux/macOS output is unchanged.
  • Checked path_buffer_pool::get() returns an RAII PoolGuard, and that the per-iteration buffer reuse is sound (each borrow is consumed by write_entry_item before the next write).
  • Confirmed ALWAYS_COPY = true is required since the result is mutated in place; platform_to_posix_buf is used for dest_path because it's a borrowed slice.
Extended reasoning...

Overview

Two-file change: src/bundler/HTMLImportManifest.rs swaps relative_normalized::<Posix, false> for relative_platform_buf::<Auto, true> + platform_to_posix_in_place when computing manifest input keys, and posix-converts output_file.dest_path before emitting it. A new test in test/bundler/html-import-manifest.test.ts asserts every manifest path is /-separated and project-relative.

Security risks

None. This is pure output-path formatting inside the bundler's manifest writer — no untrusted input parsing, no filesystem writes driven by the changed values, no auth/crypto surface.

Level of scrutiny

Low-to-medium. The change is mechanical: it replaces one path helper call with the established "relative under host semantics, then rewrite separators" idiom already used at generateChunksInParallel.rs:372 (chunk final_rel_path) and LinkerContext.rs:1087 (sourcemap sources). Both platform_to_posix_in_place and platform_to_posix_buf early-return when SEP == b'/', so POSIX output is byte-identical — confirmed by the two existing inline snapshots in the test file being untouched.

Other factors

  • The pooled buffers come from path_buffer_pool::get(), which returns a PoolGuard with Drop-based return-to-pool, so no manual cleanup is needed on the ? error paths.
  • ALWAYS_COPY = true is correct: the result must live in input_buf so platform_to_posix_in_place can rewrite it.
  • Buffer reuse across loop iterations is sound — each &input_buf[..len] borrow is fully consumed by write_entry_item before the next iteration re-borrows mutably.
  • The new test uses Bun.build in-process (matching the neighbouring etag-changes-with-referenced-chunks test) and asserts exact input values plus a no-backslash sweep, so it fails on Windows without the fix and passes on POSIX both before and after. The PR description is upfront that the Linux fail-before gate will not catch this — Windows CI is the direct proof.
  • The PR correctly notes the remaining 8.3-short-path vs long-path mismatch as a separate, pre-existing issue and does not attempt to paper over it here.

robobun and others added 3 commits July 18, 2026 03:13
Instead of re-normalising dest_path at a fourth consumer, apply the same
platform_to_posix_in_place that Chunk.final_rel_path already gets after
PathTemplate::print. Every reader either already re-normalised, routes
through platform::Auto/Loose path helpers that accept '/', or wants a
URL/module key where '/' is required; Windows openat/mkdirat normalise
'/' before the NT syscall.

Drops the now-redundant workarounds in Chunk.rs, HTMLImportManifest.rs,
StandaloneModuleGraph.rs and bake/production.rs.
@robobun
robobun force-pushed the farm/64291784/html-manifest-windows-paths branch from 2179c7e to 2de0f0a Compare July 18, 2026 03:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/bundler/html-import-manifest.test.ts`:
- Around line 387-398: Update the manifest assertions around byLoader so they
retain and validate every manifest entry instead of only the last file per
loader. For manifest.index, each file.path, and each defined file.input, assert
both posix.isAbsolute and win32.isAbsolute are false, while preserving the POSIX
separator checks and existing loader-specific expectations.
- Around line 367-373: Update the test fixture setup around tempDirWithFiles to
use the disposable tempDir helper with using, and update the harness import
accordingly. Preserve the existing fixture files and test behavior while
ensuring the temporary directory is automatically cleaned up on every execution
path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e197cc68-1f62-4b9b-88dc-1e73ad8ea2bb

📥 Commits

Reviewing files that changed from the base of the PR and between 0170259 and c4f6cb8.

📒 Files selected for processing (6)
  • src/bundler/Chunk.rs
  • src/bundler/HTMLImportManifest.rs
  • src/bundler/bundle_v2.rs
  • src/runtime/bake/production.rs
  • src/standalone_graph/StandaloneModuleGraph.rs
  • test/bundler/html-import-manifest.test.ts
💤 Files with no reviewable changes (2)
  • src/standalone_graph/StandaloneModuleGraph.rs
  • src/bundler/Chunk.rs

Comment thread test/bundler/html-import-manifest.test.ts Outdated
Comment thread test/bundler/html-import-manifest.test.ts Outdated
@robobun
robobun force-pushed the farm/64291784/html-manifest-windows-paths branch from e083d4e to 08930b7 Compare July 18, 2026 03:31
@robobun

robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

The robobun/evidence check rejects this because the gate is Linux-only and the fix is Windows-only: platform_to_posix_in_place and platform_to_posix_buf early-return when the host separator is /, and under platform::Auto the relative-path computation is identical on POSIX, so Linux output is byte-for-byte unchanged (the two inline snapshots in this test file are untouched). The fail-before proof is the Windows run in the PR description.

Windows CI on this PR is green (windows-2019-x64-test-bun, windows-11-aarch64-test-bun both passed on build #75104), so html-import/manifest-paths-are-posix-relative and the rest of the bundler suite pass there with the fix.

Remaining CI failures are unrelated to this diff: cargo clippy is a vendor-fetch infra failure (failed to get lol_html as a dependency ... No such file or directory), test-worker-message-port-transfer-terminate.js is pre-existing on main, and the rest are flaky install/network tests that passed on retry.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues. The producer-side normalization mirrors the existing Chunk.final_rel_path invariant and the removed consumer workarounds are demonstrably redundant, but this is a cross-cutting Windows path-format change spanning bundler → standalone graph → bake, so it's worth a maintainer glance once Windows CI is green.

What was reviewed:

  • Confirmed platform_to_posix_in_place early-returns on POSIX (resolve_path.rs:2458), so Linux/macOS output is unchanged.
  • Confirmed Chunk.final_rel_path already carries the same posix invariant (generateChunksInParallel.rs:372) through the same disk-write / write_to_disk consumers, so extending it to additional_output_files[].dest_path introduces no new consumer requirement.
  • Checked writeOutputFilesToDisk.rs and OutputFile::write_to_disk route dest_path through platform::Auto dirname + bun_sys openat, which accept / on Windows.
  • bun_paths::strings::remove_leading_dot_slash already handles both ./ and .\\, so the bake/production.rs inline checks were exact duplicates of the shared helper.
Extended reasoning...

Overview

Two-part Windows path-format fix in the bundler. (1) HTMLImportManifest::write switches the manifest input derivation from relative_normalized::<Posix, false> on native absolute paths (which mis-parses C:\\... as one segment) to relative_platform_buf::<Auto, true> + platform_to_posix_in_place, using a pooled buffer. (2) bundle_v2.rs posix-converts additional_output_files[].dest_path immediately after PathTemplate::print, matching the existing normalization applied to Chunk.final_rel_path at the same stage. Three consumer-side workarounds that had each independently re-normalized this field (Chunk.rs scratch-buffer copy, StandaloneModuleGraph.rs #[cfg(windows)] re-normalize, bake/production.rs .\\-prefix special case) are removed. A new Bun.build-driven test asserts every manifest input/path is backslash-free and not absolute/..-escaping.

Security risks

None. This is output-path formatting for build artifacts; no user-controlled parsing, auth, or filesystem-escape surface is touched. The producer emits a posix-relative path derived from paths already validated by the bundler; consumers that write to disk go through bun_sys openat wrappers under root_dir.

Level of scrutiny

Moderate-high. The per-site changes are mechanical and each individually small, but the producer-side change to dest_path's separator convention flows to every consumer of additional_output_files on Windows: chunk assembly, standalone-graph key lookup, bake module keys, disk writes, HTTP route registration, and the CLI summary. The safety argument rests on Chunk.final_rel_path already carrying the identical invariant through the same consumers (verified), and on bun_sys openat/mkdirat normalising / before the NT syscall (stated, and consistent with how chunk final_rel_path already works). The fix cannot be validated by the Linux fail-before gate; Windows CI is the direct proof.

Other factors

The PR follows REVIEW.md's "fix at the layer that owns the invariant" guidance precisely — moving normalization to the producer and deleting three consumer workarounds that each carried a comment pointing at the others. Both CodeRabbit findings (disposable tempDir, assert every manifest entry) were addressed in follow-up commits. The new test is well-shaped: exercises a nested directory + file loader so both the input and file-loader path regressions are covered, and asserts on the full entry list rather than a per-loader sample. Deferring only because the Windows-only surface across three subsystems merits a maintainer confirming Windows CI, not because anything looks wrong.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

@robobun fix clippy

@robobun

robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

The clippy failure on 08930b7 was a transient fetch flake: ninja -C build/debug codegen clone-lolhtml ran 33 tasks but the fetch lolhtml step never appeared (so vendor/lolhtml/Cargo.toml was missing when cargo ran). The same workflow passed on the two prior commits of this branch, and 08930b7 only touches test/bundler/html-import-manifest.test.ts relative to dd02243 where clippy was green. Re-ran the job; now green.

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Still reproduces at current main (1cf8af0, 1.4.0-canary) and in release 1.4.0 on Windows Server 2019, so this is still needed. The branch conflicts with main now (bundle_v2.rs moved to compile_mode.is_executable(), Chunk.rs line shifts) and needs a rebase.

Two more places the same dest_path shows up, in case it is worth extending the test when rebasing (both come out right with the producer-side conversion in this PR, since they read dest_path verbatim):

  • bun build server.js --target=bun --outdir out --asset-naming "static/[dir]/[name]-[hash].[ext]" summary line on Windows: .\static\img\icon-c3frhez7.png 5 bytes (asset) (Linux: static/img/icon-....png); the manifest entry is ".\\static\\img\\icon-c3frhez7.png".
  • Bun.build({ entrypoints: ["./home.html"] }) without outdir: the asset artifact's .path is ".\\icon-c3frhez7.png" while the chunk artifacts are "./chunk-....js" (Linux: "./icon-....png").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants