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

@hy-bricks/core

v0.3.0

Published

HyperCard 运行时核心 — 编译用户源码 / CSS scope / 实例注册表 / 跨实例 SDK / JS 静态解析

Readme

@hy-bricks/core

HyperCard 低代码运行时核心 — Vue 3 SDK。把用户写的 {html, js, css} 编译成真 Vue 组件、做 CSS scope、跨实例事件总线、统一 libs 注入。

version license vue


30 秒 Hello World

pnpm add @hy-bricks/core vue element-plus axios
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import axios from 'axios'

import { createHyperCard } from '@hy-bricks/core'
import App from './App.vue'

const app = createApp(App)

app.use(
  createHyperCard({
    libs: {
      ui: ElementPlus, // SDK 自动 app.use + 挂 libs.ui
      http: axios,     // 普通对象/函数 → 只挂 libs.http
    },
  }),
)

app.mount('#app')
<!-- App.vue -->
<script setup lang="ts">
import { RuntimeBox } from '@hy-bricks/core'

const html = `<div class="hello"><el-button @click="say">点我</el-button></div>`
const js = `
  export default {
    methods: {
      say() {
        __HYPERCARD__.libs.http.get('/api/ping')
        alert('hello hypercard')
      },
    },
  }
`
const css = `.hello { padding: 16px; }`
</script>

<template>
  <RuntimeBox
    instance-id="demo-1"
    component-id="hello"
    :html-source="html"
    :js-source="js"
    :css-source="css"
  />
</template>

启动后控制台应看到:

[hypercard] 已注册 2 个 libs:
  ui               ✓ Vue plugin → 已 app.use
  http             · function

安装

pnpm add @hy-bricks/core
# peerDeps: vue ^3.5

运行时依赖(自动装):postcss mitt acorn acorn-walk

发布形态:

| 字段 | 路径 | |---|---| | main(CJS) | dist/index.cjs | | module(ESM) | dist/index.mjs | | types | dist/index.d.ts |


核心 API

Vue plugin 工厂

| API | 说明 | 用法 | |---|---|---| | createHyperCard(config) | 主入口,返回 Vue plugin | app.use(createHyperCard({ libs })) | | isVuePlugin(v) | 检测是不是 { install: fn } 对象 / [plugin, opts] 元组 | if (isVuePlugin(x)) ... | | HC_INJECT_KEY | provide/inject 的 key,值是字符串 '__HYPERCARD__' | inject(HC_INJECT_KEY) | | VERSION | SDK 包版本字符串 | '0.1.0' | | HyperCardConfig HyperCardInstance LibsRegistry | TS 类型 | 见下文 |

编译用户源码

| API | 说明 | |---|---| | compileComponent(source) | {html, js}Promise<CompiledComponent>(Vue.compile + acorn parse ExportDefaultDeclaration + new Function('module','exports', ...) CJS 跑 + LRU 缓存)| | getCompileCacheSize() | 返回缓存内编译产物个数 | | clearCompileCache() | 清缓存(切换租户 / 项目时用)| | CompiledComponent ComponentMeta ComponentSource CustomDecl | TS 类型 |

CSS scope

为同一 scopeId 的多实例共享一份带 [data-vbi-type="<scopeId>"] 前缀的 <style>,refCount 归零自动卸载。

scopeId 是稳定字符串,通常情况:

  • 编辑器场景:scopeId = componentId(API 默认)
  • 多版本画布场景:scopeId = ${componentId}@${version}(同页同组件不同版本各自隔离)

| API | 说明 | |---|---| | scopeCss(rawCss, scopeId) | 纯函数:返回带前缀的 css 字符串 | | attachScope(scopeId, rawCss) | 实例 mount:refCount++,首次注入 <style> | | detachScope(scopeId) | 实例 unmount:refCount--,归零移除 <style> | | injectScopedCss(scopeId, rawCss) | 只刷新内容,不动 refCount(watch(cssSource) 用)| | removeScopedCss(scopeId) | 强制移除(测试 / reset 用)| | getScopedCssCount() | 返回已挂载 scope 个数 | | debugScopedCssRefs() | dev 调试,返回每个 scope 的 {scopeId, componentId(deprecated alias), refCount, cssHash} |

