npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-vscode-webview-ipc

v0.1.1

Published

[![npm version](https://badge.fury.io/js/react-vscode-webview-ipc.svg)](https://www.npmjs.com/package/react-vscode-webview-ipc) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/hbmartin/react-vscode-webview-ipc) [![CI](https://github

Readme

React + VSCode Webview = IPC

npm version Ask DeepWiki CI NPM License

A small library to make two-way communication between a VS Code extension host and a React webview simple and type‑safe.

Two complementary paradigms are supported (you can use one or both):

  • UDF reducer IPC: Dispatch actions from the webview; the host computes a patch; the webview applies it via a reducer. (unidirectional dataflow)
  • RPC promises IPC: Call host functions from the webview and await typed results; the host can also push typed events to all connected webviews.

This README explains how to implement both and shows how they can coexist.

Install

  • Install the package in your VS Code extension project.
npm i react-vscode-webview-ipc
  • You’ll consume two entry points:
    • react-vscode-webview-ipc/host for your extension host code
    • react-vscode-webview-ipc/client for your React webview code

Concepts Overview

  • WebviewKey: a branded string identifying your view instance. Use a stable id (often your view type).
  • Messages:
    • Reducer IPC: { type: 'act' } messages from webview → host; { type: 'patch' } messages from host → webview.
    • RPC IPC: { type: 'request' } from webview → host; { type: 'response'|'error' } from host → webview; { type: 'event' } from host → webview broadcast.
  • Logging: webview logs are forwarded to the host’s Output channel.

UDF Reducer IPC (Action → Patch → Reduce)

Use this when your webview wants unidirectional state updates managed via a reducer.

Types and Building Blocks

  • On the webview:
    • useVscodeState<S, A>(vscode, providerId, postReducer, initialState) returns [state, actor].
      • state: S – your current state
      • actor: A – a proxy with methods matching your action interface
    • postReducer: StateReducer<S, A> maps each action key to (prevState, patch) => newState.
  • On the host:
    • Extend BaseWebviewViewProvider<A> and implement:
      • webviewActionDelegate: ActionDelegate<A> – map action keys to host handlers that return a patch (sync or async)
      • generateWebviewHtml(webview, extensionUri) – return the webview HTML
      • handleMessage(message, webview) – handle any messages you want besides reducer IPC (e.g., your RPC requests)
    • Optionally pass a WebviewApiProvider instance to the base class constructor to enable host→webview event broadcasting (RPC paradigm).

Minimal Example

Host (extension):

// src/extension/MyViewProvider.ts
import * as vscode from 'vscode';
import { BaseWebviewViewProvider, type ActionDelegate } from 'react-vscode-webview-ipc/host';
import type { WebviewKey } from 'react-vscode-webview-ipc/client';

// 1) Define the action interface A: methods return the “patch” type
interface MyActions {
  increment: (by: number) => number; // patch is a number
  setMessage: (msg: string) => { message: string }; // patch is an object
}

export class MyViewProvider extends BaseWebviewViewProvider<MyActions> {
  protected readonly webviewActionDelegate: ActionDelegate<MyActions> = {
    increment: (by) => by, // just echo back the increment amount as the patch
    setMessage: (msg) => ({ message: msg }),
  };

  constructor(
    private readonly id: WebviewKey,
    private readonly ctx: vscode.ExtensionContext
  ) {
    // You can also pass a WebviewApiProvider instance as 3rd arg to enable events
    super(id, ctx.extensionUri);
  }

  protected generateWebviewHtml(webview: vscode.Webview): string {
    const scriptUri = webview.asWebviewUri(
      vscode.Uri.joinPath(this.ctx.extensionUri, 'dist', 'webview.js')
    );
    const styleUri = webview.asWebviewUri(
      vscode.Uri.joinPath(this.ctx.extensionUri, 'dist', 'webview.css')
    );

    return `<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="Content-Security-Policy"
          content="default-src 'none'; img-src ${webview.cspSource} https:; style-src ${webview.cspSource} 'unsafe-inline'; script-src ${webview.cspSource};" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="${styleUri}">
    <title>My View</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="${scriptUri}"></script>
  </body>
</html>`;
  }

  protected async handleMessage(_message: unknown, _webview: vscode.Webview): Promise<void> {
    // No-op for reducer-only example. Use this for RPC too (see later).
  }
}

Register the provider in your extension activation:

// src/extension/activate.ts
import * as vscode from 'vscode';
import { MyViewProvider } from './MyViewProvider';

export function activate(context: vscode.ExtensionContext) {
  const viewType = 'myExtension.myView' as unknown as WebviewKey; // brand to WebviewKey
  const provider = new MyViewProvider(viewType, context);
  context.subscriptions.push(vscode.window.registerWebviewViewProvider(viewType, provider));
}

Webview (React):

// src/webview/App.tsx
import { useMemo } from 'react';
import {
  useVscodeState,
  type StateReducer,
  type WebviewKey,
} from 'react-vscode-webview-ipc/client';

declare function acquireVsCodeApi(): {
  postMessage(message: unknown): Thenable<boolean>;
  getState(): unknown;
  setState(state: unknown): void;
};

interface State {
  count: number;
  message: string;
}
interface MyActions {
  increment: (by: number) => number;
  setMessage: (msg: string) => { message: string };
}

const initial: State = { count: 0, message: '' };

const reducers: StateReducer<State, MyActions> = {
  increment: (prev, by) => ({ ...prev, count: prev.count + by }),
  setMessage: (prev, patch) => ({ ...prev, message: patch.message }),
};

export default function App() {
  const vscode = useMemo(() => acquireVsCodeApi(), []);
  const providerId = 'myExtension.myView' as unknown as WebviewKey;
  const [state, act] = useVscodeState(vscode, providerId, reducers, initial);

  return (
    <div>
      <p>Count: {state.count}</p>
      <p>Message: {state.message}</p>
      <button onClick={() => act.increment(1)}>+1</button>
      <button onClick={() => act.setMessage('Hello!')}>Set Message</button>
    </div>
  );
}

UDF Flow (Sequence)

sequenceDiagram
  participant W as Webview (React)
  participant VS as VS Code API
  participant H as Extension Host
  W->>VS: postMessage { type: 'act', providerId, key, params }
  VS->>H: onDidReceiveMessage(message)
  H->>H: webviewActionDelegate[key](...params) => patch
  H-->>VS: postMessage { type: 'patch', providerId, key, patch }
  VS-->>W: window message
  W->>W: postReducer[key](prev, patch) => newState

RPC Promises IPC (Typed Requests/Responses + Events)

Use this when your webview needs to call host functions and await results. The host can also broadcast typed events back to all connected webviews.

Types and Building Blocks

  • On the webview:
    • Wrap your app in <WebviewProvider viewType contextKey>.
    • Use createCtxKey<T>() to create a unique key tying the context to your API type T.
    • Call const { api, addListener, removeListener, vscode } = useWebviewApi(ctxKey) inside components.
      • api.method(...) returns a promise (typed from your ClientCalls interface).
      • addListener('eventKey', cb) / removeListener(...) manage host-pushed events.
  • On the host:
    • Create a WebviewApiProvider<HostEvents>() and pass it to your BaseWebviewViewProvider constructor (to register views for events).
    • In your provider’s handleMessage, detect requests via isViewApiRequest(message), dispatch to your host API handlers, and respond with { type: 'response'|'error', id, value }.
    • Use apiProvider.triggerEvent('eventKey', ...args) to broadcast events to connected webviews.

Minimal Example

Shared types:

// Host receives these requests from the webview (must return promises)
import type { ClientCalls } from 'react-vscode-webview-ipc/client';
import type { HostCalls } from 'react-vscode-webview-ipc/client';

export interface MyClientApi extends ClientCalls {
  fetchGreeting: (name: string) => Promise<string>;
  saveCount: (count: number) => Promise<void>;
}

// Host can push these events to all webviews
export interface MyHostEvents extends HostCalls {
  onTick: (count: number) => void;
}

Host (extension):

import * as vscode from 'vscode';
import {
  BaseWebviewViewProvider,
  WebviewApiProvider,
  isViewApiRequest,
  type ViewApiResponse,
  type ViewApiError,
} from 'react-vscode-webview-ipc/host';
import type { MyClientApi, MyHostEvents } from './types';
import type { WebviewKey } from 'react-vscode-webview-ipc/client';

export class MyRpcViewProvider extends BaseWebviewViewProvider<{}> {
  protected readonly webviewActionDelegate = {}; // not using reducer actions here

  constructor(
    private readonly id: WebviewKey,
    private readonly ctx: vscode.ExtensionContext,
    private readonly api = new WebviewApiProvider<MyHostEvents>()
  ) {
    super(id, ctx.extensionUri, api);
  }

  protected generateWebviewHtml(webview: vscode.Webview): string {
    const scriptUri = webview.asWebviewUri(
      vscode.Uri.joinPath(this.ctx.extensionUri, 'dist', 'webview.js')
    );
    return `<div id="root"></div><script src="${scriptUri}"></script>`;
  }

  protected async handleMessage(message: unknown, webview: vscode.Webview): Promise<void> {
    if (isViewApiRequest<MyClientApi>(message)) {
      try {
        switch (message.key) {
          case 'fetchGreeting': {
            const [name] = message.params;
            const value = `Hello, ${name}!`;
            const response: ViewApiResponse<MyClientApi, 'fetchGreeting'> = {
              type: 'response',
              id: message.id,
              value,
            };
            await webview.postMessage(response);
            return;
          }
          case 'saveCount': {
            const [count] = message.params;
            // persist count...
            const response: ViewApiResponse<MyClientApi, 'saveCount'> = {
              type: 'response',
              id: message.id,
            };
            await webview.postMessage(response);
            return;
          }
        }
        const error: ViewApiError = {
          type: 'error',
          id: message.id,
          value: `Unknown method: ${String(message.key)}`,
        };
        await webview.postMessage(error);
      } catch (e) {
        const error: ViewApiError = {
          type: 'error',
          id: message.id,
          value: e instanceof Error ? e.message : String(e),
        };
        await webview.postMessage(error);
      }
    }
  }
}

// elsewhere in your extension, you can broadcast events
// provider.api.triggerEvent('onTick', currentCount)

Webview (React):

import React, { useEffect } from 'react';
import {
  WebviewProvider,
  useWebviewApi,
  createCtxKey,
  type CtxKey,
} from 'react-vscode-webview-ipc/client';
import type { MyClientApi, MyHostEvents } from './types';

const Ctx: CtxKey<MyClientApi> = createCtxKey<MyClientApi>('my-rpc');

function Inner() {
  const { api, addListener, removeListener } = useWebviewApi(Ctx);

  useEffect(() => {
    const onTick = (n: number) => console.log('tick', n);
    addListener('onTick', onTick as MyHostEvents['onTick']);
    return () => removeListener('onTick', onTick as MyHostEvents['onTick']);
  }, [addListener, removeListener]);

  useEffect(() => {
    (async () => {
      const greeting = await api.fetchGreeting('VS Code');
      console.log(greeting);
      await api.saveCount(42);
    })();
  }, [api]);

  return <div />;
}

export default function App() {
  return (
    <WebviewProvider<MyClientApi> viewType="myExtension.myView" contextKey={Ctx}>
      <Inner />
    </WebviewProvider>
  );
}

RPC Flow (Sequence)

sequenceDiagram
  participant W as Webview (React)
  participant VS as VS Code API
  participant H as Extension Host
  W->>VS: postMessage { type: 'request', id, key, params, context }
  VS->>H: onDidReceiveMessage(message)
  alt success
    H-->>VS: postMessage { type: 'response', id, value }
    VS-->>W: resolve pending promise
  else error
    H-->>VS: postMessage { type: 'error', id, value }
    VS-->>W: reject pending promise
  end
  H-->>VS: postMessage { type: 'event', key, value[] }
  VS-->>W: Invoke registered listeners for key

Using Both Paradigms Together

  • They are designed to coexist. The webview can dispatch reducer actions for state, and call RPC methods for imperative operations.
  • The library ensures messages don’t conflict:
    • useVscodeState listens for { providerId, type: 'patch' } messages.
    • WebviewProvider listens for { type: 'response'|'error'|'event' } messages and ignores messages with providerId present.
  • In your host provider, resolveWebviewView (from the base class) handles reducer act/patch automatically; implement handleMessage for RPC requests.

Logging

  • Webview: useLogger(tag, vscode) returns a logger that posts to the host output channel.
  • Host: getLogger(tag) returns an Output channel logger; BaseWebviewViewProvider automatically routes webview log messages to it.

Webview example:

import { useLogger } from 'react-vscode-webview-ipc/client';
const logger = useLogger('MyView', acquireVsCodeApi());
logger.info('hello');

Tips & Troubleshooting

  • Brand your view type to WebviewKey at the edges to keep types happy: const id = 'ext.view' as unknown as WebviewKey.
  • Always use stable providerIds; the reducer IPC ties messages to a specific provider.
  • Clean up listeners on unmount in the webview.
  • When posting RPC responses/errors from the host, always echo the same id you received.
  • If you use both paradigms, keep your reducer patches focused on state updates and use RPC for IO or long‑running tasks.

API Surface (Quick Reference)

Host exports (react-vscode-webview-ipc/host):

  • BaseWebviewViewProvider<A>
  • WebviewApiProvider<T extends HostCalls>
  • isViewApiRequest(message)
  • Logger, getLogger, disallowedLogKeys

Client exports (react-vscode-webview-ipc/client):

  • WebviewProvider<T extends ClientCalls>
  • useWebviewApi(ctxKey) and createCtxKey<T>()
  • useVscodeState<S, A>(vscode, providerId, postReducer, initial)
  • useLogger(tag, vscode)
  • Types: ClientCalls, HostCalls, CtxKey, WebviewKey, StateReducer

References

Legal

  • Apache 2.0 License
  • Visual Studio Code, VS Code, and the Visual Studio Code icon are trademarks of Microsoft Corporation.