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

@shangchien/mol-editor

v0.1.0

Published

RDKit-powered molecular editor kernel for Vue 3 applications.

Readme

Mol Editor

Mol Editor 是一个基于 RDKit、Vue 3、Vite 和 VRender 的分子画板内核。它的职责是提供稳定的分子编辑、文档模型、交互编排和插件 runtime;业务域能力由宿主与下游插件实现,而不是继续堆进 core。

npm package 使用

当前包面向 Vue 3 应用发布,根入口提供编辑器组件、插件 SDK、文档快照类型和 RDKit runtime 配置;mol-editor/utils 子入口提供浏览器优先的分子解析、校验和 SVG/PNG 生成函数。

pnpm add mol-editor
import { createApp, ref } from 'vue'
import { MolEditor, configureRDKit, type DocumentSnapshot } from 'mol-editor'
import 'mol-editor/style.css'

configureRDKit({ wasmUrl: new URL('mol-editor/RDKit_minimal.wasm', import.meta.url).href })

const documentSnapshot = ref<DocumentSnapshot | null>(null)
<template>
  <MolEditor
    v-model:document-snapshot="documentSnapshot"
    :options="{ width: 960, height: 640, readonly: false }"
  />
</template>

如果不需要受控模式,也可以只监听 document-change,并通过组件实例调用 getDocumentSnapshot() / restoreDocumentSnapshot() 完成保存与恢复。受控模式和现有命令式 API 会并存,方便宿主逐步迁移。

工具函数

import {
  validateSmiles,
  smilesToRdkitJson,
  smilesToSvg,
  smilesToPngBlob,
} from 'mol-editor/utils'

const validation = await validateSmiles('CCO')
if (validation.ok) {
  const svg = await smilesToSvg('CCO')
  const png = await smilesToPngBlob('CCO', { scale: 2 })
}

公开工具函数统一返回 { ok, data }{ ok, error }。SVG 返回字符串;PNG 返回 Blob,依赖浏览器栅格化环境,不承诺 Node/SSR 直接可用。

构建与发布

  • pnpm run build:lib:构建 npm package 产物与 .d.ts
  • pnpm run build:demo:构建 GitHub Pages demo
  • pnpm run pack:smoke:发布前检查 npm tarball 内容
  • GitHub Pages demo 默认按根路径 / 构建,适配自定义域;如需仓库子路径部署,可在仓库变量 PAGES_BASE_URL 中覆盖为 /mol-editor/。RDKit wasm 默认通过 Vite BASE_URL 定位;npm consumer 可用 configureRDKit({ wasmUrl }) 接管。

当前仓库的对外主入口已经是 plugin families:

  • object:定义对象类型与 live runtime
  • ui:定义工具栏、菜单、选区栏、快捷键与命令
  • workflow:定义上传、布局、持久化、分享、剪贴板、导入导出等服务

这描述的是 public surface,也已经对应到当前 kernel 主路径。当前 package 入口与 kernel 主装载器都以 plugin families 作为正式插件契约;legacy EditorPlugin / PluginContext 不再作为 public API 暴露,但内核内部仍通过 registryBridge 把 kernel 状态适配为 PluginActivateContext

当前判断

项目已经从“分子编辑器原型”进入“RDKit 分子画板内核 + 插件平台”阶段:

  • core / builtin / optional 三层装载已进入 live path
  • direct ObjectTypePlugin 已可通过 createRuntime(ctx) 接入当前 object controller
  • direct UIContributionPlugin 已可接入当前 toolbar / context menu / selection bar / shortcut 壳层,并可通过 uiComponents 渲染 leftDock / slots
  • direct WorkflowServicePlugin 已可进入 workflow registry;layout / persistence / share / clipboard / upload 已由 kernel 主路径直接消费,import / export 也可通过 registry 提供扩展实现
  • public surface 与 kernel 主装载路径都已以 direct activation + plugin families 为主线;legacy EditorPlugin / PluginContext 已退到内部适配层,plugin families 已成为唯一 public 插件契约

当前 live 矩阵

| 装载层 \ 能力族 | object | ui | workflow | |---|---|---|---| | core | molecule、connector | 默认壳层与选区工具 | 当前无 | | builtin | 默认 text、image | 当前不建议重建整套 builtin shell | 默认 workflow 服务:import / export / clipboard / layout / persistence / share | | optional | 宿主自定义对象 | 宿主自定义按钮、菜单、快捷键与命令 | 宿主覆盖 workflow 服务,如 upload、layout、持久化、分享、导入导出 |

私有项目格式

Mol Editor 的私有项目文件统一使用 .mpz 后缀。内部结构为 gzip(JSON(DocumentSnapshot)),其中 DocumentSnapshot 是当前 normalized 文档快照格式。浏览器分享链接使用同一份快照数据,并通过 ?mpz=<url-safe-base64-gzip> query 字段承载;演示页从 ?smi=?sdf= 加载后也会升级为 ?mpz= 快照链接。

运行时架构

flowchart TB
  Host["Host / Downstream App"] --> MolEditor["MolEditor.vue\nShell Entry"]
  MolEditor --> UseEditor["useEditor.ts\nKernel Orchestrator"]

  subgraph Kernel["Kernel"]
    Services["Kernel Services\nselection / gesture / commands / overlay"]
    Stores["DocumentStore + molDataStore\nsnapshot / transaction / history"]
    Scene["useScene / useMoleculeScene\nVRender stage + 5 layers"]
    Events["Editor Events"]
  end

  subgraph Plugins["Plugin Runtime"]
    Adapter["registryBridge\nPluginActivateContext adapter"]
    Registry["PluginRegistryRuntime\nobject / ui / workflow"]
    Aggregator["usePluginRegistry\nactivation + UI/object aggregation"]
  end

  subgraph Controllers["Object Controllers"]
    MolCtrl["molecule"]
    ConnCtrl["connector"]
    TextCtrl["text"]
    ImageCtrl["image"]
  end

  subgraph Shell["Editor Shell"]
    Toolbar["Toolbar / Context Menu"]
    SelectionBar["SelectionActionBar / overlays"]
    DockSlots["LeftDock / Slots / HUD"]
  end

  UseEditor --> Services
  UseEditor --> Stores
  UseEditor --> Scene
  UseEditor --> Events
  UseEditor --> Adapter
  Adapter --> Registry
  UseEditor --> Aggregator
  Aggregator --> Registry
  Aggregator --> Controllers
  Registry --> Controllers
  Registry --> Toolbar
  Registry --> SelectionBar
  Registry --> DockSlots
  Controllers --> Scene
  UseEditor --> Toolbar
  UseEditor --> SelectionBar
  MolEditor --> Toolbar
  MolEditor --> SelectionBar
  MolEditor --> DockSlots

架构评审结论

  1. 对外契约已经清晰收口到 plugin families,宿主扩展入口明确,core / builtin / optional 三层装载也已经进入当前 live path。
  2. 内核内部仍处于 controller-first 迁移阶段:useEditor.ts 负责 kernel、registry、scene、interaction 与 public API 的总装配,usePluginRegistry.ts 负责 direct runtime 激活与 UI/object 聚合,二者共同构成当前主路径。
  3. 文档模型已经部分统一到 DocumentStore,但 molecule 仍委托给 molDataStore。对于一个主打分子编辑能力、且深度依赖 RDKit 的画板来说,这个特例应保留在 chemistry / molecule store / scene build 边界;后续要收口的是它在 selection / history / workflow / shell 条件判断中的外溢,而不是强行把 molecule 压平成与所有对象完全一致的内部实现。
  4. workflow registry 现在更准确的定位是“宿主覆盖与扩展边界”:layout / persistence / share / clipboard / upload 由 kernel 直接消费,importer / exporter 则由 shell 或 workflow helper 查询调用。
  5. scene sync 已经开始向 object controller 下放,但当前驱动与兜底协调仍在 useScene / useEditor 一侧,尚未完全变成 controller 自治。

快速开始

不传 plugins 时,编辑器会保留默认 builtin 对象层。宿主通常只需要通过 optionalPlugins 追加 plugin families。

import {
  EDITOR_COMMANDS,
  MolEditor,
  type UIContributionPlugin,
  type WorkflowServicePlugin,
} from 'mol-editor'

const hostUiPlugin: UIContributionPlugin = {
  manifest: {
    id: 'host:ui.viewport',
    name: 'Host Viewport Actions',
    version: '1.0.0',
  },
  toolbar: [
    {
      id: 'host:ui.viewport.autofit',
      slot: 'top-utility',
      label: '重置视图',
      commandId: EDITOR_COMMANDS.viewAutofit,
    },
  ],
}

const hostWorkflowPlugin: WorkflowServicePlugin = {
  manifest: {
    id: 'host:workflow.upload',
    name: 'Host Upload Workflow',
    version: '1.0.0',
  },
  upload: {
    id: 'host:upload',
    accept: ['image/*'],
    async upload(file) {
      const formData = new FormData()
      formData.append('file', file, 'image.png')
      const res = await fetch('/api/upload', { method: 'POST', body: formData })
      const data = await res.json()
      return { url: data.url }
    },
  },
}
<template>
  <MolEditor
    :optional-plugins="[hostUiPlugin, hostWorkflowPlugin]"
    :options="{ width: 960, height: 640, readonly: false }"
  />
</template>

Plugin 使用案例教程

下面用一个宿主场景把最常见的 plugin 接法串起来。假设宿主想做四件事:

  1. 在 toolbar 放一个“重置视图”按钮
  2. 在 leftDock 挂一个宿主 inspector,在 HUD 放状态组件
  3. 接入 upload、share 和 exporter 这类 workflow 能力
  4. 试验一个自定义 object plugin

1. 先准备公共常量与组件映射

import { ref } from 'vue'
import {
  EDITOR_COMMANDS,
  type DescriptorCommand,
  type ObjectControllerRuntime,
  type ObjectDescriptor,
  type ObjectTypePlugin,
  type UIContributionPlugin,
  type WorkflowServicePlugin,
} from 'mol-editor'
import HostInspectorPanel from './HostInspectorPanel.vue'
import HostStatusHud from './HostStatusHud.vue'

const INSPECTOR_ICON = '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 5.5C4 4.67 4.67 4 5.5 4H18.5C19.33 4 20 4.67 20 5.5V18.5C20 19.33 19.33 20 18.5 20H5.5C4.67 20 4 19.33 4 18.5V5.5Z" stroke="currentColor" stroke-width="1.8"/><path d="M8 9H16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8 12H16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8 15H13" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>'

const uiComponents = {
  'host:inspector-panel': HostInspectorPanel,
  'host:status-hud': HostStatusHud,
}

当前壳层会直接消费 SVG 字符串,因此 iconKey 建议直接传 SVG。leftDock.panelComponentslots.component 则通过 uiComponents 做字符串到 Vue 组件的映射。

2. 案例一:UI 插件,把按钮、右键菜单、选区栏和面板挂进去

const hostCommands: DescriptorCommand[] = [
  {
    id: 'host:command.autofit',
    title: '重置视图',
    async run(ctx) {
      await ctx.services.commands.execute(EDITOR_COMMANDS.viewAutofit)
      ctx.events.emit('host:ui.command', { id: 'autofit' })
    },
  },
  {
    id: 'host:command.openInspector',
    title: '打开宿主检查面板',
    run(ctx) {
      ctx.events.emit('host:ui.command', { id: 'open-inspector' })
    },
  },
]

export const hostUiPlugin: UIContributionPlugin = {
  manifest: {
    id: 'host:ui.workspace',
    name: 'Host Workspace UI',
    version: '1.0.0',
  },
  commands: hostCommands,
  toolbar: [
    {
      id: 'host:toolbar.autofit',
      slot: 'top-utility',
      label: '重置视图',
      commandId: 'host:command.autofit',
    },
  ],
  contextMenu: [
    {
      id: 'host:context.inspect',
      label: '查看宿主面板',
      iconKey: INSPECTOR_ICON,
      commandId: 'host:command.openInspector',
    },
  ],
  selectionBar: [
    {
      id: 'host:selection.inspect',
      label: '检查选区',
      iconKey: INSPECTOR_ICON,
      visibility: ({ selectionCount, readonly }) => ({
        visible: selectionCount > 0,
        enabled: !readonly,
      }),
      commandId: 'host:command.openInspector',
    },
  ],
  leftDock: [
    {
      id: 'host:leftDock.inspector',
      label: 'Inspector',
      iconKey: INSPECTOR_ICON,
      panelComponent: 'host:inspector-panel',
    },
  ],
  slots: [
    {
      id: 'host:hud.status',
      slot: 'hud',
      component: 'host:status-hud',
    },
  ],
}

这类 UI plugin 适合做三件事:

  1. commands 连接宿主动作和内建命令
  2. visibility 读取共享选区上下文,而不是自己维护一套 UI 状态
  3. leftDockslots 把宿主 Vue 组件挂进编辑器壳层

3. 案例二:Workflow 插件,把 upload、share 和 exporter 接到宿主侧

export const hostWorkflowPlugin: WorkflowServicePlugin = {
  manifest: {
    id: 'host:workflow.io',
    name: 'Host Workflow IO',
    version: '1.0.0',
  },
  upload: {
    id: 'host:upload',
    accept: ['image/*'],
    async upload(file) {
      const formData = new FormData()
      formData.append('file', file, 'upload.bin')
      const res = await fetch('/api/upload', { method: 'POST', body: formData })
      const data = await res.json()
      return { url: data.url }
    },
  },
  share: {
    id: 'host:share',
    async share(_ctx, nodeIds) {
      const params = new URLSearchParams()
      if (nodeIds.length > 0) {
        params.set('selection', nodeIds.map(String).join(','))
      }
      return {
        url: `https://host.example.com/mol-editor/share?${params.toString()}`,
      }
    },
  },
  exporters: [
    {
      id: 'host:export.selection.json',
      mime: 'application/vnd.host.mol-editor.selection+json',
      async serialize(nodes) {
        return JSON.stringify(nodes, null, 2)
      },
    },
  ],
}

当前 workflow 边界建议这样理解:

  1. uploadsharelayoutpersistenceclipboard 会被 kernel 主路径直接读取
  2. importersexporters 通过 registry 注册,再由 shell 或 workflow helper 在需要时查询
  3. 如果宿主只需要上传,可以继续传 uploadService 便捷 prop;但长期建议仍是正式 WorkflowServicePlugin

4. 案例三:Object 插件,先做一个最小运行时骨架

type HostNotePayload = {
  kind: 'host:note'
  text: string
}

const hostNoteDescriptor: ObjectDescriptor<HostNotePayload> = {
  id: 'host:note',
  kind: 'host:note',
  family: 'leaf',
  title: 'Host Note',
  schemaVersion: 1,
}

export const hostNotePlugin: ObjectTypePlugin = {
  manifest: {
    id: 'host:note.object',
    name: 'Host Note Object',
    version: '1.0.0',
  },
  descriptors: [hostNoteDescriptor],
  createRuntime(ctx) {
    const selectedIds = ref(new Set<string>())

    const controller: ObjectControllerRuntime = {
      key: 'host:note',
      descriptor: hostNoteDescriptor,
      actions: {},
      selectedIds,
      hitObject: () => null,
      isSelected: id => selectedIds.value.has(id),
      selectObject: id => {
        selectedIds.value = id ? new Set([id]) : new Set()
      },
      clearSelection: () => {
        selectedIds.value = new Set()
      },
      getSelectedObject: () => null,
      beginDrag: () => false,
      updateDrag: () => {},
      endDrag: () => {},
      cancelDrag: () => {},
    }

    ctx.events.emit('host:object.runtime-ready', {
      key: controller.key,
      moleculeCount: ctx.molecule.groupList.value.length,
    })

    return { controllers: [controller] }
  },
}

这个骨架说明了三件事:

  1. descriptors 负责 kind 身份与生命周期契约
  2. createRuntime(ctx) 负责把 controller 接进当前 object plane
  3. 如果插件需要感知分子平面,请走稳定的 ctx.molecule 边界,而不是直接依赖内部 molDataStore 或 RDKit 运行时对象

5. 案例四:在宿主里把这些插件装起来

const optionalPlugins = [
  hostUiPlugin,
  hostWorkflowPlugin,
  hostNotePlugin,
]
<template>
  <MolEditor
    :optional-plugins="optionalPlugins"
    :ui-components="uiComponents"
    :options="{ width: 960, height: 640, readonly: false }"
    @document-change="onDocumentChange"
  />
</template>

装载时可以按下面这条规则判断:

  1. optionalPlugins 用于在 core + builtin 之后追加宿主能力,这是最常见的接法
  2. plugins 用于替换 builtin 层,只有当你想完全接管默认 text / image / workflow 时才需要
  3. uiComponents 只负责把字符串组件 ID 解析成真实 Vue 组件,不参与 plugin 注册

三类插件分别负责什么

ObjectTypePlugin

用于新增或接管一种文档对象类型。当前 public 形态由两部分组成:

  1. descriptors:定义 kind、schema 和对象级生命周期
  2. createRuntime(ctx):返回 object controllers,通过标准 runtime 接口把对象接入当前命中、选区、拖拽、工具和 scene sync 主路径

UIContributionPlugin

用于扩展当前编辑器壳层:

  • toolbar
  • contextMenu
  • selectionBar
  • leftDock
  • shortcuts
  • commands
  • slots

当前 leftDock.panelComponentslots.component 会通过 MolEditoruiComponents 映射解析为真实 Vue 组件;toolbar 六个位置也已直接按 toolbar layout model 渲染,legacy toolbar slot 名只在 compatibility boundary 归一化。

WorkflowServicePlugin

用于连接宿主与外部世界:

  • upload
  • layout
  • persistence
  • share
  • clipboard
  • importers
  • exporters

workflow registry 负责提供宿主覆盖与扩展入口。当前 layout / persistence / share / clipboard / upload 已由 kernel 主路径直接消费;import / export 也可通过 registry 提供 importer / exporter,并在 shell 与 workflow helper 中被查询调用。

当前缺口

  1. useEditor.ts 仍承担较大的交互装配职责,后续仍需继续压缩 orchestration 与 interaction wiring 的耦合面
  2. molecule 会继续保留 molDataStore + RDKit 的特化路径,但 selection snapshot、kernel services 与 shell 判断仍需减少对这层特化的外溢依赖
  3. scene helper 与 workflow helper 仍有内部协调边界,需要继续向 registry / controller-first 主路径收口
  4. 仍需补齐更高层组件级与集成测试基线

文档导航

开发命令

pnpm install
pnpm run dev
pnpm run typecheck
pnpm run test:run

下一阶段重点

  1. 继续压缩 workflow / scene helper 的内部耦合,进一步降低宿主与插件作者对内核实现细节的感知