注意:compileComponent 的 LRU 缓存仍按 sourceHash(不分 scopeId)— 编译产物只跟 html/js 有关,加 scopeId 反而降低复用率。scopeId 只管 CSS / DOM 隔离。

实例注册表

跨实例方法调用 + 事件总线。RuntimeBox 会自动完成 register / unregister。

| API | 说明 | |---|---| | register(instanceId, componentId, vm) | 注册,返回 ComponentInstanceHandle | | unregister(instanceId) | 注销,自动 dispose mitt | | getInstance(id) | 拿 handle | | listInstances({ componentId? }) | 列实例,可按 componentId 过滤 | | registryVersion | Ref<number>,register/unregister 时 +1,UI watch 它触发刷新 | | ComponentInstanceHandle | { instanceId, componentId, vm, call, on, emit, setProp, setDataInput } |

setDataInput(key, value)vm.custom[key].value(customValues / binding runtime 通路)。 跟 setProp 区分:setProp 写顶层 vm[key](host 主动改 prop);setDataInput 走组件作者声明的 custom.<key>.dataInput = true slot,canvas binding runtime 灌数据用这条。 边界:vm.custom[key] 不存在 → console.warn + no-op;slot 形状不是 { value: ... } → 同样 warn + no-op。

跨实例 runtime

__HYPERCARD__.runtime 暴露的就是它,直接 import { runtime } from '@hy-bricks/core' 也行。

| 方法 | 签名 | |---|---| | runtime.call(id, method, ...args) | 调对方 vm 上的方法 | | runtime.on(id, event, handler) | 订阅,返回 unsubscribe | | runtime.emit(id, event, payload) | 触发对方事件 | | runtime.getInstance(id) / listInstances(filter) | 查 |

assets 当前是 { pickerUrl(id) } 占位,后续接资源中心。

静态解析

import { parseComponentSource } from '@hy-bricks/core'

const parsed = parseComponentSource(jsSource)
// → { methodsDecl: [{ name, params }], emitsDecl: [{ event }], propsDecl: [{ key, value, type, name, enum }] }

acorn 静态扫 export default {} 块,抽 methods / emits / custom 字段供属性面板和补全用。

容器组件

<RuntimeBox>

实例容器:接源码 + 配置,自动跑 compile + attachScope + register 全套流程。

Props:

| Prop | 类型 | 默认值 | 说明 | |---|---|---|---| | instanceId | string | 必传 | 实例唯一 ID,注册表 + 事件总线用 | | componentId | string | 必传 | 组件 ID,CSS scope 默认键 | | scopeId | string? | componentId | CSS/DOM 隔离键。编辑器场景不传(默认=componentId);画布场景必传(如 'button@v3',同组件不同版本各自隔离) | | htmlSource | string | 必传 | 用户写的 HTML(Vue template) | | jsSource | string | 必传 | 用户写的 JS(Vue Options + 平台扩展字段) | | cssSource | string? | '' | 用户写的 CSS(PostCSS 自动加 [data-vbi-type] scope) | | customValues | Record<string, unknown>? | undefined | 覆盖 custom.*.value 的运行时值 — 画布属性面板通过它做所见即所得。不传时用 declaration 默认值(编辑器场景) | | position | { x: number, y: number }? | undefined | 绝对定位(画布场景),不传时流式布局(编辑器场景) | | size | { w: number, h: number }? | undefined | 尺寸(画布场景用) | | zIndex | number? | undefined | 层级(画布场景用) |

画布场景 vs 编辑器场景:

