Skip to content

feat: fetchable dev environments#15574

Open
teemingc wants to merge 196 commits into
version-3from
fetchable-dev-environment
Open

feat: fetchable dev environments#15574
teemingc wants to merge 196 commits into
version-3from
fetchable-dev-environment

Conversation

@teemingc

@teemingc teemingc commented Mar 20, 2026

Copy link
Copy Markdown
Member

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:

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

changeset-bot Bot commented Mar 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3ad4a3b

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
@Rich-Harris

Copy link
Copy Markdown
Member

The recent on main and version-3 are proving rather difficult to merge into this branch. The nub of the problem is that dynamic environment variables need to be set before anything gets imported from $app/env/*, and there are some brain-bending circular dependencies involved. Trying to figure it out

@Rich-Harris Rich-Harris force-pushed the fetchable-dev-environment branch from 7f3e0a6 to e64d61f Compare June 5, 2026 15:44
@Rich-Harris

Rich-Harris commented Jun 5, 2026

Copy link
Copy Markdown
Member

Alright, rewinding the merge, since I fucked things up beyond repair. Sorry 😬 Will see if I can get it right on the second attempt, though I'm a little worried that the new explicit env stuff is going to pose a real challenge

@Rich-Harris Rich-Harris force-pushed the fetchable-dev-environment branch from e87ebce to e64d61f Compare June 5, 2026 16:36
@teemingc

teemingc commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

I’ll take a look too if you haven’t had another go at it already

@Rich-Harris

Copy link
Copy Markdown
Member

It's all working now, just needed to point to __SERVER__/env.js. One of those obvious-in-hindsight things that takes several hours of headbashing to realise

@Nic-Polumeyv

Nic-Polumeyv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Heads up for when this rebases. The eager import('/@vite/client') added in render.js is gated on esm-env DEV, the same pattern that caused #16008. Verified here, a NODE_ENV=staging build puts @vite/client in the prerendered output. #16023 swaps that for an import.meta.hot guard in the generated app.js.

const server_root = join(config.outDir, 'output');

/** @type {import('types').ServerInternalModule} */
const internal = await import(pathToFileURL(`${server_root}/server/internal.js`).href);

@teemingc teemingc Jun 30, 2026

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.

Instead of running the server directly in the current process, we're going to run invoke the dev environment and run it there (whatever process that is which has been configured in the Vite config). Most of the instructions here have been moved to the analysis_entry.js file which is the module we run in the dev environment

if (!svelte_config.kit.adapter?.vite?.plugins) {
if (!vite.isFetchableDevEnvironment(server.environments.ssr)) {
throw new Error(
'The Vite configured dev SSR environment must be a FetchableDevEnvironment'

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.

Might make this doable in a future PR but not worth the extra branches for now

Rich-Harris pushed a commit that referenced this pull request Jul 2, 2026
Extracts some changes from #15574
that allows adapters to provide their own Vite plugins. If the adapters
provide a Vite plugin that configures dev/preview, it's expected for
them to fully handle that.

This will allow adapters to change dev/build/preview behaviour that's
beneficial regardless of if we adopt the Vite environment API. For
example:
- the Cloudflare adapter could specify the build config platform to
target webworkers instead of node
- adapters can configure the preview server to run the adapter build
output with a custom runtime rather than the current behaviour of
running the Vite build output in a Node process all the time

---

### Please don't delete this checklist! Before submitting the PR, please
make sure you do the following:
- [x] 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
- [x] This message body should clearly illustrate what problems it
solves.
- [ ] Ideally, include a test that fails without this PR but passes with
it.
- will probably add some in a follow-up PR. It's more meaningful when
done in the adapter packages that actually make use of the change
	- 
### Tests
- [ ] Run the tests with `pnpm test` and lint the project with `pnpm
lint` and `pnpm check`

### Changesets
- [x] 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

- [x] Please ensure that 'Allow edits from maintainers' is checked. PRs
without this option may be closed.

---------

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
@pkg-svelte-dev

pkg-svelte-dev Bot commented Jul 3, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 3ad4a3b:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/3ad4a3ba9c554150bd09adc7057533d027118ae2

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/15574

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? Use the new Vite Runtime API to support server-side HMR correctly Adapter api for dev mode support

4 participants