Repository navigation
Conversation
Summary: Part of a stack that replaces `server.enhanceMiddleware` with declarative dev server extension points. The previous change adds `server.unstable_middleware` and `server.unstable_priorityMiddleware`; this one adds their websocket counterpart, `server.unstable_websocketEndpoints`, so apps and config wrappers can register websocket endpoints in `metro.config.js`. ### Why **Deprecation** - The [2023 deprecation of `server.enhanceMiddleware`](22e85fd) argued that dev server extensions belong with the tools that start Metro, configured through `runServer`, rather than in Metro's config. As the previous change in this stack explains, that assumed whoever adds a handler controls the `runServer` call, which apps and config wrappers don't. The same holds for websocket endpoints, which so far could only be passed to `runServer`. **Current State** Two different parties add websocket endpoints to Metro's server, and each controls a different entry point: - Frameworks (RN Community CLI, Expo CLI) start the server, and pass the endpoints every app needs, such as React Native's debugger sockets, to `runServer`'s `websocketEndpoints` option. - Apps, and libraries that ship config wrappers, never call `runServer`. `metro.config.js` is all they control. An app composes the wrappers of the libraries it uses: ``` // my-lib/metro.js exports.withMyLib = config => ({ ...config, server: {...config.server /* , my-lib's handlers */}, }); // metro.config.js const {getDefaultConfig} = require('expo/metro-config'); const {withNativeWind} = require('nativewind/metro'); const {withMyLib} = require('my-lib/metro'); module.exports = withMyLib( withNativeWind(getDefaultConfig(__dirname), {input: './global.css'}), ); ``` - So Metro needs the option in both places, combined by Metro, so each party can add endpoints without knowing about the other. - `metro.config.js` has no websocket option, so today apps and wrappers have to patch the framework. For example, internally we patch Expo CLI's `instantiateMetro.js` (details under Internal below) so it serves endpoints from a config field: ``` if (metroConfig.server.unstable_websocketEndpoints) { Object.assign(websocketEndpoints, metroConfig.server.unstable_websocketEndpoints); } ``` - HTTP middleware and websocket endpoints are naturally added together (`react-native/dev-middleware`'s `createDevMiddleware` returns both). With the previous change, the HTTP half can come from config; this change adds the websocket half. **Why a map keyed by path** Endpoints are matched by exact path, so unlike middleware there is no run order to preserve: combining two sets of endpoints is a plain object merge, and `mergeConfig` does it for you. For the same path, the outer layer wins, as with middleware: `runServer`'s `websocketEndpoints` win over the config's, and a later config wins over an earlier one in `mergeConfig`. `/hot` is reserved for Metro. ``` config.server.unstable_websocketEndpoints = {...config.server.unstable_websocketEndpoints, '/my-ws': wss}; // same result: mergeConfig(config, {server: {unstable_websocketEndpoints: {'/my-ws': wss}}}); ``` **API** - `server.unstable_websocketEndpoints: {[path]: WebSocketServer}` (servers created with `noServer: true`), defaulting to `{}`. - `createConnectMiddleware`'s `attachHmrServer` serves the config's endpoints alongside Metro's `/hot`, for frameworks that build their own HTTP server on `createConnectMiddleware`. Expo CLI's `runServer` fork attaches `/hot` itself instead of calling `attachHmrServer`, so it still needs a change to pick them up. - `mergeConfig` combines `server.unstable_websocketEndpoints` from both configs instead of replacing them. For the same path, the later config's server wins. A config that leaves the field out, or sets it to `undefined`, keeps the other config's endpoints. - `runServer` merges its `websocketEndpoints` option into the config it passes to `createConnectMiddleware`, so `attachHmrServer` serves both. For the same path, `runServer`'s win over the config's. `createConnectMiddleware`'s API is unchanged. - `/hot` stays reserved for Metro's hot reloading. A supplied `/hot` is still ignored, as before, but Metro now warns about it instead of dropping it silently. - The field is excluded from config validation's recursive key check, so endpoint paths aren't reported as unknown options. - `runServer` calls `attachHmrServer` instead of attaching its own upgrade handler. The upgrade handling is factored into `attachWebsocketEndpoints`, which merges the endpoint maps by precedence: earlier maps win, with Metro's `/hot` first. Changelog: * **[Feature]:** Add `server.unstable_websocketEndpoints` config option to register websocket endpoints, served by `runServer` and by `createConnectMiddleware`'s `attachHmrServer`. `mergeConfig` combines it from both configs. * **[Feature]:** Metro warns when a supplied `/hot` websocket endpoint is ignored. Previously it was dropped silently. Differential Revision: D123479780
Contributor
|
@vzaidman has exported this pull request. If you are a Meta employee, you can view the originating Diff in D123479780. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Following #2031 that replaces
server.enhanceMiddlewarewith declarative dev server extension points. The previous change addsserver.unstable_middlewareandserver.unstable_priorityMiddleware; this one adds their websocket counterpart,server.unstable_websocketEndpointsfor completion, so apps and config wrappers can register websocket endpoints inmetro.config.js.Why
Deprecation
server.enhanceMiddlewareargued that dev server extensions belong with the tools that start Metro, configured throughrunServer, rather than in Metro's config. As the previous change in this stack explains, that assumed whoever adds a handler controls therunServercall, which apps and config wrappers don't. The same holds for websocket endpoints, which so far could only be passed torunServer.Current State
Two different parties add websocket endpoints to Metro's server, and each controls a different entry point:
runServer'swebsocketEndpointsoption.runServer.metro.config.jsis all they control. An app composes the wrappers of the libraries it uses:metro.config.jshas no websocket option, so today apps and wrappers have to patch the framework. For example, internally we patch Expo CLI'sinstantiateMetro.js(details under Internal below) so it serves endpoints from a config field:react-native/dev-middleware'screateDevMiddlewarereturns both). With the previous change, the HTTP half can come from config; this change adds the websocket half.Why a map keyed by path
Endpoints are matched by exact path, so unlike middleware there is no run order to preserve: combining two sets of endpoints is a plain object merge, and
mergeConfigdoes it for you. For the same path, the outer layer wins, as with middleware:runServer'swebsocketEndpointswin over the config's, and a later config wins over an earlier one inmergeConfig./hotis reserved for Metro.API
server.unstable_websocketEndpoints: {[path]: WebSocketServer}(servers created withnoServer: true), defaulting to{}.createConnectMiddleware'sattachHmrServerserves the config's endpoints alongside Metro's/hot, for frameworks that build their own HTTP server oncreateConnectMiddleware. Expo CLI'srunServerfork attaches/hotitself instead of callingattachHmrServer, so it still needs a change to pick them up.mergeConfigcombinesserver.unstable_websocketEndpointsfrom both configs instead of replacing them. For the same path, the later config's server wins. A config that leaves the field out, or sets it toundefined, keeps the other config's endpoints.runServermerges itswebsocketEndpointsoption into the config it passes tocreateConnectMiddleware, soattachHmrServerserves both. For the same path,runServer's win over the config's.createConnectMiddleware's API is unchanged./hotstays reserved for Metro's hot reloading. A supplied/hotis still ignored, as before, but Metro now warns about it instead of dropping it silently.runServercallsattachHmrServerinstead of attaching its own upgrade handler. The upgrade handling is factored intoattachWebsocketEndpoints, which merges the endpoint maps by precedence: earlier maps win, with Metro's/hotfirst.Changelog:
server.unstable_websocketEndpointsconfig option to register websocket endpoints, served byrunServerand bycreateConnectMiddleware'sattachHmrServer.mergeConfigcombines it from both configs./hotwebsocket endpoint is ignored. Previously it was dropped silently.Differential Revision: D123479780