Skip to content

Bun.build: mark the type of an in-memory output as set, so routes and uploads send it - #47547

Open
robobun wants to merge 1 commit into
mainfrom
robobun/6e6c1a7e/build-output-type
Open

robobun wants to merge 1 commit into
mainfrom
robobun/6e6c1a7e/build-output-type

Conversation

@robobun

@robobun robobun commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Related to #3011

Problem

  • routes: { "/app.js": new Response(output) } sends no Content-Type for an in-memory Bun.build() output (no outdir). response.headers reports the type.
  • set_blob_mime (src/runtime/api/output_file_jsc.rs:23) stores the type and does not set content_type_was_set. Readers that send only a set type drop it.

Fix

  • set_blob_mime sets content_type_was_set, as embedded files do (standalone_graph_jsc.rs:52).
  • An in-memory output now behaves like new Blob(bytes, { type }). Of 22 readers compared on both builds, 6 change (Notes).
  • Verified: test/bundler/bun-build-api.test.ts (the new test fails on main in 8 values). Also the route suites, blob, body, response.
  • Self-reviewed: a first version changed StaticRoute::from_js. The review asked for the producer and a declared upload change. Both are here.

Background

Downsides

  • fetch(url, { body: output }) now sends the Content-Type of the output, as an outdir output does. output.stream() as a body sends it too. Both sent none. A caller that needs no Content-Type must set its own.
  • A handler's new Response(output.stream()) sends that type, not application/octet-stream.
  • Cost: one byte store per in-memory output. Release .text +256 bytes, same file size (GNU size).
Notes

What changes. output is the entry point of an in-memory build, a slice of it, or its source map. Debug builds of main 34cbc70 and of this branch, one script, 22 readers for each of 6 bodies. Every row that is not listed is the same on both builds.

Reader main this branch new Blob(bytes, { type }) outdir output
Route new Response(output): Content-Type none the type the type the type
Handler new Response(output.stream()): Content-Type application/octet-stream the type the type the type
fetch(url, { body: output }) sends none the type the type the type
fetch(new Request(url, { body: output })) sends none the type the type the type
fetch(url, { body: output.stream() }) sends none the type the type none
(await new Response(output.stream()).blob()).type text/plain;charset=utf-8 the type the type the type

The same on both builds: output.type, output.slice().type, new Response(output).headers, a handler's new Response(output) for GET and HEAD, new Request(url, { body: output }).headers, a request with its own Content-Type, (await new Response(output).blob()).type, Bun.readableStreamToBlob(output.stream()), new Blob([output]), new File([output], name), fetch(URL.createObjectURL(output)).

Each kind of output. Release builds of main 34cbc70 and of this branch, one build with an entry point, a CSS asset, a source map, a .png asset and an asset whose extension has no known type (its type is application/octet-stream). On this branch a route, a handler and an upload send the same type for the in-memory output as for the outdir output, for every kind. On main the route and the upload of the in-memory output send none.

A deleted header. A route sends the type of an output also after response.headers.delete("content-type"). That is main's rule for a set type: a route does so for a typed Blob on main, and a fetch handler does so for an output on main. A Content-Type that the caller gives to the Response replaces the type, as before.

The second caller. set_blob_mime also types the Blobs of HTML routes (OutputFile::to_blob, src/runtime/server/HTMLBundle.rs:519). Those routes write their Content-Type from content_type_or_mime_type(), which does not read the flag. bun-serve-html.test.ts, bun-serve-html-entry.test.ts and bun-serve-html-manifest.test.ts pass.

Other producers. blob.slice(start, end, type), the body of a data: URL and a multipart part typed by its file name lose their type on a route in the same way (measured on 1.4.3). They are other producers, and this PR does not change them. #46516 and #47335 are open for slice().

Overlap. #46114 removes the store.mime_type write from set_blob_mime, one line below this one. #46528 does not touch this file.

The ETag of #3011 is not in this PR. It needs a maintainer decision. The issue has the details.

