-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cli][dev-menu][go] add react devtools (#21462)
close ENG-7468 close ENG-7469 - [cli] add websocket proxy to forward react-devtools events. - [cli] add static page for react-devtools frontend. since react-devtools only ships commonjs format, this pr tries to use jspm to support it on browsers. - [dev-menu][go] listen `reconnectDevTools` metro websocket event and send `RCTDevMenuShown` to js for app to reconnect devtools websocket manual test only. please let me know if there's any proper points to add unit tests (cherry picked from commit fd05555)
- Loading branch information
Showing
15 changed files
with
344 additions
and
6 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import assert from 'assert'; | ||
import { EventEmitter } from 'events'; | ||
import WebSocket from 'ws'; | ||
|
||
let serverInstance: WebSocket.WebSocketServer | null = null; | ||
|
||
const eventEmitter = new EventEmitter(); | ||
|
||
/** | ||
* Private command to support DevTools frontend reload. | ||
* | ||
* The react-devtools maintains state between frontend(webpage) and backend(app). | ||
* If we reload the frontend without reloading the app, the react-devtools will stuck on incorrect state. | ||
* We introduce this special reload command. | ||
* As long as the frontend reload, we will close app's WebSocket connection and tell app to reconnect again. | ||
*/ | ||
const RELOAD_COMMAND = 'Expo::RELOAD'; | ||
|
||
/** | ||
* Start the react-devtools WebSocket proxy server | ||
*/ | ||
export async function startReactDevToolsProxyAsync(options?: { port: number }) { | ||
if (serverInstance != null) { | ||
return; | ||
} | ||
|
||
serverInstance = new WebSocket.WebSocketServer({ port: options?.port ?? 8097 }); | ||
|
||
serverInstance.on('connection', function connection(ws) { | ||
ws.on('message', function message(rawData, isBinary) { | ||
assert(!isBinary); | ||
const data = rawData.toString(); | ||
|
||
if (data === RELOAD_COMMAND) { | ||
closeAllOtherClients(ws); | ||
eventEmitter.emit(RELOAD_COMMAND); | ||
return; | ||
} | ||
|
||
serverInstance?.clients.forEach(function each(client) { | ||
if (client !== ws && client.readyState === WebSocket.OPEN) { | ||
client.send(data, { binary: isBinary }); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
serverInstance.on('close', function () { | ||
serverInstance = null; | ||
}); | ||
} | ||
|
||
/** | ||
* Close the WebSocket server | ||
*/ | ||
export function closeReactDevToolsProxy() { | ||
serverInstance?.close(); | ||
serverInstance = null; | ||
} | ||
|
||
/** | ||
* add event listener from react-devtools frontend reload | ||
*/ | ||
export function addReactDevToolsReloadListener(listener: (...args: any[]) => void) { | ||
eventEmitter.addListener(RELOAD_COMMAND, listener); | ||
} | ||
|
||
/** | ||
* Close all other WebSocket clients other than the current `self` client | ||
*/ | ||
function closeAllOtherClients(self: WebSocket.WebSocket) { | ||
serverInstance?.clients.forEach(function each(client) { | ||
if (client !== self && client.readyState === WebSocket.OPEN) { | ||
client.close(); | ||
} | ||
}); | ||
} |
This file contains 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
26 changes: 26 additions & 0 deletions
26
packages/@expo/cli/src/start/server/middleware/ReactDevToolsPageMiddleware.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { readFile } from 'fs/promises'; | ||
import path from 'path'; | ||
import resolveFrom from 'resolve-from'; | ||
|
||
import { ExpoMiddleware } from './ExpoMiddleware'; | ||
import { ServerRequest, ServerResponse } from './server.types'; | ||
|
||
export const ReactDevToolsEndpoint = '/_expo/react-devtools'; | ||
|
||
export class ReactDevToolsPageMiddleware extends ExpoMiddleware { | ||
constructor(projectRoot: string) { | ||
super(projectRoot, [ReactDevToolsEndpoint]); | ||
} | ||
|
||
async handleRequestAsync(req: ServerRequest, res: ServerResponse): Promise<void> { | ||
const templatePath = | ||
// Production: This will resolve when installed in the project. | ||
resolveFrom.silent(this.projectRoot, 'expo/static/react-devtools-page/index.html') ?? | ||
// Development: This will resolve when testing locally. | ||
path.resolve(__dirname, '../../../../../static/react-devtools-page/index.html'); | ||
const content = (await readFile(templatePath)).toString('utf-8'); | ||
|
||
res.setHeader('Content-Type', 'text/html'); | ||
res.end(content); | ||
} | ||
} |
Oops, something went wrong.