Skip to content

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

Draft
stipsan wants to merge 6 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 6 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.

Also bumps the vite catalog to ^8.1.3 and adds rolldown/@rolldown/* to minimumReleaseAgeExclude (matching the existing vite entry) — see below.

It works end-to-end now (vite 8.1.3 + rolldown 1.1.4)

Earlier revisions of this PR documented an upstream crash: on vite 8.1.0–8.1.2 (rolldown ≤1.1.3), the bundled studio threw ReferenceError: allConstants is not defined — Rolldown's lazy-barrel dev codegen mis-compiled a pure-ESM import * as namespace import in focus-lock (transitive dep of @sanity/ui; tracked around vitejs/vite#22756 and friends).

rolldown@1.1.4 (2026-07-01) disables lazy barrel in dev mode (rolldown#10060, #10071), which fixes it. With vite@8.1.3 resolving rolldown@1.1.4, the studio now renders correctly in bundled dev mode: the broken var constants = allConstants; no longer appears in the served bundle, and the browser console has no ReferenceErrors (only the expected CORS errors for an unregistered studio).

The minimumReleaseAgeExclude addition is needed because the repo's 3-day release-age gate would otherwise hold vite's rolldown: ~1.1.3 range at the broken 1.1.3 until fresh rolldown patches age out; rolldown is vite's own bundler (same trust domain/cadence as the existing vite exclusion).

bundled_dev_mode_studio_renders_vite813.mp4

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.
  • pnpm-workspace.yaml: vite catalog ^8.1.2^8.1.3; rolldown/@rolldown/* added to minimumReleaseAgeExclude.
  • 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.3.

Manual (fixtures/basic-studio):

  • sanity dev --experimental-bundle on vite 8.1.3: boots, serves bundled /assets/index.js, and the studio renders and is interactive in Chrome with no ReferenceErrors (see video). Same behavior via the sanity.cli.ts opt-in.
  • Normal sanity dev is unaffected (serves /@vite/client + unbundled app.js).

Studio renders in bundled dev mode on vite 8.1.3
Normal sanity dev renders the studio (no regression)

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 +19 B, +0.0%
Import time 911ms +11ms, +1.2%

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.30s +1.35s, +142.5% ⚠️

🗺️ 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 794ms -11ms, -1.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 — 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% (new)

Comparing 4 changed files against main @ bebd59b0002c52355efa41eea473b1e02b8b930c

Overall Coverage

Metric Coverage
Statements 73.6% (+ 0.0%)
Branches 63.6% (+ 0.0%)
Functions 67.2% (- 0.0%)
Lines 74.2% (+ 0.0%)

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.
vite@8.1.3 resolves rolldown@1.1.4, which fixes the namespace-import
codegen bug that crashed `sanity dev --experimental-bundle` at runtime
(allConstants is not defined, from focus-lock via @sanity/ui). Also adds
rolldown/@rolldown/* to minimumReleaseAgeExclude, matching vite's own
entry, so vite's ~x.y.z rolldown range can resolve fresh patch fixes.
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​vite@​8.1.3971008298100

View full report

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