Suites on the debug ASAN build. bun-build-api (92 pass, 1 skip, 1 todo), bun-serve-static, bun-serve-html, bun-serve-html-entry, bun-serve-html-manifest, bun-serve-html-405, bun-serve-routes, bun-serve-file, serve-if-none-match, blob, body, response, stream-fast-path, bun-cryptohasher, regression 10004.

Tools. The container has no bloaty. The size is from GNU size.

The bundler gives each output its type. For an output that is in memory,
set_blob_mime stored the type but did not mark it as set, so every reader
that sends only a set type dropped it: a Response route, a fetch() upload,
and the stream of the output. An output written with outdir is a file, and
a file's type already counts as set.
@robobun

robobun commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 9:02 AM PT - Oct 31st, 2026

❌ @robobun, your commit 79d3949 has 1 failures in Build #136099 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 47547

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

bun-47547 --bun

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

An error occurred during the review process. Please try again later.

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 34cbc70 and 79d3949.

Files selected for processing: 2
  • src/runtime/api/output_file_jsc.rs
  • test/bundler/bun-build-api.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

Walkthrough

The runtime now marks assigned blob MIME types as explicitly set. Tests cover content-type propagation from in-memory build artifacts across responses, upload bodies, and blobs created from artifact streams.

Changes

Build artifact MIME propagation

Layer / File(s) Summary
MIME flag and propagation coverage
src/runtime/api/output_file_jsc.rs, test/bundler/bun-build-api.test.ts
set_blob_mime marks the content type as explicitly set. Tests check JavaScript and JSON content types and verify that an explicit text/plain request header takes precedence.

Suggested reviewers: jarred-sumner, alii

Merge Risk: Minimal · up to 79d39

No actionable merge-blocking risk is identified in the MIME propagation change; merge after normal checks pass.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly identifies the main change: marking in-memory build output MIME types as set so routes and uploads send them.
Description check Passed The description explains the problem, fix, behavior changes, downsides, and verification results. It does not use the template headings exactly, but it provides the required information in equivalent …
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.
Full details: Description check

Explanation

The description explains the problem, fix, behavior changes, downsides, and verification results. It does not use the template headings exactly, but it provides the required information in equivalent sections.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@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 — a one-line flag set that brings in-memory build artifacts in line with file-backed ones and new Blob(bytes, { type }).

What was reviewed:

  • set_blob_mime now mirrors standalone_graph_jsc.rs:52; the flag is consumed by has_content_type_from_user (fetch uploads), Body.rs (.blob() fallback to text) and ByteBlobLoader::setup (stream()), and Blob::slice propagates it, so all nine assertions in the new test exercise the change (eight fail on base, uploadWithHeader is the negative contract).
  • Checked the file-loader / unknown-extension case: to_mime_type returns application/octet-stream, which now gets sent — same as the outdir path already does, so not a regression.
  • HTML-route caller (HTMLBundle.rs:523) reads content_type_or_mime_type(), which ignores the flag — unaffected.
Extended reasoning...

The diff adds blob.content_type_was_set.set(true) in the shared set_blob_mime helper in src/runtime/api/output_file_jsc.rs (used by both to_js and to_blob for the in-memory Buffer variant) and one hermetic test in test/bundler/bun-build-api.test.ts using port: 0 and await using. It touches no security-sensitive surface; it only affects which Content-Type header is emitted for in-memory Bun.build outputs on routes, streams and fetch uploads. Approve was decided by the change being a single line that copies an existing sibling pattern, the test covering every consumer of the flag plus the explicit-header precedence case, and no CODEOWNERS entry or outstanding reviewer objection on the changed files.

@robobun

robobun commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator Author

CI build 136099 is complete: 183 of 188 jobs passed. The 5 red jobs have one cause. test/aot/engine/ir-6.test.ts fails on each of its four attempts (an IR comparison for pipeline/gc/custom-accessor-reached-computed-key.ir). The same test is red on main (build 135971), and this PR does not touch the AOT compiler. No other test failed on every attempt. test/bundler/bun-build-api.test.ts, which has the new test, failed on no lane.

This branch has not been deployed

No deployments
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