Skip to content

feat: use vite environment API#3617

Open
frandiox wants to merge 18 commits intomainfrom
fd-vite-8
Open

feat: use vite environment API#3617
frandiox wants to merge 18 commits intomainfrom
fd-vite-8

Conversation

@frandiox
Copy link
Copy Markdown
Contributor

@frandiox frandiox commented Mar 24, 2026

Migrate to Vite Environment API to support Vite 8 (backwards compat with 6 and 7).

This PR also changes the local Vite versions to 8, and the default one in skeleton template too.
There's no migration required from Vite 6/7 to 8 in the skeleton, the only change is removing tsconfigPaths plugin because it's now native in Vite 8 and it would show a warning otherwise.


The only problem I saw is that React Router is not officially supporting Vite 8 yet. It works, but Vite shows a warning about this because RR is using esbuild options (Vite still support this under the hood but warns about it).
So the question is: do we migrate to Vite 8 directly, or change the skeleton version to Vite 7 until RR updates?

Let's maybe sync with the RR folks and see what's their plan.

frandiox and others added 8 commits March 21, 2026 21:06
Create a fetchable mini-oxygen SSR environment using Vite 8's environment APIs and move runtime lifecycle management into a dedicated environment module.

Keep Hydrogen and CLI option passing compatible via registerPluginOptions while routing dev requests through server.environments.ssr and preserving build-time compatibility_date handling.

Update the custom workerd module runner bridge for Vite 8's invoke contract, including getBuiltins support and fetchModule options forwarding, so local SSR still runs inside MiniOxygen.

Upgrade workspace Vite dependencies to v8, switch example/template configs to native resolve.tsconfigPaths, and add the environment API refactor plan document.

Co-authored-by: Codex <codex@openai.com>
Replace mini-oxygen's custom /__vite_fetch_module bridge with a service binding that forwards Vite runner invoke payloads to the SSR environment's built-in handleInvoke() path.

This removes bespoke request parsing and builtins serialization while keeping the current fetchable SSR environment and Hydrogen/CLI runtime option flow intact.

Co-authored-by: Codex <codex@openai.com>
Drop the leftover unused Hydrogen plugin lookup in dev, and switch MiniOxygen and bundle analyzer asset emission to emitFile() so Vite 8/Rolldown no longer warns about mutating the bundle object.

Co-authored-by: OpenAI Codex <codex@openai.com>
Align CLI internal MiniOxygen typing with monorepo source types, make the bundle analyzer tolerate Vite 8 and Rolldown RenderedModule typing, and fix MiniOxygen proxy port parsing strictness.

Co-authored-by: OpenAI Codex <codex@openai.com>
Drop the leftover __VITE_ROOT runtime binding from the MiniOxygen Vite worker bridge now that the Vite 8 Environment API path no longer uses it.

Co-authored-by: OpenAI Codex <codex@openai.com>
Throw on late runtime reconfiguration, remove the refactor plan doc, and add coverage for MiniOxygen's Vite environment lifecycle.

Co-authored-by: OpenAI Codex <codex@openai.com>
- Upgrade vitest from ^1.0.4 to ^3.2.4 in packages/cli (only package still on v1, which only supports Vite ^5)
- Update build test assertions to match Vite 8 renamed log messages

Co-Authored-By: Claude <noreply@anthropic.com>
@shopify
Copy link
Copy Markdown
Contributor

shopify bot commented Mar 24, 2026

Oxygen deployed a preview of your fd-vite-8 branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment March 31, 202611:11 AM

Learn more about Hydrogen's GitHub integration.

Three bugs were preventing HMR from working after the migration to
Vite's Environment API:

