多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

@open-pencil/dom-css 实战指南:在 OpenPencil 中打通 HTML/CSS/JSX/Tailwind 与场景图的双向投影

@open-pencil/dom-css 实战指南:在 OpenPencil 中打通 HTML/CSS/JSX/Tailwind 与场景图的双向投影 前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载open-pencil/dom-css是 OpenPencil 的场景图SceneGraph与 DOM 形态设计文档DesignDOM之间的兼容层它提供浏览器运行时与无头运行时两套 CSS 计算引擎并将 HTML、JSX、Tailwind CSS 统一投影为设计场景图。阅读本文后你将掌握该包的全部公开 API、两种运行时各自的适用边界与沙箱策略、Tailwind 在浏览器与 Bun/Node 下的三套接入配方以及场景图与 CSS 字段之间的完整映射关系可以直接在编辑器、CLI 或浏览器扩展场景中落地使用。包定位与设计动机open-pencil/dom-css在仓库中位于 packages/dom-css它解决的问题是让以 DOM/CSS 形态描述的设计内容HTML 片段、JSX 树、Tailwind 类名能够进入 OpenPencil 的场景图体系同时也能把场景图投影回 DOM/CSS 形态的文档。其核心数据模型是DesignDocument/DesignElement/DesignText定义在 src/types.tsexport interface DesignDocument { type: document children: DesignNode[] stylesheets?: DesignStyleSheet[] sourceGraph?: SceneGraph tokens?: DesignTokens } export interface DesignElement { type: element tagName: string attrs: Recordstring, string children: DesignNode[] inlineStyle?: DesignStyleDeclaration // 内联 style 解析结果 computedStyle?: DesignStyleDeclaration // CSS 计算后的样式快照 sourceSceneNodeId?: string sourceSceneNode?: SceneNode }从源码结构看包被刻意拆分为runtime浏览器/无头两套引擎、importHTML/JSX/Tailwind → DesignDOM/SceneGraph、exportSceneGraph → HTML/JSX/Tailwind 等反向投影三组模块见 src/index.ts 的导出清单。它被设计为独立于open-pencil/core的包这样浏览器/CSS 解析器相关的集成可以独立演进而不会把 DOM 依赖拖入渲染器与编辑器核心。这一点从包的目录组织与sideEffects: false、独立dist构建见 package.json可以得到印证。安装与工程配置README 给出的安装命令为bun add open-pencil/dom-css open-pencil/core需要说明的是README 中将open-pencil/core列为 peer 依赖理由是该包需要与 OpenPencil 场景图双向投影从 package.json 的peerDependencies实际声明看当前仓库中该包声明的工作区 peer 依赖为open-pencil/scene-graphworkspace:*协议。因此在安装时务必同时保证场景图类型包可用——即使只做 DesignDOM 的解析/序列化入口文件也会引用该类型。包内自检命令该包可脱离应用外壳独立验证cd packages/dom-css bun run test bun run typecheck bun run check各脚本含义与 package.json 的scripts一一对应bun run test—— 运行包内 Bun 测试覆盖运行时、HTML/CSS 转换与 Tailwind API测试位于 packages/dom-css/testsbun run typecheck—— 通过tsc --noEmit -p tsconfig.test.json对src、测试及包脚本做类型检查bun run build—— 用 tsdown 构建可分发的dist入口.,./browser,./export,./jsx-runtime,./jsx-dev-runtime五个导出点bun run smoke:dist—— 导入构建产物并演练公开 API脚本见 packages/dom-css/scripts/smoke-dist.tsbun run check—— 依次执行 typecheck、test、build 与 dist smoke。测试分为三层包内单元测试聚焦库的公开 API仓库级的tests/engine/dom-css与tests/e2e/dom-css提供集成/oracle 覆盖其中 E2E 套件通过 Playwright 验证浏览器getComputedStyle()的保真度即浏览器运行时输出与真实浏览器计算样式的一致性。两种 CSS 运行时浏览器运行时与无头运行时包的运行时模型是只要存在 DOM就把浏览器运行时当作高保真的真相源。它使用原生解析与getComputedStyle()在隔离沙箱内计算样式而 headless 运行时面向 Bun/Node 测试、CLI 流程与快速近似转换。浏览器运行时与沙箱策略import { createBrowserCSSRuntime } from open-pencil/dom-css const runtime createBrowserCSSRuntime({ sandbox: iframe }) const document runtime.parseHTML(article classcardOpenPencil/article) const styled await runtime.computeStyles(document, .card { width: calc(10rem 16px); })从 src/runtime/browser.ts 的实现可以看到关键细节沙箱选项BrowserCSSRuntimeOptions支持sandbox?: shadow-root | iframe默认值为shadow-rootREADME 建议生产级转换优先使用sandbox: iframe因为它把被创作 CSS 与宿主页面样式彻底隔离。宿主样式隔离applySandboxHostStyle()给沙箱宿主元素写入position: fixed; left: -100000px; top: 0; width: 1000px; height: auto; visibility: hidden; pointer-events: none; contain: layout style paint把计算过程完全移出视口。渲染帧等待两种沙箱都会调用requestFrame()等待一帧确保布局与计算样式稳定后再拷贝结果。计算属性清单默认只抓取DEFAULT_COMPUTED_PROPERTIES中与场景图相关的 60 余个属性包括 flex 相关display、flex-direction、flex-wrap、gap、justify-content、align-items、align-self、盒模型width/height/min/max、四向padding、四向border-*-width/color/style/radius、文本font-*、line-height、letter-spacing、text-align、text-decoration-line、text-transform、white-space、视觉background-color/image、box-shadow、text-shadow、opacity、object-fit与定位position、top/left/right/bottom。当传入compute: { includeBrowserDefaults: true }时会改为抓取全部计算样式见computedStyleToRecord与CSSComputeOptions。非渲染标签剔除head、link、meta、script、style、template、title会被过滤空白文本节点被丢弃。内联 style 保留元素的style属性会被解析进inlineStyle计算样式写入computedStyle二者在DesignElement上并存。runtime模块还提供了自动选择入口createCSSRuntime()见 src/runtime/index.ts当全局存在document时返回浏览器运行时否则返回 headless 运行时——这也是htmlToDesignDocument等便捷函数在未显式传runtime时的默认行为。headless 运行时与能力边界import { createHeadlessCSSRuntime } from open-pencil/dom-css const runtime createHeadlessCSSRuntime()从 src/runtime/headless.ts 看headless 运行时用parse5的parseFragment解析 HTML用acemir/cssom支撑样式计算见 package.json 依赖。它支持常见选择器、继承、简写属性、CSSOM 分组规则以及简单的变量/calc()值但它不是浏览器的替代品。README 明确划定了禁用场景不要把它作为浏览器独有 CSS 行为的 oracle例如依赖布局的计算值、完整的自定义属性回退行为、现代颜色序列化、UA 默认样式不要用临时拼凑的正则/字符串解析器扩展 headless CSS 解析应当引入被维护的解析器/运行时依赖或改用浏览器运行时。这两条边界也直接决定了无头路径适合快速近似转换而需要getComputedStyle()保真度的生产路径必须走浏览器运行时。HTML/CSS → DesignDOM → 场景图完整管线便捷函数htmlToSceneGraph运行完整管线HTML 解析 → CSS 提取/合并 → 样式计算 → DesignDOM → 场景图。import { htmlToSceneGraph } from open-pencil/dom-css const graph await htmlToSceneGraph( article classcardh1OpenPencil/h1/article, { cssText: .card { display: flex; gap: 12px; width: 320px; padding: 24px; } } )如果只需要 DesignDOM 而不想生成场景图使用htmlToDesignDocument()。底层实现要点从 src/import/html.ts 可以看到嵌入式style自动提取extractEmbeddedCSSText()会递归收集 HTML 中的style文本内容CSS 合并mergeCSSText()把嵌入式样式与options.cssText合并后再交给运行时计算运行时选择runtimeForOptions()在未传runtime时调用createCSSRuntime()自动探测环境管线终点htmlToSceneGraph先产出DesignDocument再由designDocumentToSceneGraph()见 src/import/scene-graph.ts投影为场景图。CSS → 场景图字段映射designDocumentToSceneGraph是映射的核心它将每个DesignElement转成一个 FRAME 或 TEXT 节点映射关系包括CSS 输入场景图输出SceneNode字段display: flex / inline-flexlayoutMode: HORIZONTAL \| VERTICAL按flex-directionjustify-contentprimaryAxisAlign: MIN / CENTER / MAX / SPACE_BETWEENalign-items/align-selfcounterAxisAlign/layoutAlignSelf含 stretch、baselineflex-wrap: wraplayoutWrap: WRAP \| NO_WRAPgap/row-gap/column-gapitemSpacing/counterAxisSpacingpadding及四向简写、padding-block/inlinepaddingTop/Right/Bottom/Leftwidth/height/min/max-*、aspect-ratio尺寸与约束字段单边可用aspect-ratio反推另一轴border-*-width/color/stylestrokes、四向独立border*Weight、dashPatterndashed/dottedborder-*-radiuscornerRadius、四角独立半径与independentCornersbackground-colorfills通过colorToFillFromCSSbox-shadow/text-shadoweffects经parseCSSShadowsopacityopacityoverflow: hidden / clipclipsContent: trueposition: absolute / fixedleft/toplayoutPositioning: ABSOLUTE、x/yfont-size/weight/line-height/letter-spacing/family、font-style、text-align、text-decoration-line、text-transform、white-space: nowrap文本节点对应字段white-space: nowrap还会设置maxLines: 1object-fitimg的 data URLIMAGE填充FIT/FILL非法 Base64 只丢弃图片不中断导入几点值得注意的实现细节均出自 src/import/scene-graph.ts文本判定isTextLikeElement()识别span/p/label/strong/em/button/a/h1-h6等标签若元素无盒模型样式且子节点全是文本则直接创建 TEXT 节点否则创建 FRAME图片资源applyImageFill()会把img的合法 data URL 解码写入graph.images通过computeImageHash而普通 URL 则存入pluginData插件 IDopen-pencil-dom-css键image-source-url保证无效数据不破坏整体导入页面自动适配fitPageToChildren()以所有子节点包围盒计算画布尺寸。JSX/DOM 创作DOM 形态的声明式编写当你想用 DOM 形态而非直接构造 DesignDOM 树进行创作并把内容流经 DesignDOM、CSSOM、SceneGraph 转换时可以把该包当作 JSX import source 使用/** jsxImportSource open-pencil/dom-css */ import { createBrowserCSSRuntime, jsxToSceneGraph } from open-pencil/dom-css const graph await jsxToSceneGraph( article classcard h1OpenPencil/h1 /article, { cssText: .card { display: flex; width: 320px; padding: 24px; }, runtime: createBrowserCSSRuntime({ sandbox: iframe }) } )JSX 层的行为边界README 明确说明它保留class、属性、内联style、文本、Fragment 与简单的函数组件产出 DesignDOM类名语义仍由传入 CSS 运行时的 CSS 决定——JSX 层本身不解释 Tailwind 或 CSS 工具类名。浏览器优先/browser入口当代码运行在浏览器中时应使用浏览器优先的辅助函数让原生getComputedStyle()被自动采用并且从open-pencil/dom-css/browser导入避免浏览器 bundle 加载 headless 专用的 CSSOM 依赖bundle 体积与运行时依赖的隔离是入口拆分的直接动机见 package.json 的./browser导出点与 src/browser.ts/** jsxImportSource open-pencil/dom-css */ import { browserJSXToSceneGraph } from open-pencil/dom-css/browser const graph await browserJSXToSceneGraph( article classcard h1OpenPencil/h1 /article, { cssText: .card { display: flex; width: 320px; padding: 24px; }, sandbox: iframe } )从 src/browser.ts 的实现可见浏览器辅助函数内部会强制sandbox: iframecreateRuntime中{ sandbox: iframe, ...options }并在缺少全局document时抛出明确的TypeError防止在无 DOM 环境误用。浏览器入口下的 API 分工README 明确划分browserHTMLToDesignDocument()/browserHTMLToSceneGraph()—— HTML 输入browserJSXToDesignDocument()/browserJSXToSceneGraph()—— JSX 输入、作者自管 CSSbrowserTailwindHTMLToDesignDocument()/browserTailwindHTMLToSceneGraph()与browserTailwindJSXToDesignDocument()/browserTailwindJSXToSceneGraph()—— 仅在宿主应用需要在运行时编译 Tailwind 工具类时使用。Tailwind 管线三套接入配方Tailwind 类名先流经 Tailwind 自己的编译器再进入 CSS 运行时。当 DOM 可用时优先用浏览器辅助函数让自定义属性、calc()、现代颜色与浏览器默认行为全部来自原生getComputedStyle()。配方一浏览器 预编译 Tailwind CSS最便携在宿主应用中预先编译 Tailwind CSS把产物当作普通cssText传入import { browserHTMLToSceneGraph } from open-pencil/dom-css/browser import tailwindCSS from ./generated-tailwind.css?raw const graph await browserHTMLToSceneGraph( article classflex w-80 rounded-xl bg-white p-6OpenPencil/article, { cssText: tailwindCSS, sandbox: iframe } )JSX 版本同理/** jsxImportSource open-pencil/dom-css */ import { browserJSXToSceneGraph } from open-pencil/dom-css/browser import tailwindCSS from ./generated-tailwind.css?raw const graph await browserJSXToSceneGraph( article classflex w-80 rounded-xl bg-white p-6OpenPencil/article, { cssText: tailwindCSS, sandbox: iframe } )配方二浏览器 运行时 Tailwind 编译自带 CSS如果应用希望在运行时编译工具类候选集必须自己提供 Tailwind 源 CSS不要依赖浏览器 bundle 中默认的面向 Node 的样式表加载器import { browserTailwindHTMLToSceneGraph } from open-pencil/dom-css/browser const classes [flex, w-80, rounded-xl, bg-white, p-6] const graph await browserTailwindHTMLToSceneGraph( article class${classes.join( )}OpenPencil/article, classes, { css: await fetch(/tailwind-source.css).then((response) response.text()), sandbox: iframe } )当 Tailwind 源 CSS 含import、且导入解析由宿主应用负责时使用loadStylesheet回调import { browserTailwindHTMLToSceneGraph } from open-pencil/dom-css/browser const classes [flex, w-80, rounded-xl, bg-white, p-6] const graph await browserTailwindHTMLToSceneGraph( article class${classes.join( )}OpenPencil/article, classes, { css: import tailwindcss;, loadStylesheet: async (id) { const url id tailwindcss ? /tailwindcss/index.css : /tailwindcss/${id} return fetch(url).then((response) response.text()) }, sandbox: iframe } )配方三Bun/Node在 Bun 或 Node 中包可以通过文件系统支撑的模块解析加载 Tailwind 默认样式表。没有 DOM 时走 headless CSS 运行时只有进程真实持有document如 Playwright 或浏览器扩展页面时才传入浏览器运行时import { tailwindHTMLToSceneGraph } from open-pencil/dom-css const classes [flex, w-80, p-6, rounded-xl, bg-white] const graph await tailwindHTMLToSceneGraph( article class${classes.join( )}OpenPencil/article, classes )如果调用方想自己管理 CSS 编译与运行时选择可以单独使用compileTailwindCSS()。其底层实现在 src/import/tailwind.ts调用tailwindcss的compile()构建器默认入口 CSS 为import tailwindcss;若传入的css不含该导入则自动补全类名候选集经normalizeClasses()按空白切分、去重后交给compiler.build()loadStylesheet缺省时通过import.meta.resolve(tailwindcss/index.css)走文件系统读取。测试覆盖见 packages/dom-css/tests/import/tailwind.test.ts。反向投影场景图 → DesignDOM → HTML/JSX/TailwindREADME 的 Current scope 明确把 SceneGraph ⇄ DesignDOM conversion 列为双向能力反向路径由export模块提供见 src/index.tssceneGraphToDesignDocument()/sceneNodeToDesignDocument()—— 场景图节点投影为 DOM 形态设计文档src/export/projection.ts 中实现了 flex 对齐、HUG尺寸hugs()依据primaryAxisSizing/counterAxisSizing、文本自动尺寸、token 引用变量绑定字段以var(--name)写出等映射serializeHTML()/serializeNode()—— 序列化为 HTML 字符串src/export/html.ts 支持style: inline | tailwind两种输出模式、themeVariables主题变量识别以及 void 元素集合br、img、input等的序列化处理designDocumentToTailwindJSX()/sceneNodesToTailwindJSX()—— 投影为 Tailwind 风格的 JSX 片段相关测试见 packages/dom-css/tests/export/tailwind-jsxexportHTMLBundle()—— 导出独立 HTML bundle含 Web font 资产解析见 src/export/bundle.tsexportStorybook()—— 按 Storybook 框架组织导出框架清单STORYBOOK_FRAMEWORKS见 src/export/storybook/export.ts。tokens相关能力DesignTokens、theme变量引用、token stylesheet 生成分布在 src/tokens由 src/tokens/references.ts 定义类型测试见 packages/dom-css/tests/tokens 与 packages/dom-css/tests/export/projection/tokens.test.ts。当前范围与路线图当前已覆盖的能力DOM 形态的DesignDocument/DesignElement类型浏览器运行时适配器原生 HTML 解析、序列化与计算样式提取headless 运行时适配器parse5HTML 解析 CSSOM 样式计算支持基础选择器、嵌套 CSSOM 规则、级联顺序、继承、常见简写、简单自定义属性与calc()SceneGraph ⇄ DesignDOM 转换flex 对齐/换行、自对齐、基础绝对定位、逻辑 padding、独立四向边框、约束、裁剪、透明度、排版与阴影JSX 运行时辅助DOM 形态创作进入 DesignDOM 与场景图Tailwind v4 编译器适配器浏览器 oracle 测试夹具CSS 变量、calc()、沙箱化浏览器运行时输出、现代颜色输出、JSX/Tailwind 浏览器辅助函数与 Tailwind 工具类输出对应仓库级tests/engine/dom-css与tests/e2e/dom-css套件。路线图README 列出的演进方向包括扩展可复用夹具inputs、badges、nav/menu 行、dialog 外壳、更丰富的卡片通过浏览器原生计算样式或依赖解析器映射更多计算 CSS 属性到场景图字段更丰富的阴影、排版细节、位置约束、边框、渐变以及待 OpenPencil 网格能力成熟后的 grid 支持改进 SceneGraph → CSS 导出使生成的 HTML/CSS 对 JSX、Tailwind 与 Web 导出更有用在拆分更底层的文件格式包如未来的open-pencil/kiwi、open-pencil/fig之前保持open-pencil/dom-css的 API 稳定。小结如何选择正确的入口结合 README 的定位与源码实现可以给出如下选型建议有 DOM 的生产环境一律走open-pencil/dom-css/browser入口并显式设置sandbox: iframe以获得宿主样式隔离与getComputedStyle()保真度Bun/Node 测试、CLI 与快速近似转换走默认入口的 headless 运行时但牢记它不是浏览器 oracleTailwind 内容优先采用宿主预编译 → 普通cssText的最便携路径只有需要动态候选集编译时才使用运行时编译并自行提供 Tailwind 源 CSS 与loadStylesheet。这套分层让浏览器、Node 与编辑器核心各取所需也正是不把 DOM 依赖塞进渲染器核心的设计初衷。赞分享前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载相关推荐OpenPencil DOM/CSS 映射参考浏览器样式与 SceneGraph 字段的双向投影实战OpenPencil DOM/CSS 映射参考浏览器样式与 SceneGraph 字段的双向投影实战 OpenPencil 通过独立的 open penci前端桌面应用AI 应用MCP 服务daisyUI × Lit 实战在 Lit Web Component 与 Shadow DOM 中使用 Tailwind CSS 组件库daisyUI × Lit 实战在 Lit Web Component 与 Shadow DOM 中使用 Tailwind CSS 组件库 Lit 以小型、可前端UI组件OpenPencil 自动布局Auto Layout完全指南Flex 与 CSS Grid 的建模原理、配置详解与 JSX/Tailwind 导出OpenPencil 自动布局Auto Layout完全指南Flex 与 CSS Grid 的建模原理、配置详解与 JSX/Tailwind 导出 自动布前端桌面应用AI 应用MCP 服务上一篇kubeasz与Istio服务网格流量管理与安全策略配置最佳实践下一篇无锁队列革命moodycamel::ConcurrentQueue如何解决多线程并发难题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表