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

@axhub/annotation

v1.0.20

Published

Read-only annotation runtime for Axhub prototype pages.

Readme

@axhub/annotation

@axhub/annotation 是一个面向原型页面的标注运行时包,负责加载标注数据并用页面 marker 与点击后的标注气泡渲染出来。运行时默认会为没有稳定属性的业务节点补充临时 data-testid,帮助宿主的通用定位器支持手动标注;该属性只存在于当前页面,不会写回原型源码。

它可以脱离 axhub-make 单独集成到宿主页面:React 18 项目使用 AnnotationViewer 或 createAnnotationViewer 的 ESM 入口;普通 HTML、React 17、Vue、Angular 或托管注入场景使用包内自带的 standalone browser bundle。

1.0.20 发布内容

  • 目录支持独立的 documents 文档目录,Markdown 与可执行 HTML 文档可以在同一阅读器内切换,并支持相对文档链接、页内锚点和 #axhub-target: 目标定位。
  • Markdown 阅读器改用 XMarkdown 适配器,支持 Mermaid 图表、语法高亮和中文 Mermaid locale;这些运行时依赖会随模块和 standalone browser bundle 一起打包。
  • 增加跨页面目标路由、locator 校验和 CSP nonce 支持,React 与普通 HTML 宿主共用同一套目标定位协议。
  • HTML 文档在隔离 iframe 中执行脚本,文档导航通过运行时桥接,不直接访问父页面 DOM。

功能边界

适合:

  • 已经有标注数据,希望在页面运行时展示
  • 需要同时支持 React 组件接入和实例化接入
  • 需要一套统一的 JSON 数据源,同时兼容不同加载方式

不适合:

  • 浏览器内编辑、保存、回写标注
  • 扫描页面自动生成标注数据(运行时提示属性不等同于标注数据)
  • 把 editor / devtools 能力当成稳定公开接口

快速开始

最小 React 示例,直接把同一份 JSON 导入到 TS:

import { AnnotationViewer, type AnnotationSourceDocument } from '@axhub/annotation';
import annotationSource from './annotation-source.json';

const source = annotationSource as AnnotationSourceDocument;

export function DemoPage() {
  return (
    <AnnotationViewer
      source={source}
      defaultVisible
    />
  );
}

如果宿主需要延迟组装数据,可以传函数:

import { AnnotationViewer, type AnnotationSourceDocument } from '@axhub/annotation';
import annotationSource from './annotation-source.json';

function loadSource(): AnnotationSourceDocument {
  return annotationSource as AnnotationSourceDocument;
}

export function DemoPage() {
  return <AnnotationViewer source={loadSource} defaultVisible />;
}

本地演示

包内提供两个可预览示例,统一放在 examples/:

  • examples/react:React 宿主示例,使用 AnnotationViewer 组件接入。
  • examples/html:普通 HTML 宿主示例,页面主体用原生 DOM,通过 window.AxhubAnnotation.createAnnotationViewer 接入自托管 browser bundle。

启动命令:

pnpm examples:react
pnpm examples:html

两个示例共享 examples/shared/annotation-source.json,覆盖 Markdown 标注、短标注、多颜色筛选、同一节点多条标注、状态标注 controls、页面目录 route/markdown/link、独立文档目录以及可执行 HTML 文档等能力,并提供运营后台首页、订单列表、订单详情三个可切换页面,方便体验文章默认侧边栏阅读、全屏切换和原型缩放。

