
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
@hy-bricks/editor
Advanced tools
嵌入式低代码组件编辑器:Monaco 三栏(html/js/css)+ 拖 tab 多分栏 + 实时预览 + 草稿持久化 + Monaco 类型注入。
📦 当前发布版 0.6.3 ── 与
@hy-bricks/core同步(fixed 组同版本号)。逐版变更(含历史 BREAKING)以 CHANGELOG 为准;注意 0.6.0 跟随 core 收口公开面(移除 test-only / 内部导出)。
┌─────────────────────────────────────────────────────────────────────┐
│ demo-component v3 · 已发布 ☐ 自动预览 ⚙ 保存草稿 推版本 │
├──────────────────────────────────┬──────────────────────────────────┤
│ html │ javascript │ + css │ 预览 ⧉ │
│ ─────────────────────────────── │ ─────────────────────────────── │
│ <div class="root"> │ │
│ <h2>{{ greeting }}</h2> │ Hello World ! │
│ <el-input v-model="msg" /> │ ┌───────────────┐ │
│ <el-button @click="add"> │ │ 试试输入 │ │
│ 点我 +1 │ └───────────────┘ │
│ </el-button> │ 你输入了: │
│ </div> │ │
│ │ [ 点我 +1 ] 点了 0 次 │
└──────────────────────────────────┴──────────────────────────────────┘
(拖 tab 头 → 多分栏)
<HyperCardEditor> 是一个受控组件:你给一份 {html, js, css} 源码,它给你一个 IDE 风界面 + 一份能跑的实时预览。所有副作用(保存、推版本、开窗口、设置面板)都通过事件抛回宿主,SDK 不调任何 API、不依赖 vue-router、不强制宿主目录结构。
reka-ui / cva / clsx / tailwind-merge / lucide-vue-next 在 SDK 构建时被 vite external,必须由宿主装(详见下面的 peerDependencies)。
pnpm add @hy-bricks/editor @hy-bricks/core \
vue monaco-editor \
reka-ui class-variance-authority clsx tailwind-merge lucide-vue-next \
tailwindcss tailwindcss-animate autoprefixer postcss
tailwind.config.js —— 不要用 SDK 的 preset包内
.vue用了大量 Tailwind utility,但 Tailwind v3 默认 ignorenode_modules,即便content写上node_modules/@hy-bricks/**也大概率扫不全(symlink、pnpm hoist 路径变化)。正确做法:本包发布时已经 self-compile 出
dist/style.css,把全部需要的 utility 都打进去了。宿主只要 import 这一个 css 就行,不需要让自己的 Tailwind 扫包内源码。
/** tailwind.config.js — 宿主只配自己的内容,不扫 SDK */
export default {
content: [
'./index.html',
'./src/**/*.{vue,ts,tsx,js,jsx}',
],
theme: { extend: {} },
plugins: [],
}
src/style.css@tailwind base;
@tailwind components;
@tailwind utilities;
⚠ 不要在这个 css 里
@import '@hy-bricks/editor/style.css'。PostCSS 的@import走它自己的解析器,不识别 npm scope alias(@hy-bricks/...),通常会 silent 失败: 编译没报错,主题变量却丢失。唯一推荐:在main.ts里用 JSimport(下一步), 走 bundler 的 ESM 解析,稳定可靠。
src/main.tsimport { createApp } from 'vue'
import { createHyperCard } from '@hy-bricks/core'
// 通过 vite ESM resolve 直接拿包内 self-compiled 样式 — 这是引入主题 CSS 的
// **唯一推荐方式**(不要在 style.css 里 @import,见上一步说明)
import '@hy-bricks/editor/style.css'
import App from './App.vue'
import './style.css'
const app = createApp(App)
app.use(
createHyperCard({
libs: {
// 你提供给组件作者用的运行时库,组件代码里通过 __HYPERCARD__.libs.* 拿
http: /* axios 或自己封装的 fetch */ undefined,
ui: /* ElementPlus / antdv / 你自己的 UI 库 */ undefined,
},
}),
)
app.mount('#app')
App.vue<script setup lang="ts">
import { ref } from 'vue'
import { HyperCardEditor } from '@hy-bricks/editor'
const source = ref({ html: '', js: '', css: '' })
</script>
<template>
<div class="h-screen">
<HyperCardEditor v-model="source" draft-key="my-component" />
</div>
</template>
跑 pnpm dev 就能看到完整 IDE。三栏 Monaco 都能编辑、改了立刻预览、Cmd+S 存草稿、刷新页面草稿还在。
| 包 | 版本 | 必装 | 说明 |
|---|---|---|---|
vue | ^3.5 | ✅ | Composition API + <script setup> |
monaco-editor | ^0.55 | ✅ | 三栏代码编辑器内核 |
tailwindcss | ^3.4 || ^4 | ✅ | 宿主自己的 Tailwind(只扫自己,不扫 SDK) |
@hy-bricks/core | 同 SDK 版本 | ✅ | 运行时(预览盒子、createHyperCard plugin) |
reka-ui | ^1.0.0-alpha.8 | ✅ | shadcn-vue 的 headless 底座(Button 等用了) |
class-variance-authority | ^0.7 | ✅ | buttonVariants 生成器 |
clsx | ^2.1 | ✅ | className 合并 |
tailwind-merge | ^2.5 | ✅ | dedupe Tailwind 冲突 class |
lucide-vue-next | ^0.469 | ✅ | 顶栏图标(Settings / RefreshCw 等) |
tailwindcss-animate | ^1.0 | 推荐 | shadcn-vue 默认动画(若你启用宿主侧 shadcn 组件) |
SDK
package.json里 reka-ui / cva / clsx / tailwind-merge / lucide-vue-next 已经移到peerDependencies(语义就是"宿主项目需要装"):宿主必须显式安装;SDK build 时把它们external,避免重复打包。
<HyperCardEditor> props| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
modelValue | { html: string; js: string; css: string } | — | 必填。v-model 双向绑定;形状写死,宿主必须适配 |
draftKey | string | undefined | 启用 localStorage 草稿;实际 key = hc:draft:<draftKey>。留空 = 不启用草稿 |
componentId | string | 回退 draftKey,再回退实例级唯一 id(__hc_editor_<uid>__,用 getCurrentInstance().uid) | 给运行时实例的 id。默认回退已自动每实例唯一,不再撞;多实例仍建议显式传,方便 runtime / cssScope / 调试定位 |
showBackButton | boolean | false | 顶栏左侧返回按钮。点击触发 go-back 事件,SDK 自己不路由 |
title | string | undefined | 顶栏组件标题 |
statusLabel | string | undefined | 顶栏右侧的版本号 / 状态文字(如 'v3 · 已发布') |
autoPreview | boolean | true | 改源码后自动 debounce 渲染预览;false 时显示「重新渲染」按钮,需手点 |
previewRatio | number | 0.62 | 编辑器与预览的初始宽度比 0–1 |
extraLibs | ExtraLib[] | undefined | 给 Monaco 注入的额外类型 lib(自动补全 __HYPERCARD__.libs.*),详见 Monaco 类型注入 |
interface ExtraLib {
filePath: string // 必须形如 'file:///foo.d.ts'
content: string // ambient .d.ts 字符串(不能含 export {})
}
| 事件 | payload | 触发时机 | 宿主该怎么处理 |
|---|---|---|---|
update:modelValue | { html, js, css } | 任意一栏 Monaco 内容变化(throttle) | v-model 自动接,无需手写 |
dirty | boolean | 用户改了源码 / 调用 applyDraft 后 → true;v-model 重置或保存草稿后 → false | 顶栏 dot、disable「推版本」按钮等 |
save-draft | — | 用户点顶栏「保存草稿」或 Cmd+S(草稿已写入 localStorage) | toast「已保存」即可,无需自己再写 |
push-version | — | 用户点顶栏「推版本」 | 弹自家 dialog 让填备注 → 调后端 API |
open-preview-window | — | 用户点预览面板右上的「⧉ 独立窗口」 | window.open(routerResolve('/preview/...'), ...);开了之后 SDK 自动用 BroadcastChannel 同步源码 |
open-settings | — | 用户点顶栏齿轮图标 | 弹自家组件元数据 drawer(name / desc / tags 等) |
go-back | — | showBackButton=true 时点击返回按钮 | router.back() 之类 |
error | { message, stack? } | 编译 / 运行时报错 | 写 console、上报 Sentry、Toast 等 |
ref 拿到的 instance)const editor = useTemplateRef<{
isDirty: Ref<boolean> // ← 注意是 Ref,不是裸 boolean
confirmLeave(): boolean
applyDraft(draft: ComponentDraft): void
manualPreview(): void
applyPreview(): void
}>('editor')
| 方法 / 属性 | 签名 | 说明 |
|---|---|---|
isDirty | Ref<boolean> | 是 ref 不是裸值,读用 editor.value.isDirty.value、模板里直接 editor?.isDirty |
confirmLeave() | () => boolean | 同步弹 confirm,dirty 时提示「未保存,确定离开?」;返回 true 表示放行,false 表示用户取消 |
applyDraft(draft) | (d: ComponentDraft) => void | 把外部草稿灌进编辑器(用于「恢复历史版本」),会把 dirty 设为 true |
manualPreview() | () => void | autoPreview=false 时手动触发预览 |
applyPreview() | () => void | 用 v-model 灌进新 source 后,如果想立刻刷预览(watch(modelValue) 已经会调,基本用不到) |
import { onBeforeRouteLeave } from 'vue-router'
const editor = useTemplateRef<{ confirmLeave(): boolean }>('editor')
onBeforeRouteLeave(() => editor.value?.confirmLeave() ?? true)
beforeunload(关浏览器、刷新)SDK 自动接,无需宿主写。
让组件作者写 __HYPERCARD__.libs.http. 时 Monaco 自动补全到具体方法。SDK 默认已经注册 __HYPERCARD__ / runtime / assets 三个 ambient,但 libs 的具体类型必须由宿主补(因为只有宿主知道你提供了 axios 还是 ky 还是 ElementPlus)。
import { HyperCardEditor, type ExtraLib } from '@hy-bricks/editor'
const extraLibs: ExtraLib[] = [
{
// 必须 file:/// 协议,Monaco 用这个判断模块身份
filePath: 'file:///host-libs.d.ts',
content: `
// 关键:用 declaration merging 跟 SDK 自带的 HyperCardLibs 接口合并,
// 不要写 export {},一旦文件里有 import/export,
// 它就变成 module 而不是 ambient,声明就泄漏不到全局了
interface HyperCardLibs {
http: {
get<T = any>(url: string, config?: any): Promise<{ data: T; status: number }>
post<T = any>(url: string, data?: any, config?: any): Promise<{ data: T; status: number }>
}
ui: { version: string; install: (...args: any[]) => void }
}
`,
},
]
<HyperCardEditor v-model="source" :extra-libs="extraLibs" />
⚠ 致命陷阱:
content字符串里不能出现export {}/import ...,否则 .d.ts 自动从 ambient 转成 module,顶层declare const __HYPERCARD__就不再是 global,组件作者那边补全直接灰掉。
SDK 用 shadcn-vue 风的 HSL CSS variables。不需要重 build,在宿主自己的 :root 覆盖任意一条即可。
| 变量 | 默认 (light) | 用途 |
|---|---|---|
--background / --foreground | 0 0% 100% / 222.2 84% 4.9% | 页面底色 / 主文字 |
--primary / --primary-foreground | 222.2 47.4% 11.2% / 210 40% 98% | 「推版本」主按钮 |
--secondary / --secondary-foreground | 210 40% 96.1% / 222.2 47.4% 11.2% | 次按钮 |
--muted / --muted-foreground | 210 40% 96.1% / 215.4 16.3% 46.9% | 弱化区域、placeholder |
--accent / --accent-foreground | 210 40% 96.1% / 222.2 47.4% 11.2% | hover 反馈 |
--destructive / --destructive-foreground | 0 84.2% 60.2% / 210 40% 98% | 删除 / 警告 |
--border / --input / --ring | 214.3 31.8% 91.4% / 同左 / 222.2 84% 4.9% | 边框 / input / focus ring |
--radius | 0.5rem | 基础圆角(Button、面板) |
/* 改主色 + dark mode 一起改 */
:root {
--primary: 220 80% 50%;
--radius: 0.25rem;
}
.dark {
--primary: 220 70% 60%;
--background: 222.2 84% 4.9%;
}
值要写纯 HSL 三元(无
hsl()包裹、无逗号),包内hsl(var(--primary) / <alpha>)会自己拼。
| 场景 | SDK 做了什么 | 宿主做什么 |
|---|---|---|
设置 draft-key="abc" | 启用 localStorage,key = hc:draft:abc | 传 prop |
顶栏「保存草稿」/ Cmd+S | 写 localStorage,清除 dirty,触发 @save-draft | 接事件 toast 反馈 |
| 进入页面检测到旧草稿 | 顶栏渲染琥珀色 pill「本地草稿 · 5 分钟前 · [恢复] [丢弃]」 | — |
关浏览器 / 刷新 (beforeunload) | dirty 时弹浏览器原生 confirm | — |
切路由 (onBeforeRouteLeave) | 暴露 confirmLeave() | 在 router guard 中调:return editor.value?.confirmLeave() |
| 推版本成功 | — | 宿主自己 clearDraft(key)(从 SDK 导出) |
import { clearDraft, loadDraft, saveDraft } from '@hy-bricks/editor'
clearDraft('my-component') // 推版本成功后清
const d = loadDraft('my-component') // SSR / 列表预读
saveDraft('my-component', { html, javascript, css }) // 自己触发存(很少用)
不想要默认的「IDE 顶栏 + 三栏 + 预览」布局?可以拿乐高块自己拼:
import {
// 布局
EditorLayout, EditorGroup, MonacoEditor, Splitter,
// multi-instance scope(每个独立编辑器一份,model 不串)
createEditorScope, useEditorScope, EDITOR_SCOPE_KEY,
// 布局算法(纯函数,immutable)
defaultLayout, activateTab, closeTab, splitWithTab, moveTab, setSplitRatio,
// 草稿
loadDraft, saveDraft, clearDraft,
// 预览跨窗口总线
createPreviewBus,
// shadcn-vue 风 Button(包内 vendor)
Button, buttonVariants,
// Monaco 类型钩子
setupHyperCardMonacoTypes, addMonacoExtraLib,
} from '@hy-bricks/editor'
EditorLayout / EditorGroup / MonacoEditor / Splitter —— 自拼 IDE 界面createEditorScope / useEditorScope —— multi-instance 的 monaco model 隔离Button / buttonVariants —— vendor 自 shadcn-vue,跟 SDK 主题变量一致原因:Tailwind v3 出于性能默认 ignore node_modules,所以即便宿主 tailwind.config.js 的 content 写了 node_modules/@hy-bricks/**,也基本扫不到包内 .vue 里的 utility class。
解法:不要让宿主 Tailwind 扫这个包。本包发布时已经 self-compile 出 dist/style.css,把全部需要的 utility 都打进去。宿主只要:
import '@hy-bricks/editor/style.css'
就拿到完整样式 + 主题变量。Tailwind preset 共享只是给「我也想跟本包用同一套色板」的宿主用,跟样式生效无关。
Cannot read file ... ?worker 报错原因:Vite 默认会把 node_modules 里的 ESM 包预 bundle(esbuild),但 esbuild 不认识 Vite 的 ?worker query —— monaco-editor 的 worker import 就这样炸。
解法:宿主 vite.config.ts 把 SDK 排除:
export default defineConfig({
optimizeDeps: {
exclude: ['@hy-bricks/core', '@hy-bricks/editor'],
},
})
Cannot find module 'postcss' / CJS 加载失败原因:Vite 把 SDK 当 ESM 加载时,如果宿主和 SDK 用了不同 PostCSS 版本,Node 模块解析会撞到 CJS/ESM 边界。
解法:SDK 已经把 postcss 处理打进自己的 dist 里了。如果宿主仍然另行 import postcss,在 vite.config.ts 里加 dedupe:
export default defineConfig({
resolve: {
dedupe: ['postcss', 'monaco-editor', 'vue'],
},
})
editor.value.isDirty 永远 truthy原因:isDirty 暴露的是 Ref<boolean> 不是裸 boolean,而 Ref 对象本身永远 truthy。
解法:读 .value,或在模板里依赖自动 unwrap。TS 类型也要写对:
const editor = useTemplateRef<{ isDirty: Ref<boolean> }>('editor')
if (editor.value?.isDirty.value) { /* ... */ }
或干脆听 @dirty 事件,最推荐:
<HyperCardEditor v-model="source" @dirty="d => isDirty = d" />
__HYPERCARD__.libs. 是空的原因:你的 extraLibs[].content 里出现了 export {} / import x from ...,文件被 TS 识别成 module,声明不再 global。
解法:见 Monaco 类型注入。content 必须是纯 ambient,只有 declare / interface / type,没有任何 import / export。
<HyperCardEditor> 同页,样式 / 实例 id 区分默认行为:不传 componentId 时,自动回退成实例级唯一 id(__hc_editor_<uid>__,基于 getCurrentInstance().uid),每个 <HyperCardEditor> 实例自动拿不同 id,不会撞。
仍建议:多实例并存时显式传 componentId(或不同 draftKey,会被自动回退用),让 runtime / cssScope / 调试堆栈里看得清是哪个实例:
<HyperCardEditor v-model="a" component-id="card-a" />
<HyperCardEditor v-model="b" component-id="card-b" />
MIT © hy_top
FAQs
HyperCard 嵌入式编辑器 — Monaco 三栏 + 拖 tab 分栏 + 实时预览 + shadcn-vue 风格 UI
We found that @hy-bricks/editor demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.