-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for launching expo dev plugin tools in separate panels within…
… VSCode (#878) This PR adds support for redux and react query dev tools expo plugins and makes their UI display as separate panel in VSCode. Apart from the support for the listed plugins, this change builds an infrastructure for supporting more expo dev plugins and other types of plugins in the future. Key changes that the PR introduces: 1. We are expanding panels configuration in package.json. Apparently there's no way to define panels dynamically and we need one panel for every dev tool such that they can be run side-by-side when necessary. To avoid having too many panels, every panel is hidden behind its own context variable that we can dynamically set when we detect the user requested to open this panel 2. We introduce a change to babel transformer that allows us to capture the use of expo-dev-plugins we currently support. This way we can detect which plugins the user has installed and only show relevant tools for the user to open. 3. For the above to work correctly, we are adding a new type of event that is passed via the devtools channel (same that we use for events such as "app ready"). We use this event to notify about the plugins that the app uses. 4. We're adding a UI component to the top menu bar with a "tools" dropdown. The dropdown displays the list of available tools and a checkbox next to every tool allowing the user to enable each tool separately. When tools is enabled, the new panel displaying the tool UI will launch. 5. Finally, we're implementing a new "ToolPlugin" interface and its implementation that coordinates all the expo-dev-plugin specific needs. The interface allows for tools to be listed as available and implements a persistent storage for saving which tools are enabled. The expo-dev-plugin implementation of the tool manages the registration of webview panels, listening to events about the plugins being used by the app, and controls the context variables that shows/hide the panel. As a follow up to this change, we will be adding a docs update that explains how tools can be used. Currently, we only support epxo-dev-plugin tools, so for the whole setup to work corrently the user needs to use Expo and have the specific tools configured according to the documentation here: https://github.com/expo/dev-plugins ### How Has This Been Tested: 1. I used a sample Expo project and added redux and react query dev tools following the docs here: https://github.com/expo/dev-plugins 2. I tested enabling/disabling each plugin separately. When plugin is enabled we expect the panel to open immediately. 3. I tested resetting metro cache which forces metro to reset at which point it stars at a new port. This is important as the plugin UI is hosted via metro. In this scenario is it expected for the panel to close while the app is reloading. --------- Co-authored-by: filip131311 <[email protected]>
- Loading branch information
1 parent
bf16a0c
commit d776a85
Showing
18 changed files
with
488 additions
and
23 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,3 @@ | ||
export function register(pluginName) { | ||
global.__RNIDE_register_expo_dev_plugin && global.__RNIDE_register_expo_dev_plugin(pluginName); | ||
} |
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
50 changes: 50 additions & 0 deletions
50
packages/vscode-extension/src/plugins/expo-dev-plugins/ExpoDevPluginWebviewProvider.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,50 @@ | ||
import { | ||
CancellationToken, | ||
ExtensionContext, | ||
Uri, | ||
WebviewView, | ||
WebviewViewProvider, | ||
WebviewViewResolveContext, | ||
} from "vscode"; | ||
import { IDE } from "../../project/ide"; | ||
import { ExpoDevPluginToolName } from "./expo-dev-plugins"; | ||
import { Logger } from "../../Logger"; | ||
|
||
function generateWebviewContent(pluginName: ExpoDevPluginToolName, metroPort: number): string { | ||
const iframeURL = `http://localhost:${metroPort}/_expo/plugins/${pluginName}`; | ||
return /*html*/ ` | ||
<!DOCTYPE html> | ||
<html style="height: 100%;"> | ||
<head/> | ||
<body style="height: 100%; overflow:hidden"> | ||
<iframe src="${iframeURL}" style="width: 100%; height: 100%; border: none;"/> | ||
</body> | ||
</html> | ||
`; | ||
} | ||
|
||
export class ExpoDevPluginWebviewProvider implements WebviewViewProvider { | ||
constructor( | ||
private readonly context: ExtensionContext, | ||
private readonly pluginName: ExpoDevPluginToolName | ||
) {} | ||
public resolveWebviewView( | ||
webviewView: WebviewView, | ||
context: WebviewViewResolveContext, | ||
token: CancellationToken | ||
): void { | ||
webviewView.webview.options = { | ||
enableScripts: true, | ||
localResourceRoots: [Uri.joinPath(this.context.extensionUri, "dist")], | ||
}; | ||
|
||
const metroPort = IDE.getInstanceIfExists()?.project.metro.port; | ||
if (!metroPort) { | ||
Logger.error( | ||
"Metro port is unknown while expected to be set, the devtools panel cannot be opened." | ||
); | ||
} else { | ||
webviewView.webview.html = generateWebviewContent(this.pluginName, metroPort); | ||
} | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
packages/vscode-extension/src/plugins/expo-dev-plugins/expo-dev-plugins.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,97 @@ | ||
import { commands, window } from "vscode"; | ||
import { ExpoDevPluginWebviewProvider } from "./ExpoDevPluginWebviewProvider"; | ||
import { ToolPlugin, ToolsManager } from "../../project/tools"; | ||
import { extensionContext } from "../../utilities/extensionContext"; | ||
|
||
export type ExpoDevPluginToolName = | ||
| "@dev-plugins/react-query" | ||
| "@dev-plugins/react-native-mmkv" | ||
| "redux-devtools-expo-dev-plugin"; | ||
|
||
type ExpoDevPluginInfo = { | ||
viewIdPrefix: string; | ||
label: string; | ||
}; | ||
|
||
// Define the map of plugins using the string union type | ||
const ExpoDevPluginToolMap: Record<ExpoDevPluginToolName, ExpoDevPluginInfo> = { | ||
"@dev-plugins/react-query": { | ||
label: "React Query", | ||
viewIdPrefix: "RNIDE.Tool.ExpoDevPlugin.ReactQuery", | ||
}, | ||
"@dev-plugins/react-native-mmkv": { | ||
label: "MMKV", | ||
viewIdPrefix: "RNIDE.Tool.ExpoDevPlugin.MMKV", | ||
}, | ||
"redux-devtools-expo-dev-plugin": { | ||
viewIdPrefix: "RNIDE.Tool.ExpoDevPlugin.ReduxDevTools", | ||
label: "Redux DevTools", | ||
}, | ||
}; | ||
|
||
let initialzed = false; | ||
function initializeExpoDevPluginIfNeeded() { | ||
if (initialzed) { | ||
return; | ||
} | ||
initialzed = true; | ||
|
||
for (const [name, pluginInfo] of Object.entries(ExpoDevPluginToolMap)) { | ||
extensionContext.subscriptions.push( | ||
window.registerWebviewViewProvider( | ||
`${pluginInfo.viewIdPrefix}.view`, | ||
new ExpoDevPluginWebviewProvider(extensionContext, name as ExpoDevPluginToolName), | ||
{ webviewOptions: { retainContextWhenHidden: true } } | ||
) | ||
); | ||
} | ||
} | ||
|
||
export function createExpoDevPluginTools(toolsManager: ToolsManager): ToolPlugin[] { | ||
initializeExpoDevPluginIfNeeded(); | ||
|
||
const plugins: ToolPlugin[] = []; | ||
|
||
function devtoolsListener(event: string, payload: any) { | ||
if (event === "RNIDE_expoDevPluginsChanged") { | ||
// payload.plugins is a list of expo dev plugin names | ||
const availablePlugins = new Set(payload.plugins); | ||
for (const plugin of plugins) { | ||
plugin.available = availablePlugins.has(plugin.id); | ||
} | ||
// notify tools manager that the state of requested plugins has changed | ||
toolsManager.handleStateChange(); | ||
} | ||
} | ||
let disposed = false; | ||
function dispose() { | ||
if (!disposed) { | ||
toolsManager.devtools.removeListener(devtoolsListener); | ||
disposed = false; | ||
} | ||
} | ||
|
||
for (const [id, pluginInfo] of Object.entries(ExpoDevPluginToolMap)) { | ||
plugins.push({ | ||
id: id as ExpoDevPluginToolName, | ||
label: pluginInfo.label, | ||
available: false, | ||
activate() { | ||
commands.executeCommand("setContext", `${pluginInfo.viewIdPrefix}.available`, true); | ||
}, | ||
deactivate() { | ||
commands.executeCommand("setContext", `${pluginInfo.viewIdPrefix}.available`, false); | ||
}, | ||
openTool() { | ||
commands.executeCommand(`${pluginInfo.viewIdPrefix}.view.focus`); | ||
}, | ||
dispose, | ||
}); | ||
} | ||
|
||
// Listen for events passed via devtools that indicate which plugins are loaded | ||
// by the app. | ||
toolsManager.devtools.addListener(devtoolsListener); | ||
|
||
return plugins; | ||
} |
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
Oops, something went wrong.