常见问题排查
按"症状"分类。每条都给最小可复现 + 根因 + 修法。如果新踩到坑没在这里,优先开个 issue 再到这一页加一节。
Packaging / 装包
EUNSUPPORTEDPROTOCOL: workspace:^ 装不上
把 @hy-bricks/* 当成普通 npm 包装(用 file: tarball 或真发布),tarball 里看到 peerDependencies 写 workspace:^ 就会跪。
根因:npm pack 不认 workspace: 协议,会原样保留。pnpm pack 会自动重写成实际版本(workspace:^ → ^0.0.0)。
修法:打 tarball 一律用 pnpm pack,会自动把 workspace:^ 重写成实际版本。
装完包 npm install 报 monaco 找不到 / reka-ui 找不到
@hy-bricks/editor 的 peerDependencies 列了 9 个内部 UI 依赖(reka-ui / cva / clsx / tailwind-merge / tailwindcss-animate / lucide-vue-next / tailwindcss / monaco-editor / vue),host 必须自己装。这是设计:让 host 控制版本 dedup,避免 SDK 把 UI 库打死。
完整依赖清单见 packages/editor/package.json 的 peerDependencies。
tarball 里看到 src/__tests__/*.spec.ts 或 package/src/
发布产物偏脏,本地包不会坏但 npm publish 之前必须清。
根因:package.json 的 files 字段写了 src,把整个源码 + 测试一起 pack 进去。
修法:files 改成 ["dist", "README.md"],确认 exports 全指向 ./dist/*。各包 package.json 已统一收窄。
Runtime / typecheck
enableHyBricksDiagnostics: 'canvases' is missing in type ...
canvases 是 optional 的,这条 typecheck 不再出现。简单接入直接:
ts
enableHyBricksDiagnostics({ level: 'debug' })createCanvasesRegistry() 是 @hy-bricks/core 全局 instanceRegistry 的只读视图,fallback 跟 host 自己 create 的看同一份数据,不会"看不到 host 画布"。
复杂多画布仍建议显式传同一个 registry(语义清晰 / 初始化顺序可控 / 防 micro-frontend 多版本 core 分叉):
ts
import { createCanvasesRegistry } from '@hy-bricks/core'
const canvases = createCanvasesRegistry()
enableHyBricksDiagnostics({ canvases, level: 'debug' })完整字段见 devtools API。
Type 'string' is not assignable to type 'PageLayout | CanvasLayoutConfig'
ts
// ❌ 旧 schema:layout 写成字符串
const doc: PageDocument = { layout: 'free', ... }
// ✓ 用 helper 一行(canvas 字段、rootLayout 都自动兜底)
import { createMinimalPageDocument } from '@hy-bricks/canvas'
const doc = createMinimalPageDocument({ canvasWidth: 1280, canvasHeight: 720 })SDK 暴露 createMinimalPageDocument / createFreePageDocument 工厂,直接输出 normalize 后的 v1 形态(canvas: { width: { mode:'px', value }, height: { mode:'px', value } },CanvasSizeValue 协议)。createFreePageDocument 是 free 顶层布局的快捷版。
后端 getRender 返回的 payload 已经 normalize 过,host 直接喂 Renderer 即可;只有自己手造 doc(测试 / smoke / demo)用 helper。
修改 handle.getInstance(id) 返回值后 SDK 内部不变了
getInstance / getInstanceList / getBreadcrumb 全是 深拷贝口径:host 改返回对象的嵌套字段(props.x = 2 / rect.x = 99 / layoutBox.width = ...)不污染 SDK 内部 reactive。
要真改文档:走 handle.dispatch({ type: 'updateInstance', ... }),见 CanvasHandle。
组件滚动 / 重渲染后,用户选值 / 查询条件变回默认
症状:看板里的组件,用户改了选值(如维度选成"年"),滚出视口再滚回、或页面重渲染后,选值又变回默认("日"),查询条件也丢。
两条独立根因,0.6.3 分别处理:
- 重渲染冲刷(已默认修复,无需开关):渲染器每次重渲染都传"内容相同、引用不同"的 custom 值对象,老 watch 无条件写回文档默认,冲掉用户运行时改的值。
0.6.3改为 diff 同步(Object.is比上次 prop,真变才同步)—— 所有宿主常开,升到0.6.3即生效。 - 滚出视口被拆重建(opt-in):视口虚拟化把滚出超约 1.5s 的实例真 dispose 卸 vm,滚回重建
data()重跑、选值复位。给 Renderer / Designer 传:persist-instance-state="true"开启快照持久化(dispose 前拍$data快照、重建还原)。或更直接:0.6.5/0.6.6起给:scheduler-options传{ disposeOnExit: false }或{ disabled: true }让它根本不卸载(从根上不重建、也不重取,代价是已挂常驻内存)——persistInstanceState是"卸载 + 还原",这俩是"不卸载"。各档见 调度器 · RenderSchedulerOptions。
注意区分:隐藏(
instance.hidden,v-show)是另一回事,由keepHiddenMounted(默认开)保活,vm 不拆、不需快照。详见 渲染一个页面 · 组件滚出视口再滚回。
已知限制(开 persistInstanceState 时):
- 只保护同步状态。组件若在
mounted里异步重置状态(fetch().then(set)),会盖过还原值、选值仍丢 —— 别在mounted盲目异步重置被持久化字段。 instanceId必须逻辑唯一、不可删后复用:删掉实例后用相同instanceId+componentId新建会继承陈旧快照(快照仅画布 dispose / LRU 满 200 才清)。
样式
装了 @hy-bricks/canvas 但选中框 / runtime wrapper 没样式
老版本 canvas 输出独立 dist/canvas.css,host 忘 import 就丢样式。当前版本 canvas SFC <style> 已通过 vite-plugin-css-injected-by-js 内联进 JS bundle,host 不需要 import css,运行时自动生效。
如果还是丢样式,检查:
- 用的
@hy-bricks/canvas是不是含 inline 样式注入的版本 - 有没有手动屏蔽 inline
<style>注入
@hy-bricks/editor 装了但 shadcn 主题没生效
editor 的 SFC <style> 已注入 JS bundle,但 tailwind utility / shadcn CSS variable 是独立的 dist/style.css(tailwind CLI 编译产物,无法注入)。host 必须:
ts
// main.ts
import '@hy-bricks/editor/style.css'而且 host 的 tailwind.config.js 要 presets: [hyBricksPreset],见 快速开始。
devtools 浮窗看不到 / 显示但样式 broken
devtools 浮窗 CSS 在 dist/index.{mjs,cjs} + dist/auto.{mjs,cjs} 双 entry 都注入。看不到先确认:
- 是不是 DEV 模式?prod 不会自动启
app.use(createHyperCard({ ... }))是不是在enableHyBricksDiagnostics之前调?(canvases注册顺序)- 是不是
import '@hy-bricks/devtools/auto'但忘了先createHyperCard?
拖拽 / 选中
Designer 里点容器子项点不中蓝框
较早版本存在此问题:选中实例 DOM 在正确位置但被父 stacking context 压住。修法:升 SDK 到包含 CanvasStage 加 isolation: isolate 的版本(InteractionLayer z-index 9999 在 stage 内生效)。
Flex / Grid / Split 容器里子项拖不动 / 拖了不动
子项是 layout-managed,位置由父布局算,直接 drag 会被 SDK 拒。SDK 会触发 handle.cannotDragLayoutManagedChildEvent,devtools collector 自动 watch 进 recentDragFailures。
host 可以监听做 toast 提示;真要改位置走 updateLayoutItem(id, patch)(改子项 layout 参数)或换 reparent 到 free 容器。见 跨容器移动。
slot 加第 4 个子项被拒 + reason 'max-children-exceeded'
SlotDecl.maxChildren 守门。组件契约里声明的 slot 上限到了,canReparent dry-run 和真 drop 都会拒,host 收到 cannotReparentEvent.reason === 'max-children-exceeded',toast 提示用户即可。优先级低于 multiple: false(单 slot 排他先拒)。
Build / bundle
dist/index.js 巨大(几 MB)— monaco / element-plus 全打进 bundle
那是 host 端的 build chunk warning,不是 SDK 问题。SDK 自身把这些重 dep 都 external 出去了,host 自己的 build config 才决定怎么打。生产 host 一般用 build.rollupOptions.output.manualChunks 把 monaco / vue / element-plus / @hy-bricks 拆成单独 chunk。