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

@tofrankie/vscode-webview-rpc

v0.0.2

Published

Typed RPC for communication between VS Code Extension Host and WebView

Readme

@tofrankie/vscode-webview-rpc

npm version node version npm package license npm last update

一个用于 VS Code Extension Host 和 WebView 之间通信的小型 RPC 库。

它把消息通道包装成有类型提示的调用方式,适合 callnotifyevent 这三类场景。

通信类型

分为三种通信类型,它们都可以由 WebView 或 Extension 任意一端发起。

  • call: 需要返回结果的请求-响应通信。适合读取数据、执行动作并等待结果。
  • notify: 单向消息,不等待返回。适合触发宿主能力、上报日志、发起一次性动作。
  • event: 跨端事件通道。emit 发给对端,on 监听对端发来的事件。

快速使用

安装

pnpm add @tofrankie/vscode-webview-rpc

同一个 WebView 通道里,WebView 端创建一次 createWebviewRPC()。Extension 端创建一次 createExtensionRPC()。通常在各端的入口文件创建,后续由其他模块共享这个实例,而不是每个模块各自再创建一遍。

call

两端使用相同的 method 名称(指示例中的 'settings.get' 名称,下同)关联请求和处理函数。下面的 rpc.call('settings.get') 会交给 Extension 端 calls['settings.get'] 处理。

Extension 端处理请求:

import { createExtensionRPC } from '@tofrankie/vscode-webview-rpc'

const rpc = createExtensionRPC(webview, {
  calls: {
    'settings.get': async () => getSettings(),
  },
})

WebView 端发起请求:

import { createWebviewRPC } from '@tofrankie/vscode-webview-rpc'

const vscode = acquireVsCodeApi() // WebView 提供的 API
const rpc = createWebviewRPC(vscode)

const settings = await rpc.call('settings.get')

notify

两端同样通过 method 名称关联。下面的 rpc.notify('external-link.open') 会交给 WebView 端 notifications['external-link.open'] 处理。

WebView 端处理单向消息:

const rpc = createWebviewRPC(vscode, {
  notifications: {
    'external-link.open': payload => {
      window.open(payload.url)
    },
  },
})

Extension 端发送单向消息:

rpc.notify('external-link.open', {
  url: 'https://example.com',
})

event

事件也通过 method 名称关联。下面的 rpc.emit('editor.changed') 会触发对端通过 rpc.on('editor.changed') 注册的 listener。

Extension 端监听事件:

const off = rpc.on('editor.changed', state => {
  console.log(state.dirty)
})

WebView 端发布事件:

rpc.emit('editor.changed', {
  dirty: true,
})

TypeScript

本库提供了类型声明,会根据你声明的 RPC 定义自动推导方法名、参数和返回值。

简单示例

对于复杂度不高的项目,推荐把共享 RPC 类型定义放在 shared/rpc.ts 之类的共享文件,让 WebView 和 Extension 双端都能引用。

// shared/rpc.ts
import type { RPCDefinition } from '@tofrankie/vscode-webview-rpc'

type Calls = {
  'settings.get': {
    params: void
    result: Settings
  }
}

type Notifications = {
  'external-link.open': {
    payload: { url: string }
  }
}

type Events = {
  'editor.changed': {
    payload: { dirty: boolean }
  }
}

// 如果没有使用到某类消息,可以使用 `Record<string, never>` 这样的“空定义”。
export type AppRPC = RPCDefinition<Calls, Notifications, Events>
  • Calls 定义 rpc.call() 能用哪些 method,以及它们的参数和返回值
  • Notifications 定义 rpc.notify() 能用哪些 method,以及它们的 payload
  • Events 定义 rpc.on() / rpc.emit() 能用哪些 method,以及它们的 payload
// extension
import type { AppRPC } from './shared/rpc'
import { createExtensionRPC } from '@tofrankie/vscode-webview-rpc'

const rpc = createExtensionRPC<AppRPC>(webview, {
  calls: {
    'settings.get': async () => getSettings(),
  },
})
// webview
import type { AppRPC } from './shared/rpc'
import { createWebviewRPC } from '@tofrankie/vscode-webview-rpc'

const rpc = createWebviewRPC<AppRPC>(vscode)

const settings = await rpc.call('settings.get')

共享定义,不共享实现

建议 shared/rpc.ts 只放 AppRPC 这类类型定义,不放具体实现。原因是通信可以由 WebView 或 Extension 任意一端发起,而处理端可能会使用到另一端无法使用的 API,如果共享实现可能会导致运行时错误。

// webview/rpc.ts
import type { AppRPC } from '../shared/rpc'
import { createWebviewRPC } from '@tofrankie/vscode-webview-rpc'

const rpc = createWebviewRPC<AppRPC>(vscode)

const settings = await rpc.call('settings.get')
// extension/rpc.ts
import type { AppRPC } from '../shared/rpc'
import { createExtensionRPC } from '@tofrankie/vscode-webview-rpc'

const rpc = createExtensionRPC<AppRPC>(webview, {
  calls: {
    // workspace 为 Extension 端特有 API,在 WebView 端无法直接使用
    'settings.get': async () => workspace.getConfiguration('myExtension'),
  },
})

按模块拆分

大项目里可以把 RPC 定义拆到各个模块,再在入口组合起来。

import type { RPCDefinition } from '@tofrankie/vscode-webview-rpc'

type EmptyDefinitions = Record<string, never>

type SettingsRPC = RPCDefinition<
  {
    'settings.get': {
      params: void
      result: Settings
    }
  },
  EmptyDefinitions,
  {
    'settings.changed': {
      payload: Settings
    }
  }
>

type EditorRPC = RPCDefinition<
  {
    'editor.get-draft': {
      params: void
      result: Draft
    }
  },
  EmptyDefinitions,
  {
    'editor.changed': {
      payload: { dirty: boolean }
    }
  }
>

type AppRPC = RPCDefinition<
  SettingsRPC['calls'] & EditorRPC['calls'],
  EmptyDefinitions, // 占位类型,表示“这个模块当前没有这一类消息定义”
  SettingsRPC['events'] & EditorRPC['events']
>
const rpc = createExtensionRPC<AppRPC>(webview, {
  calls: {
    'settings.get': async () => getSettings(),
    'editor.get-draft': async () => getDraft(),
  },
})

API

创建实例

createWebviewRPC(vscode, options?)

WebView 侧创建实例。第一个参数传 acquireVsCodeApi() 的返回值,第二个参数传处理定义和配置。通常在 WebView 入口文件里创建一次。

createExtensionRPC(webview, options?)

Extension 侧创建实例。第一个参数传 VS Code 的 webview 对象,第二个参数传处理定义和配置。通常在创建 WebviewPanel、组装 webview.html、绑定消息通道的地方创建一次。

同一个 WebView 通道只需要这一对实例:

  • WebView 端一个 createWebviewRPC(...)
  • Extension 端一个 createExtensionRPC(...)

不要因为有多个业务模块、多个文件,或者同时需要收发消息,就重复创建多个实例。更常见的做法是由入口创建实例,再把 rpc 传给模块工厂、注册函数或业务对象复用。

// src/panels/HelloWorldPanel.ts
const rpc = createExtensionRPC<AppRPC>(webview, {
  calls: {
    'settings.get': async () => getSettings(),
  },
})

registerEditorModule(rpc)
registerSettingsModule(rpc)
// src/webview/main.ts
const rpc = createWebviewRPC<AppRPC>(vscode)

mountApp({ rpc })

options

const rpc = createExtensionRPC(webview, {
  calls: {
    'settings.get': async () => getSettings(),
  },
  notifications: {
    'external-link.open': async payload => {
      await openExternalLink(payload.url)
    },
  },
  timeout: 10_000,
  missingNotificationBehavior: 'ignore',
})
  • calls 用来处理对端发来的 call 请求
  • notifications 用来处理对端发来的 notify 消息。
  • missingNotificationBehavior 用来控制“对端发来了一个 notify,但当前端没有对应处理函数”时怎么处理。通常业务稳定后用 ignore 更宽松;开发和联调阶段用 error 更容易暴露问题。
    • ignore:忽略这条消息
    • error:按 method not found 处理,便于在开发期尽早发现消息名写错或接线遗漏
  • timeout 是当前 RPC 实例里 call 的默认超时时间,单位是毫秒。没有在规定时间内收到对端 response 时,这次 call 会以 RPCTimeoutError 失败。也可以在单次调用时覆盖:
await rpc.call('settings.get', {
  timeout: 3_000,
})

event 的消息不放在 options 里。事件监听是运行时行为,使用公开的 rpc.on() 注册;rpc.emit() 发给对端,rpc.on() 监听对端发来的同名事件。events 的方法名和 payload 类型仍然来自你的 AppRPC,所以 rpc.on()rpc.emit() 一样会有完整的 TypeScript 提示。

call

发送需要响应的请求。实际可用的 method、参数类型和返回值类型都来自你的 AppRPC['calls']params: void 的方法可以省略参数。

const result = await rpc.call('settings.get')

notify

发送单向消息,不等待返回值。实际可用的 methodpayload 类型都来自你的 AppRPC['notifications']

rpc.notify('external-link.open', {
  url: 'https://example.com',
})

event

emit 发送到对端,on 只监听对端事件。实际可用的 methodpayload 类型都来自你的 AppRPC['events']

const off = rpc.on('editor.changed', state => {
  console.log(state.dirty)
})

rpc.emit('editor.changed', {
  dirty: true,
})

off()

dispose

释放监听、处理定义和 pending 请求。

rpc.dispose()

错误类型

import {
  RPCDisposedError,
  RPCError,
  RPCMethodNotFoundError,
  RPCProtocolError,
  RPCRemoteError,
  RPCTimeoutError,
} from '@tofrankie/vscode-webview-rpc'

导出

import type {
  RPCCallDefinition,
  RPCCallMethodArgs,
  RPCCallParams,
  RPCCallResult,
  RPCDefinition,
  RPCEventDefinition,
  RPCEventPayload,
  RPCNotificationDefinition,
  RPCNotificationPayload,
  RPCPayloadArgs,
} from '@tofrankie/vscode-webview-rpc'

import { createExtensionRPC, createWebviewRPC } from '@tofrankie/vscode-webview-rpc'
  • RPCDefinition:组合应用级 RPC 类型定义
  • RPCCallDefinition / RPCNotificationDefinition / RPCEventDefinition:分别描述三类消息的单项结构
  • RPCCallParams / RPCCallResult:从某个 call method 里提取参数和返回值类型
  • RPCCallMethodArgs:表示 rpc.call(method, ...) 里 method 后面的完整参数列表,适合封装一层 rpcCall() helper 时复用
  • RPCNotificationPayload / RPCEventPayload:从 notifyevent method 里提取 payload 类型
  • RPCPayloadArgs:表示 notifyemit、handler、listener 这一类 payload 参数列表

License

MIT License © Frankie