Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 42 additions & 1 deletion docs/guide/api-environment-frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,48 @@ if (isRunnableDevEnvironment(server.environments.ssr)) {
```

:::warning
The `runner` is evaluated eagerly when it's accessed for the first time. Beware that Vite enables source map support when the `runner` is created by calling `process.setSourceMapsEnabled` or by overriding `Error.prepareStackTrace` if it's not available.
The `runner` is evaluated lazily only when it's accessed for the first time. Beware that Vite enables source map support when the `runner` is created by calling `process.setSourceMapsEnabled` or by overriding `Error.prepareStackTrace` if it's not available.
:::

Frameworks that communicate with their runtime via the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) can utilize the `FetchableDevEnvironment` that provides a standardized way of handling requests via the `handleRequest` method:

```ts
import {
createServer,
createFetchableDevEnvironment,
isFetchableDevEnvironment,
} from 'vite'

const server = await createServer({
server: { middlewareMode: true },
appType: 'custom',
environments: {
custom: {
dev: {
createEnvironment(name, config) {
return createFetchableDevEnvironment(name, config, {
handleRequest(request: Request): Promise<Response> | Response {
// handle Request and return a Response
},
})
},
},
},
},
})

// Any consumer of the environment API can now call `dispatchFetch`
if (isFetchableDevEnvironment(server.environments.custom)) {
const response: Response = await server.environments.custom.dispatchFetch(
new Request('/request-to-handle'),
)
}
```

:::warning
Vite validates the input and output of the `dispatchFetch` method: the request must be an instance of the global `Request` class and the response must be the instance of the global `Response` class. Vite will throw a `TypeError` if this is not the case.

Note that although the `FetchableDevEnvironment` is implemented as a class, it is considered an implementation detail by the Vite team and might change at any moment.
:::

## Default `RunnableDevEnvironment`
Expand Down
8 changes: 7 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ export default tseslint.config(
'n/no-exports-assign': 'error',
'n/no-unpublished-bin': 'error',
'n/no-unsupported-features/es-builtins': 'error',
'n/no-unsupported-features/node-builtins': 'error',
'n/no-unsupported-features/node-builtins': [
'error',
{
// TODO: remove this when we don't support Node 18 anymore
ignores: ['Response', 'Request', 'fetch'],
},
],
'n/process-exit-as-throw': 'error',
'n/hashbang': 'error',

Expand Down
2 changes: 2 additions & 0 deletions packages/vite/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const disallowedVariables = [
'createServerModuleRunner',
'createServerModuleRunnerTransport',
'isRunnableDevEnvironment',
'createFetchableDevEnvironment',
'isFetchableDevEnvironment',
]
disallowedVariables.forEach((name) => {
Object.defineProperty(module.exports, name, {
Expand Down
6 changes: 6 additions & 0 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export {
type RunnableDevEnvironment,
type RunnableDevEnvironmentContext,
} from './server/environments/runnableEnvironment'
export {
createFetchableDevEnvironment,
isFetchableDevEnvironment,
type FetchableDevEnvironment,
type FetchableDevEnvironmentContext,
} from './server/environments/fetchableEnvironments'
export {
DevEnvironment,
type DevEnvironmentContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { ResolvedConfig } from '../../config'
import type { DevEnvironmentContext } from '../environment'
import { DevEnvironment } from '../environment'
import type { Environment } from '../../environment'

export interface FetchableDevEnvironmentContext extends DevEnvironmentContext {
handleRequest(request: Request): Promise<Response> | Response
}

export function createFetchableDevEnvironment(
name: string,
config: ResolvedConfig,
context: FetchableDevEnvironmentContext,
): FetchableDevEnvironment {
if (typeof Request === 'undefined' || typeof Response === 'undefined') {
throw new TypeError(
'FetchableDevEnvironment requires a global `Request` and `Response` object.',
)
}

if (!context.handleRequest) {
throw new TypeError(
'FetchableDevEnvironment requires a `handleRequest` method during initialisation.',
)
}

return new FetchableDevEnvironment(name, config, context)
}

export function isFetchableDevEnvironment(
environment: Environment,
): environment is FetchableDevEnvironment {
return environment instanceof FetchableDevEnvironment
}

class FetchableDevEnvironment extends DevEnvironment {
private _handleRequest: (request: Request) => Promise<Response> | Response

constructor(
name: string,
config: ResolvedConfig,
context: FetchableDevEnvironmentContext,
) {
super(name, config, context)
this._handleRequest = context.handleRequest
}

public async dispatchFetch(request: Request): Promise<Response> {
if (!(request instanceof Request)) {
throw new TypeError(
'FetchableDevEnvironment `dispatchFetch` must receive a `Request` object.',
)
}
const response = await this._handleRequest(request)
if (!(response instanceof Response)) {
throw new TypeError(
'FetchableDevEnvironment `context.handleRequest` must return a `Response` object.',
)
}
return response
}
}

export type { FetchableDevEnvironment }