Skip to content

Commit c5b7191

Browse files
authored
feat(experimental): add fetchable environment interface (#19664)
1 parent 04d58b4 commit c5b7191

File tree

5 files changed

+121
-2
lines changed

5 files changed

+121
-2
lines changed

docs/guide/api-environment-frameworks.md

+42-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,48 @@ if (isRunnableDevEnvironment(server.environments.ssr)) {
3838
```
3939

4040
:::warning
41-
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.
41+
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.
42+
:::
43+
44+
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:
45+
46+
```ts
47+
import {
48+
createServer,
49+
createFetchableDevEnvironment,
50+
isFetchableDevEnvironment,
51+
} from 'vite'
52+
53+
const server = await createServer({
54+
server: { middlewareMode: true },
55+
appType: 'custom',
56+
environments: {
57+
custom: {
58+
dev: {
59+
createEnvironment(name, config) {
60+
return createFetchableDevEnvironment(name, config, {
61+
handleRequest(request: Request): Promise<Response> | Response {
62+
// handle Request and return a Response
63+
},
64+
})
65+
},
66+
},
67+
},
68+
},
69+
})
70+
71+
// Any consumer of the environment API can now call `dispatchFetch`
72+
if (isFetchableDevEnvironment(server.environments.custom)) {
73+
const response: Response = await server.environments.custom.dispatchFetch(
74+
new Request('/request-to-handle'),
75+
)
76+
}
77+
```
78+
79+
:::warning
80+
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.
81+
82+
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.
4283
:::
4384

4485
## Default `RunnableDevEnvironment`

eslint.config.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ export default tseslint.config(
6363
'n/no-exports-assign': 'error',
6464
'n/no-unpublished-bin': 'error',
6565
'n/no-unsupported-features/es-builtins': 'error',
66-
'n/no-unsupported-features/node-builtins': 'error',
66+
'n/no-unsupported-features/node-builtins': [
67+
'error',
68+
{
69+
// TODO: remove this when we don't support Node 18 anymore
70+
ignores: ['Response', 'Request', 'fetch'],
71+
},
72+
],
6773
'n/process-exit-as-throw': 'error',
6874
'n/hashbang': 'error',
6975

packages/vite/index.cjs

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const disallowedVariables = [
4848
'createServerModuleRunner',
4949
'createServerModuleRunnerTransport',
5050
'isRunnableDevEnvironment',
51+
'createFetchableDevEnvironment',
52+
'isFetchableDevEnvironment',
5153
]
5254
disallowedVariables.forEach((name) => {
5355
Object.defineProperty(module.exports, name, {

packages/vite/src/node/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export {
2727
type RunnableDevEnvironment,
2828
type RunnableDevEnvironmentContext,
2929
} from './server/environments/runnableEnvironment'
30+
export {
31+
createFetchableDevEnvironment,
32+
isFetchableDevEnvironment,
33+
type FetchableDevEnvironment,
34+
type FetchableDevEnvironmentContext,
35+
} from './server/environments/fetchableEnvironments'
3036
export {
3137
DevEnvironment,
3238
type DevEnvironmentContext,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { ResolvedConfig } from '../../config'
2+
import type { DevEnvironmentContext } from '../environment'
3+
import { DevEnvironment } from '../environment'
4+
import type { Environment } from '../../environment'
5+
6+
export interface FetchableDevEnvironmentContext extends DevEnvironmentContext {
7+
handleRequest(request: Request): Promise<Response> | Response
8+
}
9+
10+
export function createFetchableDevEnvironment(
11+
name: string,
12+
config: ResolvedConfig,
13+
context: FetchableDevEnvironmentContext,
14+
): FetchableDevEnvironment {
15+
if (typeof Request === 'undefined' || typeof Response === 'undefined') {
16+
throw new TypeError(
17+
'FetchableDevEnvironment requires a global `Request` and `Response` object.',
18+
)
19+
}
20+
21+
if (!context.handleRequest) {
22+
throw new TypeError(
23+
'FetchableDevEnvironment requires a `handleRequest` method during initialisation.',
24+
)
25+
}
26+
27+
return new FetchableDevEnvironment(name, config, context)
28+
}
29+
30+
export function isFetchableDevEnvironment(
31+
environment: Environment,
32+
): environment is FetchableDevEnvironment {
33+
return environment instanceof FetchableDevEnvironment
34+
}
35+
36+
class FetchableDevEnvironment extends DevEnvironment {
37+
private _handleRequest: (request: Request) => Promise<Response> | Response
38+
39+
constructor(
40+
name: string,
41+
config: ResolvedConfig,
42+
context: FetchableDevEnvironmentContext,
43+
) {
44+
super(name, config, context)
45+
this._handleRequest = context.handleRequest
46+
}
47+
48+
public async dispatchFetch(request: Request): Promise<Response> {
49+
if (!(request instanceof Request)) {
50+
throw new TypeError(
51+
'FetchableDevEnvironment `dispatchFetch` must receive a `Request` object.',
52+
)
53+
}
54+
const response = await this._handleRequest(request)
55+
if (!(response instanceof Response)) {
56+
throw new TypeError(
57+
'FetchableDevEnvironment `context.handleRequest` must return a `Response` object.',
58+
)
59+
}
60+
return response
61+
}
62+
}
63+
64+
export type { FetchableDevEnvironment }

0 commit comments

Comments
 (0)