<!-- 编辑器场景:一个组件一个实例,不需要 scopeId -->
<RuntimeBox
  instance-id="editor-preview-1"
  component-id="cmp_abc"
  :html-source="html"
  :js-source="js"
  :css-source="css"
/>

<!-- 画布场景:同组件不同版本共存,必须传 scopeId;属性面板通过 customValues 透传 -->
<RuntimeBox
  instance-id="page-inst-1"
  component-id="cmp_abc"
  scope-id="cmp_abc@v3"
  :html-source="html"
  :js-source="js"
  :css-source="css"
  :custom-values="{ title: 'Hello' }"
  :position="{ x: 100, y: 200 }"
  :size="{ w: 320, h: 240 }"
  :z-index="1"
/>

<ErrorBoundary>

| 组件 | 作用 | |---|---| | <ErrorBoundary> | 渲染期错误兜底,接 label? |


createHyperCard 详细配置

interface HyperCardConfig {
  libs?: LibsConfig // 注入到 __HYPERCARD__.libs 的库 / 函数 / 常量,key 任意
  strict?: boolean  // 默认 false。true 时启动检测一旦 warn 就抛 Error
  version?: string  // 默认 'v1',挂在 instance.version 上,组件兼容判断用
}

libs 槽位允许的形态

createHyperCard({
  libs: {
    ui: ElementPlus,                         // ① Vue plugin 对象 → 自动 app.use,libs.ui = plugin
    ui2: [ElementPlus, { locale: zhCn }],    // ② 元组 → app.use(plugin, options),libs.ui2 = plugin 本身
    http: axios,                             // ③ 函数 → 原样挂(裸函数不当 plugin)
    constants: { brand: 'hy' },              // ④ 普通对象 → 原样挂
    formatPrice: (n: number) => `¥${n}`,     // ⑤ 函数 / 工具
  },
})

启动检测

install 里会一次性 console.info 出注册清单,例如:

[hypercard] 已注册 5 个 libs:
  ui               ✓ Vue plugin → 已 app.use
  ui2              ✓ Vue plugin (with options) → 已 app.use
  http             · function
  constants        · object
  formatPrice      · function

紧接着会 console.warn(strict: truethrow):

| 触发条件 | 解释 | |---|---| | 多个 Vue plugin 共存 | 全局组件命名可能撞车,提示自查 | | 命中保留词 | vue / Vue / window / global / globalThis / console / document / process / $ / libs / runtime / assets / version 不能作为 lib 名 | | app.use(plugin) 抛错 | 普通模式 console.error 继续,strict 模式 throw |


LibsRegistry 类型 augment(必看)

SDK 自身的 LibsRegistry 是空接口,宿主必须在自己项目里 augment 一次,IDE 才能在 __HYPERCARD__.libs.<name> 上给出补全。

新建 src/types/hypercard.d.ts:

import type { default as ElementPlus } from 'element-plus'
import type { AxiosStatic } from 'axios'

declare module '@hy-bricks/core' {
  interface LibsRegistry {
    ui: typeof ElementPlus
    http: AxiosStatic
    formatPrice: (n: number) => string
  }
}

declare global {
  interface Window {
    __HYPERCARD__?: import('@hy-bricks/core').HyperCardInstance
  }
}

export {}

确保 tsconfig.jsoninclude 覆盖到该文件。之后:

  • 宿主 setup 里 inject(HC_INJECT_KEY).libs.http. → 完整 axios 补全
  • 组件作者源码里 __HYPERCARD__.libs.formatPrice(99) → 类型校验

提示:LibsRegistry 只影响类型,运行时只看你 createHyperCard({ libs }) 实际传了啥 — augment 写漏 / 写错不会让 SDK 崩,只会少补全。


三种访问 instance 的路径

SDK 把 HyperCardInstance 同时挂在三个位置,按使用场景挑。

1. window.__HYPERCARD__ — 组件作者源码用

