Repository navigation
Conversation
The dev server rebuilds the import list of a file each time it bundles the file. It pushed each edge on the front of the list, so the list was in reverse import order. The route trace walks that list, and the `<link>` tags of an HTML route and `meta.styles` of a framework route follow the walk. A page got its stylesheets in reverse order, so the cascade resolved ties the other way from the source. The rebuild now appends each edge through a tail pointer. The list is in import record order, so the trace reaches each stylesheet in source order: depth first, each file at its first reference. For an HTML route with local stylesheets, `bun build` concatenates them in that order. This covers the order that the server sends. The client still moves a stylesheet that a hot update replaces behind the `<link>` stylesheets until the page loads again. Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
|
Status: reproduced on a debug build of main (449877f) and on 1.4.3-canary.1+367d939d9. An HTML route that links PR: #46990 |
|
Updated 9:43 AM PT - Oct 26th, 2026
✅ @robobun, your commit bcd9ea7db5153c46d998d395321ed21ebd9d2784 passed in 🧪 To try this PR locally: bunx bun-pr 46990That installs a local version of the PR into your bun-46990 --bun |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Ordered import edge rebuild src/runtime/bake/dev_server/incremental_graph.rs |
Import edges are appended in record order, whether reused or newly allocated. The rebuilt list head is assigned to the file’s import list. |
Stylesheet order validation test/bake/dev/css.test.ts, test/bake/dev/bundle.test.ts |
Tests check stylesheet order across no-op and changing rebuilds, duplicate references, and framework route metadata in both server-graph configurations. |
Possibly related PRs
- oven-sh/bun#46742: Preserves import records and updates dev-server barrel handling, which relates to how import ordering is represented and traversed.
Suggested reviewers: jarred-sumner
Priority:
Severity of issue fixed: Medium
Merge Risk: 
Minimal · up to bcd9e
-
The stylesheet-order change is ready to merge after normal checks; no actionable merge-blocking issue remains.
Pre-merge checks: 
4
-
Full details: Title check
-
Explanation
The title "dev server: serve a route's stylesheets in source order" matches the main change. The raw summary and PR objectives show that import edges are now appended in source-record order instead of prepended, so route stylesheets are sent in import order. The title is specific and does not use vague terms. It omits the framework-route and HTML-route scope, but it still names the primary change.
Full details: Description check
-
Explanation
The description follows the template. It has a "What does this PR do?" section, covered by the Problem, Fix, Background, and Downsides sections. It has a "How did you verify your code works?" section, covered by the Verified and Tests content and the Measurements section. It links the issue (
#28117) and gives concrete evidence: failing-before and passing-after test runs, a comparison with bun build, and performance measurements. It is very detailed, and the main body is long, but every required section has substantive content. Some open items are noted as follow-up work, so the description is mostly complete.
Full details: Linked Issues check
-
Explanation
Issue
#28117requires Bun.serve to preserve sequential stylesheet import order so the later stylesheet wins as in Bun.build. The PR changes rebuilt import lists from front insertion to tail append insrc/runtime/bake/dev_server/incremental_graph.rs. The added HTML-route test verifies source order across rebuilds, additions, removals, and repeated references. The framework-route test verifies the corresponding metadata order.
Full details: Out of Scope Changes check
-
Explanation
The changed Rust code directly implements the stylesheet import-order fix. The added tests cover HTML and framework routes and are supporting automated coverage for issue
#28117. No unrelated product behavior or unrelated files appear in the reviewed changes.
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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.
Comment @coderabbitai help to get the list of available commands.
|
#48141 changes where a bundle puts the imports of an eager Today the dev server passes that row only because it links stylesheets in reverse. The parser adds the import records of a glob during the visit pass, so they come behind the file's own records. For If the dev server follows import record order, development links |
Fixes #28117
Problem
one two threebecomesthree two one), so the cascade breaks ties the other way frombun build.process_edge_attachment(src/runtime/bake/dev_server/incremental_graph.rs:1049on main) pushes each import edge on the front of a file's import list.trace_importsemits stylesheets in walk order.Fix
NewImports), so the list is in import order.trace_importsis the only reader that uses the order. Each stylesheet is linked at its first reference, asbun builddoes for an HTML route with local stylesheets.test/bake/dev/css.test.tsandbundle.test.tsfail on 1.4.3-canary.1.test/bake/dev/passes.Background
Downsides
media="print"stylesheet linked last now wins on screen, as inbun build(Handling of media="print" incorrectly #18526).<link>stylesheets. Withbun-plugin-tailwindthat happens on each save. No issue tracks this..text: +0 B.Notes
Relation to #37845, #39488 and #30488. The Rust change is #37845, cut again on main. #37845 was closed into the consolidation PR #39488 (commit
ab5c1811f0there). #39488 is open and conflicts with main, so main still has the bug. TheNewImportsblock is the text of that branch, so a later merge of main into #39488 gets no new conflict.git merge-file --diff3reports 11 conflict hunks inincremental_graph.rsbefore this PR and 11 after. One line differs from #37845: a file that the walk already attached returnsStop, as main does since #46270. That is the guard against a circular@import. #30488 reported the<link>shape of this bug and was closed as a duplicate of #28117.The reference is source order. The server now sends the stylesheets of a route depth first, each file at its first reference. For an HTML route with local stylesheets,
bun buildconcatenates them in the same order (Measurements). Two differences frombun buildstay:bun buildmoves an external@importabove each stylesheet of the page. The dev server links one chunk per stylesheet, so the external import stays in its stylesheet. For<link a.css><link b.css>whereb.cssimports an externalx.css:bun buildgivesx a b, this PRa x b, mainx b a.What else moves to source order. These read the same list through the same walk:
/_bun/client/*.js) and thesourcesof its source map. Probe:index.tsimportsa.ts,b.ts,c.ts, anda.tsimportsa1.ts,a2.ts. This PR givesindex.html index.ts a.ts a1.ts a2.ts b.ts c.ts. Main givesindex.html index.ts c.ts b.ts a.ts a2.ts a1.ts. The bundle is an object with one key per module, so only the byte order changes.ensure_route.rs:423).finalize.rs:959). The client reads it as a set.Not fixed here.
The stylesheet order of an open page after a hot update. The owner is
editCssContent(src/runtime/bake/client/css-reloader.ts:171). It puts each stylesheet that a hot update sends intodocument.adoptedStyleSheets. Those come after each<link>stylesheet in the cascade, until the page loads again. Enabled stylesheets in cascade order, read in the test client:a,b,c, then a save ofa.cssa b cb c ac b ac b aa,b,cthat each importp.css, then a save ofp.cssa b cc b ac b ac b atailwind-noop, linkstailwind.css,app.css, then a save of a scripttailwind appapp tailwindapp tailwindapp tailwindWhen the name of a serve plugin contains
tailwind, each rebuild bundles the Tailwind stylesheet again and sends it (hot_reload_event.rs:208). So each save of any file is a hot update of that stylesheet. A page with the Tailwind stylesheet before another stylesheet is in source order from load to the first save. On main that page is in reverse order all the time. A page with the Tailwind stylesheet last no longer changes on a save. The same applies to HTMLBundle is inconsistent with the css content of Bun.build #28117 withhmr: true: after a save offoo.css,bar.csscomes first again until a reload. The fix belongs in the client: keep a stylesheet at the position of its<link>. No issue tracks this yet.With
framework: "react", a client navigation appends the<link>of a new stylesheet at the end of<head>(bun-framework-react/client.tsx:226). The order then depends on the navigation history. Main does the same.meta.stylesof a framework route is cached for each route, and not each rebuild clears the cache (finalize.rs:928-950). For example, a swap of two imports in a route with no client component keeps the old list. dev server: refresh a framework route's meta.styles when its imports change #42849 has a fix. The framework tests read the rebuilt lists through a route that has no cached list.A
<link>loses itsmediaanddisabledattributes in the dev server and inbun build(Handling of media="print" incorrectly #18526, Inconsistent Bundling and Path Handling Between Development and Production in Bun #18809). Forscreen.cssthenprint.css media="print", main linksprint screen, this PR linksscreen print, andbun buildconcatenatesscreen print. No tag hasmedia, so the print rules now win on screen in the dev server too.The comment at
incremental_graph.rs:893namescheck_edge_removal, which does not exist. The early return at:868cannot run, becauseserver_seen_bit_setis always empty. Both stay as they are.Self-review. The review raised 16 concerns. Most ask for wording. The body now names source order as the reference and the two differences from
bun build. It also names themediacase and each action that reorders an open page. Two ask for tests. The framework test now runs with one server graph and with a separate SSR graph. It goes through a"use client"component and reads the lists after a rebuild. Not done:tailwindplugin. On load that mode takes the same path as the HTML route test. The order after a save is what the mode changes, and that assertion belongs with the client fix.Measurements. Main 449877f against this PR. Release means
--profile=release(ThinLTO).bun build, six inputs: differs on 0 of 6 on this PR and on 5 of 6 on main. The inputs: three<link>tags. Links with a script between them, and the script has a nested import. A stylesheet that two scripts import. A stylesheet that the page links and a script imports.<link a><link b><link a>.import,require()andimport()in one file. None of them has an external@import.process_edge_attachment: 151 to 162 instructions for an import that stays (+11), 171 to 183 for a new import (+12).process_chunk_dependencies: +1 instruction per bundled file. Nothing per request. Method: qemu-user execution trace of the release build, one script with 500 stylesheet imports, one cold bundle and 20 rebuilds.process_chunk_dependencies: 1,195 on main and 1,195 on this PR. The run is one cold bundle and 21 rebuilds of that script (gdb breakpoints on the mimalloc entry points).Edge(32 B) andEdgeLists(16 B) do not change.NewImportsis 16 B of stack. The head pointer that it replaces was 8 B..textis 82,164,617 B on both. The three list builder symbols change by +16, +32 and -11 B (llvm-size,llvm-nm).Tests.
html route links its stylesheets in source order(css.test.ts). The page has links with a script between them, a nested import, and a repeated<link>. Two modules import one stylesheet, and one stylesheet is linked and imported. The test then rebuilds with no change, swaps two links, swaps two imports, and adds and removes imports.bun buildgives the expected order for the files of each step. A reverse of the emitted list cannot pass. Main servesseven one four six three five two. The test expectsone two three four five six seven.framework route lists its stylesheets in import order(bundle.test.ts) runs once with one server graph and once with a separate SSR graph. It readsmeta.stylesof a route that imports stylesheets directly, through a module, and through a"use client"component. It then rewrites the component and the module (swap, add, remove) and reads a second route. It is inbundle.test.tsbecause that file already importsminimalFramework. The open PR test(bake): cover edits to a stylesheet that two CSS roots share #46957 edits the import line ofcss.test.ts.test/bake/dev/(20 files) on a debug build of this PR: all pass. On a debug build of main the new tests fail. That run of main also had 6 timeouts at the 5 s default inproduction.test.tsandreact-spa.test.ts. Those tests take 3 to 5 s on a debug build with ASAN. A later run ofproduction.test.tswith the build of this PR had 9 such timeouts, so the timeouts do not come from this change.