Skip to content

[wrangler][vite-plugin] Fix cf builds for root static projects - #15471

Draft
edmundhung wants to merge 3 commits into
mainfrom
edmundhung/fix-root-static-build-output
Draft

edmundhung wants to merge 3 commits into
mainfrom
edmundhung/fix-root-static-build-output

Conversation

@edmundhung

@edmundhung edmundhung commented Sep 2, 2026

Copy link
Copy Markdown
Member

Root-level static projects can use the project root as their asset source: Wrangler receives . from cf autoconfig, while Vite supports the equivalent publicDir: ".". Both Build Output paths place assets under that same root at .cloudflare/output/v0/workers/default/assets, so their default directory copying recursed into the output itself.

Asset writing now lives in @cloudflare/build-output-utils. Its Build Output-specific rule is narrow: when the source is the project root, copy its direct children except the reserved .cloudflare directory; otherwise preserve the existing recursive copy behavior.

Wrangler delegates asset copying to that shared writer. The Vite plugin keeps Vite's native public-directory behavior for normal directories, but for the exact root-public case it disables Vite's recursive copier and invokes the shared writer during renderStart. This runs after Vite prepares the output directory but before generated client files are written, preserving generated-output precedence and supporting custom app builders. This PR does not change .assetsignore policy.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: This fixes experimental build-output behavior without changing the public configuration interface.

Note

This is a contribution from an AI agent: Codex, GPT-5.

@changeset-bot

changeset-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 283a5fc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@cloudflare/build-output-utils Patch
@cloudflare/vite-plugin Patch
wrangler Patch
@cloudflare/vitest-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ask-bonk

ask-bonk Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

LGTM

github run

@pkg-pr-new

pkg-pr-new Bot commented Sep 2, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15471

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15471

@cloudflare/codemods

npm i https://pkg.pr.new/@cloudflare/codemods@15471

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15471

@cloudflare/containers-shared

npm i https://pkg.pr.new/@cloudflare/containers-shared@15471

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15471

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15471

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15471

miniflare

npm i https://pkg.pr.new/miniflare@15471

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15471

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15471

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15471

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15471

@cloudflare/vitest-plugin

npm i https://pkg.pr.new/@cloudflare/vitest-plugin@15471

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15471

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15471

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15471

wrangler

npm i https://pkg.pr.new/wrangler@15471

commit: 283a5fc

@petebacondarwin petebacondarwin added this to the Birthday Week 2026 milestone Sep 14, 2026
@edmundhung
edmundhung marked this pull request as ready for review September 16, 2026 09:06
@workers-devprod
workers-devprod requested review from a team and cjol and removed request for a team September 16, 2026 09:07
@workers-devprod

workers-devprod commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

devin-ai-integration[bot]

This comment was marked as resolved.

@dario-piotrowicz
dario-piotrowicz requested review from dario-piotrowicz and removed request for cjol September 16, 2026 09:35

@dario-piotrowicz dario-piotrowicz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

Comment thread .changeset/root-static-build-output.md Outdated

@workers-devprod workers-devprod 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.

Codeowners reviews satisfied

@edmundhung edmundhung changed the title [wrangler] Fix cf builds for root static projects [wrangler][vite-plugin] Fix cf builds for root static projects Sep 16, 2026
@edmundhung
edmundhung marked this pull request as draft September 16, 2026 10:15
devin-ai-integration[bot]

This comment was marked as resolved.

@edmundhung
edmundhung force-pushed the edmundhung/fix-root-static-build-output branch from a3502ef to 4cc76c9 Compare September 16, 2026 11:24
Add a shared asset writer for the Build Output Specification. Normal
asset directories retain recursive copying, while a project-root source
excludes the reserved `.cloudflare` directory and preserves files
already generated in the output tree.

## write.ts

The root-specific branch copies direct children except `.cloudflare`;
non-overwriting copies let generated artifacts take precedence when a
build tool emits files before public assets are added.

Reviewable-Commit-Version: 1
Replace Wrangler's unconditional recursive copy with the shared Build
Output writer. Root static projects now omit `.cloudflare` instead of
copying the output into itself, while normal asset directories and
project-provided `.assetsignore` files retain their existing behavior.

## build-output.ts

Wrangler keeps bundle emission local and delegates only static asset
copying to the shared utility.

Reviewable-Commit-Version: 1
Vite's public-directory copier recurses into the nested Build Output
tree when `publicDir` is the project root. Disable that copier only for
this configuration, then use Vite's post-build hook to fill in missing
public files after generated client output exists.

## config.ts

Normal public directories keep Vite's native copy behavior; only the
root-public case is redirected.

## build-output.ts

The post-build hook works with both the default and custom app builders.
The shared writer preserves transformed HTML and other generated output
without relying on concurrent bundler-hook timing.

Reviewable-Commit-Version: 1
@edmundhung
edmundhung force-pushed the edmundhung/fix-root-static-build-output branch from 4cc76c9 to 283a5fc Compare September 16, 2026 11:47
@edmundhung
edmundhung marked this pull request as ready for review September 16, 2026 14:17
@edmundhung
edmundhung marked this pull request as draft September 16, 2026 14:17

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 new potential issues.

Devin Review

// Vite's public directory copier recurses into its nested output when
// the project root is public. The Build Output plugin copies missing
// public files after the client build has generated its output.
clientEnvironment.build.copyPublicDir = false;

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.

🟡 Vite 6 root assets disappear

With Vite 6 root assets, copyPublicDir is disabled while the replacement buildApp hook is unsupported. Public files outside generated entries disappear from deployments.

Learn more

Vite 6 does not run the buildApp plugin hook, as documented by the existing hook guard in the config plugin. This assignment disables Vite's only public-directory copy path for every supported Vite version. The replacement copy runs exclusively from the new buildApp hook, so Vite 6 builds retain generated client output but omit other root assets.

Example: A Vite 6 project uses publicDir: "." with index.html and robots.txt. Vite generates the built index.html, but robots.txt is absent from .cloudflare/output/v0/workers/default/assets.

Recommended fix: Add a Vite 6-compatible post-build copy path for this exact root-public case. Keep copyPublicDir disabled to avoid recursion, and ensure the fallback runs after client output generation so force: false preserves generated files.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@@ -0,0 +1,9 @@
---
"@cloudflare/build-output-utils": patch

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.

🟡 New export receives patch bump

Publishing writeAssets under a patch bump adds an exported API without the required minor classification. The release metadata understates the package change.

Suggested change
"@cloudflare/build-output-utils": patch
"@cloudflare/build-output-utils": minor
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +18 to +29
buildApp: {
order: "post",
async handler(builder) {
if (!builder.environments.client?.isBuilt) {
return;
}

const { publicDir, root } = ctx.resolvedViteConfig;
if (publicDir && path.resolve(publicDir) === path.resolve(root)) {
await writeAssets({ root, sourceDirectory: publicDir });
}
},

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.

🔍 Copy timing description is stale

The description promises a pre-generation renderStart copy. The implementation uses post-order buildApp after the client build, so its lifecycle claim needs correction.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

4 participants