@hy-bricks/core 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hy_top
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,422 @@
1
+ # @hy-bricks/core
2
+
3
+ > HyperCard 低代码运行时核心 — Vue 3 SDK。把用户写的 `{html, js, css}` 编译成真 Vue 组件、做 CSS scope、跨实例事件总线、统一 libs 注入。
4
+
5
+ [![version](https://img.shields.io/npm/v/@hy-bricks/core.svg)](https://www.npmjs.com/package/@hy-bricks/core)
6
+ [![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
7
+ [![vue](https://img.shields.io/badge/vue-%5E3.5-42b883)](https://vuejs.org/)
8
+
9
+ ---
10
+
11
+ ## 30 秒 Hello World
12
+
13
+ ```bash
14
+ pnpm add @hy-bricks/core vue element-plus axios
15
+ ```
16
+
17
+ ```ts
18
+ // main.ts
19
+ import { createApp } from 'vue'
20
+ import ElementPlus from 'element-plus'
21
+ import 'element-plus/dist/index.css'
22
+ import axios from 'axios'
23
+
24
+ import { createHyperCard } from '@hy-bricks/core'
25
+ import App from './App.vue'
26
+
27
+ const app = createApp(App)
28
+
29
+ app.use(
30
+ createHyperCard({
31
+ libs: {
32
+ ui: ElementPlus, // SDK 自动 app.use + 挂 libs.ui
33
+ http: axios, // 普通对象/函数 → 只挂 libs.http
34
+ },
35
+ }),
36
+ )
37
+
38
+ app.mount('#app')
39
+ ```
40
+
41
+ ```vue
42
+ <!-- App.vue -->
43
+ <script setup lang="ts">
44
+ import { RuntimeBox } from '@hy-bricks/core'
45
+
46
+ const html = `<div class="hello"><el-button @click="say">点我</el-button></div>`
47
+ const js = `
48
+ export default {
49
+ methods: {
50
+ say() {
51
+ __HYPERCARD__.libs.http.get('/api/ping')
52
+ alert('hello hypercard')
53
+ },
54
+ },
55
+ }
56
+ `
57
+ const css = `.hello { padding: 16px; }`
58
+ </script>
59
+
60
+ <template>
61
+ <RuntimeBox
62
+ instance-id="demo-1"
63
+ component-id="hello"
64
+ :html-source="html"
65
+ :js-source="js"
66
+ :css-source="css"
67
+ />
68
+ </template>
69
+ ```
70
+
71
+ 启动后控制台应看到:
72
+
73
+ ```
74
+ [hypercard] 已注册 2 个 libs:
75
+ ui ✓ Vue plugin → 已 app.use
76
+ http · function
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 安装
82
+
83
+ ```bash
84
+ pnpm add @hy-bricks/core
85
+ # peerDeps: vue ^3.5
86
+ ```
87
+
88
+ 运行时依赖(自动装):`postcss` `mitt` `acorn` `acorn-walk`。
89
+
90
+ 发布形态:
91
+
92
+ | 字段 | 路径 |
93
+ |---|---|
94
+ | `main`(CJS) | `dist/index.cjs` |
95
+ | `module`(ESM) | `dist/index.mjs` |
96
+ | `types` | `dist/index.d.ts` |
97
+
98
+ ---
99
+
100
+ ## 核心 API
101
+
102
+ ### Vue plugin 工厂
103
+
104
+ | API | 说明 | 用法 |
105
+ |---|---|---|
106
+ | `createHyperCard(config)` | 主入口,返回 Vue plugin | `app.use(createHyperCard({ libs }))` |
107
+ | `isVuePlugin(v)` | 检测是不是 `{ install: fn }` 对象 / `[plugin, opts]` 元组 | `if (isVuePlugin(x)) ...` |
108
+ | `HC_INJECT_KEY` | provide/inject 的 key,值是字符串 `'__HYPERCARD__'` | `inject(HC_INJECT_KEY)` |
109
+ | `VERSION` | SDK 包版本字符串 | `'0.1.0'` |
110
+ | `HyperCardConfig` `HyperCardInstance` `LibsRegistry` | TS 类型 | 见下文 |
111
+
112
+ ### 编译用户源码
113
+
114
+ | API | 说明 |
115
+ |---|---|
116
+ | `compileComponent(source)` | `{html, js}` → `Promise<CompiledComponent>`(`Vue.compile` + acorn parse `ExportDefaultDeclaration` + `new Function('module','exports', ...)` CJS 跑 + LRU 缓存)|
117
+ | `getCompileCacheSize()` | 返回缓存内编译产物个数 |
118
+ | `clearCompileCache()` | 清缓存(切换租户 / 项目时用)|
119
+ | `CompiledComponent` `ComponentMeta` `ComponentSource` `CustomDecl` | TS 类型 |
120
+
121
+ ### CSS scope
122
+
123
+ 为同一 `scopeId` 的多实例共享一份带 `[data-vbi-type="<scopeId>"]` 前缀的 `<style>`,refCount 归零自动卸载。
124
+
125
+ `scopeId` 是稳定字符串,通常情况:
126
+ - 编辑器场景:scopeId = componentId(API 默认)
127
+ - 多版本画布场景:scopeId = `${componentId}@${version}`(同页同组件不同版本各自隔离)
128
+
129
+ | API | 说明 |
130
+ |---|---|
131
+ | `scopeCss(rawCss, scopeId)` | 纯函数:返回带前缀的 css 字符串 |
132
+ | `attachScope(scopeId, rawCss)` | 实例 mount:refCount++,首次注入 `<style>` |
133
+ | `detachScope(scopeId)` | 实例 unmount:refCount--,归零移除 `<style>` |
134
+ | `injectScopedCss(scopeId, rawCss)` | 只刷新内容,不动 refCount(`watch(cssSource)` 用)|
135
+ | `removeScopedCss(scopeId)` | 强制移除(测试 / reset 用)|
136
+ | `getScopedCssCount()` | 返回已挂载 scope 个数 |
137
+ | `debugScopedCssRefs()` | dev 调试,返回每个 scope 的 `{scopeId, componentId(deprecated alias), refCount, cssHash}` |
138
+
139
+ > 注意:`compileComponent` 的 LRU 缓存**仍按 sourceHash**(不分 scopeId)— 编译产物只跟 html/js 有关,加 scopeId 反而降低复用率。`scopeId` 只管 CSS / DOM 隔离。
140
+
141
+ ### 实例注册表
142
+
143
+ 跨实例方法调用 + 事件总线。`RuntimeBox` 会自动完成 register / unregister。
144
+
145
+ | API | 说明 |
146
+ |---|---|
147
+ | `register(instanceId, componentId, vm)` | 注册,返回 `ComponentInstanceHandle` |
148
+ | `unregister(instanceId)` | 注销,自动 dispose mitt |
149
+ | `getInstance(id)` | 拿 handle |
150
+ | `listInstances({ componentId? })` | 列实例,可按 componentId 过滤 |
151
+ | `registryVersion` | `Ref<number>`,register/unregister 时 +1,UI watch 它触发刷新 |
152
+ | `ComponentInstanceHandle` | `{ instanceId, componentId, vm, call, on, emit, setProp, setDataInput }` |
153
+
154
+ > `setDataInput(key, value)` 走 `vm.custom[key].value`(customValues / binding runtime 通路)。
155
+ > 跟 `setProp` 区分:`setProp` 写顶层 `vm[key]`(host 主动改 prop);`setDataInput` 走组件作者声明的 `custom.<key>.dataInput = true` slot,canvas binding runtime 灌数据用这条。
156
+ > 边界:`vm.custom[key]` 不存在 → `console.warn` + no-op;slot 形状不是 `{ value: ... }` → 同样 warn + no-op。
157
+
158
+ ### 跨实例 runtime
159
+
160
+ `__HYPERCARD__.runtime` 暴露的就是它,直接 `import { runtime } from '@hy-bricks/core'` 也行。
161
+
162
+ | 方法 | 签名 |
163
+ |---|---|
164
+ | `runtime.call(id, method, ...args)` | 调对方 vm 上的方法 |
165
+ | `runtime.on(id, event, handler)` | 订阅,返回 unsubscribe |
166
+ | `runtime.emit(id, event, payload)` | 触发对方事件 |
167
+ | `runtime.getInstance(id)` / `listInstances(filter)` | 查 |
168
+
169
+ `assets` 当前是 `{ pickerUrl(id) }` 占位,后续接资源中心。
170
+
171
+ ### 静态解析
172
+
173
+ ```ts
174
+ import { parseComponentSource } from '@hy-bricks/core'
175
+
176
+ const parsed = parseComponentSource(jsSource)
177
+ // → { methodsDecl: [{ name, params }], emitsDecl: [{ event }], propsDecl: [{ key, value, type, name, enum }] }
178
+ ```
179
+
180
+ acorn 静态扫 `export default {}` 块,抽 `methods` / `emits` / `custom` 字段供属性面板和补全用。
181
+
182
+ ### 容器组件
183
+
184
+ #### `<RuntimeBox>`
185
+
186
+ 实例容器:接源码 + 配置,自动跑 compile + attachScope + register 全套流程。
187
+
188
+ **Props:**
189
+
190
+ | Prop | 类型 | 默认值 | 说明 |
191
+ |---|---|---|---|
192
+ | `instanceId` | `string` | 必传 | 实例唯一 ID,注册表 + 事件总线用 |
193
+ | `componentId` | `string` | 必传 | 组件 ID,CSS scope 默认键 |
194
+ | `scopeId` | `string?` | `componentId` | CSS/DOM 隔离键。编辑器场景不传(默认=componentId);画布场景必传(如 `'button@v3'`,同组件不同版本各自隔离) |
195
+ | `htmlSource` | `string` | 必传 | 用户写的 HTML(Vue template) |
196
+ | `jsSource` | `string` | 必传 | 用户写的 JS(Vue Options + 平台扩展字段) |
197
+ | `cssSource` | `string?` | `''` | 用户写的 CSS(PostCSS 自动加 `[data-vbi-type]` scope) |
198
+ | `customValues` | `Record<string, unknown>?` | `undefined` | 覆盖 `custom.*.value` 的运行时值 — 画布属性面板通过它做所见即所得。不传时用 declaration 默认值(编辑器场景) |
199
+ | `position` | `{ x: number, y: number }?` | `undefined` | 绝对定位(画布场景),不传时流式布局(编辑器场景) |
200
+ | `size` | `{ w: number, h: number }?` | `undefined` | 尺寸(画布场景用) |
201
+ | `zIndex` | `number?` | `undefined` | 层级(画布场景用) |
202
+
203
+ **画布场景 vs 编辑器场景:**
204
+
205
+ ```vue
206
+ <!-- 编辑器场景:一个组件一个实例,不需要 scopeId -->
207
+ <RuntimeBox
208
+ instance-id="editor-preview-1"
209
+ component-id="cmp_abc"
210
+ :html-source="html"
211
+ :js-source="js"
212
+ :css-source="css"
213
+ />
214
+
215
+ <!-- 画布场景:同组件不同版本共存,必须传 scopeId;属性面板通过 customValues 透传 -->
216
+ <RuntimeBox
217
+ instance-id="page-inst-1"
218
+ component-id="cmp_abc"
219
+ scope-id="cmp_abc@v3"
220
+ :html-source="html"
221
+ :js-source="js"
222
+ :css-source="css"
223
+ :custom-values="{ title: 'Hello' }"
224
+ :position="{ x: 100, y: 200 }"
225
+ :size="{ w: 320, h: 240 }"
226
+ :z-index="1"
227
+ />
228
+ ```
229
+
230
+
231
+ #### `<ErrorBoundary>`
232
+
233
+ | 组件 | 作用 |
234
+ |---|---|
235
+ | `<ErrorBoundary>` | 渲染期错误兜底,接 `label?` |
236
+
237
+ ---
238
+
239
+ ## `createHyperCard` 详细配置
240
+
241
+ ```ts
242
+ interface HyperCardConfig {
243
+ libs?: LibsConfig // 注入到 __HYPERCARD__.libs 的库 / 函数 / 常量,key 任意
244
+ strict?: boolean // 默认 false。true 时启动检测一旦 warn 就抛 Error
245
+ version?: string // 默认 'v1',挂在 instance.version 上,组件兼容判断用
246
+ }
247
+ ```
248
+
249
+ ### libs 槽位允许的形态
250
+
251
+ ```ts
252
+ createHyperCard({
253
+ libs: {
254
+ ui: ElementPlus, // ① Vue plugin 对象 → 自动 app.use,libs.ui = plugin
255
+ ui2: [ElementPlus, { locale: zhCn }], // ② 元组 → app.use(plugin, options),libs.ui2 = plugin 本身
256
+ http: axios, // ③ 函数 → 原样挂(裸函数不当 plugin)
257
+ constants: { brand: 'hy' }, // ④ 普通对象 → 原样挂
258
+ formatPrice: (n: number) => `¥${n}`, // ⑤ 函数 / 工具
259
+ },
260
+ })
261
+ ```
262
+
263
+ ### 启动检测
264
+
265
+ `install` 里会一次性 `console.info` 出注册清单,例如:
266
+
267
+ ```
268
+ [hypercard] 已注册 5 个 libs:
269
+ ui ✓ Vue plugin → 已 app.use
270
+ ui2 ✓ Vue plugin (with options) → 已 app.use
271
+ http · function
272
+ constants · object
273
+ formatPrice · function
274
+ ```
275
+
276
+ 紧接着会 `console.warn`(`strict: true` 则 `throw`):
277
+
278
+ | 触发条件 | 解释 |
279
+ |---|---|
280
+ | 多个 Vue plugin 共存 | 全局组件命名可能撞车,提示自查 |
281
+ | 命中保留词 | `vue / Vue / window / global / globalThis / console / document / process / $ / libs / runtime / assets / version` 不能作为 lib 名 |
282
+ | `app.use(plugin)` 抛错 | 普通模式 `console.error` 继续,strict 模式 throw |
283
+
284
+ ---
285
+
286
+ ## `LibsRegistry` 类型 augment(必看)
287
+
288
+ SDK 自身的 `LibsRegistry` 是空接口,**宿主必须在自己项目里 augment 一次**,IDE 才能在 `__HYPERCARD__.libs.<name>` 上给出补全。
289
+
290
+ 新建 `src/types/hypercard.d.ts`:
291
+
292
+ ```ts
293
+ import type { default as ElementPlus } from 'element-plus'
294
+ import type { AxiosStatic } from 'axios'
295
+
296
+ declare module '@hy-bricks/core' {
297
+ interface LibsRegistry {
298
+ ui: typeof ElementPlus
299
+ http: AxiosStatic
300
+ formatPrice: (n: number) => string
301
+ }
302
+ }
303
+
304
+ declare global {
305
+ interface Window {
306
+ __HYPERCARD__?: import('@hy-bricks/core').HyperCardInstance
307
+ }
308
+ }
309
+
310
+ export {}
311
+ ```
312
+
313
+ 确保 `tsconfig.json` 的 `include` 覆盖到该文件。之后:
314
+
315
+ - 宿主 setup 里 `inject(HC_INJECT_KEY).libs.http.` → 完整 axios 补全
316
+ - 组件作者源码里 `__HYPERCARD__.libs.formatPrice(99)` → 类型校验
317
+
318
+ > 提示:`LibsRegistry` 只影响类型,运行时只看你 `createHyperCard({ libs })` 实际传了啥 — augment 写漏 / 写错不会让 SDK 崩,只会少补全。
319
+
320
+ ---
321
+
322
+ ## 三种访问 instance 的路径
323
+
324
+ SDK 把 `HyperCardInstance` 同时挂在三个位置,按使用场景挑。
325
+
326
+ ### 1. `window.__HYPERCARD__` — 组件作者源码用
327
+
328
+ `{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 上下文,直接拿全局:
329
+
330
+ ```ts
331
+ // 组件作者写的 jsSource
332
+ export default {
333
+ async mounted() {
334
+ const { data } = await __HYPERCARD__.libs.http.get('/api/me')
335
+ this.user = data
336
+ __HYPERCARD__.runtime.emit('header', 'user-loaded', data)
337
+ },
338
+ }
339
+ ```
340
+
341
+ ### 2. `inject(HC_INJECT_KEY)` — 宿主自己的 Vue setup
342
+
343
+ ```ts
344
+ import { inject } from 'vue'
345
+ import { HC_INJECT_KEY, type HyperCardInstance } from '@hy-bricks/core'
346
+
347
+ const hc = inject<HyperCardInstance>(HC_INJECT_KEY)!
348
+ hc.runtime.call('list-1', 'refresh')
349
+ ```
350
+
351
+ ### 3. `this.$hc` — Options API
352
+
353
+ ```ts
354
+ export default {
355
+ mounted() {
356
+ this.$hc.runtime.emit('toast', 'show', { msg: '保存成功' })
357
+ },
358
+ }
359
+ ```
360
+
361
+ 类型扩展(`ComponentCustomProperties.$hc`)由 SDK 内置,`import '@hy-bricks/core'` 后自动生效。
362
+
363
+ ---
364
+
365
+ ## Troubleshooting
366
+
367
+ 实际接入时踩过的坑,按"症状 → 原因 → 解法"列。
368
+
369
+ ### 误传函数库当 plugin(`Maximum call stack size exceeded`)
370
+
371
+ - 症状:`app.use(createHyperCard({ libs: { http: axios } }))` 时栈溢出 / `axios is not a function`。
372
+ - 原因:Vue 的 `app.use` 看到函数会当成 legacy plugin 调,axios 又是个函数,递归自调爆栈。
373
+ - 解法:SDK 已用 `isVuePlugin`(只认 `{ install: fn }` 对象 / `[obj, opts]` 元组)拒掉裸函数。**不要**手动包 `{ install: axios }`,直接传 axios 即可,SDK 会原样挂到 `libs.http`。极少数老 plugin 是纯函数签名时,自己包成 `{ install(app) { ... } }`。
374
+
375
+ ### Tailwind v3 ignore `node_modules`,SDK 内 class 扫不到
376
+
377
+ - 症状:用了 `@hy-bricks/editor` 后样式发飞,inspector 看到 class 没生成。
378
+ - 原因:Tailwind v3 默认 `content` 不扫 `node_modules`,SDK dist 内 class 全被 purge。
379
+ - 解法:宿主的 `tailwind.config.js` 加 preset 或扩 content:
380
+
381
+ ```js
382
+ module.exports = {
383
+ presets: [require('@hy-bricks/editor/tailwind-preset')],
384
+ content: [
385
+ './index.html',
386
+ './src/**/*.{vue,ts,tsx}',
387
+ './node_modules/@hy-bricks/editor/dist/**/*.{js,mjs,cjs}',
388
+ ],
389
+ }
390
+ ```
391
+
392
+ ### `@import '@hy-bricks/...'` PostCSS 不识别 npm scope alias
393
+
394
+ - 症状:`@import '@hy-bricks/editor/style.css'` 编译报 `Can't resolve`。
395
+ - 原因:PostCSS `@import` 走自己的解析器,不认 vite/webpack 的 npm scope。
396
+ - 解法:在 JS 入口 `import '@hy-bricks/editor/style.css'`(走 bundler 的 ESM 解析),不要在 CSS 里 `@import`。
397
+
398
+ ### Vite `?worker` query 在 SDK build 后保留
399
+
400
+ - 症状:宿主 dev 启动 `Could not resolve ".../editor.worker?worker"`。
401
+ - 原因:`?worker` 是 Vite 专属语法,打进 dist 后 esbuild dev 不认。
402
+ - 解法:`@hy-bricks/editor` 已改成 monaco worker 工厂动态注册,直接装 npm 上的发布版本即可,宿主 dev 启动后无 query 残留。
403
+
404
+ ### 启动后 `__HYPERCARD__.libs.xxx` IDE 无补全
405
+
406
+ - 症状:类型 `unknown`,`.` 后没提示。
407
+ - 原因:没在宿主项目 augment `LibsRegistry`(SDK 自身故意保持空接口,避免硬绑具体库)。
408
+ - 解法:照 [LibsRegistry 类型 augment](#libsregistry-类型-augment必看) 一节,落一份 `src/types/hypercard.d.ts`。
409
+
410
+ ---
411
+
412
+ ## 配合使用 / Ecosystem
413
+
414
+ - [@hy-bricks/editor](https://www.npmjs.com/package/@hy-bricks/editor) — 嵌入式低代码编辑器(Monaco 三栏 + 实时预览)。需要 IDE 体验时一起装
415
+ - [@hy-bricks/canvas](https://www.npmjs.com/package/@hy-bricks/canvas) — 多组件实例画布 + 自由布局 + binding 协议
416
+ - [@hy-bricks/devtools](https://www.npmjs.com/package/@hy-bricks/devtools) — 运行时诊断浮窗
417
+
418
+ ---
419
+
420
+ ## License
421
+
422
+ [MIT](./LICENSE) © hy_top