Skip to content

feat: support bundled dev mode in ssr#21626

Open
sheremet-va wants to merge 69 commits into
vitejs:mainfrom
sheremet-va:fix/support-full-bundle-mode-in-ssr
Open

feat: support bundled dev mode in ssr#21626
sheremet-va wants to merge 69 commits into
vitejs:mainfrom
sheremet-va:fix/support-full-bundle-mode-in-ssr

Conversation

@sheremet-va

@sheremet-va sheremet-va commented Feb 12, 2026

Copy link
Copy Markdown
Member

TODO

I am exploring how full bundle mode can be supported in SSR. At the moment the goal is to support environments.ssr.runner.import and environment.fetchModule. Module graph, transformRequest, pluginContainer are out of scope of this PR, but they will be revisited later.

Known quirks (to discuss)

  • url in runner.import(url) is not resolved with resolveId like it is done usually. Instead it accepts:
    • Absolute URL to the bundled entry point specified in rolldownOptions.input
    • Relative URL (relative to root) to the bundled entry point
    • The chunk name
    • Relative URL to the bundled entry point starting with / (/entry-point.js vs ./entry-point.js)
    • The url must be an actual filename or a URL with the extension at the end
  • import.meta.url, import.meta.filename and import.meta.dirname reference "would be" (or "virtual") files, these are the URLs that would exist if vite kept the bundle on the file system. Doing something like readFileSync(import.meta.filename) will throw an error because the file does not exist on disk
    • For example for the build configuration like { ourDir: './dist' }, the filename would be something like <root>/dist/asset/chunk-1.js

To enable full bundle mode in SSR, add this to the config:

export default defineConfig({
  environments: {
    ssr: {
      isBundled: true,
    },
  },
})

@sheremet-va sheremet-va changed the title feat: support ful bundle mode in ssr feat: support full bundle mode in ssr Feb 12, 2026
@jamesopstad

Copy link
Copy Markdown
Contributor

Very excited to see the beginnings of this!

Comment thread packages/vite/src/node/server/environments/fullBundleEnvironment.ts Outdated
Comment thread packages/vite/src/module-runner/createImportMeta.ts Outdated
Comment thread packages/vite/src/module-runner/runner.ts
Comment thread packages/vite/src/module-runner/runner.ts Outdated
Comment thread packages/vite/src/module-runner/runner.ts
Comment thread packages/vite/src/node/config.ts Outdated
Comment thread packages/vite/src/node/ssr/fetchModule.ts Outdated
Comment thread packages/vite/src/node/ssr/runtime/__tests__/server-hmr.spec.ts Outdated
Comment thread packages/vite/src/node/ssr/runtime/serverModuleRunner.ts
// It should removed on rolldown side

// TODO: this crashes the server, not yet supported(?)
test.skip(`circular dependencies modules doesn't throw`, async () => {

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.

This breaks outside of try/catch in the server entry (valueA.concat is not defined)

Not sure why it happens in a separate tick 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure why it happens in a separate tick 🤔

This seems to be a bug in Rolldown. Made a test case in rolldown/rolldown#9975

@sheremet-va

Copy link
Copy Markdown
Member Author

I think playground/ssr should just have a separate config file for full bundle mode. I didn't change anything in the source there 🤔 Only one test if failing

Comment thread packages/vite/src/node/ssr/fetchModule.ts
import kill from 'kill-port'
import { createInMemoryLogger, hmrPorts, ports, rootDir } from '~utils'

export const port = ports.ssr

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should use a unique port here ports['ssr-bundled-dev']

if (!ssrResult) {
throw new Error(`[vite] cannot apply ssr transform to '${url}'.`)
}
result.code = ssrResult.code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, yeah, we need triggerLazyBundling to return a source map.

// It should removed on rolldown side

// TODO: this crashes the server, not yet supported(?)
test.skip(`circular dependencies modules doesn't throw`, async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure why it happens in a separate tick 🤔

This seems to be a bug in Rolldown. Made a test case in rolldown/rolldown#9975

graphite-app Bot pushed a commit to rolldown/rolldown that referenced this pull request Jun 26, 2026
This PR adds a failing test that covers a bug found in vitejs/vite#21626.

When lazy bundling is enabled, the following bug exists:

```javascript
try {
  await import('./foo.js'); // throws an error inside
} catch (e) {
  // the error is not caught here and a unhandled error happens
}
```

<!--
- What is this PR solving? Write a clear and concise description.
- Reference the issues it solves (e.g. `fixes #123`).
- What other alternatives have you explored?
- Are there any parts you think require more attention from reviewers?

Also, please make sure you do the following:

- Read the Contributing Guidelines at https://rolldown.rs/contribution-guide/.
- Check that there isn't already a PR that solves the problem the same way. If you find a duplicate, please help us review it.
- Update the corresponding documentation if needed.
- Include relevant tests that fail without this PR but pass with it. If the tests are not included, explain why.

Thank you for contributing to Rolldown!
-->
graphite-app Bot pushed a commit to rolldown/rolldown that referenced this pull request Jun 26, 2026
Builds on #9975 (which added the failing test). Fixes the bug found in vitejs/vite#21626.

## Problem

With lazy compilation enabled, an error thrown while a lazily-imported module *initializes* never reached the consumer:

```js
try {
  await import('./foo.js'); // throws during init
} catch (e) {
  // never runs — instead the error escaped as an unhandled rejection,
  // and `await import(...)` resolved as if nothing went wrong
}
```

The lazy proxy wraps the import in an eagerly-created `lazyExports` async IIFE. On the first compile of the lazy chunk (the on-demand `@vite/lazy` response), the real module's initializer runs synchronously inside that IIFE and throws, so `lazyExports` rejects before any consumer can attach a handler. The rejection went unhandled, and the proxy then returned the module namespace as if the import had succeeded.

## Fix

- **`proxy-module-template.js`**: after loading the chunk, `await` the real module's own `rolldown:exports` promise instead of just handing back the namespace from `loadExports`. An error during init now rejects `lazyExports` too, so it surfaces at the consumer's `await import(...)` (catchable) rather than escaping as an unhandled rejection.

## Tests

- Rewrote the `lazy-init-error` spec to assert both cases against the same module: with `try/catch` the error is caught at `await import(...)`; with no handler it surfaces as exactly one `unhandledrejection`.
@sapphi-red sapphi-red changed the title feat: support full bundle mode in ssr feat: support bundled dev mode in ssr Jun 29, 2026
}

function resolveBundledEntryFilename(
environment: BundledDev,

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.

Not environment anymore

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants