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

open-feishu-card

v0.5.0

Published

独立、零依赖的飞书 (Lark) Universal Card 渲染器,支持 Node/SSR 与浏览器,输入飞书 RAW 卡片数据或公开 DSL(JSON Schema 2.0,内置 DSL→RAW 转换),输出 HTML 字符串或 DOM。

Readme

open-feishu-card

独立、零运行时依赖的飞书 (Lark) Universal Card 渲染器。

  • ✅ 输入飞书 RAW 卡片数据(服务端归一化后的 json_card),输出 HTML 字符串DOM
  • ✅ 同时适用于 Node / SSR浏览器
  • ✅ TypeScript 编写,自带类型定义
  • ✅ ESM + CJS 双格式
  • ✅ header 高光纹理图已 base64 内联,无外部资源依赖

渲染规则(颜色、字号、间距、header 模板色等)均逆向自网页版飞书引擎,力求视觉还原。

安装

npm i open-feishu-card
# 或 pnpm add open-feishu-card / yarn add open-feishu-card

使用

Node / SSR —— 渲染成 HTML 字符串

import { renderCardToString } from 'open-feishu-card';

const html = renderCardToString(cardRawJson);
// 把 html 塞进页面/邮件/截图服务即可

样式表需一并引入(任选其一):

// 打包器里直接 import
import 'open-feishu-card/styles.css';
<!-- 或在 HTML 里链接构建产物 -->
<link rel="stylesheet" href="node_modules/open-feishu-card/dist/styles.css" />

浏览器 —— 渲染成 DOM

import { renderCard } from 'open-feishu-card';
import 'open-feishu-card/styles.css';

const el = renderCard(cardRawJson);       // 返回 Node
document.getElementById('app')!.appendChild(el);

API

| 导出 | 说明 | |---|---| | renderCardToString(card, options?) | 渲染为 HTML 字符串(Node/SSR,同步) | | renderCard(card, options?) | 渲染为 DOM 节点(浏览器;Node 端会抛错,同步) | | renderDslToString(dsl, options?) | 输入公开 DSL,渲染为 HTML 字符串(= renderCardToString(dslToRaw(dsl))) | | renderDsl(dsl, options?) | 输入公开 DSL,渲染为 DOM 节点 | | dslToRaw(dsl) | 公开 DSL(JSON Schema 2.0)→ RAW(纯函数、同步、零依赖) | | parseMarkdown(content) | markdown 字符串 → RAW 子元素树(dslToRaw 内部用,也可单独调) | | renderCardToStringAsync(input, options?) | 异步渲染 HTML 字符串,支持用 token 自动拉图 | | renderCardAsync(input, options?) | 异步渲染 DOM 节点,支持用 token 自动拉图 | | fetchCardMessage(messageId, options) | 按 message_id 拉取整条卡片消息(含 json_card/json_attachment) | | registerComponent(tag, fn) | 注册/覆盖某个 tag 的渲染组件 | | getComponent(tag) | 取某 tag 的组件 | | h(tag, props, ...children) | 轻量 VNode 构造器(写自定义组件用) | | HEADER_BG_LIGHT / HEADER_BG_DARK | header 高光纹理的 base64 data URI | | ICON_FONT_CSS | 图标字体完整 CSS(@font-face 内联 base64 + 每图标 content 规则),用于自助注入 | | ICON_CODEPOINTS | 图标名 -> 私有区码点 映射(如 warning_outlined: "fa0c"),共 2484 个 |

RenderOptions

interface RenderOptions {
  document?: Document;            // 浏览器 document(默认取全局)
  warnOnUnknownTag?: boolean;     // 未注册 tag 时打印警告
  resolveImage?: (p) => string;   // 自定义图片地址解析
  imageBaseURL?: string;          // 最终 src = imageBaseURL + imageID
}

输入格式:RAW(json_card

接受 { json_card: {...} }(飞书 card.raw.json 顶层)或直接传 json_card 对象。 元素统一形如 { tag, id?, property },正文在 body.property.elements

注:开发者编写的 DSL 格式(body.elements + markdown 字符串)也可直接渲染, 见下方「输入格式:DSL」——内部由 dslToRaw 转成 RAW,无需经飞书服务端。

输入格式:DSL(公开 JSON Schema 2.0)

开发者手写、im.message.create 用来发卡片的那套公开 DSL 也能直接渲染:

import { renderDslToString } from 'open-feishu-card';
import 'open-feishu-card/styles.css';

const html = renderDslToString({
  schema: '2.0',
  header: { template: 'green', title: '标题' },     // 文本可直接写字符串
  body: {
    elements: [
      { tag: 'markdown', content: '**加粗** 与 `代码` 与 [链接](https://feishu.cn)' },
      { tag: 'hr' },
    ],
  },
});

与 RAW 的差异(dslToRaw 负责抹平,纯函数、同步、零依赖):

  • markdown 是字符串content: "**x**"),内部解析成 RAW 的子元素树(heading/list/link/code_span/<font>/<at>/<text_tag> 等);
  • 元素字段扁平、snake_casetext_align / background_style / user_id …),转成渲染器认得的形态;
  • padding/margin 是 CSS 简写字符串"12px 8px"),转成四向对象;
  • 未知 tag / 字段原样透传(配合 warnOnUnknownTag 温和跳过)。

已覆盖 markdown 常用子集与本渲染器支持的全部 tag;table、嵌套 form 等罕见构造走透传, 后续按需补齐。需要「飞书能接收的合法 raw envelope」而非「能渲染的形态」时,仍应走真实发送链路。

支持的 tag

