Skip to content

feat: fetchable dev environments#15574

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

feat: fetchable dev environments#15574
teemingc wants to merge 174 commits into
version-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: b6389f7

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
Comment thread documentation/docs/40-best-practices/07-images.md Outdated
Comment thread documentation/docs/40-best-practices/07-images.md Outdated
teemingc and others added 2 commits May 15, 2026 02:48
Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
@Rich-Harris
Copy link
Copy Markdown
Member

I wonder if we should break this PR into smaller chunks so that it's a bit more reviewable — at the moment it's quite overwhelming. Started here: #15837

@teemingc
Copy link
Copy Markdown
Member Author

teemingc commented May 15, 2026

Yeah, agreed. Merging the stacked PR was my mistake because that made this PR include all the changed files from that. I'll try to split the rest into something like:

EDIT: the PR is already looking smaller and will be smaller once the PRs above are merged. Going to hold off on splitting it up more for the moment

teemingc added a commit that referenced this pull request May 15, 2026
#15574 has become somewhat overwhelming; thought I might experiment with
extracting some of the changes into more manageable PRs that are useful
independently of the env API stuff.

This PR moves the `adapter` option from `svelte.config.js` to
`vite.config.ts`. This is valuable in its own right, since it makes it
possible for adapters to include their own Vite plugins, without the
need to awkwardly hack around the timing of config resolution (for
example, you could imagine an adapter providing platform-specific routes
in dev/preview). That part isn't implemented in this PR though, that can
be a follow-up — for now, it just moves the option.

Draft because I had Opus do the work, and I haven't yet looked closely
at the diff (either against `version-3`, or against
`fetchable-dev-environment`)

---------

Co-authored-by: Tee Ming <chewteeming01@gmail.com>
timeout
}),
`Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender`
/Error: The entries export from \/\[slug\]\/\[notSpecific\] generated entry \/whatever\/specific, which was matched by \/\[slug\]\/specific - see the `handleEntryGeneratorMismatch` option in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender for more info\.\r?\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender/
Copy link
Copy Markdown
Member Author

@teemingc teemingc May 15, 2026

Choose a reason for hiding this comment

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

Changes the assertion from an exact string match to an includes type of match. This is needed because throwing the error from the environment includes some additional details before the expected message

teemingc added a commit that referenced this pull request May 18, 2026
splitting out changes from #15574

This PR creates and moves some utils to be Node-agnostic so that we can
use them to resolve paths in the dev environment

---

### 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.
teemingc added a commit that referenced this pull request May 18, 2026
split out from #15574

This PR replaces our use of
[`als.enterWith`](https://nodejs.org/api/async_context.html#asynclocalstorageenterwithstore)
with the standard `als.run` because `enterWith` is not standard in other
runtimes such as workerd (it's an experimental node feature). This
change requires wrapping our server response code in a function and
running async local storage with that function

---

### 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.
}

if (__SVELTEKIT_DEV__ && typeof error == 'object') {
fix_stack_trace(error);
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.

AFAIK there's no need to fix stack traces inside environments as Vite handles that

@@ -0,0 +1,3 @@
declare module '__SERVER__/index.js' {
Copy link
Copy Markdown
Member Author

@teemingc teemingc May 18, 2026

Choose a reason for hiding this comment

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

helps avoids a type error when importing this in the prerender_entry.js file

serve_static_middleware.handle(req, res, () => {
void setResponse(res, rendered);
// fallback to our own fetch handler if the adapter doesn't provide one
if (!adapter?.vite?.plugins) {
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 be better to add adapter config options such as adapter.vite.customDev and adapter.vite.customPreview which default to false and can be set to true to disable the default SSR handlers during vite dev and vite preview

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?

3 participants