Skip to content

feat: fetchable dev environments#15574

Open
teemingc wants to merge 135 commits intoversion-3from
fetchable-dev-environment
Open

feat: fetchable dev environments#15574
teemingc wants to merge 135 commits intoversion-3from
fetchable-dev-environment

Conversation

@teemingc
Copy link
Copy Markdown
Member

@teemingc teemingc commented Mar 20, 2026

This PR changes the dev, preview, build analysis and prerender to run inside of the configured Vite SSR environment. This required the following fundamental changes:

1. Avoiding Node.js imports in the runtime

  • Some of our utilities had to be moved to a separate file to avoid importing Node.js modules. Others had to be recreated to avoid adding another dependency if we want the same functionality in a Node-agnostic environment.
  • node:fs is a requirement but we import it in the main process instead and communicate the results back to the Vite SSR environment.
  • We can't use AsyncLocalStorage.enterWith because it's a Node.js-only experimental API

2. Replacing Vite's ssrLoadModule

The Vite docs recommendation is to create a ModuleRunner or the RunnableDevEnvironment instance to achieve a similar functionality but these don't work with Cloudflare's environment. There's also no strict contract for environments to make these available to us so they can't be relied on. We solve this in two different ways:

  • We use Vite's import.meta.hot.on in the SSR environment to listen for events from the main process, compute the result, and send it back.
  • Instead of importing the Server class from the build output in the main process, we spin up a Vite development server with the build output but proxy the Server class by intercepting module resolution with Vite's resolveId hook. Starting a dev server and sending a request or HMR event seems to be the only way to run a module in the environment.

3. Communication between the main process (where Vite runs) and the SSR environment (where user code runs)

The two points before this makes this a requirement. Now we'll detail the different types of communication that exist as a result:

One way communication

  • Main process to SSR environment. e.g., when we detect a server asset import from a Vite hook, we can update the server manifest in the environment using environments.ssr.hot.send. This helps us retain synchronous access to the filesystem from a non-Node environment such as checking if a filename exists as a key in the server assets map. We can also construct virtual modules with serialised data that the can be imported and accessed in the environment.
  • SSR environment to main process. e.g., when an error occurs on the server, we use import.meta.hot.send so that the main process can then create a Vite error overlay in the browser. This replaces our loudSsrLoadModule utility.

Two way communication

  • Main process to SSR environment and back to the main process. Previously we could just run ssrLoadModule to run some code in Vite's pipeline and get a result back. Now, we have to ensure the SSR environment has an import.meta.hot.on event listener attached, emit an event, compute in the environment, and receive the results back in the main process through environments.ssr.hot.on and Promise.withResolvers to await the result. This is used for retrieving remote function info, etc. Alternatively, we can also proxy the Server class during analysis and prerendering to respond with our environment computed result as mentioned earlier.
  • SSR environment to main process and back to the environment. This requires the operation to be asynchronous if it wasn't already. We also can't re-use the two-way import.meta.hot approach above because Cloudflare's workerd doesn't like responding to requests from a context created by import.meta.hot.on. Therefore, we use fetch to send a request to the running Vite dev server, configure the Vite dev server using the configureServer hook to intercept the request via vite.middlewares.use, and respond with the computed result. This is primarily used for getting CSS to inline to avoid FOUC during dev, finding out which param matchers exist from the filesystem, or even checking if a feature should be allowed by the adapter.supports function which we can't serialise.

Most of the import.meta.hot and fetch style communication requires serialising and deserialising data using devalue.

Future PRs

  • adapter-static environment that uses sirv on the build output instead of running the SSR server
  • adapter-node environment that allows using a custom entry point similar to after building
  • adapter-netlify environment to run things in serverless/edge mode?

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 20, 2026

🦋 Changeset detected

Latest commit: a5507cb

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

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Major

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

@teemingc teemingc changed the title feat: fetchable dev environment feat: fetchable dev environments Mar 20, 2026
@teemingc teemingc changed the base branch from main to version-3 March 20, 2026 09:59
@svelte-docs-bot
Copy link
Copy Markdown

Comment thread packages/kit/src/types/internal.d.ts
Comment thread packages/kit/src/exports/vite/index.js Outdated
});

test('skips optimizing +page.server.js dependencies', async ({ page }) => {
test('optimizes +page.server.js dependencies', async ({ page }) => {
Copy link
Copy Markdown
Member Author

@teemingc teemingc Apr 25, 2026

Choose a reason for hiding this comment

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

These tests are now flipped around because they were only there when Vite was still using esbuild and it didn't play well with CJS deps. This is no longer an issue now that optimizeDeps uses Rolldown and resolves the CJS -> ESM conversion

@teemingc teemingc requested a review from Rich-Harris April 26, 2026 18:30
import 'vite/types/customEvent.d.ts';
import type { PageOptions } from './static_analysis/index.js';

declare module 'vite/types/customEvent.d.ts' {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Types the import.meta.hot and environments.ssr.hot APIs

Comment thread .changeset/short-chefs-stare.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove Emulator?

2 participants