Skip to content

unplugin under Vite: environments built in parallel share one pipeline - #402

Merged
codecaaron merged 5 commits into
mainfrom
wc/unplugin-vite-parallel
Oct 11, 2026
Merged

codecaaron merged 5 commits into
mainfrom
wc/unplugin-vite-parallel

Conversation

@codecaaron

@codecaaron codecaaron commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Release fix (released by reading; loud and opt-in): unplugin's Vite adapter failed any custom buildApp that builds environments in parallel. Found by the teardown follow-up probe after #395.

The bug

Each environment's buildStart made a new ExtractionSession for the same build (runClaimedPipeline), and the second collided with the first's claim: [animus] "animus-unplugin:…" started an analysis of build …, which "animus-unplugin:…" publishes. Unlike vite-plugin before #395, it failed at once, not only when one environment was slow. v0.1.25 had the same shape (a new Session per buildStart, closed in buildEnd, a process-global claim). Vite's default buildApp is sequential, so only a custom parallel one hits it.

Fix

Under Vite, Rollup and Rolldown the plugin tracks open environment builds by name:

  • a buildStart while another environment's build is open joins the running pipeline (joinPipeline) instead of claiming again;
  • buildEnd emits its assets, and disposes the session directory and releases the claim only when the last environment ends;
  • a build of an environment whose build is open (a reused builder, or a second environment under one name) waits for that build's end, then opens its own generation; buildEnd (once per build) ends only the generation its environment opened (last round, 51a9c07, replacing the refusal, which let a refused build's end dispose the shared session directory under the original build).

webpack-like hosts run one compile at a time and are not tracked (#397 covers their restarts).

Witness

unplugin/tests/vite-parallel-build.test.ts › both environments build from one shared analysis: a real Vite 8.2.2 createBuilder over vite-plugin's dev fixture with animusUnplugin.vite, client and SSR in parallel; SSR output must be extracted. Red on main a5d1f49 with the claim error.

Last-round witness

a builder reused while an earlier build still runs keeps that build its assets (f799c47): badge.svg through an asset() image scale read by a Badge background, SSR emitting assets, the builder reused while the slow original SSR build still runs; all four builds settle, the original SSR fulfils and its output carries the badge SVG. Red at c449922.

Checks

macOS at f799c47: unplugin 34/34, lint and fmt clean. Linux CI: run 38176081147 (c449922) and run 38177499710 (f799c47), every job green.

🤖 Generated with Claude Code

codecaaron and others added 4 commits October 11, 2026 17:21
A custom Vite buildApp that built environments in parallel failed every
time: each environment's buildStart made a new Session for the same
build, and the second collided with the first's claim ("… started an
analysis of build …, which … publishes"). Released: v0.1.25 had the same
shape (a new Session per buildStart, a process-global claim).

Under Vite, Rollup and Rolldown the plugin now tracks open environment
builds by name: a buildStart while another environment's build is open
joins the running pipeline, and buildEnd disposes and releases only when
the last ends. As in vite-plugin (#395), a second build of an active
environment and a second environment under an active name are refused,
naming the environment. webpack-like hosts run one compile at a time and
are not tracked.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…lysis

A real Vite createBuilder over vite-plugin's dev fixture with
animusUnplugin.vite, client and SSR built in parallel; SSR output must be
extracted. Red on main a5d1f49 with the claim error.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…buildEnd

spike-codex (P1, silent): a refused build owned the active generation's
end. Under a builder reused after a failed parallel buildApp, the second
client's buildEnd disposed the shared session directory before the
original SSR build ended, which then fulfilled with animus.css
referencing ./assets/badge.*.svg and no SVG emitted. As in vite-plugin
(#395): a buildStart for an environment whose build is open (the same
object, or one named alike) waits for that build's end, then opens its
own generation; buildEnd, which runs once per build, ends only the
generation its environment opened; the last to end disposes the session
directory and releases the claim. No refusal is left, so no stray end.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
spike-codex's asset witness on the dev fixture: badge.svg through an
asset() image scale read by a Badge background, SSR emitting assets, the
builder reused while the slow original SSR build still runs. All four
builds settle; the original SSR build fulfils and its output carries the
badge SVG. Red at c449922.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@codecaaron
codecaaron marked this pull request as draft October 11, 2026 21:57
@codecaaron

Copy link
Copy Markdown
Owner Author

Parked by root under the stopping rule after the final round. spike-codex confirmed the reused-builder findings closed (all four builds settle correctly; the original SSR keeps its assets), but the serialise shape introduces a circular wait: a plugin whose transform awaits a nested build of the same environment hangs, because the nested buildStart awaits the outer build's buildEnd (vite-plugin context.ts:517-521 / unplugin core.ts:529-537). The earlier refusal heads fail that case loudly instead. Owner decision pending: ship this head with the nested-build case as a stated limit, ship the refusal head, or leave parallel buildApp unsupported (main fails it loudly).

🤖 Generated with Claude Code

@codecaaron
codecaaron marked this pull request as ready for review October 11, 2026 23:17
@codecaaron
codecaaron merged commit e8d8646 into main Oct 11, 2026
14 checks passed
codecaaron added a commit that referenced this pull request Oct 12, 2026
Rollup runs the plugin's own `rollup.buildStart`. Since #402, buildEnd
retires the build only when it closes an environment build the start
opened, and the Rollup start opens none, so the claim stays held and the
watch's next build throws "started an analysis of build …, which the same
session publishes".

Red on main 5cfcde7; green on #402's parent c0f0ab9 by the same probe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
codecaaron added a commit that referenced this pull request Oct 12, 2026
…ires

#402 made buildEnd retire the shared Session only when it closes an
environment build its start opened. Rollup runs the plugin's own
`rollup.buildStart`, which opened none, so the claim was never released
and every Rollup watch rebuild threw "started an analysis of build …,
which the same session publishes". Rollup's start now opens (or joins)
the environment build as the normalized buildStart does.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
codecaaron added a commit that referenced this pull request Oct 12, 2026
Session C. #395 (vite-plugin) and #402 (unplugin) each tracked open
environment builds with the same algorithm: open by name after any open
build of that name ends, end only the entry this environment opened, and
retire the shared build with the last. extract/session's EnvironmentBuilds
now holds it once, with the single environment a host without
environments builds as; both plugins delete their copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant