Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/vite/rolldown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ const moduleRunnerConfig = defineConfig({
'@vitejs/devtools/cli-commands',
...Object.keys(pkg.dependencies),
],
plugins: [bundleSizeLimit(54), enableSourceMapsInWatchModePlugin()],
plugins: [bundleSizeLimit(55), enableSourceMapsInWatchModePlugin()],
output: {
...sharedNodeOptions.output,
minify: {
Expand Down
5 changes: 5 additions & 0 deletions packages/vite/src/module-runner/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ export const ssrDynamicImportKey = `__vite_ssr_dynamic_import__`
export const ssrExportAllKey = `__vite_ssr_exportAll__`
export const ssrExportNameKey = `__vite_ssr_exportName__`
export const ssrImportMetaKey = `__vite_ssr_import_meta__`

export const ssrRolldownRuntimeKey = `__rolldown_runtime__`
export const ssrRolldownRuntimeDefineMethod = `__vite_ssr_defineRuntime__`
export const ssrRolldownRuntimeCreateHotContextMethod = `__vite_ssr_createHotContext__`
export const ssrRolldownRuntimeTransport = `__vite_ssr_transport__`
11 changes: 6 additions & 5 deletions packages/vite/src/module-runner/createImportMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ const envProxy = new Proxy({} as any, {
export function createDefaultImportMeta(
modulePath: string,
): ModuleRunnerImportMeta {
const href = posixPathToFileHref(modulePath)
const filename = modulePath
const dirname = posixDirname(modulePath)
const isVirtual = modulePath.startsWith('data:application/javascript,')
Copy link
Member Author

Choose a reason for hiding this comment

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

This is what Node returns in import.meta.url if it runs in REPL or inside --import=data/application. I think we can reuse this idea

import.meta.dirname and filename are also not available there since files are virtual

But it will be quite inconvenient at runtime

Copy link
Member Author

Choose a reason for hiding this comment

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

Another idea is to use the file where it would be bundled. However then something like readFileSync(import.meta.filename) will throw an error

Copy link
Member Author

Choose a reason for hiding this comment

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

I decided to go with the second approach for now to avoid any breaking changes

const href = isVirtual ? modulePath : posixPathToFileHref(modulePath)
const filename = isVirtual ? undefined : modulePath
const dirname = isVirtual ? undefined : posixDirname(modulePath)
return {
filename: isWindows ? toWindowsPath(filename) : filename,
dirname: isWindows ? toWindowsPath(dirname) : dirname,
filename: isWindows && filename ? toWindowsPath(filename) : filename,
dirname: isWindows && dirname ? toWindowsPath(dirname) : dirname,
url: href,
env: envProxy,
resolve(_id: string, _parent?: string) {
Expand Down
3 changes: 3 additions & 0 deletions packages/vite/src/module-runner/esmEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
ssrRolldownRuntimeKey,
} from './constants'
import type { ModuleEvaluator, ModuleRunnerContext } from './types'

Expand All @@ -28,6 +29,7 @@ export class ESModulesEvaluator implements ModuleEvaluator {
ssrDynamicImportKey,
ssrExportAllKey,
ssrExportNameKey,
ssrRolldownRuntimeKey,
// source map should already be inlined by Vite
'"use strict";' + code,
)
Expand All @@ -39,6 +41,7 @@ export class ESModulesEvaluator implements ModuleEvaluator {
context[ssrDynamicImportKey],
context[ssrExportAllKey],
context[ssrExportNameKey],
context[ssrRolldownRuntimeKey],
)

Object.seal(context[ssrModuleExportsKey])
Expand Down
4 changes: 4 additions & 0 deletions packages/vite/src/module-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ export {
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
ssrRolldownRuntimeKey,
ssrRolldownRuntimeDefineMethod,
ssrRolldownRuntimeCreateHotContextMethod,
ssrRolldownRuntimeTransport,
} from './constants'
export type { InterceptorOptions } from './sourcemap/interceptor'
79 changes: 67 additions & 12 deletions packages/vite/src/module-runner/runner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ViteHotContext } from '#types/hot'
import type { DevRuntime } from 'rolldown/experimental/runtime-types'
import { HMRClient, HMRContext, type HMRLogger } from '../shared/hmr'
import { cleanUrl, isPrimitive } from '../shared/utils'
import { analyzeImportedModDifference } from '../shared/ssrTransform'
Expand All @@ -16,14 +16,18 @@ import type {
ResolvedResult,
SSRImportMetadata,
} from './types'
import { posixDirname, posixPathToFileHref, posixResolve } from './utils'
import { posixDirname, posixJoin } from './utils'
import {
ssrDynamicImportKey,
ssrExportAllKey,
ssrExportNameKey,
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
ssrRolldownRuntimeCreateHotContextMethod,
ssrRolldownRuntimeDefineMethod,
ssrRolldownRuntimeKey,
ssrRolldownRuntimeTransport,
} from './constants'
import { hmrLogger, silentConsole } from './hmrLogger'
import { createHMRHandlerForRunner } from './hmrHandler'
Expand All @@ -47,6 +51,42 @@ export class ModuleRunner {
>()
private isBuiltin?: (id: string) => boolean
private builtinsPromise?: Promise<void>
private rolldownDevRuntime?: DevRuntime

// We need the proxy because the runtime MUST be ready before the first import is processed.
// Because `context['__rolldown_runtime__']` is passed down before the modules are executed as a function argument.
private rolldownDevRuntimeProxy = new Proxy(
{},
{
get: (_, p, receiver) => {
// Special `__rolldown_runtime__.__vite_ssr_defineRuntime__` method only for the module runner,
// It's not available in the browser because it's a global there. We cannot have it as a global because
// - It's possible to have multiple runners (`ssrLoadModule` has its own compat runner);
// - We don't want to pollute Dev Server's global namespace.
if (p === ssrRolldownRuntimeDefineMethod) {
return (runtime: DevRuntime) => {
this.rolldownDevRuntime = runtime
}
}

if (p === ssrRolldownRuntimeCreateHotContextMethod) {
return this.closed
? () => {}
: (url: string) => this.ensureModuleHotContext(url)
}

if (p === ssrRolldownRuntimeTransport) {
return this.closed ? undefined : this.transport
}

if (!this.rolldownDevRuntime) {
throw new Error(`__rolldown_runtime__ was not initialized.`)
}

return Reflect.get(this.rolldownDevRuntime, p, receiver)
},
},
) as DevRuntime

private closed = false

Expand Down Expand Up @@ -89,7 +129,7 @@ export class ModuleRunner {
*/
public async import<T = any>(url: string): Promise<T> {
const fetchedModule = await this.cachedModule(url)
return await this.cachedRequest(url, fetchedModule)
return await this.cachedRequest(fetchedModule.url, fetchedModule)
}

/**
Expand Down Expand Up @@ -353,7 +393,7 @@ export class ModuleRunner {
// it's possible to provide an object with toString() method inside import()
dep = String(dep)
if (dep[0] === '.') {
dep = posixResolve(posixDirname(url), dep)
dep = posixJoin(posixDirname(url), dep)
Copy link
Member Author

@sheremet-va sheremet-va Feb 13, 2026

Choose a reason for hiding this comment

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

resolve always adds process.cwd() at the start, but we are resolving ./file1.js relative to asset/parent.js

}
return request(dep, { isDynamicImport: true })
}
Expand All @@ -380,9 +420,10 @@ export class ModuleRunner {
const createImportMeta =
this.options.createImportMeta ?? createDefaultImportMeta

const modulePath = cleanUrl(file || moduleId)
const modulePath = file
? cleanUrl(file)
: `data:application/javascript,${code};`
// disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710
const href = posixPathToFileHref(modulePath)
const meta = await createImportMeta(modulePath)
const exports = Object.create(null)
Object.defineProperty(exports, Symbol.toStringTag, {
Expand All @@ -393,20 +434,19 @@ export class ModuleRunner {

mod.exports = exports

let hotContext: ViteHotContext | undefined
if (this.hmrClient) {
Object.defineProperty(meta, 'hot', {
enumerable: true,
get: () => {
if (!this.hmrClient) {
throw new Error(`[module runner] HMR client was closed.`)
return
}
this.debug?.('[module runner] creating hmr context for', mod.url)
hotContext ||= new HMRContext(this.hmrClient, mod.url)
return hotContext
this.ensureModuleHotContext(mod.url)
return this.moduleHotContexts.get(mod.url)
},
set: (value) => {
hotContext = value
this.moduleHotContexts.set(mod.url, value)
},
})
}
Expand All @@ -423,14 +463,29 @@ export class ModuleRunner {
get: getter,
}),
[ssrImportMetaKey]: meta,
[ssrRolldownRuntimeKey]: this.rolldownDevRuntimeProxy,
}

this.debug?.('[module runner] executing', href)
this.debug?.('[module runner] executing', meta.href)

await this.evaluator.runInlinedModule(context, code, mod)

return exports
}

private moduleHotContexts = new Map<string, HMRContext>()

private ensureModuleHotContext(url: string) {
if (!this.hmrClient) {
return
}

if (!this.moduleHotContexts.has(url)) {
const hotContext = new HMRContext(this.hmrClient, url)
this.moduleHotContexts.set(url, hotContext)
}
return this.moduleHotContexts.get(url)
}
}

function exportAll(exports: any, sourceModule: any) {
Expand Down
6 changes: 6 additions & 0 deletions packages/vite/src/module-runner/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DevRuntime } from 'rolldown/experimental/runtime-types'
import type { ViteHotContext } from '#types/hot'
import type { HMRLogger } from '../shared/hmr'
import type {
Expand All @@ -19,6 +20,7 @@ import type {
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
ssrRolldownRuntimeKey,
} from './constants'
import type { InterceptorOptions } from './sourcemap/interceptor'

Expand All @@ -41,6 +43,10 @@ export interface ModuleRunnerContext {
[ssrExportAllKey]: (obj: any) => void
[ssrExportNameKey]: (name: string, getter: () => unknown) => void
[ssrImportMetaKey]: ModuleRunnerImportMeta
/**
* @internal
*/
[ssrRolldownRuntimeKey]: DevRuntime | undefined
}

export interface ModuleEvaluator {
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/module-runner/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function encodePathChars(filepath: string) {

export const posixDirname: (path: string) => string = pathe.dirname
export const posixResolve: (...paths: string[]) => string = pathe.resolve
export const posixJoin: (...paths: string[]) => string = pathe.join

export function posixPathToFileHref(posixPath: string): string {
let resolved = posixResolve(posixPath)
Expand Down
26 changes: 24 additions & 2 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ import {
} from './server/pluginContainer'
import { nodeResolveWithVite } from './nodeResolve'
import { FullBundleDevEnvironment } from './server/environments/fullBundleEnvironment'
import { createFullBundleRunnableDevEnvironment } from './server/environments/fullBundleRunnableEnvironment'

const debug = createDebugger('vite:config', { depth: 10 })
const promisifiedRealpath = promisify(fs.realpath)
Expand Down Expand Up @@ -257,6 +258,13 @@ function defaultCreateClientDevEnvironment(
})
}

function defaultCreateSSRDevEnvironment(name: string, config: ResolvedConfig) {
if (config.experimental.ssrBundledDev) {
return createFullBundleRunnableDevEnvironment(name, config)
}
return createRunnableDevEnvironment(name, config)
}

function defaultCreateDevEnvironment(name: string, config: ResolvedConfig) {
return createRunnableDevEnvironment(name, config)
}
Expand Down Expand Up @@ -585,6 +593,14 @@ export interface ExperimentalOptions {
* @default false
*/
bundledDev?: boolean
/**
* Enable full bundle mode in SSR.
*
* This is highly experimental.
*
* @experimental
*/
ssrBundledDev?: boolean
}

export interface LegacyOptions {
Expand Down Expand Up @@ -835,6 +851,7 @@ const configDefaults = Object.freeze({
hmrPartialAccept: false,
enableNativePlugin: process.env._VITE_TEST_JS_PLUGIN ? false : 'v2',
bundledDev: false,
ssrBundledDev: false,
},
future: {
removePluginHookHandleHotUpdate: undefined,
Expand Down Expand Up @@ -897,7 +914,9 @@ export function resolveDevEnvironmentOptions(
createEnvironment:
environmentName === 'client'
? defaultCreateClientDevEnvironment
: defaultCreateDevEnvironment,
: environmentName === 'ssr'
? defaultCreateSSRDevEnvironment
: defaultCreateDevEnvironment,
recoverable: consumer === 'client',
moduleRunnerTransform: consumer === 'server',
},
Expand Down Expand Up @@ -1888,7 +1907,10 @@ export async function resolveConfig(
cacheDir,
command,
mode,
isBundled: config.experimental?.bundledDev || isBuild,
isBundled:
config.experimental?.bundledDev ||
config.experimental?.ssrBundledDev ||
isBuild, // TODO: ssr shouldn't break client
isWorker: false,
mainConfig: null,
bundleChain: [],
Expand Down
5 changes: 5 additions & 0 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export {
type RunnableDevEnvironment,
type RunnableDevEnvironmentContext,
} from './server/environments/runnableEnvironment'
export {
createFullBundleRunnableDevEnvironment,
isFullBundleRunnableDevEnvironment,
type FullBundleRunnableDevEnvironment,
} from './server/environments/fullBundleRunnableEnvironment'
export {
createFetchableDevEnvironment,
isFetchableDevEnvironment,
Expand Down
Loading
Loading