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

sk-wasm-surface

v0.1.2

Published

Platform-agnostic Skia/CanvasKit-Wasm surface renderer for React hosts

Readme

sk-wasm-surface

基于 CanvasKit-Wasm 的平台无关 Skia 表面渲染引擎,供 Electron 与 Capacitor 等宿主复用。

设计原则:本包负责「渲染 + 画布交互 + 画布状态」,即输入场景数据→绘制,以及平移/缩放/框选/拖拽/手绘/连线/撤销等交互。数据持久化、IPC/HTTP、快捷键配置、上层 React UI(toolbar/overlay)与 Redux 仍由宿主提供——这些通过 configureCanvasHost() 注入适配器接入,引擎不直接 import 任何 app 源码(由 scripts/check-boundaries.mjs 强制约束)。

包含什么

  • core/ 引擎核心(CanvasEngineengine 单例、RenderContext/ViewState 类型)

  • renderers/ 各类渲染器单例(node / connection / selection / mindmap-group / pinned …)

  • drawers/ 各卡片/元素绘制器

  • layers/animation/rich-text/elements/utils/ 绘制相关模块

  • minimap/ 纯绘制小地图 MinimapCanvas + 坐标工具(不含与 store 绑定的 CanvasKitMinimap)

  • store.ts 画布状态(Zustand,useCanvasStore)

  • hooks/ 画布交互 hooks(平移/缩放/框选/拖拽 use-canvas-interaction、连线、手绘、演示、撤销)

  • canvas-undo.ts 撤销快照

  • host/ 宿主注入适配器(configureCanvasHost):交互层用到的 serviceHandler/dataGen/getShortcutKeyByAction 在此注入

  • lib/ 从 app 抽出的纯逻辑/类型副本(flow-utils 子集、mindmap-layout、whiteboard-preview、focus-mode、env、interface 类型、枚举、shortcuts 枚举、space-preview-guard、highlight 图标 path)

  • wasm/canvaskit.wasm CanvasKit 运行时(供宿主拷贝到 web 资源目录)

  • MyCanvasKit.tsx 完整画布 React 组件(原 index.tsx),平台相关依赖经 CanvasKitHostProvider 注入

  • *-toolbar.tsx / *-overlay.tsx 画布浮层与工具条 UI

不包含(留在宿主 app 的「集成层」)

service/store(Redux)/lib/data 等的具体实现、各 slice、路由/主题/i18n 的具体接入。这些通过 configureCanvasHost()(交互层)与 <CanvasKitHostProvider>(组件层)注入。

开箱即用:直接渲染整块画布

import {
  MyCanvasKit,
  CanvasKitHostProvider,
  configureCanvasHost,
} from "sk-wasm-surface";
import { useRouter } from "next/router";        // Electron;Capacitor 用等价 stub
import { useTheme } from "next-themes";

// 1) 交互层注入(启动时一次)
configureCanvasHost({
  serviceHandler, dataGen: { spaceId, userId }, getShortcutKeyByAction,
});

// 2) 组件层注入(包裹 MyCanvasKit)
function CanvasHost() {
  const router = useRouter();
  const { resolvedTheme } = useTheme();
  return (
    <CanvasKitHostProvider
      value={{
        resolvedTheme,
        i18n,
        router,                                   // Capacitor: { pathname, query, push } stub
        getTargetCardFromState,                   // 从 Redux 取卡片展示数据
        screenshotVideo,                          // 视频/网页截帧
        DrawingStrokeToolbar,                     // app 手绘工具条
        LoadingIndicator,                         // 可选,默认内置简易实现
        actions: { updateWebsiteCard, updateImageGallery, updateWhiteboard },
      }}
    >
      <MyCanvasKit /* 原有 props 不变 */ />
    </CanvasKitHostProvider>
  );
}

移动端复用全部画布(渲染 + 交互 + UI),只需提供上面这些注入项;其中 router 用一个 { pathname, query, push } stub 即可,serviceHandler 接你已实现的移动端数据库。

注入宿主能力(必做)

交互层依赖三项宿主能力,应用启动时注入一次:

import { configureCanvasHost } from "sk-wasm-surface";

configureCanvasHost({
  // 通用服务调度。交互层会调用:
  //   createDrawingStroke / updateDrawingStroke / deleteDrawingStroke / openFileInShell
  // Electron 走 window.ipc;Capacitor 走你已实现的移动端数据库或 HTTP。
  serviceHandler: (apiName, params) => myServiceHandler(apiName, params),
  // 当前身份(新建笔画等需要)
  dataGen: { spaceId: currentSpaceId, userId: currentUserId },
  // 由 action 取当前生效快捷键(含用户自定义),配合 ShortcutActions 枚举
  getShortcutKeyByAction: (action) => myShortcutMap[action],
});

⚠️ store 必须单例:画布状态(useCanvasStore)现在归本包所有。宿主务必从 sk-wasm-surface 引用 useCanvasStore,不要再保留 app 内旧的 store.ts 副本,否则会出现两个互不同步的 store 实例。zustand / react 设为 peerDependency 也是为避免重复实例。

构建

cd packages/canvaskit
npm install      # 或在仓库根用 yarn(已配置 workspaces)
npm run build        # tsup → dist/(ESM + d.ts)
npm run typecheck    # tsc --noEmit
npm run check-boundaries   # 校验无逃逸 import

已验证:tsc --noEmit 通过、边界检查通过。

在 Capacitor 移动端使用

  1. 安装 sk-wasm-surface 与 peer 依赖 canvaskit-wasm(以及 react,如使用 React 包装组件)。

  2. 拷贝 wasm 到 web 资源目录。引擎用绝对路径 /canvaskit/canvaskit.wasm 加载(见 utils/canvaskit-loader.tsCANVASKIT_BASE_PATH)。在构建脚本里把 wasm 拷到你的 webDir/canvaskit/:

    cp node_modules/sk-wasm-surface/wasm/canvaskit.wasm <webDir>/canvaskit/canvaskit.wasm
    # 然后 npx cap copy

    Capacitor 的 WebView 把 webDir 服务于 capacitor://localhost(iOS)/ https://localhost(Android),/canvaskit/canvaskit.wasm 即可解析,无需改 loader。

  3. 绘制:创建 <canvas>,用 makeSurfaceForCanvas(canvasEl) 拿到 Surface,构造 RenderContext(注入 getMindmap / getWhiteboardById / getEmbedCardData 等数据回调,数据来自你已实现的移动端数据库),调用各 renderer 绘制。

  4. 注意事项:

    • WebGL2:老设备 WebView 若不支持,MakeCanvasSurface 可能返回 null,需软件渲染兜底。
    • 触摸:交互层不在本包内,宿主接 Pointer/Touch 事件后更新数据再触发重绘。
    • viewport:画布容器建议 touch-action: none 并禁用 WebView 双指缩放/橡皮筋滚动。

基本用法

import {
  makeSurfaceForCanvas,
  engine,
  nodeRenderer,
  type RenderContext,
} from "sk-wasm-surface";

const res = await makeSurfaceForCanvas(canvasEl);
if (!res) throw new Error("WebGL/CanvasKit surface 创建失败");
const { CanvasKit, surface } = res;

surface.requestAnimationFrame((skCanvas) => {
  const ctx: RenderContext = {
    CanvasKit, skCanvas, dpi: window.devicePixelRatio,
    theme: "light", view: { translateX: 0, translateY: 0, scale: 1 },
    canvasWidth: canvasEl.width, canvasHeight: canvasEl.height,
    lodLevel: "high",
    getWhiteboardById: (id) => myDb.getWhiteboardMeta(id), // 来自移动端数据库
    // …其余数据回调
  };
  nodeRenderer.render(/* nodes */ [], ctx);
});

让现有 Electron app 改为消费本包(迁移步骤)

这些步骤涉及删除 app 内重复文件与重写 import,建议在本地带构建运行(逐步替换、随时 tsc/nextron dev 验证)。

  1. 仓库根已加 workspaces: ["packages/*"];执行一次 yarn installsk-wasm-surface 链接。
  2. 在应用启动处调用 configureCanvasHost({ serviceHandler, dataGen, getShortcutKeyByAction }) 注入宿主能力。
  3. 把 app 中对渲染层 + 交互层 + store 的本地 import 改为包引用,例如 import { useCanvasStore } from "./store"from "sk-wasm-surface"; import { useCanvasInteraction } from "./hooks/use-canvas-interaction"from "sk-wasm-surface"; import { nodeRenderer } from "./renderers/node-renderer"from "sk-wasm-surface"。 主要集中在 renderer/components/canvaskit/index.tsx
  4. 确认通过后,删除 renderer/components/canvaskit/ 下已迁入本包的目录与文件 (core/ drawers/ renderers/ layers/ animation/ rich-text/ elements/ utils/ minimap/ hooks/types.tsstore.tscanvas-undo.ts),仅保留集成层(index.tsx、各 toolbar/overlay)。
  5. store 单例:确保 app 内不再存在旧的 store.ts,所有 useCanvasStore 都来自包,避免双实例。
  6. 枚举/常量:本包内 lib/enums.ts(EPinStyle/EPinPosition)与 lib/shortcuts.ts(ShortcutActions)是 app 端 constants.tsx/shortcuts-list.tsx副本,需保持同步(已在文件注释标注)。 getShortcutKeyByAction 的实现留在 app(读用户自定义配置),通过 host 注入。

CI 建议

在 lint/CI 中加入 npm run check-boundaries,防止渲染引擎再次与 app 的 store/service/UI 耦合。

性能

画布平移/缩放的性能优化(问题定位、各项措施、最终采用的「缩放手势期栅格缓存」架构、取舍与待办)见 ZOOM-PERFORMANCE.md