1. `server.watch: null` (accidentally introduced in #2722) caused Vite
   to create a NoopWatcher, so no file-change events were ever emitted.
   Removed it to restore chokidar.

2. The SSR environment was passed `transport: context.ws` (the same
   WebSocket server the browser uses). Because `isWebSocketServer` is
   true on that object, Vite used it directly as the SSR environment's
   hot channel. When the SSR module graph found no React Fast Refresh
   boundaries (SSR code has none), it sent `full-reload` over that
   shared WebSocket — hard-refreshing the browser on every file change.
   Dropping the transport gives the SSR environment a noop hot channel
   so those events are silently discarded.

3. `@vite/client` was never injected into HTML responses from workerd,
   so the browser never established a WebSocket connection to Vite's
   HMR server. React Router's <Scripts /> already injects the
   inject-hmr-runtime preamble; the only missing piece was @vite/client.
   Now injected via a simple string replace on HTML responses.

Server-side invalidation (route handlers, server.ts) is handled by a
new watcher listener in environment.ts: when a file in the SSR module
graph changes, the mini-oxygen instance is disposed so the next request
starts a fresh workerd with a clean module-runner cache.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@frandiox
Copy link
Copy Markdown
Contributor Author

/snapit

frandiox and others added 3 commits March 25, 2026 10:47
Remove redundant MiniOxygen watcher and HTML injection cleanup while keeping the SSR environment request-driven.

Co-authored-by: Codex <codex@openai.com>
Remove the custom MiniOxygen environment marker/getter and read the entrypoint error handler from plugin state instead.

Co-authored-by: Codex <codex@openai.com>
Update MiniOxygen Vite runtime comments to reflect the current request-driven server refresh model and inspector behavior.

Co-authored-by: Codex <codex@openai.com>
Comment on lines -8 to +10
plugins: [hydrogen(), oxygen(), reactRouter(), tsconfigPaths()],
plugins: [hydrogen(), oxygen(), reactRouter()],
resolve: {
tsconfigPaths: true,
},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

New in Vite 8, it does the tsconfigPaths behavior by default. Vite 6 and 7 still need the plugin though.

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.

if we are going to default to vite 8, sure!

Comment on lines +74 to +78
"vite": "^8.0.1",
"vitest": "^3.2.4"
},
"peerDependencies": {
"vite": "^6.2.1"
"vite": "^6.2.1 || ^7.0.0 || ^8.0.0"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As long as MiniOxygen is built with Vite 8 including the fixes of this PR, it seems to be compatible when consumed in apps using Vite 6 and 7 (and 8 obv), so there's no breaking change to my understanding 🤔

"ts-expect": "^1.3.0",
"typescript": "5.9.2",
"vite": "^6.2.4",
"vite": "^8.0.1",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This change in hydrogen-react means we start building it with Vite 8


export async function createCpuStartupProfiler(root: string) {
type MiniOxygenType = typeof import('@shopify/mini-oxygen/node');
type MiniOxygenType = typeof import('~/mini-oxygen/node/index.js');
Copy link
Copy Markdown
Contributor Author

@frandiox frandiox Mar 25, 2026

Choose a reason for hiding this comment

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

This type of change (here and in other files) is just a fix to remove build order requirement for TS types (before we needed mini-oxygen built before CLI, now it doesn't matter).

It can be ignored for review.

Comment on lines -201 to +211
bundle[BUNDLE_ANALYZER_HTML_FILE] = {
this.emitFile({
type: 'asset',
fileName: BUNDLE_ANALYZER_HTML_FILE,
needsCodeReference: false,
source: injectAnalyzerTemplateData(
analysisTemplate,
JSON.stringify(metafile),
),
names: [BUNDLE_ANALYZER_HTML_FILE],
originalFileNames: [BUNDLE_ANALYZER_HTML_FILE],
// name and originalFileName should be deprecated .. but
// for some reason, removing them breaks typescript check
name: BUNDLE_ANALYZER_HTML_FILE,
originalFileName: BUNDLE_ANALYZER_HTML_FILE,
};
});
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same functionality, just fixing some types

Comment on lines +9 to +10
Request as MiniOxygenRequest,
Response as MiniOxygenResponse,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed for clarity

@frandiox frandiox marked this pull request as ready for review March 25, 2026 12:20
@frandiox frandiox requested a review from a team as a code owner March 25, 2026 12:20
@github-actions

This comment has been minimized.

Document the warmup timing and SSR environment replacement behavior in MiniOxygen's Vite environment integration.

Co-authored-by: Codex <codex@openai.com>
@frandiox frandiox changed the title [PoC] Vite Environment API Vite Environment API Mar 25, 2026
InternalMiniOxygenOptions,
MiniOxygenViteOptions,
} from './server-middleware.js';
import {
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.

non-blocking: these imports (startMiniOxygenRuntime, toMiniflareRequest, WARMUP_PATHNAME, getViteUrl) are all implementation details of this module's handleRequest and warmup functions. Meanwhile server-middleware.ts also provides setupOxygenMiddleware consumed by plugin.ts - so it's serving two unrelated concerns.

Worth considering in a follow-up whether these helpers should live here (or in a shared runtime.ts) to align the module boundary with the information boundary. Not blocking for a migration PR though.

let apiOptions: OxygenApiOptions = {};
let miniOxygenEnvironment: MiniOxygenDevEnvironment | undefined;

const resolveMiniOxygenOptions = async (
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.

non-blocking: this ~25-line function understands the full internal structure of MiniOxygenViteOptions - entry resolution, env merging, debug/inspector defaults, logRequestLine priority. That's option-resolution knowledge leaking into the plugin layer.

Worth considering whether environment.ts could own this logic (accepting OxygenPluginOptions directly) so plugin.ts doesn't need to understand MiniOxygenViteOptions internals. Not blocking for this migration, but a design observation for follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It needs to understand both, plugin/CLI provided options and MiniOxygen receiving options, because it's transforming from the former to the latter. So it can live in any of those 2 areas, or a separate one.

toMiniflareRequest(request),
);

return new Response(response.body as unknown as BodyInit, {
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.

Suggested change
return new Response(response.body as unknown as BodyInit, {
return new Response(response.body as ReadableStream | null, {

still a type cast because of the freaking <any> type parameter, but this is a less fake one

ramonclaudio added a commit to ramonclaudio/shopify-hydrogen-shadcn-template that referenced this pull request Mar 27, 2026
- @shopify/hydrogen 2025.7.0 -> 2026.1.2
- react/react-dom 18.3.1 -> 19.2.4
- react-router 7.9.2 -> 7.13.2
- @react-router/dev 7.9.2 -> 7.13.2
- vite 6.2.4 -> 8.0.3
- tailwindcss 4.1.6 -> 4.2.2
- typescript 5.9.2 -> 6.0.2
- lucide-react 0.546.0 -> 1.7.0
- all other deps bumped to latest
- remove baseUrl from tsconfig (deprecated in TS 6)
- use relative paths in tsconfig paths

Build: PASSES (client + SSR)
Dev server: FAILS (__vite_ssr_exportName__ in mini-oxygen)
Blocked on Shopify/hydrogen#3617 landing
ramonclaudio added a commit to ramonclaudio/shopify-hydrogen-shadcn-template that referenced this pull request Mar 27, 2026
Build @shopify/hydrogen, @shopify/mini-oxygen, and @shopify/cli-hydrogen
from the fd-vite-8 branch (Shopify/hydrogen#3617) and install as local
tarballs to test the full Vite 8 Environment API migration.

Results:
- Build: PASSES (both client and SSR)
- Dev server: PASSES (no __vite_ssr_exportName__ error)
- oxygen:main bundle-assign warning: FIXED by PR
- hydrogen:bundle-analyzer warning: still present (fix is in CLI)
- react-router esbuild deprecation: cosmetic, not blocking
- Invalid comparator warning: artifact of file: dep references

PR #3617 is validated. The Environment API rewrite in mini-oxygen
resolves the Vite 8 SSR module format incompatibility.
ramonclaudio added a commit to ramonclaudio/shopify-hydrogen-shadcn-template that referenced this pull request Mar 27, 2026
Complete patch set for Shopify/hydrogen#3617:
- @shopify/mini-oxygen@4.0.1 (environment API rewrite)
- @shopify/hydrogen@2026.1.2 (vite plugin, peerDep)
- @shopify/hydrogen-react@2026.1.1 (vite peerDep)

Verified: build, dev server, HMR, all routes 200, zero console
errors, zero hydration errors, clean checkout applies patches.
ramonclaudio added a commit to ramonclaudio/shopify-hydrogen-shadcn-template that referenced this pull request Mar 27, 2026
Pin @shopify/mini-oxygen to 4.0.1 for patch compatibility.
Three pnpm patches from Shopify/hydrogen#3617 (fd-vite-8 branch):
- @shopify/mini-oxygen@4.0.1
- @shopify/hydrogen@2026.1.2
- @shopify/hydrogen-react@2026.1.1

Tested on Vite 6.4.1, 7.3.1, 8.0.3. All pass.
ramonclaudio added a commit to ramonclaudio/patches that referenced this pull request Mar 27, 2026
…ronment API)

Patches built from fd-vite-8 branch, tested on Vite 6.4.1, 7.3.1, 8.0.3:
- @shopify/mini-oxygen@4.0.1: environment API rewrite, worker-entry transport
- @shopify/hydrogen@2026.1.2: vite plugin, peerDep ^8.0.0
- @shopify/hydrogen-react@2026.1.1: vite peerDep ^8.0.0
ramonclaudio added a commit to ramonclaudio/patches that referenced this pull request Mar 27, 2026
…ronment API)

Patches built from fd-vite-8 branch, tested on Vite 6.4.1, 7.3.1, 8.0.3:
- @shopify/mini-oxygen@4.0.1: environment API rewrite, worker-entry transport
- @shopify/hydrogen@2026.1.2: vite plugin, peerDep ^8.0.0
- @shopify/hydrogen-react@2026.1.1: vite peerDep ^8.0.0
@fredericoo fredericoo changed the title Vite Environment API feat: use vite environment API Mar 27, 2026
Replace the double-cast `as unknown as BodyInit` with `as ReadableStream | null`,
which accurately describes the Miniflare Response.body type and avoids the
lossy intermediate cast through `unknown`.
Replace the bare `200` timeout with a named constant that communicates intent
at a glance. The delay lets Vite settle after listen/config reload before the
synthetic warmup request fires.
Mark the getBuiltins workaround so it's easy to find and remove once
Vite 6 support is dropped. Vite 7+ handles this internally.
Patch changeset covering the type cast improvement, magic number
extraction, and Vite 6 shim TODO marker.
'@shopify/create-hydrogen': patch
---

Add support for Vite 7 and Vite 8. Hydrogen, Mini Oxygen, the Hydrogen CLI, and Hydrogen React now accept Vite 7 and 8 as peer dependencies while remaining backwards-compatible with Vite 5 and 6.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@fredericoo I think the only one compatible with Vite 5 is Hydrogen. MiniOxygen likely requires 6+ since older versions didn't have Environment API support (I think).

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.

ok! will adjust


Mini Oxygen's dev server has been refactored to use the [Vite Environment API](https://vite.dev/guide/api-environment), which is the standard way to run non-browser runtimes in Vite. This replaces the previous custom middleware approach with a first-class `FetchableDevEnvironment`, improving compatibility with Vite's built-in HMR and module invalidation.

New Hydrogen projects created with `npm create @shopify/hydrogen` will default to Vite 8. The `vite-tsconfig-paths` plugin is no longer needed in the skeleton template since Vite 8 supports `resolve.tsconfigPaths` natively.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in OP, perhaps it would be good to sync with the RR7 folks and see if they add official support for Vite 8 as well. Mostly to remove warnings from the terminal, otherwise it already works.

Mini Oxygen requires Vite 6+ (Environment API didn't exist in Vite 5).
Only Hydrogen itself supports Vite 5+.
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