目录 Markdown 文档支持用带定位图标的目标链接定位当前原型页面元素,例如 [查看订单表格](#axhub-target:orders-table)。运行时会拦截 #axhub-target:<节点 id>;当前 pageId 内的目标有标注内容时打开标注气泡,targets 中声明但不在 data.nodes 的普通元素只显示定位轮廓。目标属于其他页面或不存在时保持当前页面,不触发宿主路由,也不执行浏览器原生 hash 跳转。targets 数组保持纯数据,不放回调,因此同一份 JSON 可供 React、原生 HTML 和其他页面系统复用。

目录文章之间也可以继续使用标准 Markdown 相对链接,例如 [财务结算](./prd-04-finance.md)。当当前文章和目标文章都声明了可唯一匹配的 markdownPath 时,运行时会在阅读器内切换文章;外部链接、页内锚点、无法匹配或存在重复路径的链接仍保持普通链接行为。

文档目录也支持 type: "html"。HTML 文档在隔离的 iframe 中使用 sandbox="allow-scripts" 渲染,因此普通 JavaScript、canvas 图表、动画和页面内交互可以直接执行;HTML 不会直接访问父页面 DOM。HTML 中的标准 #anchor 链接会通过运行时桥接,由阅读器像 Markdown 锚点一样控制文档内跳转;#axhub-target:<id> 会复用 Markdown 的原型目标定位能力,点击后可以打开标注或显示普通目标的定位轮廓。相对 .md、.markdown、.html、.htm 链接会在文档目录内切换文章,未匹配的外部链接保持浏览器默认行为。

ESM 运行时依赖:

  • 宿主提供 React 18
  • 宿主提供 ReactDOM 18
  • Ant Design 运行时样式与组件依赖由 @axhub/annotation 包内打包携带
  • 页面里能通过 ElementLocator 找到目标元素

AnnotationViewerOptions.enableTargetHints 默认为 true。设为 false 可以关闭运行时提示属性注入;启用时优先复用唯一的 id、data-testid、name、aria-label 等稳定属性,对没有可唯一定位属性的节点补充提示,并自动跟踪后续动态插入的节点。

standalone browser bundle 不要求宿主提供 React、ReactDOM 或 antd。pnpm build 会生成 dist/browser/axhub-annotation.global.js 和 sourcemap,用户可以把该文件从 npm 包中复制到自己的静态资源服务器、内网 CDN、OSS 或任意 web server 后用普通 <script> 注入。

<script src="/static/vendor/axhub-annotation/axhub-annotation.global.js"></script>
<script>
  const viewer = window.AxhubAnnotation.createAnnotationViewer({
    source: () => fetch('/annotation-source.json').then((response) => response.json()),
  });

  void viewer.start();
</script>

推荐接入方式

推荐只维护一套 JSON 数据源,推荐 wire format 为 AnnotationSourceDocument。运行时不再按 URL 请求 JSON;请把 annotation-source.json 通过静态 import 或宿主内部函数组装进页面代码。

推荐目录结构:

demo-page/
├── index.tsx
├── annotation-source.json
└── assets/
    ├── hero-card.png
    └── order-table.png

推荐 JSON 示例:

{
  "documentVersion": 1,
  "format": "axhub-annotation-source",
  "presentation": {
    "layerSelectors": [".example-modal-layer[data-open=\"true\"]"]
  },
  "data": {
    "version": 2,
    "prototypeName": "ref-app-home",
    "pageId": "home",
    "updatedAt": 1710000000000,
    "nodes": [
      {
        "id": "hero-card",
        "index": 1,
        "title": "Hero Card",
        "locator": {
          "selectors": ["[data-testid=\"hero-card\"]"],
          "fingerprint": "div|hero-card",
          "path": [0, 1, 0]
        },
        "aiPrompt": "说明首屏卡片的结构和状态",
        "annotationText": "",
        "hasMarkdown": true,
        "color": "#1677ff",
        "images": [],
        "createdAt": 1710000000000,
        "updatedAt": 1710000000000
      }
    ]
  },
  "markdownMap": {
    "hero-card": "# Hero Card\n\n这里放该节点的详细说明。"
  },
  "assetMap": {},
  "directory": {
    "nodes": [
      {
        "type": "folder",
        "id": "guide",
        "title": "演示目录",
        "defaultExpanded": true,
        "children": [
          {
            "type": "route",
            "id": "route-empty",
            "title": "切换缺省态",
            "route": "orders:empty",
            "payload": { "status": "empty" }
          },
          {
            "type": "link",
            "id": "external-doc",
            "title": "外部文档",
            "href": "https://example.com/docs",
            "target": "blank"
          },
          {
            "type": "markdown",
            "id": "readme",
            "title": "目录说明",
            "readerMode": "full",
            "markdown": "# 目录说明\n\n这里会在目录左侧阅读面板中展示。"
          }
        ]
      }
    ]
  }
}

presentation.layerSelectors 可声明当前页面的前景层根节点;打开层存在时,仅展示该层内部目标的 marker 和已选标注气泡。选择器应匹配包含弹窗内容的活动容器。

AnnotationNode 关键字段建议这样理解:

  • id:节点稳定标识,建议长期不变
  • annotationText:短标注,仅用于 hasMarkdown: false 的节点
  • hasMarkdown:是否使用 Markdown 文档作为唯一标注正文
  • title:节点标题,建议直接写入数据源,不依赖运行时补全

JSON 目录

directory.nodes 用于驱动“页面目录”面板。目录支持四类节点:

  • folder:多级文件夹,defaultExpanded 控制初始展开。
  • route:点击时调用 options.onDirectoryRoute(node),运行时不强制跳转。
  • link:target: "blank" 新窗口打开,target: "self" 当前页打开。
  • markdown:使用内联 markdown,点击后在目录左侧打开阅读面板。可选 readerMode: "full" | "split" 配置这篇文章首次打开时的全屏或分屏状态;省略时默认为 split。
  • html:使用内联 html,点击后在目录左侧打开可执行 HTML 阅读面板;可选 htmlPath 记录源文件路径,readerMode 与 Markdown 相同。

分屏阅读新打开文章默认使用 420px 宽度(可用空间不足时使用全部可用宽度);拖动分隔线后文章最大扩展到 80%,原型区域最多占 60%。文章实际阅读区域宽度不超过 820px 时使用紧凑排版,超过 820px 后恢复宽屏文章排版。

页面目录中的页面缩放按视口宽度渲染:完整显示会自动适配剩余宽度,电脑稿、平板稿和手机稿预设分别使用 1440px、1024px 和 390px 的宽度;也可以设置 320px~2560px 的自定义宽度,或选择原始尺寸并允许横向滚动。

markdownPath 可以作为 Make 原型的源文件约定,例如 docs/prd-03-status.md。@axhub/annotation 运行时只消费已经内联好的 markdown 正文,不会按路径 fetch .md 文件;它只用规范化后的路径在当前目录内解析文章间的相对链接。运行时也不会内置文档编辑按钮,Make 的文档编辑入口由批注工具按当前选中的目录 Markdown 正文元素注入到批注气泡卡片。

HTML 节点需要把完整正文内联到 html 字段,htmlPath 只作为源文件位置和相对文档链接解析依据,运行时不会按路径 fetch 文件。HTML 通过 iframe 隔离并允许脚本执行;不应依赖 HTML 脚本访问父页面对象,原型目标跳转请使用 #axhub-target:<id>。

如果文档需要和原型页面平级、并在页面切换时保持独立入口,可以使用顶层 documents:

{
  "documents": {
    "nodes": [
      {
        "type": "folder",
        "id": "product-docs",
        "title": "产品文档",
        "children": [
          {
            "type": "markdown",
            "id": "prd-status",
            "title": "状态说明",
            "markdownPath": "docs/prd-status.md",
            "markdown": "# 状态说明\n\n文档正文"
          }
        ]
      }
    ]
  }
}

documents 只接受 folder、markdown 和 html 节点,驱动工具条中的“文档目录”入口;它与 directory(“页面目录”)分别配置且互不改写。两个入口同时最多打开一个,但切换入口不会关闭当前文章;打开文档目录时只展示目录树,不会自动选中文章。未提供 documents 时,既有 directory 数据保持原有行为。

路由节点的推荐做法是让宿主自行决定行为:可以切 SPA 路由、改 URL、切换页面状态,或替换数据源展示缺省/异常/活动态。程序化 selectTarget 的跨页回调与目录 route 共用同一套 pageId 页面上下文;React 宿主更新 currentPageId 即可,普通 HTML 宿主在渲染新 DOM 后调用 viewer.refresh()。

  • controls:节点关联的属性调整项,属于标注内容的一部分
  • markdownMap[nodeId]:Markdown 标注正文,仅用于 hasMarkdown: true 的节点
  • images:节点引用的资源文件名,仅用于 hasMarkdown: false 的节点
  • assetMap[filename]:资源文件名到 URL 的映射

hasMarkdown: true 与短标注/图片展示互斥:运行时会直接显示 markdownMap[nodeId] 的内容,不再显示 annotationText,也不会展示该节点的 images。需要配图时请把图片语法写进 Markdown 正文。

更详细的目录、字段和加载说明见 推荐 JSON 加载。

全局数据快照

运行时加载成功后,会在页面全局输出 window.__AXHUB_ANNOTATION_SOURCE__:

interface AnnotationSourceRuntimeSnapshot {
  directory: AnnotationDirectory | null;
  documents: AnnotationDocumentDirectory | null;
  nodes: AnnotationNode[];
  targets?: AnnotationElementTarget[];
  presentation?: AnnotationSourcePresentation;
  source?: {
    root?: string;
    manifest?: string;
  };
}

interface AnnotationDocumentDirectory {
  nodes: AnnotationDocumentDirectoryNode[];
}

type AnnotationDocumentDirectoryNode =
  | {
      type: 'folder';
      id: string;
      title: string;
      defaultExpanded?: boolean;
      children?: AnnotationDocumentDirectoryNode[];
    }
  | AnnotationDirectoryMarkdownNode
  | AnnotationDirectoryHtmlNode;

这个快照用于给宿主、调试工具或 Agent 读取当前页面目录和全部标注节点。它会在 viewer 停止、卸载或没有可展示内容时清除;消费方应把它当作只读数据,不要依赖修改该对象来影响运行时。

当发布 HTML 勾选“带源码”且页面实际使用了 @axhub/annotation 时,发布产物会注入 sourceReference,快照会额外包含 source,其中 manifest 指向发布包内的 source/manifest.json。普通宿主不需要手写这个字段。

运行时控制器与侧边栏注入

viewer 启动后会同时暴露 window.__AXHUB_ANNOTATION_RUNTIME__,用于宿主或托管页注入脚本在页面运行后读取运行时状态、调整工具栏配置,并向侧边栏注入自定义动作。

const runtime = window.__AXHUB_ANNOTATION_RUNTIME__;
const action = document.querySelector('#my-annotation-action');

if (action) {
  runtime?.configure({
    toolbarAutoHide: false,
    toolbarActions: {
      title: '安全提示',
      content: action,
    },
  });
}

自定义动作固定显示在侧边栏“标注配置”按钮之后、“设置”按钮之前。toolbarActions 只接受 { title?, content } 或数组;title 可选,传入时运行时统一套和“页面目录 / 设置”一致的 Tooltip,不传时只渲染 content。content 可以是 DOM Element 或 React 节点,宿主自己负责图标、点击和业务弹窗。React 宿主如果需要根据当前页面或标注数量渲染,可继续使用 AnnotationViewerOptions.renderToolbarActions(context) 或全局 configure({ renderToolbarActions })。

runtime.metadata / runtime.getMetadata() 会返回 packageName、runtimeVersion、features、当前页面、当前页标注数量、侧边栏显示状态与是否已注册自定义动作,可用于发布前检测或注入脚本识别。控制器还提供 openDirectoryArticle、closeDirectoryArticle、setDirectoryPanelOpen、setDirectoryReaderMode、selectAnnotation 和 selectTarget,适合宿主在进入页面时自动打开文章或定位正文元素。该控制器只提供通用运行时协议;Axhub 托管安全提示、举报、业务 URL、埋点和弹窗内容应由宿主或注入脚本自行实现。

批注适配约定

标注运行时使用 #__axhub_annotation_host__ 的 open Shadow Root。为了让 axhub-genie-editor 可以稳定批注标注组件内部内容,标注组件会在明确可批注的内部元素上声明:

data-axhub-annotation-comment-target="true"

当前声明范围包括目录节点按钮、目录阅读器的正文 Markdown、目录阅读器右侧 TOC 按钮、标注气泡正文 Markdown。批注侧只在 #__axhub_annotation_host__ 的 Shadow 命中栈内识别这个属性;如果命中的是被声明元素的子节点,会回到最近的声明祖先。不要依赖批注侧按尺寸、样式或 DOM 层级猜测具体目标。

兼容接入方式

当前包仍兼容现有对象直传和 AnnotationStorage 模式。

legacy AnnotationStorage 示例:

import { createAnnotationViewer, type AnnotationStorage } from '@axhub/annotation';

const storage: AnnotationStorage = {
  load: async () => null,
  loadAnnotationsMd: async () => null,
  loadMarkdown: async (_nodeId) => null,
  getAssetUrl: (filename) => `/runtime/assets/${filename}`,
};

const viewer = createAnnotationViewer({
  prototypeName: 'ref-app-home',
  pageId: 'home',
  storage,
});

void viewer.start();

兼容模式仍可继续使用,但不再是主推荐路径。字段映射和迁移方式见 Legacy MD / Storage 兼容说明。

公开接口导航

当前“只读标注能力”建议直接使用这些根入口接口:

  • AnnotationViewer
  • createAnnotationViewer
  • AnnotationSourceInput
  • AnnotationSource
  • AnnotationSourceDocument
  • AnnotationData
  • AnnotationNode
  • AnnotationSourceRuntimeSnapshot
  • AnnotationElementTarget
  • AnnotationRuntimeController
  • AnnotationRuntimeMetadata
  • AnnotationRuntimeConfigureOptions
  • AnnotationToolbarActionItem
  • AnnotationToolbarActionsContent
  • AnnotationToolbarActionsRenderer
  • AnnotationToolbarActionsContext
  • AnnotationTargetRouteContext
  • AnnotationViewerOptions
  • AnnotationViewerApi
  • ElementLocator
  • validateAnnotationSource
  • validateAnnotationRuntime
  • AnnotationLocatorValidationReport
  • AnnotationRuntimeValidationOptions
  • parseAnnotationsMarkdown
  • parseAnnotationMarkdownRecords
  • serializeAnnotationsMarkdown
  • buildAnnotationPrompt
  • AnnotationStorage

详细签名和最小示例见 API Reference。

深入文档