| 分类 | tag | |---|---| | 结构 | bodycard_headerdivnote | | 布局 | column_setcolumn | | 文本 | markdownplain_textbrhr | | markdown 子元素 | headingblockquotelistcode_blockcode_spanemoji | | 内容/交互 | imgbuttonactioninteractive_containeratat_alllinktext_tag | | 图标 | standard_iconud_icon(见下方「图标」一节) |

未注册的 tag 会被温和跳过(传 { warnOnUnknownTag: true } 可打印警告)。

图标

standard_icon / ud_icon 复刻网页版飞书的图标字体机制:空的 <i class="ud-icon_<token>"> 靠内联 woff2 字体的 :before{content} 画出 glyph。property 取 token(如 warning_outlined)、 sizesmall/normal/large1~large4,默认 16px)、color(命名色 red/green/blue… 或直接 #hex/rgb();缺省继承父级文字色)。*_colorful 结尾的彩色图标字体自带色,不受 color 覆盖。

字体(2484 个图标、约 160KB woff2)已 base64 内联,零外部依赖:

  • 浏览器(renderCard:首次渲染图标时自动注入一次 <style>,无需额外引样式。

  • Node / SSR(renderCardToString:无 DOM 不自动注入,需引入图标字体 CSS:

    import 'open-feishu-card/icon-font.css';            // 打包器
    <link rel="stylesheet" href="node_modules/open-feishu-card/dist/icon-font.css" />

    或在运行时把导出的 ICON_FONT_CSS 字符串自行注入页面。

更新图标字体:跑 node scripts/gen-icon-font.mjs(从飞书资源重新抽取,重写 src/icon-font.ts + src/icon-font.css)。

图片地址解析

RAW 里图片通常只有 imageID(飞书图床 key),不是直链:

renderCardToString(card, {
  resolveImage: (p) => myCDN(p.imageID),   // 完全自定义
  // 或
  imageBaseURL: 'https://my-host/img/',    // 拼前缀
});

若 property 自带 src/url 则直接用;都没有时渲染为占位框。

异步渲染 / 自动拉图

飞书 RAW 卡片里的 img 元素只带局部序号 imageID(如 "19"),不是图片直链;真实图床 key(img_v3_...)在消息的 json_attachment.images 里,需要再用 access token 调接口换二进制。renderCardToStringAsync / renderCardAsync 把这套流程内置了:

  • 传了 accessToken → 渲染器自动拉取图片并内联为 base64 data URI
  • 不传 accessToken → 图片渲染成占位图(其余内容照常渲染)。

输入有两种形态:

import { renderCardToStringAsync } from 'open-feishu-card';

// 形态 1:只给 message_id —— 渲染器自己把整条消息拉回来再渲染(需 token)
const html1 = await renderCardToStringAsync('om_xxx', { accessToken: 't-xxx' });

// 形态 2:已有卡片数据(接口 content 解包后的形态)—— 直接渲染
// 传 json_attachment + messageId + token 才能内联图片;否则图片走占位
const html2 = await renderCardToStringAsync(
  { json_card, json_attachment, messageId: 'om_xxx' },
  { accessToken: 't-xxx' },
);

只传 message_id 但缺 accessToken 会抛错——拉取消息本身就需要鉴权。

AsyncRenderOptions

RenderOptions 基础上扩展:

interface AsyncRenderOptions extends RenderOptions {
  accessToken?: string;   // 可选:有则拉图内联,无则占位
  messageId?: string;     // 输入是卡片对象时,用它拼图片接口 URL
  fetch?: typeof fetch;   // 注入自定义 fetch(默认全局 fetch;便于代理 / 单测 mock)
  apiBaseURL?: string;    // 默认 https://open.feishu.cn/open-apis/im/v1
}

运行环境

  • 异步功能依赖全局 fetch(Node 18+ / 现代浏览器原生支持)。Node 16 用户:同步渲染不受影响;异步功能请升级运行时,或通过 options.fetch 注入实现。
  • 浏览器场景注意 CORS:浏览器直连 open.feishu.cn 会被跨域拦截,且会暴露 token。正确做法是让服务端做代理转发(用 apiBaseURL 指向你的代理),或在服务端预渲染(renderCardToStringAsync)后把 HTML 下发给前端。

扩展自定义元素

import { registerComponent, h } from 'open-feishu-card';

registerComponent('my_widget', (el, ctx) =>
  h('div', { className: 'my-widget' }, el.property?.content ?? '')
);

暗色主题

给根节点加 theme-dark,或自行覆盖样式变量:

const el = renderCard(card);
el.classList.add('theme-dark');

开发

pnpm install
pnpm build        # tsup 产出 dist/(esm + cjs + d.ts + styles.css)
pnpm test         # node --test(含异步拉图的 mock-fetch 测试,无需联网、无需凭据)
pnpm example      # 渲染示例到 examples/output.html

测试用注入的 mock fetch,不需要任何凭据或联网

pnpm example 联网拉取真实卡片并自动内联图片——把凭据放进 .env(已被 gitignore):

cp .env.example .env
# 编辑 .env,填入自建应用的 App ID / Secret(开发者后台 -> 凭证与基础信息)
#   FEISHU_APP_ID=cli_xxx
#   FEISHU_APP_SECRET=xxx
pnpm example     # 自动用 App ID/Secret 换取 tenant_access_token,无需手动给 token

不配 .env 也能跑:用内置兜底卡片,图片渲染成占位图。凭据解析优先级:FEISHU_TOKEN(直接给的 token)> App ID + Secret 自动换取。

License

MIT