Skip to content

Commit

Permalink
feat: support transport options to communicate between the environmen…
Browse files Browse the repository at this point in the history
…t and the runner (#16209)
  • Loading branch information
sheremet-va authored Mar 22, 2024
1 parent d9ed857 commit dbcc375
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 68 deletions.
2 changes: 1 addition & 1 deletion packages/vite/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function createModuleRunnerConfig(isProduction: boolean) {
isProduction ? false : './dist/node',
),
esbuildMinifyPlugin({ minify: false, minifySyntax: true }),
bundleSizeLimit(45),
bundleSizeLimit(46),
],
})
}
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/module-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
export { ModuleCacheMap } from './moduleCache'
export { ModuleRunner } from './runner'
export { ESModulesEvaluator } from './esmEvaluator'
export { RemoteRunnerTransport } from './transport'

export type { RunnerTransport } from './transport'
export type { HMRLogger, HMRConnection } from '../shared/hmr'
export type {
ModuleEvaluator,
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/module-runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export class ModuleRunner {
// fast return for established externalized patterns
const fetchedModule = id.startsWith('data:')
? ({ externalize: id, type: 'builtin' } satisfies FetchResult)
: await this.options.fetchModule(id, importer)
: await this.options.transport.fetchModule(id, importer)
// base moduleId on "file" and not on id
// if `import(variable)` is called it's possible that it doesn't have an extension for example
// if we used id for that, it's possible to have a duplicated module
Expand Down
84 changes: 84 additions & 0 deletions packages/vite/src/module-runner/transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { FetchFunction, FetchResult } from './types'

export interface RunnerTransport {
fetchModule: FetchFunction
}

// TODO: tests
export class RemoteRunnerTransport implements RunnerTransport {
private rpcPromises = new Map<
string,
{
resolve: (data: any) => void
reject: (data: any) => void
timeoutId?: NodeJS.Timeout
}
>()

constructor(
private readonly options: {
send: (data: any) => void
onMessage: (handler: (data: any) => void) => void
timeout?: number
},
) {
this.options.onMessage(async (data) => {
if (typeof data !== 'object' || !data || !data._v) return

const promise = this.rpcPromises.get(data.i)
if (!promise) return

if (promise.timeoutId) clearTimeout(promise.timeoutId)

this.rpcPromises.delete(data.i)

if (data.e) {
promise.reject(data.e)
} else {
promise.resolve(data.r)
}
})
}

private resolve<T>(method: string, ...args: any[]) {
const promiseId = nanoid()
this.options.send({
_v: true,
m: method,
a: args,
i: promiseId,
})

return new Promise<T>((resolve, reject) => {
const timeout = this.options.timeout ?? 60000
let timeoutId
if (timeout > 0) {
timeoutId = setTimeout(() => {
this.rpcPromises.delete(promiseId)
reject(
new Error(
`${method}(${args.map((arg) => JSON.stringify(arg)).join(', ')}) timed out after ${timeout}ms`,
),
)
}, timeout)
timeoutId?.unref?.()
}
this.rpcPromises.set(promiseId, { resolve, reject, timeoutId })
})
}

fetchModule(id: string, importer?: string): Promise<FetchResult> {
return this.resolve<FetchResult>('fetchModule', id, importer)
}
}

// port from nanoid
// https://github.com/ai/nanoid
const urlAlphabet =
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
function nanoid(size = 21) {
let id = ''
let i = size
while (i--) id += urlAlphabet[(Math.random() * 64) | 0]
return id
}
7 changes: 3 additions & 4 deletions packages/vite/src/module-runner/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
} from './constants'
import type { DecodedMap } from './sourcemap/decoder'
import type { InterceptorOptions } from './sourcemap/interceptor'
import type { RunnerTransport } from './transport'

export type { DefineImportMetadata, SSRImportBaseMetadata as SSRImportMetadata }

Expand Down Expand Up @@ -123,11 +124,9 @@ export interface ModuleRunnerOptions {
*/
root: string
/**
* A method to get the information about the module.
* For SSR, Vite exposes `server.ssrFetchModule` function that you can use here.
* For other runner use cases, Vite also exposes `fetchModule` from its main entry point.
* A set of methods to communicate with the server.
*/
fetchModule: FetchFunction
transport: RunnerTransport
/**
* Configure how source maps are resolved. Prefers `node` if `process.setSourceMapsEnabled` is available.
* Otherwise it will use `prepareStackTrace` by default which overrides `Error.prepareStackTrace` method.
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export type {
EnvironmentModuleNode,
ResolvedUrl,
} from './server/moduleGraph'
export { RemoteEnvironmentTransport } from './server/environmentTransport'
export type { SendOptions } from './server/send'
export type { ProxyOptions } from './server/middlewares/proxy'
export type {
Expand All @@ -133,7 +134,7 @@ export type {
HMRBroadcasterClient,
} from './server/hmr'

export type { FetchFunction } from '../module-runner/index'
export type { FetchFunction, FetchResult } from 'vite/module-runner'
export { createServerModuleRunner } from './ssr/runtime/serverModuleRunner'
export type { ServerModuleRunnerOptions } from './ssr/runtime/serverModuleRunner'
export { ServerHMRConnector } from './ssr/runtime/serverHmrConnector'
Expand Down
21 changes: 20 additions & 1 deletion packages/vite/src/node/server/environment.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import type { FetchResult } from 'vite/module-runner'
import { Environment } from '../environment'
import type { ViteDevServer } from '../server'
import { ERR_OUTDATED_OPTIMIZED_DEP } from '../plugins/optimizedDeps'
import type { DevEnvironmentConfig } from '../config'
import { getDefaultResolvedDevEnvironmentConfig } from '../config'
import { mergeConfig } from '../utils'
import type { FetchModuleOptions } from '../ssr/fetchModule'
import { fetchModule } from '../ssr/fetchModule'
import { EnvironmentModuleGraph } from './moduleGraph'
import type { HMRChannel } from './hmr'
import { createNoopHMRChannel } from './hmr'
import { transformRequest } from './transformRequest'
import type { TransformResult } from './transformRequest'
import { ERR_CLOSED_SERVER } from './pluginContainer'
import type { RemoteEnvironmentTransport } from './environmentTransport'

// Maybe we will rename this to DevEnvironment
export class DevEnvironment extends Environment {
mode = 'dev' as const // TODO: should this be 'serve'?
moduleGraph: EnvironmentModuleGraph
server: ViteDevServer
config: DevEnvironmentConfig
/**
* @internal
*/
_ssrRunnerOptions: FetchModuleOptions | undefined
/**
* HMR channel for this environment. If not provided or disabled,
* it will be a noop channel that does nothing.
Expand All @@ -29,9 +37,11 @@ export class DevEnvironment extends Environment {
server: ViteDevServer,
name: string,
options?: {
// TODO: use `transport` instead to support any hmr channel?
hot?: false | HMRChannel
config?: DevEnvironmentConfig
runner?: FetchModuleOptions & {
transport?: RemoteEnvironmentTransport
}
},
) {
super(name)
Expand All @@ -42,12 +52,21 @@ export class DevEnvironment extends Environment {
}),
)
this.hot = options?.hot || createNoopHMRChannel()

this.config =
server.config.environments[name] ??
getDefaultResolvedDevEnvironmentConfig(server.config)
if (options?.config) {
this.config = mergeConfig(this.config, options?.config)
}

const ssrRunnerOptions = options?.runner || {}
this._ssrRunnerOptions = ssrRunnerOptions
options?.runner?.transport?.initialize(this)
}

fetchModule(id: string, importer?: string): Promise<FetchResult> {
return fetchModule(this, id, importer, this._ssrRunnerOptions)
}

transformRequest(url: string): Promise<TransformResult | null> {
Expand Down
38 changes: 38 additions & 0 deletions packages/vite/src/node/server/environmentTransport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { DevEnvironment } from './environment'

export class RemoteEnvironmentTransport {
constructor(
private readonly options: {
send: (data: any) => void
onMessage: (handler: (data: any) => void) => void
},
) {}

initialize(environment: DevEnvironment): void {
this.options.onMessage(async (data) => {
if (typeof data !== 'object' || !data || !data.__v) return

const method = data.m as 'fetchModule'
const parameters = data.a as [string, string]

try {
const result = await environment[method](...parameters)
this.options.send({
__v: true,
r: result,
i: data.i,
})
} catch (error) {
this.options.send({
__v: true,
e: {
name: error.name,
message: error.message,
stack: error.stack,
},
i: data.i,
})
}
})
}
}
23 changes: 23 additions & 0 deletions packages/vite/src/node/server/environments/ssrEnvironment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DevEnvironment } from '../environment'
import type { ServerHMRChannel } from '../hmr'
import type { ViteDevServer } from '../index'
import { asyncFunctionDeclarationPaddingLineCount } from '../../../shared/utils'

export function createSsrEnvironment(
server: ViteDevServer,
name: string,
hotChannel: ServerHMRChannel,
): DevEnvironment {
return new DevEnvironment(server, name, {
hot: hotChannel,
runner: {
processSourceMap(map) {
// this assumes that "new AsyncFunction" is used to create the module
return Object.assign({}, map, {
mappings:
';'.repeat(asyncFunctionDeclarationPaddingLineCount) + map.mappings,
})
},
},
})
}
42 changes: 25 additions & 17 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import launchEditorMiddleware from 'launch-editor-middleware'
import type { SourceMap } from 'rollup'
import picomatch from 'picomatch'
import type { Matcher } from 'picomatch'
import type { ModuleRunner } from 'vite/module-runner'
import type { CommonServerOptions } from '../http'
import {
httpServerStart,
Expand Down Expand Up @@ -50,8 +51,7 @@ import { printServerUrls } from '../logger'
import { createNoopWatcher, resolveChokidarOptions } from '../watch'
import { initPublicFiles } from '../publicDir'
import { getEnvFilesForMode } from '../env'
import type { FetchResult } from '../../module-runner/types'
import { ssrFetchModule } from '../ssr/ssrFetchModule'
import { createServerModuleRunner } from '../ssr/runtime/serverModuleRunner'
import type { PluginContainer } from './pluginContainer'
import { ERR_CLOSED_SERVER, createPluginContainer } from './pluginContainer'
import type { WebSocketServer } from './ws'
Expand Down Expand Up @@ -93,6 +93,7 @@ import { transformRequest } from './transformRequest'
import { searchForWorkspaceRoot } from './searchRoot'
import { warmupFiles } from './warmup'
import { DevEnvironment } from './environment'
import { createSsrEnvironment } from './environments/ssrEnvironment'

export interface ServerOptions extends CommonServerOptions {
/**
Expand Down Expand Up @@ -264,9 +265,13 @@ export interface ViteDevServer {
*/
pluginContainer: PluginContainer
/**
* Dev Environments. Module execution environments attached to the Vite server.
* Module execution environments attached to the Vite server.
*/
environments: Record<string, DevEnvironment>
/**
* Default SSR module runner.
*/
nodeModuleRunner: ModuleRunner
/**
* Module graph that tracks the import relationships, url to file mapping
* and hmr state.
Expand Down Expand Up @@ -318,11 +323,6 @@ export interface ViteDevServer {
url: string,
opts?: { fixStacktrace?: boolean },
): Promise<Record<string, any>>
/**
* Fetch information about the module for Vite SSR runtime.
* @experimental
*/
ssrFetchModule(id: string, importer?: string): Promise<FetchResult>
/**
* Returns a fixed version of the given stack
*/
Expand Down Expand Up @@ -528,6 +528,8 @@ export async function _createServer(
onCrawlEndCallbacks.push(cb)
}

let nodeModuleRunner: ModuleRunner | undefined

let server: ViteDevServer = {
config,
middlewares,
Expand All @@ -540,6 +542,16 @@ export async function _createServer(
pluginContainer,
moduleGraph,

get nodeModuleRunner() {
if (!nodeModuleRunner) {
nodeModuleRunner = createServerModuleRunner(server.environments.ssr)
}
return nodeModuleRunner
},
set nodeModuleRunner(runner) {
nodeModuleRunner = runner
},

resolvedUrls: null, // will be set on listen
ssrTransform(
code: string,
Expand Down Expand Up @@ -589,9 +601,6 @@ export async function _createServer(
opts?.fixStacktrace,
)
},
async ssrFetchModule(url: string, importer?: string) {
return ssrFetchModule(server, url, importer)
},
ssrFixStacktrace(e) {
ssrFixStacktrace(e, server.moduleGraph)
},
Expand Down Expand Up @@ -774,20 +783,19 @@ export async function _createServer(

// Create Environments

const createClientEnvironment =
const client_createEnvironment =
config.environments.client?.dev?.createEnvironment ??
((server: ViteDevServer, name: string) =>
new DevEnvironment(server, name, { hot: ws }))

environments.client = createClientEnvironment(server, 'client')
environments.client = client_createEnvironment(server, 'client')

const createSsrEnvironment =
/* config.ssr?.dev?.createEnvironment ?? */
const ssr_createEnvironment =
config.environments.ssr?.dev?.createEnvironment ??
((server: ViteDevServer, name: string) =>
new DevEnvironment(server, name, { hot: ssrHotChannel }))
createSsrEnvironment(server, name, ssrHotChannel))

environments.ssr = createSsrEnvironment(server, 'ssr')
environments.ssr = ssr_createEnvironment(server, 'ssr')

Object.entries(config.environments).forEach(([name, environmentConfig]) => {
// TODO: move client and ssr inside the loop?
Expand Down
Loading

0 comments on commit dbcc375

Please sign in to comment.