{html, js, css} 是字符串,跑在 new Function('module','exports', ...) 里(SDK 通过 acorn parse 把 export default 段重写成 module.exports = ,所以你只能写 export default { ... } Vue Options + 用 __HYPERCARD__.libs.* 拿运行时依赖,支持 import ... from / named export)。组件作者代码里没有 ESM import 上下文,直接拿全局:

// 组件作者写的 jsSource
export default {
  async mounted() {
    const { data } = await __HYPERCARD__.libs.http.get('/api/me')
    this.user = data
    __HYPERCARD__.runtime.emit('header', 'user-loaded', data)
  },
}

2. inject(HC_INJECT_KEY) — 宿主自己的 Vue setup

import { inject } from 'vue'
import { HC_INJECT_KEY, type HyperCardInstance } from '@hy-bricks/core'

const hc = inject<HyperCardInstance>(HC_INJECT_KEY)!
hc.runtime.call('list-1', 'refresh')

3. this.$hc — Options API

export default {
  mounted() {
    this.$hc.runtime.emit('toast', 'show', { msg: '保存成功' })
  },
}

类型扩展(ComponentCustomProperties.$hc)由 SDK 内置,import '@hy-bricks/core' 后自动生效。


Troubleshooting

实际接入时踩过的坑,按"症状 → 原因 → 解法"列。

误传函数库当 plugin(Maximum call stack size exceeded)

  • 症状:app.use(createHyperCard({ libs: { http: axios } })) 时栈溢出 / axios is not a function
  • 原因:Vue 的 app.use 看到函数会当成 legacy plugin 调,axios 又是个函数,递归自调爆栈。
  • 解法:SDK 已用 isVuePlugin(只认 { install: fn } 对象 / [obj, opts] 元组)拒掉裸函数。不要手动包 { install: axios },直接传 axios 即可,SDK 会原样挂到 libs.http。极少数老 plugin 是纯函数签名时,自己包成 { install(app) { ... } }

Tailwind v3 ignore node_modules,SDK 内 class 扫不到

  • 症状:用了 @hy-bricks/editor 后样式发飞,inspector 看到 class 没生成。

  • 原因:Tailwind v3 默认 content 不扫 node_modules,SDK dist 内 class 全被 purge。

  • 解法:宿主的 tailwind.config.js 加 preset 或扩 content:

    module.exports = {
      presets: [require('@hy-bricks/editor/tailwind-preset')],
      content: [
        './index.html',
        './src/**/*.{vue,ts,tsx}',
        './node_modules/@hy-bricks/editor/dist/**/*.{js,mjs,cjs}',
      ],
    }

@import '@hy-bricks/...' PostCSS 不识别 npm scope alias

  • 症状:@import '@hy-bricks/editor/style.css' 编译报 Can't resolve
  • 原因:PostCSS @import 走自己的解析器,不认 vite/webpack 的 npm scope。
  • 解法:在 JS 入口 import '@hy-bricks/editor/style.css'(走 bundler 的 ESM 解析),不要在 CSS 里 @import

Vite ?worker query 在 SDK build 后保留

  • 症状:宿主 dev 启动 Could not resolve ".../editor.worker?worker"
  • 原因:?worker 是 Vite 专属语法,打进 dist 后 esbuild dev 不认。
  • 解法:@hy-bricks/editor 已改成 monaco worker 工厂动态注册,直接装 npm 上的发布版本即可,宿主 dev 启动后无 query 残留。

启动后 __HYPERCARD__.libs.xxx IDE 无补全

  • 症状:类型 unknown,. 后没提示。
  • 原因:没在宿主项目 augment LibsRegistry(SDK 自身故意保持空接口,避免硬绑具体库)。
  • 解法:照 LibsRegistry 类型 augment 一节,落一份 src/types/hypercard.d.ts

配合使用 / Ecosystem


License

MIT © hy_top