Skip to content

feat(cli): add --experimental-bundle opt-in for Vite bundled dev mode#1413

Draft
stipsan wants to merge 5 commits into
mainfrom
cursor/vite-bundled-dev-optin-e987
Draft

feat(cli): add --experimental-bundle opt-in for Vite bundled dev mode#1413
stipsan wants to merge 5 commits into
mainfrom
cursor/vite-bundled-dev-optin-e987

Conversation

@stipsan

@stipsan stipsan commented Jul 1, 2026

Copy link
Copy Markdown
Member

Description

Adds an opt-in for Vite 8.1's experimental bundled dev mode to sanity dev. When enabled, Vite serves a Rolldown-bundled app during development instead of the unbundled per-module ESM dev server.

Because sanity dev builds its Vite config programmatically (configFile: false), the option can't be set via a user vite.config.*. This exposes it two ways:

  • CLI flag: sanity dev --experimental-bundle (and --no-experimental-bundle to force off).
  • Config: experimental: { bundledDev: true } in sanity.cli.ts (persistent across runs).

Resolution is flag-first, then config, then off (mirrors how host/port/basePath resolve). The sanity.cli.ts option required adding experimental.bundledDev to the CliConfig type and the zod cliConfigSchema in @sanity/cli-core — otherwise the parser strips unknown keys before the value reaches the dev server.

Two things are set on the inline dev config when enabled (before user vite config is merged, so a user override keeps final say):

  • experimental.bundledDev = true
  • build.rolldownOptions.input = <root>/.sanity/runtime/index.html — bundled mode bundles from an HTML entry and defaults to <root>/index.html, which doesn't exist in a Sanity project (the studio HTML is virtual, served from .sanity/runtime/index.html). Pointing the bundler at the real runtime HTML is the intended integration per Vite's bundled-dev design doc. Without it, bundled mode fails immediately with UNRESOLVED_ENTRY.

What to review

  • @sanity/cli-core: new experimental.bundledDev field in the CliConfig type and cliConfigSchema.
  • @sanity/cli: new --experimental-bundle flag on DevCommand; resolution in getDevServerConfig (flag → config → off) with an info notice; application of experimental.bundledDev + build.rolldownOptions.input in startDevServer.
  • Covers plain studios/apps and the app/studio server embedded under a workbench app; the workbench shell server is unchanged.

Testing

Automated:

  • @sanity/cli-core: experimental.bundledDev survives config parse instead of being stripped.
  • getDevServerConfig: flag → config → off resolution, --no-experimental-bundle override, info notice.
  • New devServer.test.ts: createServer receives experimental.bundledDev: true and the runtime-HTML entry only when enabled, preserves other experimental options, and still applies user vite config afterward.
  • pnpm check:types, pnpm check:lint, pnpm check:deps pass (on vite@8.1.2 after merging main). Scoped suites pass except 2 pre-existing formatDocumentValidation snapshot failures that also fail on origin/main.

Manual (fixtures/basic-studio):

  • Both opt-ins (flag and sanity.cli.ts) print the notice and serve a bundled /assets/index.js Rolldown bundle instead of the unbundled /.sanity/runtime/app.js.
  • Normal sanity dev is unaffected (serves /@vite/client + unbundled app.js) and the studio renders correctly.

Normal sanity dev renders the studio (no regression)

Upstream status (Vite 8.1.2)

Branch merged with main, so this now runs on vite@8.1.2. Bundled mode still crashes at runtime with ReferenceError: allConstants is not defined (/assets/index.js:69382) — identical to 8.1.0. Root cause is Rolldown mis-compiling a pure-ESM import * as namespace import (focus-lock, a transitive dep of @sanity/ui) in bundled-dev mode; the same fixture renders fine unbundled, confirming it's upstream and not Sanity's setup.

There is no newer version to move to: vite@8.1.2 and rolldown@1.1.3 are the newest published releases (npm has nothing >8.1.2/>1.1.3; the beta/nightly/canary dist-tags point to older versions, and rolldown has no pkg.pr.new build for its latest main). This is one of several tracked Vite 8.1 bundled-dev bugs (vitejs/vite#22756, rolldown#9512) whose documented workaround is to disable the flag — consistent with Vite labeling the feature "highly experimental". The opt-in is wired so users can try it and it will start working as Rolldown ships the fix, with no further CLI changes needed.

Bundled mode still crashes on vite 8.1.2 (upstream Rolldown bug)

optin_bundled_vs_normal_evidence.log

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

cursoragent and others added 3 commits July 1, 2026 06:07
Adds a typed `experimental.bundledDev` option to the CLI config type and
zod schema so `sanity.cli.ts` can opt into Vite's bundled dev mode
without the value being stripped on parse.
Adds a `--experimental-bundle` flag to `sanity dev` and honors
`experimental.bundledDev` from sanity.cli.ts. The flag wins when set
(including `--no-experimental-bundle`), otherwise the config value is
used, defaulting to off. When enabled, `experimental.bundledDev` is set
on the inline Vite config before user vite config is applied.
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @sanity/cli

Compared against main (3801bd7e)

@sanity/cli

Metric Value vs main (3801bd7)
Internal (raw) 2.7 KB -
Internal (gzip) 1.0 KB -
Bundled (raw) 11.16 MB +93 B, +0.0%
Bundled (gzip) 2.10 MB +15 B, +0.0%
Import time 875ms +3ms, +0.3%

bin:sanity

Metric Value vs main (3801bd7)
Internal (raw) 782 B -
Internal (gzip) 423 B -
Bundled (raw) 9.87 MB -
Bundled (gzip) 1.78 MB -
Import time 2.27s -6ms, -0.3%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against main (3801bd7e)

Metric Value vs main (3801bd7)
Internal (raw) 106.8 KB +93 B, +0.1%
Internal (gzip) 26.7 KB +37 B, +0.1%
Bundled (raw) 21.72 MB +85 B, +0.0%
Bundled (gzip) 3.46 MB +20 B, +0.0%
Import time 777ms -0ms, -0.0%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against main (3801bd7e)

Metric Value vs main (3801bd7)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli-core/src/config/cli/schemas.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/dev/servers/getDevServerConfig.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/dev.ts 21.1% (±0%)
packages/@sanity/cli/src/server/devServer.ts 80.0% (+ 74.5%)

Comparing 4 changed files against main @ a99e40e39566b75e0163d0a20f33aa28b4e2d5dc

Overall Coverage

Metric Coverage
Statements 74.4% (+ 0.1%)
Branches 64.3% (+ 0.1%)
Functions 68.8% (+ 0.1%)
Lines 75.0% (+ 0.1%)

Vite bundled dev mode bundles from an HTML entry, defaulting to
`<root>/index.html`. Sanity serves a virtual document (rewritten to
`.sanity/runtime/index.html`), so without an explicit entry the bundle
fails with UNRESOLVED_ENTRY. Set `build.rolldownOptions.input` to the
runtime HTML when bundled mode is enabled.
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.

2 participants