vscode-react embeds a browser-hosted VS Code workbench inside a React application and bridges it to the host page with quickbus.
It expects the embedded VS Code host to include the file-bus extension so file-system requests can be served by the outer page. You can build and host that companion VS Code instance with vscode-static-web.
npm install vscode-reactThis package exports:
useVSCodecreateDebugAdapterHost
import React from 'react';
import { createDebugAdapterHost, useVSCode } from 'vscode-react';
function App() {
const fsHandlers = {
readdir(path) {
return ['demo.php'];
},
async readFile(path) {
return Array.from(new TextEncoder().encode('<?php echo "Hello";'));
},
analyzePath(path) {
if (path === '/' || path === '/workspace') {
return { exists: true, object: { isFolder: true } };
}
if (path === '/workspace/demo.php') {
return { exists: true, object: { isFolder: false } };
}
return { exists: false, object: { isFolder: false } };
}
};
const debugHost = createDebugAdapterHost({
commands: {
async initialize({ sendEvent }) {
await sendEvent('initialized');
return {
supportsConfigurationDoneRequest: true
};
},
launch() {
return {};
},
setBreakpoints({ arguments: args }) {
return {
breakpoints: (args?.breakpoints ?? []).map(breakpoint => ({
verified: true,
line: breakpoint.line
}))
};
},
async configurationDone({ sendEvent }) {
await sendEvent('stopped', {
reason: 'entry',
threadId: 1,
allThreadsStopped: true
});
return {};
},
threads() {
return {
threads: [{ id: 1, name: 'Main Thread' }]
};
},
stackTrace() {
return {
stackFrames: [{
id: 1,
name: 'main',
line: 1,
column: 1,
source: {
name: 'demo.php',
path: '/workspace/demo.php'
}
}],
totalFrames: 1
};
},
scopes() {
return {
scopes: [{
name: 'Locals',
variablesReference: 0,
expensive: false
}]
};
},
continue() {
return {
allThreadsContinued: true
};
},
async disconnect({ sendEvent }) {
await sendEvent('terminated');
return {};
}
}
});
const { VSCode, ready, openFile, startDebugging, sendDebugAdapterMessage } = useVSCode({
url: 'https://your-vscode-host.example/editor/',
fsHandlers,
dbgHandlers: debugHost.dbgHandlers,
});
debugHost.attachBridge({ sendDebugAdapterMessage });
async function runDebugger() {
await ready;
await openFile('/workspace/demo.php');
await startDebugging({
type: 'dbgBus',
request: 'launch',
name: 'PHP DBG Wasm',
program: '/workspace/demo.php'
});
}
return <VSCode className="editor" />;
}
export default App;The url should point at a VS Code web host that includes file-bus. If you
want debugger support as well, that host also needs dbg-bus. The iframe host
must post a ready message shaped like { kind: 'vscode-react', type: 'ready' }
to the parent window once its command bridge is usable. If you control the host
with vscode-web-static, that handshake is emitted automatically by the shared
bootstrap after window.vscodeEditorReady resolves. ready is intentionally a
one-shot "first usable boot" promise; the bridge methods below will
automatically wait for a later iframe reload to become ready again.
| Option | Type | Description |
|---|---|---|
| url | string | Base URL of the VSCode editor server. |
| fsHandlers | object | Custom file-system handler callbacks. |
| dbgHandlers | object | Optional host-side debugger bridge callbacks. |
| readyTimeoutMs | number | Optional timeout for the iframe ready handshake. Default 0 (disabled). |
| Return | Type | Description |
|---|---|---|
VSCode |
React component | The iframe-based VSCode component to render. |
ready |
Promise<object> |
Resolves when the iframe host first signals bridge readiness. |
openFile |
(path: string, options?: object) => Promise<any> |
Opens the given file in the VSCode editor. |
configure |
(options?: object) => Promise<any> |
Configures file-bus host options such as file associations. |
executeCommand |
(command: string, ...args: any[]) => Promise<any> |
Executes a VS Code command in the editor. |
startDebugging |
(configuration: object, options?: object) => Promise<any> |
Starts a dbgBus debug session. |
stopDebugging |
(sessionId?: string) => Promise<any> |
Stops a dbgBus debug session. |
sendDebugAdapterMessage |
(sessionId: string, message: object) => Promise<any> |
Sends one DAP message into the debug session. |
customRequest |
(sessionId: string, command: string, args?: any) => Promise<any> |
Sends a custom debug request. |
listDebugSessions |
() => Promise<any> |
Lists active debug sessions known to dbg-bus. |
listBreakpoints |
() => Promise<any> |
Lists all VS Code breakpoints. |
listOpenBreakpoints |
() => Promise<any> |
Lists breakpoints for open editors. |
addBreakpoint |
(uri: string, line: number, column?: number) => Promise<any> |
Adds a source breakpoint through dbg-bus. |
Creates a small host-side DAP wrapper for dbg-bus. It tracks sessions,
wraps request handlers into valid DAP responses, and can emit DAP events back
into VS Code through sendDebugAdapterMessage(...).
| Option | Type | Description |
|---|---|---|
commands |
object | Map of DAP request names such as initialize or stackTrace. |
onSessionEvent |
function | Optional lifecycle callback for dbg-bus session notifications. |
| Return | Type | Description |
|---|---|---|
dbgHandlers |
object | Host callbacks to pass into useVSCode({ dbgHandlers }). |
attachBridge |
function | Binds the helper to a sendDebugAdapterMessage function. |
getSession |
function | Returns one tracked debug session by id or the active session. |
getActiveSession |
function | Returns the active debug session or null. |
listSessions |
function | Returns a snapshot array of tracked debug sessions. |
sendEvent |
function | Sends one DAP event into an active debug session. |
sendRequest |
function | Sends one DAP request into an active debug session. |
executeCommand proxies to the VS Code command registry. You can call any built-in or extension command by its identifier. For example:
workbench.action.files.newUntitledFileworkbench.action.openFolderworkbench.action.quickOpenworkbench.action.findInFileseditor.action.gotoLineeditor.action.rename
See the full list of VS Code commands.
The fsHandlers option lets you override the file-system callbacks. This API
mirrors the file-bus host-page
contract, which is loosely modeled on filesystem-style operations. By default,
this hook uses the following stub handlers:
const defaultFsHandlers = {
readdir(path: string, opts?: object): string[],
async readFile(path: string, opts?: object): number[],
analyzePath(path: string): { exists: boolean, object?: { isFolder?: boolean } },
writeFile(path: string, data: number[]): void,
rename(oldPath: string, newPath: string): void,
mkdir(path: string, opts?: { recursive?: boolean }): void,
unlink(path: string): void,
rmdir(path: string): void,
activate(): void
};| Handler | Signature | Description |
|---|---|---|
readdir |
(path: string, opts?: object) => string[] |
Reads a directory and returns an array of entry names. |
readFile |
(path: string, opts?: object) => Promise<number[]> |
Reads a file and returns content as an array of bytes (number[]). |
analyzePath |
(path: string) => { exists: boolean, object?: { isFolder?: boolean } } |
Checks if the path exists and whether file-bus should treat it as a folder. |
writeFile |
(path: string, data: number[]) => void |
Writes raw bytes to a file (data should be an array of numbers representing bytes). |
rename |
(oldPath: string, newPath: string) => void |
Renames or moves a file or directory. |
mkdir |
(path: string, opts?: { recursive?: boolean }) => void |
Creates a directory. Use opts.recursive to create nested directories if needed. |
unlink |
(path: string) => void |
Removes a file. |
rmdir |
(path: string) => void |
Removes a (empty) directory. |
activate |
() => void |
Called when the FS bridge is activated (e.g., after initial mount). |
The dbgHandlers option lets the host page provide the runtime-facing half of
the dbg-bus
bridge. These handlers are optional until you actually start a dbgBus
session.
If dbgHandlers.acceptVSCodeMessage(...) is omitted, vscode-react returns a
generic DAP failure response so VS Code fails clearly instead of waiting
forever for a missing host adapter.
| Handler | Signature | Description |
|---|---|---|
acceptVSCodeMessage |
(session: object, message: object) => Promise<object> | object |
Accepts one DAP request from VS Code and returns the adapter reply. |
debugSessionStarted |
(session: object) => void |
Called when the inline adapter is created for a new debug session. |
didStartDebugSession |
(session: object) => void |
Called when VS Code reports that the session has started. |
didTerminateDebugSession |
(session: object) => void |
Called when VS Code terminates the session. |
didChangeActiveDebugSession |
(session: object | null) => void |
Called when the active debug session changes. |
The smallest useful dbg-bus flow looks like this:
- VS Code sends
initialize. - Your host responds with capabilities and emits
initialized. - VS Code sends
launchorattach. - VS Code sends
setBreakpoints. - VS Code sends
configurationDone. - Your host emits
stoppedwhen the runtime reaches a pause point. - VS Code asks for
threads,stackTrace, andscopes. - VS Code sends stepping or resume requests such as
continue. - Your host emits
terminatedwhen the session exits.
The createDebugAdapterHost(...) helper is designed around that flow. A basic
command map normally covers:
initializelaunchorattachsetBreakpointsconfigurationDonethreadsstackTracescopescontinuedisconnect
The unit tests in tests/createDebugAdapterHost.test.mjs exercise that exact sequence with a minimal single-thread session.
npm testnpm run test:e2eThe browser E2E test bundles a small React harness, serves it alongside a local
vscode-web-static build, and verifies that a real embedded VS Code workbench
can:
- boot successfully
- request file data over
file-bus - open a file through
openFile(...) - accept
executeCommand(...) - add a breakpoint through
dbg-bus - start and stop a real
dbgBusdebug session end to end - exchange DAP events between the host page and the embedded debugger session
By default it uses the companion repo at /projects/vscode-web-static. You can
override that with:
VSCODE_REACT_COMPANION_DIR=/path/to/vscode-web-static npm run test:e2eThis package uses Babel to compile JSX and modern JavaScript for distribution.
npm run buildThe compiled files will be placed in dist/.
Apache-2.0